Na x86_64
argumenty są przekazywane w rejestrach %rdi
, %rsi
itp. (calling convention).
Dlatego, kiedy po wejściu do ramki main
, powinieneś być w stanie:
(gdb) p $rdi # == argc
(gdb) p (char**) $rsi # == argv
(gdb) set $argv = (char**)$rsi
(gdb) set $i = 0
(gdb) while $argv[$i]
> print $argv[$i++]
> end
Niestety, GDB nie będzie normalnie przywrócić $rdi
i $rsi
podczas przełączania ramek. Więc ten przykład nie działa:
cat t.c
#include <stdlib.h>
int bar() { abort(); }
int foo() { return bar(); }
int main()
{
foo();
return 0;
}
gcc t.c && ./a.out
Aborted (core dumped)
gdb -q ./a.out core
Core was generated by `./a.out'.
Program terminated with signal 6, Aborted.
#0 0x00007fdc8284aa75 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
64 ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
in ../nptl/sysdeps/unix/sysv/linux/raise.c
(gdb) bt
#0 0x00007fdc8284aa75 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x00007fdc8284e5c0 in *__GI_abort() at abort.c:92
#2 0x000000000040052d in bar()
#3 0x000000000040053b in foo()
#4 0x000000000040054b in main()
(gdb) fr 4
#4 0x000000000040054b in main()
(gdb) p $rdi
$1 = 5524 ### clearly not the right value
Więc trzeba pracować trochę więcej ...
Co może zrobić, to wykorzystać wiedzę o tym, jak Linux stos jest ustawiony na process startup w połączeniu z faktem, że GDB będzie przywrócenia wskaźnika stosu:
(gdb) set backtrace past-main
(gdb) bt
#0 0x00007ffff7a8da75 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1 0x00007ffff7a915c0 in *__GI_abort() at abort.c:92
#2 0x000000000040052d in bar()
#3 0x000000000040053b in foo()
#4 0x0000000000400556 in main()
#5 0x00007ffff7a78c4d in __libc_start_main (main=<optimized out>, argc=<optimized out>, ubp_av=<optimized out>, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdad8) at libc-start.c:226
#6 0x0000000000400469 in _start()
(gdb) frame 6
(gdb) disas
Dump of assembler code for function _start:
0x0000000000400440 <+0>: xor %ebp,%ebp
0x0000000000400442 <+2>: mov %rdx,%r9
0x0000000000400445 <+5>: pop %rsi
0x0000000000400446 <+6>: mov %rsp,%rdx
0x0000000000400449 <+9>: and $0xfffffffffffffff0,%rsp
0x000000000040044d <+13>: push %rax
0x000000000040044e <+14>: push %rsp
0x000000000040044f <+15>: mov $0x400560,%r8
0x0000000000400456 <+22>: mov $0x400570,%rcx
0x000000000040045d <+29>: mov $0x40053d,%rdi
0x0000000000400464 <+36>: callq 0x400428 <[email protected]>
=> 0x0000000000400469 <+41>: hlt
0x000000000040046a <+42>: nop
0x000000000040046b <+43>: nop
End of assembler dump.
Więc teraz oczekujemy oryginalny %rsp
być $rsp+8
(jeden P PO, dwie popycha), ale może to być co $rsp+16
powodu wyrównania że została sporządzona w instrukcji 0x0000000000400449
Zobaczmy co tam ...
(gdb) x/8gx $rsp+8
0x7fffbe5d5e98: 0x000000000000001c 0x0000000000000004
0x7fffbe5d5ea8: 0x00007fffbe5d6eb8 0x00007fffbe5d6ec0
0x7fffbe5d5eb8: 0x00007fffbe5d6ec4 0x00007fffbe5d6ec8
0x7fffbe5d5ec8: 0x0000000000000000 0x00007fffbe5d6ecf
To wygląda obiecująco: 4 (podejrzenie argc), a następnie przez 4 wskaźniki nie-NULL, po których następuje NULL.
Zobaczmy, czy to patelnie z:
(gdb) x/s 0x00007fffbe5d6eb8
0x7fffbe5d6eb8: "./a.out"
(gdb) x/s 0x00007fffbe5d6ec0
0x7fffbe5d6ec0: "foo"
(gdb) x/s 0x00007fffbe5d6ec4
0x7fffbe5d6ec4: "bar"
(gdb) x/s 0x00007fffbe5d6ec8
0x7fffbe5d6ec8: "bazzzz"
Istotnie, tak właśnie wywoływany binarny. Czy ostateczna kontrola poczytalności, czy 0x00007fffbe5d6ecf
wygląda jak część środowiska?
(gdb) x/s 0x00007fffbe5d6f3f
0x7fffbe5d6f3f: "SSH_AGENT_PID=2874"
Tak, to początek (lub koniec) środowiska.
Więc już go masz.
Ostateczne uwagi: jeśli GDB nie wydrukował tak dużo, to można by było odzyskać argc
i argv
z ramki # 5. Jest praca po obu stronach GDB i GCC, aby GDB drukować znacznie mniej „zoptymalizowane out” ...
Również podczas ładowania rdzenia, moi drukuje GDB:
Core was generated by `./a.out foo bar bazzzz'.
negując potrzebę tego całe ćwiczenie. Działa to jednak tylko dla krótkich linii poleceń, podczas gdy powyższe rozwiązanie działa dla dowolnej linii poleceń.
Z tego, co wiem, konwencja wywoływania x86_64 przekazuje kilka pierwszych argumentów w rejestrach, przez co wartość argv niekoniecznie jest zapisywana w dowolnym miejscu. – Neil