Rust musl/.NET-Glibc简析

文摘   2024-09-01 10:46   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

先看下rust-glibc/musl再看下.net-glibc。

rust-glibc

从linux内核态到用户态的第一个函数:

// https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/x86_64/start.SENTRY (_start)  /* Clearing frame pointer is insufficient, use CFI.  */  cfi_undefined (rip)  /* Clear the frame pointer.  The ABI suggests this be done, to mark     the outermost frame obviously.  */  xorl %ebp, %ebp
mov %RDX_LP, %R9_LP /* Address of the shared library termination function. */#ifdef __ILP32__ mov (%rsp), %esi /* Simulate popping 4-byte argument count. */ add $4, %esp#else popq %rsi /* Pop the argument count. */#endif /* argv starts just at the current stack top. */ mov %RSP_LP, %RDX_LP /* Align the stack to a 16 byte boundary to follow the ABI. */ and $~15, %RSP_LP
/* Push garbage because we push 8 more bytes. */ pushq %rax
/* Provide the highest stack address to the user code (for stacks which grow downwards). */ pushq %rsp
/* These used to be the addresses of .fini and .init. */ xorl %r8d, %r8d xorl %ecx, %ecx
#ifdef PIC mov main@GOTPCREL(%rip), %RDI_LP#else mov $main, %RDI_LP#endif call *__libc_start_main@GOTPCREL(%rip)
hlt /* Crash if somehow `exit' does return. */END (_start) .data .globl __data_start__data_start: .long 0 .weak data_start data_start = __data_start

看下它的ASM:

(lldb) di -brustc`_start:->  0x555555555920 <+0>:  f3 0f 1e fa           endbr64     0x555555555924 <+4>:  31 ed                 xor    ebp, ebp    0x555555555926 <+6>:  49 89 d1              mov    r9, rdx    0x555555555929 <+9>:  5e                    pop    rsi    0x55555555592a <+10>: 48 89 e2              mov    rdx, rsp    0x55555555592d <+13>: 48 83 e4 f0           and    rsp, -0x10    0x555555555931 <+17>: 50                    push   rax    0x555555555932 <+18>: 54                    push   rsp    0x555555555933 <+19>: 45 31 c0              xor    r8d, r8d    0x555555555936 <+22>: 31 c9                 xor    ecx, ecx    0x555555555938 <+24>: 48 8d 3d c1 01 00 00  lea    rdi, [rip + 0x1c1]        ; main    0x55555555593f <+31>: ff 15 5b 14 00 00     call   qword ptr [rip + 0x145b]    0x555555555945 <+37>: f4                    hlt    

此时_start里面的调用第一个参数rdi实际上已经被赋值为glibc-main。call如下

// https://elixir.bootlin.com/glibc/glibc-2.35.9000/source/csu/libc-start.cSTATIC intLIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),     int argc, char **argv,#ifdef LIBC_START_MAIN_AUXVEC_ARG     ElfW(auxv_t) *auxvec,#endif     __typeof (main) init,     void (*fini) (void),     void (*rtld_fini) (void), void *stack_end){   //部分省略     __libc_start_call_main (main, argc, argv MAIN_AUXVEC_PARAM);}

__libc_start_call_main 

// https://elixir.bootlin.com/glibc/glibc-2.35.9000/source/sysdeps/nptl/libc_start_call_main.h#L23_Noreturn static void__libc_start_call_main (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),                        int argc, char **argv#ifdef LIBC_START_MAIN_AUXVEC_ARG                            , ElfW(auxv_t) *auxvec#endif                        ){  //省略部分代码  result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);}

这里的main是glibc-main,即是上面的rdi参数。由 C 编译器和链接器在编译和链接用户程序时自动生成的,ASM如下:

(lldb) di -brustc`main:->  0x555555555b00 <+0>:  50                    push   rax    0x555555555b01 <+1>:  48 89 f2              mov    rdx, rsi    0x555555555b04 <+4>:  48 8b 05 cd 12 00 00  mov    rax, qword ptr [rip + 0x12cd]    0x555555555b0b <+11>: 8a 00                 mov    al, byte ptr [rax]    0x555555555b0d <+13>: 48 63 f7              movsxd rsi, edi    0x555555555b10 <+16>: 48 8d 3d d9 ff ff ff  lea    rdi, [rip - 0x27]         ; rustc_main::main at main.rs:38    0x555555555b17 <+23>: b9 03 00 00 00        mov    ecx, 0x3    0x555555555b1c <+28>: e8 ef fe ff ff        call   0x555555555a10            ; std::rt::lang_start::<()> at rt.rs:157    0x555555555b21 <+33>: 59                    pop    rcx    0x555555555b22 <+34>: c3                    ret

