Skip to content

Commit

Permalink
Add CFI information to __rust_probestack
Browse files Browse the repository at this point in the history
In order for GDB to correctly backtrace a stack overflow, it needs
CFI information in __rust_probestack.

This turns the following backtrace,

```
>> bt
 #0  0x0000555555576f73 in __rust_probestack () at /cargo/registry/src/github.com-1ecc6299db9ec823/compiler_builtins-0.1.14/src/probestack.rs:55
Backtrace stopped: Cannot access memory at address 0x7fffff7fedf0
```

To this:

```
>>> bt
 #0  0x0000555555574e47 in __rust_probestack ()
 #1  0x00005555555595ba in test::main ()
 #2  0x00005555555594f3 in std::rt::lang_start::{{closure}} ()
 #3  0x0000555555561ae3 in std::panicking::try::do_call ()
 #4  0x000055555556595a in __rust_maybe_catch_panic ()
 #5  0x000055555555af9b in std::rt::lang_start_internal ()
 #6  0x00005555555594d5 in std::rt::lang_start ()
 #7  0x000055555555977b in main ()
```
  • Loading branch information
da-x committed Jul 23, 2019
1 parent 79a6a16 commit a7e6876
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 88 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test = false
core = { version = "1.0.0", optional = true, package = 'rustc-std-workspace-core' }

[build-dependencies]
cc = { optional = true, version = "1.0" }
cc = { version = "1.0" }

[dev-dependencies]
panic-handler = { path = 'crates/panic-handler' }
Expand All @@ -42,7 +42,7 @@ default = ["compiler-builtins"]

# Enable compilation of C code in compiler-rt, filling in some more optimized
# implementations and also filling in unimplemented intrinsics
c = ["cc"]
c = []

# Flag this library as the unstable compiler-builtins lib
compiler-builtins = []
Expand Down
13 changes: 13 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ fn main() {
println!("cargo:rustc-cfg=feature=\"mem\"");
}

let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if target_arch == "x86_64" {
let cfg = &mut cc::Build::new();
cfg.file(&"src/probestack_x86_64.S");
cfg.compile("libcompiler-probestack.a");
}

if target_arch == "x86" {
let cfg = &mut cc::Build::new();
cfg.file(&"src/probestack_x86.S");
cfg.compile("libcompiler-probestack.a");
}

// NOTE we are going to assume that llvm-target, what determines our codegen option, matches the
// target triple. This is usually correct for our built-in targets but can break in presence of
// custom targets, which can have arbitrary names.
Expand Down
2 changes: 1 addition & 1 deletion libm
Submodule libm updated from 01bee7 to 0ae442
86 changes: 1 addition & 85 deletions src/probestack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,88 +41,4 @@
//! probes on any other architecture like ARM or PowerPC64. LLVM I'm sure would
//! be more than welcome to accept such a change!
#![cfg(not(windows))] // Windows already has builtins to do this

