Ubuntu 20.04 glibc 2.31 RTFS + GDB
glibc przeprowadza pewną konfigurację przed main, aby niektóre jego funkcje działały. Spróbujmy znaleźć kod źródłowy tego.
cześć, c
#include <stdio.h>
int main() {
puts("hello");
return 0;
}
Kompiluj i debuguj:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o hello.out hello.c
gdb hello.out
Teraz w GDB:
b main
r
bt -past-main
daje:
#0 main () at hello.c:3
#1 0x00007ffff7dc60b3 in __libc_start_main (main=0x555555555149 <main()>, argc=1, argv=0x7fffffffbfb8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffbfa8) at ../csu/libc-start.c:308
#2 0x000055555555508e in _start ()
Zawiera już linię wywołującego main: https://github.com/cirosantilli/glibc/blob/glibc-2.31/csu/libc-start.c#L308.
Funkcja ma miliard ifdef, jak można się spodziewać z poziomu starszego / ogólności glibc, ale niektóre kluczowe elementy, które wydają się dla nas działać, należy uprościć do:
# define LIBC_START_MAIN __libc_start_main
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char **),
int argc, char **argv,
{
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
exit (result);
}
Wcześniej __libc_start_main
są już w _start
, co dodając gcc -Wl,--verbose
wiemy, jest punktem wejścia, ponieważ skrypt linkera zawiera:
ENTRY(_start)
i dlatego jest faktycznie pierwszą instrukcją wykonywaną po zakończeniu dynamicznego ładowania.
Aby potwierdzić to w GDB, pozbywamy się dynamicznego modułu ładującego, kompilując z -static
:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o hello.out hello.c
gdb hello.out
a następnie zatrzymaj GDB na pierwszej instrukcji wykonanej za pomocąstarti
i wydrukuj pierwsze instrukcje :
starti
display/12i $pc
co daje:
=> 0x401c10 <_start>: endbr64
0x401c14 <_start+4>: xor %ebp,%ebp
0x401c16 <_start+6>: mov %rdx,%r9
0x401c19 <_start+9>: pop %rsi
0x401c1a <_start+10>: mov %rsp,%rdx
0x401c1d <_start+13>: and $0xfffffffffffffff0,%rsp
0x401c21 <_start+17>: push %rax
0x401c22 <_start+18>: push %rsp
0x401c23 <_start+19>: mov $0x402dd0,%r8
0x401c2a <_start+26>: mov $0x402d30,%rcx
0x401c31 <_start+33>: mov $0x401d35,%rdi
0x401c38 <_start+40>: addr32 callq 0x4020d0 <__libc_start_main>
Po przeszukaniu źródła _start
i skupieniu się na trafieniach x86_64 widzimy, że wydaje się to odpowiadać sysdeps/x86_64/start.S:58
:
ENTRY (_start)
cfi_undefined (rip)
xorl %ebp, %ebp
mov %RDX_LP, %R9_LP
#ifdef __ILP32__
mov (%rsp), %esi
add $4, %esp
#else
popq %rsi
#endif
mov %RSP_LP, %RDX_LP
and $~15, %RSP_LP
pushq %rax
pushq %rsp
#ifdef PIC
mov __libc_csu_fini@GOTPCREL(%rip), %R8_LP
mov __libc_csu_init@GOTPCREL(%rip), %RCX_LP
mov main@GOTPCREL(%rip), %RDI_LP
#else
mov $__libc_csu_fini, %R8_LP
mov $__libc_csu_init, %RCX_LP
mov $main, %RDI_LP
#endif
call *__libc_start_main@GOTPCREL(%rip)
co kończy się dzwonieniem __libc_start_main
zgodnie z oczekiwaniami.
Niestety -static
sprawia, że bt
od main
nie pokazuje tak dużo informacji:
#0 main () at hello.c:3
#1 0x0000000000402560 in __libc_start_main ()
#2 0x0000000000401c3e in _start ()
Jeśli usuniemy -static
i zaczniemy od starti
, otrzymamy zamiast tego:
=> 0x7ffff7fd0100 <_start>: mov %rsp,%rdi
0x7ffff7fd0103 <_start+3>: callq 0x7ffff7fd0df0 <_dl_start>
0x7ffff7fd0108 <_dl_start_user>: mov %rax,%r12
0x7ffff7fd010b <_dl_start_user+3>: mov 0x2c4e7(%rip),%eax # 0x7ffff7ffc5f8 <_dl_skip_args>
0x7ffff7fd0111 <_dl_start_user+9>: pop %rdx
Przez grepowanie źródła _dl_start_user
wydaje się pochodzić z sysdeps / x86_64 / dl-machine.h: L147
#define RTLD_START asm ("\n\
.text\n\
.align 16\n\
.globl _start\n\
.globl _dl_start_user\n\
_start:\n\
movq %rsp, %rdi\n\
call _dl_start\n\
_dl_start_user:\n\
# Save the user entry point address in %r12.\n\
movq %rax, %r12\n\
# See if we were run as a command with the executable file\n\
# name as an extra leading argument.\n\
movl _dl_skip_args(%rip), %eax\n\
# Pop the original argument count.\n\
popq %rdx\n\
i przypuszczalnie jest to punkt wejścia dynamicznego modułu ładującego.
Jeśli przerwiemy _start
i będziemy kontynuować, wydaje się, że kończy się to w tym samym miejscu, w którym używaliśmy -static
, który następnie wywołuje __libc_start_main
.
Kiedy zamiast tego spróbuję programu C ++:
hello.cpp
#include <iostream>
int main() {
std::cout << "hello" << std::endl;
}
z:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o hello.out hello.cpp
wyniki są w zasadzie takie same, np. ślad w miejscu main
jest dokładnie taki sam.
Myślę, że kompilator C ++ po prostu wywołuje przechwyty, aby osiągnąć jakąkolwiek specyficzną funkcjonalność C ++, a rzeczy są dość dobrze uwzględnione w C / C ++.
DO ZROBIENIA:
main()
jako "rozpoczęcie programu"