点击上方蓝字 江湖评谈设为关注/星标
前言
先看下rust-glibc/musl再看下.net-glibc。
rust-glibc
从linux内核态到用户态的第一个函数:
// https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/x86_64/start.S
ENTRY (_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. */
mov (%rsp), %esi /* Simulate popping 4-byte argument count. */
add $4, %esp
popq %rsi /* Pop the argument count. */
/* 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
mov main@GOTPCREL(%rip), %RDI_LP
mov $main, %RDI_LP
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 -b
rustc`_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.c
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
int argc, char **argv,
ElfW(auxv_t) *auxvec,
__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
, ElfW(auxv_t) *auxvec
)
{
//省略部分代码
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
}
这里的main是glibc-main,即是上面的rdi参数。由 C 编译器和链接器在编译和链接用户程序时自动生成的,ASM如下:
(lldb) di -b
rustc`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需要指定下:
musl主要是把glibc部分直接编译到了最终可执行的二进制文件里面去了
(lldb) di
hello-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
* frame
frame
frame
frame
不同点在于.NET的main是在corerun.(so/exe)的SC-corerun.cpp里面
// runtime-main/src/coreclr/hosts/corerun/corerun.cpp
int 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;
}
往期精彩回顾