On x86_64
gli argomenti vengono passati nei registri %rdi
, %rsi
, ecc. (calling convention).
Pertanto, quando si entra nella cornice main
, si dovrebbe essere in grado di:
(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
Purtroppo, GDB normalmente non ripristinare $rdi
e $rsi
quando si passa fotogrammi. Quindi questo esempio non funziona:
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
quindi dovrete lavorare ancora un po '...
Che cosa si può fare è utilizzare la conoscenza di come stack di Linux è istituito presso process startup , combinato con il fatto che GDB sarà ripristinare stack pointer:
(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.
Così ora ci aspettiamo che l'originale %rsp
essere $rsp+8
(un P OP, due spinte), ma potrebbe essere a $rsp+16
dovuta all'allineamento che è stato fatto a istruzioni 0x0000000000400449
Vediamo cosa c'è ...
(gdb) x/8gx $rsp+8
0x7fffbe5d5e98: 0x000000000000001c 0x0000000000000004
0x7fffbe5d5ea8: 0x00007fffbe5d6eb8 0x00007fffbe5d6ec0
0x7fffbe5d5eb8: 0x00007fffbe5d6ec4 0x00007fffbe5d6ec8
0x7fffbe5d5ec8: 0x0000000000000000 0x00007fffbe5d6ecf
che sembra essere molto promettente: 4 (sospetta argc), seguita di 4 puntatori non NULL, seguito da NULL.
Vediamo se che filtra fuori:
(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"
In effetti, è così che ho invocato il binario. Come verifica finale di integrità, l'aspetto 0x00007fffbe5d6ecf
sembra parte dell'ambiente?
(gdb) x/s 0x00007fffbe5d6f3f
0x7fffbe5d6f3f: "SSH_AGENT_PID=2874"
Sì, questo è l'inizio (o la fine) dell'ambiente.
Quindi ci avete.
Note finali: se GDB non ha stampato così tanto <optimized out>
, avremmo potuto recuperare argc
e argv
dal fotogramma n. C'è lavoro da entrambe le parti GDB e GCC per rendere GDB stampare molto meno di "ottimizzato out" ...
Inoltre, durante il caricamento del nucleo, le mie stampe GDB:
Core was generated by `./a.out foo bar bazzzz'.
negando la necessità di questo tutto l'esercizio Tuttavia, questo funziona solo per linee di comando brevi, mentre la soluzione sopra funzionerà per qualsiasi riga di comando.
Per quanto ne so, la convenzione di chiamata x86_64 passa i primi pochi argomenti nei registri, quindi il valore di argv non viene necessariamente salvato da nessuna parte. – Neil