В Linux знакомые переменные argc
и argv
из C всегда передаются в стеке ядром, доступным даже для программ сборки, которые полностью автономны и не связаны с кодом запуска в библиотеке C. Это задокументировано в i386 System V ABI, наряду с другими сведения о среде запуска процесса (значения регистров, выравнивание стека).
В точке входа ELF (также известной как _start
) исполняемого файла x86 Linux:
- ESP указывает на
argc
- ESP + 4 указывает на
argv[0]
, начало массива. то есть значение, которое вы должны передать в main, поскольку char **argv
равно lea eax, [esp+4]
, а не mov eax, [esp+4]
)
Как программа на минимальном ассемблере получает argc и argv
Я покажу, как читать argv
и argc[0]
в GDB.
cmdline-x86.S
#include <sys/syscall.h>
.global _start
_start:
/* Cause a breakpoint trap */
int $0x03
/* exit_group(0) */
mov $SYS_exit_group, %eax
mov $0, %ebx
int $0x80
cmdline-x86.gdb
set confirm off
file cmdline-x86
run
# We'll regain control here after the breakpoint trap
printf "argc: %d\n", *(int*)$esp
printf "argv[0]: %s\n", ((char**)($esp + 4))[0]
quit
Пример сеанса
$ cc -nostdlib -g3 -m32 cmdline-x86.S -o cmdline-x86
$ gdb -q -x cmdline-x86.gdb cmdline-x86
<...>
Program received signal SIGTRAP, Trace/breakpoint trap.
_start () at cmdline-x86.S:8
8 mov $SYS_exit_group, %eax
argc: 1
argv[0]: /home/scottt/Dropbox/stackoverflow/cmdline-x86
Объяснение
- Я установил программную точку останова (
int $0x03
), чтобы программа возвращалась в отладчик сразу после точки входа ELF (_start
).
- I then used
printf
in the GDB script to print
argc
with the expression *(int*)$esp
argv
с выражением ((char**)($esp + 4))[0]
версия x86-64
Отличия минимальны:
- Замените ESP на RSP.
- Изменить размер адреса с 4 на 8
- Соответствовать различным соглашениям о вызовах системных вызовов Linux, когда мы вызываем
exit_group(0)
для правильного завершения процесса.
cmdline.S
#include <sys/syscall.h>
.global _start
_start:
/* Cause a breakpoint trap */
int $0x03
/* exit_group(0) */
mov $SYS_exit_group, %rax
mov $0, %rdi
syscall
cmdline.gdb
set confirm off
file cmdline
run
printf "argc: %d\n", *(int*)$rsp
printf "argv[0]: %s\n", ((char**)($rsp + 8))[0]
quit
Как обычные программы на C получают argc и argv
Вы можете разобрать _start
из обычной программы на C, чтобы увидеть, как она получает argc
и argv
из стека и передает их при вызове __libc_start_main
. Используя программу /bin/true
на моей машине x86-64 в качестве примера:
$ gdb -q /bin/true
Reading symbols from /usr/bin/true...Reading symbols from /usr/lib/debug/usr/bin/true.debug...done.
done.
(gdb) disassemble _start
Dump of assembler code for function _start:
0x0000000000401580 <+0>: xor %ebp,%ebp
0x0000000000401582 <+2>: mov %rdx,%r9
0x0000000000401585 <+5>: pop %rsi
0x0000000000401586 <+6>: mov %rsp,%rdx
0x0000000000401589 <+9>: and $0xfffffffffffffff0,%rsp
0x000000000040158d <+13>: push %rax
0x000000000040158e <+14>: push %rsp
0x000000000040158f <+15>: mov $0x404040,%r8
0x0000000000401596 <+22>: mov $0x403fb0,%rcx
0x000000000040159d <+29>: mov $0x4014c0,%rdi
0x00000000004015a4 <+36>: callq 0x401310 <__libc_start_main@plt>
0x00000000004015a9 <+41>: hlt
0x00000000004015aa <+42>: xchg %ax,%ax
0x00000000004015ac <+44>: nopl 0x0(%rax)
Первые три аргумента __libc_start_main()
:
- RDI: указатель на
main()
- RSI:
argc
, вы можете видеть, что это первое, что выскочило из стека
- RDX:
argv
, значение RSP сразу после извлечения argc
. (ubp_av
в источнике GLIBC)
_start для x86 очень похож:
Dump of assembler code for function _start:
0x0804842c <+0>: xor %ebp,%ebp
0x0804842e <+2>: pop %esi
0x0804842f <+3>: mov %esp,%ecx
0x08048431 <+5>: and $0xfffffff0,%esp
0x08048434 <+8>: push %eax
0x08048435 <+9>: push %esp
0x08048436 <+10>: push %edx
0x08048437 <+11>: push $0x80485e0
0x0804843c <+16>: push $0x8048570
0x08048441 <+21>: push %ecx
0x08048442 <+22>: push %esi
0x08048443 <+23>: push $0x80483d0
0x08048448 <+28>: call 0x80483b0 <__libc_start_main@plt>
0x0804844d <+33>: hlt
0x0804844e <+34>: xchg %ax,%ax
End of assembler dump.
person
scottt
schedule
23.05.2013