#[naked]
#[no_mangle]
#[cfg(all(target_arch = "x86_64", not(feature = "mangled-names")))]
pub unsafe extern "C" fn __rust_probestack() {
// Our goal here is to touch each page between %rsp+8 and %rsp+8-%rax,
// ensuring that if any pages are unmapped we'll make a page fault.
//
// The ABI here is that the stack frame size is located in `%eax`. Upon
// return we're not supposed to modify `%esp` or `%eax`.
asm!("
mov %rax,%r11 // duplicate %rax as we're clobbering %r11
// Main loop, taken in one page increments. We're decrementing rsp by
// a page each time until there's less than a page remaining. We're
// guaranteed that this function isn't called unless there's more than a
// page needed.
//
// Note that we're also testing against `8(%rsp)` to account for the 8
// bytes pushed on the stack orginally with our return address. Using
// `8(%rsp)` simulates us testing the stack pointer in the caller's
// context.
// It's usually called when %rax >= 0x1000, but that's not always true.
// Dynamic stack allocation, which is needed to implement unsized
// rvalues, triggers stackprobe even if %rax < 0x1000.
// Thus we have to check %r11 first to avoid segfault.
cmp $$0x1000,%r11
jna 3f
2:
sub $$0x1000,%rsp
test %rsp,8(%rsp)
sub $$0x1000,%r11
cmp $$0x1000,%r11
ja 2b
3:
// Finish up the last remaining stack space requested, getting the last
// bits out of r11
sub %r11,%rsp
test %rsp,8(%rsp)
// Restore the stack pointer to what it previously was when entering
// this function. The caller will readjust the stack pointer after we
// return.
add %rax,%rsp
ret
" ::: "memory" : "volatile");
::core::intrinsics::unreachable();
}

#[naked]
#[no_mangle]
#[cfg(all(target_arch = "x86", not(feature = "mangled-names")))]
pub unsafe extern "C" fn __rust_probestack() {
// This is the same as x86_64 above, only translated for 32-bit sizes. Note
// that on Unix we're expected to restore everything as it was, this
// function basically can't tamper with anything.
//
// The ABI here is the same as x86_64, except everything is 32-bits large.
asm!("
push %ecx
mov %eax,%ecx
cmp $$0x1000,%ecx
jna 3f
2:
sub $$0x1000,%esp
test %esp,8(%esp)
sub $$0x1000,%ecx
cmp $$0x1000,%ecx
ja 2b
3:
sub %ecx,%esp
test %esp,8(%esp)
add %eax,%esp
pop %ecx
ret
" ::: "memory" : "volatile");
::core::intrinsics::unreachable();
}
// The code here moved to probestack_*.S, to support debugger frame information.
38 changes: 38 additions & 0 deletions src/probestack_x86.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// This is the same as x86_64, only translated for 32-bit sizes. Note that on
// Unix we're expected to restore everything as it was, this function basically
// can't tamper with anything.
//
// The ABI here is the same as x86_64, except everything is 32-bits large.

.text
.globl __rust_probestack
.type __rust_probestack, @function
__rust_probestack:
.cfi_startproc
pushq %ebp
.cfi_def_cfa_offset 8
.cfi_offset 6, -8
movq %esp, %ebp
.cfi_def_cfa_register 6
push %ecx
mov %eax,%ecx

cmp $0x1000,%ecx
jna 3f
2:
sub $0x1000,%esp
test %esp,8(%esp)
sub $0x1000,%ecx
cmp $0x1000,%ecx
ja 2b

3:
sub %ecx,%esp
test %esp,8(%esp)

add %eax,%esp
pop %ecx
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
57 changes: 57 additions & 0 deletions src/probestack_x86_64.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Our goal here is to touch each page between %rsp+8 and %rsp+8-%rax,
// ensuring that if any pages are unmapped we'll make a page fault.
//
// The ABI here is that the stack frame size is located in `%eax`. Upon
// return we're not supposed to modify `%esp` or `%eax`.

.text
.p2align 4,,15
.globl __rust_probestack
.type __rust_probestack, @function
__rust_probestack:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
mov %rax,%r11
// duplicate %rax as we're clobbering %r11

// Main loop, taken in one page increments. We're decrementing rsp by
// a page each time until there's less than a page remaining. We're
// guaranteed that this function isn't called unless there's more than a
// page needed.
//
// Note that we're also testing against `8(%rsp)` to account for the 8
// bytes pushed on the stack orginally with our return address. Using
// `8(%rsp)` simulates us testing the stack pointer in the caller's
// context.

// It's usually called when %rax >= 0x1000, but that's not always true.
// Dynamic stack allocation, which is needed to implement unsized
// rvalues, triggers stackprobe even if %rax < 0x1000.
// Thus we have to check %r11 first to avoid segfault.
cmp $0x1000,%r11
jna 3f
2:
sub $0x1000,%rsp
test %rsp,8(%rsp)
sub $0x1000,%r11
cmp $0x1000,%r11
ja 2b

3:
// Finish up the last remaining stack space requested, getting the last
// bits out of r11
sub %r11,%rsp
test %rsp,8(%rsp)

// Restore the stack pointer to what it previously was when entering
// this function. The caller will readjust the stack pointer after we
// return.
add %rax,%rsp
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc

0 comments on commit a7e6876

Please sign in to comment.