-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Register individual FDEs for musl libc. #1914
Conversation
05de6e4
to
b61a310
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shall work. (FWIW I know little about cfg(target_env)
)
FWIW I did a mild amount of poking with this, but I'm not sure if this works for the purposes of backtrace because Also I think the logic may be a bit clearer inverted for glibc-specific bits perhaps? I'm not sure how prevalent libgcc's logic is, but the "write a 0 length at the end of the table" on line 94 above these changes I think is only relevant for glibc. |
It is only relevant for libgcc's implementation, but doesn't really hurt to put it in the entire frame table and just skip it for the libunwind iteration. You're right, it appears stack tracing isn't working even with Let me dig into that before we merge this. |
It looks like the unwind cursor can't get past the signal handler trampoline frame and hence doesn't even get to the Wasm frames. From lldb trace:
lldb appears to special case the signal handler function names and hence can walk past this frame. However, There's a I think this may work from glib rather than musl because it appears an FDE is emitted for I'm leaning towards this being a musl bug where no FDE is emitted for |
I'd thus expect |
A simple test program compiled with #include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#define UNW_LOCAL_ONLY
#include <libunwind.h>
void backtrace() {
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_word_t offset;
char name[256];
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
while (unw_step(&cursor) > 0) {
unw_get_reg(&cursor, UNW_REG_IP, &ip);
unw_get_reg(&cursor, UNW_REG_SP, &sp);
unw_get_proc_name(&cursor, name, sizeof(name), &offset);
printf ("ip = %lx, sp = %lx: %s + %lx\n", (long) ip, (long) sp, name, (long) offset);
}
}
void handler(int sig) {
backtrace();
exit(1);
}
void crash() {
int* p = (int*) 0;
*p = 1;
}
void bar() {
crash();
}
void foo() {
bar();
}
int main() {
signal(SIGSEGV, handler);
foo();
} Interestingly, this does create an FDE for
As a result, this program is able to walk the stack, albeit it showing
A Rust port of the program: use backtrace::Backtrace;
use nix::sys::signal;
extern "C" fn handler(_: nix::libc::c_int) {
let bt = Backtrace::new();
println!("{:?}", bt);
std::process::exit(1);
}
fn crash() {
unsafe {
let p: *mut i32 = std::ptr::null_mut();
*p = 1;
}
}
fn bar() {
crash();
}
fn foo() {
bar();
}
fn main() {
unsafe {
signal::signal(
signal::Signal::SIGSEGV,
signal::SigHandler::Handler(handler),
);
}
foo();
} However, no FDE was emitted for
As a result, it only prints the top frame:
@alexcrichton any insights into why the Rust toolchain might be missing this FDE? This repros with stable (llvm 9) and nightly (llvm 10). |
Oh wow awesome investigation! In your C example you're compiling with I tried your program in an alpine container and I can't seem to get good results, but I'm not the best at investigating this too. There's two libunwind implementations (
The odd part is that with llvm-libunwind I can see a CIE for the In any case I think this is pretty far afield from what changes we should make to wasmtime itself. It seems clear here that we should register individual FDEs rather than the whole set at once like we do with glibc. In terms of changes to this PR, it seems like we now have 2/3 platforms (macos, musl) which require individual FDEs so it seems like glibc is the outlier taking the whole set at once. In that case could the code be restructured to have the individual FDE be the general case and the glibc path is just an optimization for glibc? For example the zero-length push at the end is only needed for glibc, and I think the |
Oh and to make matters worse I don't know how the source of |
I can confirm that my test cases are linking against musl libc (it's an alpine image using a musl-libc targeted clang). I've been comparing the implementations between For On the other hand, Regarding |
That comment was introduced in libunwind/libunwind@dac2d00 |
Oh geez that's quite a lot of history... Anyway definitely sounds like fuel for the fire of "not something we can fix in wasmtime". That being said this is also more fuel to the fire of "we should not rely on the native system unwinder to generate a backtrace on every single platform" since this mean wasm traps on musl will always be wrong. @peterhuene were you able to reproduce how libunwind was working better than llvm-libunwind? Oh also for |
I'll update the PR to use I agree we can declare the inability to walk the stack for musl targets using llvm-libunwind an external issue to Wasmtime. I think we should be somehow limiting our walks to sections of the stack that contain Wasm frames (as discussed in #1832) as it would both address this issue and #1845. |
b61a310
to
7536093
Compare
@alexcrichton PR updated. I added a check for |
7536093
to
a999f45
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "")))
is the right way to detect libgcc's unwind library, looks good to me.
When targeting musl, libunwind is used for the `__register_frame` implementation. Unlike when targeting libgcc which expects an entire frame table, the libunwind implementation expects a single FDE. This change ensures Wasmtime registers each individual FDE when targeting musl. Fixes bytecodealliance#1904.
a999f45
to
4087fce
Compare
* This PR is against a branch called `main` * Internally all docs/CI/etc is updated * The default branch of the repo is now `main` * All active PRs have been updated to retarget `main` Closes bytecodealliance#1914
When targeting musl, libunwind is used for the
__register_frame
implementation.
Unlike when targeting libgcc which expects an entire frame table, the libunwind
implementation expects a single FDE.
This change ensures Wasmtime registers each individual FDE when targeting musl.
Fixes #1904.