From 6009da079419c9693fe4965ecacbd473c2553173 Mon Sep 17 00:00:00 2001 From: Gabriel Majeri Date: Wed, 26 Sep 2018 19:19:55 +0300 Subject: [PATCH] Support for disabling the PLT on ELF targets Disable the PLT where possible to improve performance for indirect calls into shared libraries. This optimization is enabled by default where possible. - Add the `NonLazyBind` attribute to `rustllvm`: This attribute informs LLVM to skip PLT calls in codegen. - Disable PLT unconditionally: Apply the `NonLazyBind` attribute on every function. - Only enable no-plt when full relro is enabled: Ensures we only enable it when we have linker support. - Add `-Z plt` as a compiler option --- src/librustc/session/config.rs | 4 +++ src/librustc/session/mod.rs | 24 +++++++++++++++-- src/librustc_codegen_llvm/attributes.rs | 9 +++++++ src/librustc_codegen_llvm/context.rs | 7 +++++ src/librustc_codegen_llvm/declare.rs | 2 ++ src/librustc_codegen_llvm/llvm/ffi.rs | 1 + src/librustc_target/spec/mod.rs | 6 +++++ .../spec/x86_64_unknown_linux_gnux32.rs | 3 +++ src/rustllvm/RustWrapper.cpp | 2 ++ src/rustllvm/rustllvm.h | 1 + src/test/codegen/naked-functions.rs | 10 +++---- src/test/codegen/no-plt.rs | 27 +++++++++++++++++++ 12 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 src/test/codegen/no-plt.rs diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index c532b5ee56f..d8c36f81da3 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1387,6 +1387,10 @@ fn parse_cross_lang_lto(slot: &mut CrossLangLto, v: Option<&str>) -> bool { "output a json file with profiler results"), emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], "emits a section containing stack size metadata"), + plt: Option = (None, parse_opt_bool, [TRACKED], + "whether to use the PLT when calling into shared libraries; + only has effect for PIC code on systems with ELF binaries + (default: PLT is disabled if full relro is enabled)"), } pub fn default_lib_output() -> CrateType { diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 3c209a43246..10a506da4ea 100644 --- a/src/librustc/session/mod.rs +++ b/src/librustc/session/mod.rs @@ -40,8 +40,7 @@ use syntax_pos::{MultiSpan, Span}; use util::profiling::SelfProfiler; -use rustc_target::spec::PanicStrategy; -use rustc_target::spec::{Target, TargetTriple}; +use rustc_target::spec::{PanicStrategy, RelroLevel, Target, TargetTriple}; use rustc_data_structures::flock; use jobserver::Client; @@ -984,6 +983,27 @@ pub fn rust_2018(&self) -> bool { pub fn edition(&self) -> Edition { self.opts.edition } + + /// True if we cannot skip the PLT for shared library calls. + pub fn needs_plt(&self) -> bool { + // Check if the current target usually needs PLT to be enabled. + // The user can use the command line flag to override it. + let needs_plt = self.target.target.options.needs_plt; + + let dbg_opts = &self.opts.debugging_opts; + + let relro_level = dbg_opts.relro_level + .unwrap_or(self.target.target.options.relro_level); + + // Only enable this optimization by default if full relro is also enabled. + // In this case, lazy binding was already unavailable, so nothing is lost. + // This also ensures `-Wl,-z,now` is supported by the linker. + let full_relro = RelroLevel::Full == relro_level; + + // If user didn't explicitly forced us to use / skip the PLT, + // then try to skip it where possible. + dbg_opts.plt.unwrap_or(needs_plt || !full_relro) + } } pub fn build_session( diff --git a/src/librustc_codegen_llvm/attributes.rs b/src/librustc_codegen_llvm/attributes.rs index 51380db5b23..c871816c49c 100644 --- a/src/librustc_codegen_llvm/attributes.rs +++ b/src/librustc_codegen_llvm/attributes.rs @@ -138,6 +138,15 @@ pub fn apply_target_cpu_attr(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) { target_cpu.as_c_str()); } +/// Sets the `NonLazyBind` LLVM attribute on a given function, +/// assuming the codegen options allow skipping the PLT. +pub fn non_lazy_bind(sess: &Session, llfn: &'ll Value) { + // Don't generate calls through PLT if it's not necessary + if !sess.needs_plt() { + Attribute::NonLazyBind.apply_llfn(Function, llfn); + } +} + /// Composite function which sets LLVM attributes for function depending on its AST (#[attribute]) /// attributes. pub fn from_fn_attrs( diff --git a/src/librustc_codegen_llvm/context.rs b/src/librustc_codegen_llvm/context.rs index 9547f4a190e..fdbace4365a 100644 --- a/src/librustc_codegen_llvm/context.rs +++ b/src/librustc_codegen_llvm/context.rs @@ -208,6 +208,13 @@ pub unsafe fn create_module( llvm::LLVMRustSetModulePIELevel(llmod); } + // If skipping the PLT is enabled, we need to add some module metadata + // to ensure intrinsic calls don't use it. + if !sess.needs_plt() { + let avoid_plt = "RtLibUseGOT\0".as_ptr() as *const _; + llvm::LLVMRustAddModuleFlag(llmod, avoid_plt, 1); + } + llmod } diff --git a/src/librustc_codegen_llvm/declare.rs b/src/librustc_codegen_llvm/declare.rs index 7141c9ece89..26969e24f08 100644 --- a/src/librustc_codegen_llvm/declare.rs +++ b/src/librustc_codegen_llvm/declare.rs @@ -104,6 +104,8 @@ fn declare_raw_fn( attributes::unwind(llfn, false); } + attributes::non_lazy_bind(cx.sess(), llfn); + llfn } diff --git a/src/librustc_codegen_llvm/llvm/ffi.rs b/src/librustc_codegen_llvm/llvm/ffi.rs index 8485db4210c..c9f51efdc50 100644 --- a/src/librustc_codegen_llvm/llvm/ffi.rs +++ b/src/librustc_codegen_llvm/llvm/ffi.rs @@ -122,6 +122,7 @@ pub enum Attribute { SanitizeThread = 20, SanitizeAddress = 21, SanitizeMemory = 22, + NonLazyBind = 23, } /// LLVMIntPredicate diff --git a/src/librustc_target/spec/mod.rs b/src/librustc_target/spec/mod.rs index 3f1e8ee5528..9c0f945326d 100644 --- a/src/librustc_target/spec/mod.rs +++ b/src/librustc_target/spec/mod.rs @@ -576,6 +576,9 @@ pub struct TargetOptions { /// the functions in the executable are not randomized and can be used /// during an exploit of a vulnerability in any code. pub position_independent_executables: bool, + /// Determines if the target always requires using the PLT for indirect + /// library calls or not. This controls the default value of the `-Z plt` flag. + pub needs_plt: bool, /// Either partial, full, or off. Full RELRO makes the dynamic linker /// resolve all symbols at startup and marks the GOT read-only before /// starting the program, preventing overwriting the GOT. @@ -720,6 +723,7 @@ fn default() -> TargetOptions { has_rpath: false, no_default_libraries: true, position_independent_executables: false, + needs_plt: false, relro_level: RelroLevel::None, pre_link_objects_exe: Vec::new(), pre_link_objects_exe_crt: Vec::new(), @@ -1009,6 +1013,7 @@ pub fn from_json(obj: Json) -> TargetResult { key!(has_rpath, bool); key!(no_default_libraries, bool); key!(position_independent_executables, bool); + key!(needs_plt, bool); try!(key!(relro_level, RelroLevel)); key!(archive_format); key!(allow_asm, bool); @@ -1217,6 +1222,7 @@ fn to_json(&self) -> Json { target_option_val!(has_rpath); target_option_val!(no_default_libraries); target_option_val!(position_independent_executables); + target_option_val!(needs_plt); target_option_val!(relro_level); target_option_val!(archive_format); target_option_val!(allow_asm); diff --git a/src/librustc_target/spec/x86_64_unknown_linux_gnux32.rs b/src/librustc_target/spec/x86_64_unknown_linux_gnux32.rs index 72b5bd27c7d..fd61067ba51 100644 --- a/src/librustc_target/spec/x86_64_unknown_linux_gnux32.rs +++ b/src/librustc_target/spec/x86_64_unknown_linux_gnux32.rs @@ -17,6 +17,9 @@ pub fn target() -> TargetResult { base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-mx32".to_string()); base.stack_probes = true; base.has_elf_tls = false; + // BUG(GabrielMajeri): disabling the PLT on x86_64 Linux with x32 ABI + // breaks code gen. See LLVM bug 36743 + base.needs_plt = true; Ok(Target { llvm_target: "x86_64-unknown-linux-gnux32".to_string(), diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp index f1ab1d4ddfa..2b1bf1c0290 100644 --- a/src/rustllvm/RustWrapper.cpp +++ b/src/rustllvm/RustWrapper.cpp @@ -178,6 +178,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) { return Attribute::SanitizeAddress; case SanitizeMemory: return Attribute::SanitizeMemory; + case NonLazyBind: + return Attribute::NonLazyBind; } report_fatal_error("bad AttributeKind"); } diff --git a/src/rustllvm/rustllvm.h b/src/rustllvm/rustllvm.h index 1070068b998..b6fa9a2fa95 100644 --- a/src/rustllvm/rustllvm.h +++ b/src/rustllvm/rustllvm.h @@ -97,6 +97,7 @@ enum LLVMRustAttribute { SanitizeThread = 20, SanitizeAddress = 21, SanitizeMemory = 22, + NonLazyBind = 23, }; typedef struct OpaqueRustString *RustStringRef; diff --git a/src/test/codegen/naked-functions.rs b/src/test/codegen/naked-functions.rs index aab5f1bfb4f..2cf8ce00bfb 100644 --- a/src/test/codegen/naked-functions.rs +++ b/src/test/codegen/naked-functions.rs @@ -15,7 +15,7 @@ #![crate_type = "lib"] #![feature(naked_functions)] -// CHECK: Function Attrs: naked uwtable +// CHECK: Function Attrs: naked // CHECK-NEXT: define void @naked_empty() #[no_mangle] #[naked] @@ -24,7 +24,7 @@ pub fn naked_empty() { // CHECK-NEXT: ret void } -// CHECK: Function Attrs: naked uwtable +// CHECK: Function Attrs: naked #[no_mangle] #[naked] // CHECK-NEXT: define void @naked_with_args(i{{[0-9]+}}) @@ -35,7 +35,7 @@ pub fn naked_with_args(a: isize) { // CHECK: ret void } -// CHECK: Function Attrs: naked uwtable +// CHECK: Function Attrs: naked // CHECK-NEXT: define i{{[0-9]+}} @naked_with_return() #[no_mangle] #[naked] @@ -45,7 +45,7 @@ pub fn naked_with_return() -> isize { 0 } -// CHECK: Function Attrs: naked uwtable +// CHECK: Function Attrs: naked // CHECK-NEXT: define i{{[0-9]+}} @naked_with_args_and_return(i{{[0-9]+}}) #[no_mangle] #[naked] @@ -57,7 +57,7 @@ pub fn naked_with_args_and_return(a: isize) -> isize { a } -// CHECK: Function Attrs: naked uwtable +// CHECK: Function Attrs: naked // CHECK-NEXT: define void @naked_recursive() #[no_mangle] #[naked] diff --git a/src/test/codegen/no-plt.rs b/src/test/codegen/no-plt.rs new file mode 100644 index 00000000000..8f302e57902 --- /dev/null +++ b/src/test/codegen/no-plt.rs @@ -0,0 +1,27 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -C relocation-model=pic -Z plt=no + +#![crate_type = "lib"] + +// We need a function which is normally called through the PLT. +extern "C" { + // CHECK: Function Attrs: nounwind nonlazybind + fn getenv(name: *const u8) -> *mut u8; +} + +// Ensure the function gets referenced. +pub unsafe fn call_through_plt() -> *mut u8 { + getenv(b"\0".as_ptr()) +} + +// Ensure intrinsics also skip the PLT +// CHECK: !"RtLibUseGOT" -- GitLab