最后就到达了lang_start

// rust/library/std/src/rt.rs#[cfg(not(any(test, doctest)))]#[lang = "start"]fn lang_start<T: crate::process::Termination + 'static>(    main: fn() -> T,    argc: isize,    argv: *const *const u8,    sigpipe: u8,) -> isize {    let Ok(v) = lang_start_internal(        &move || crate::sys::backtrace::__rust_begin_short_backtrace(main).report().to_i32(),        argc,        argv,        sigpipe,    );


Rust-musl

rust默认是glibc,musl需要指定下:

# rustup target add x86_64-unknown-linux-musl  安装# cargo build --target x86_64-unknown-linux-musl --debug/release 编译


musl主要是把glibc部分直接编译到了最终可执行的二进制文件里面去了

(lldb) dihello-rust`main:->  0x7ffff7f99a10 <+0>:  push   rax    0x7ffff7f99a11 <+1>:  mov    rdx, rsi    0x7ffff7f99a14 <+4>:  lea    rax, [rip + 0x570d4]      ; __rustc_debug_gdb_scripts_section__    0x7ffff7f99a1b <+11>: mov    al, byte ptr [rax]    0x7ffff7f99a1d <+13>: movsxd rsi, edi    0x7ffff7f99a20 <+16>: lea    rdi, [rip - 0xe7]         ; hello_rust::main::h8886cd98519b4979 at main.rs:1    0x7ffff7f99a27 <+23>: xor    ecx, ecx    0x7ffff7f99a29 <+25>: call   0x7ffff7f99a50            ; std::rt::lang_start::h4843853a1732883f at rt.rs:152    0x7ffff7f99a2e <+30>: pop    rcx    0x7ffff7f99a2f <+31>: ret

上面的rust-main跟glibc一样的。步过其堆栈则如下:

(lldb) bt* thread #1, name = 'hello-rust', stop reason = breakpoint 5.2  * frame #0: 0x00007ffff7f99a10 hello-rust`main    frame #1: 0x00007ffff7fdbed7 hello-rust`libc_start_main_stage2 at __libc_start_main.c:95:2    frame #2: 0x00007ffff7f9965f hello-rust`_start + 2

但是实际上我们在地址0x00007ffff7f9965f 会发现另一个调用,这里不做细究

(lldb) b 0x00007ffff7f9965f (lldb) r(lldb) bt* thread #1, name = 'hello-rust', stop reason = breakpoint 6.1 7.1  * frame #0: 0x00007ffff7f9965f hello-rust`_start_c at dlstart.c:22:1    frame #1: 0x00007ffff7f9965f hello-rust`_start + 22

.NET-glibc

.Net glibc与rust前面部分所有都是一样

(lldb) bt* thread #1, name = 'corerun', stop reason = breakpoint 1.1  * frame #0: 0x000055555556c420 corerun`main(argc=<unavailable>, argv=<unavailable>) at corerun.cpp:616    frame #1: 0x00007ffff7829d90 libc.so.6`__libc_start_call_main(main=(corerun`main at corerun.cpp:616), argc=2, argv=0x00007fffffffde78) at libc_start_call_main.h:58:16    frame #2: 0x00007ffff7829e40 libc.so.6`__libc_start_main_impl(main=(corerun`main at corerun.cpp:616), argc=2, argv=0x00007fffffffde78, init=0x00007ffff7ffd040, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=0x00007fffffffde68) at libc-start.c:392:3    frame #3: 0x000055555556bf45 corerun`_start + 37

不同点在.NET的main是在corerun.(so/exe)的SC-corerun.cpp里面

// runtime-main/src/coreclr/hosts/corerun/corerun.cppint MAIN(const int argc, const char_t* argv[]){    configuration config{};    if (!parse_args(argc, argv, config))        return EXIT_FAILURE;
if (config.self_test) return self_test();
int exit_code = run(config); return exit_code;}

往期精彩回顾

Rustc Compile过程+线程

.NET9 Pre7 DATAS+Rustc Compile线程续


江湖评谈
记录,分享,自由。
 最新文章