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
This commit is contained in:
Gabriel Majeri 2018-09-26 19:19:55 +03:00
parent 2243fabd8f
commit 6009da0794
12 changed files with 89 additions and 7 deletions

View File

@ -1387,6 +1387,10 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"output a json file with profiler results"),
emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED],
"emits a section containing stack size metadata"),
plt: Option<bool> = (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 {

View File

@ -40,8 +40,7 @@ use syntax::parse::{self, ParseSess};
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 @@ impl Session {
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(

View File

@ -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(

View File

@ -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
}

View File

@ -104,6 +104,8 @@ fn declare_raw_fn(
attributes::unwind(llfn, false);
}
attributes::non_lazy_bind(cx.sess(), llfn);
llfn
}

View File

@ -122,6 +122,7 @@ pub enum Attribute {
SanitizeThread = 20,
SanitizeAddress = 21,
SanitizeMemory = 22,
NonLazyBind = 23,
}
/// LLVMIntPredicate

View File

@ -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 @@ impl Default for 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 @@ impl Target {
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 @@ impl ToJson for Target {
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);

View File

@ -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(),

View File

@ -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");
}

View File

@ -97,6 +97,7 @@ enum LLVMRustAttribute {
SanitizeThread = 20,
SanitizeAddress = 21,
SanitizeMemory = 22,
NonLazyBind = 23,
};
typedef struct OpaqueRustString *RustStringRef;

View File

@ -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]

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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"