Вызов вашего кода exit ()
заканчивается тем, что связывается с функцией библиотеки C (libc) exit ()
, которая на самом деле может не выполнять int $ 0x80
.
Вызов из вашего кода при вызове функции exit ()
фактически компилируется как инструкция call
в Таблицу Связи Программы или PLT. Динамический компоновщик времени выполнения заботится о отображении файла /usr/lib/libc.so
в память. Это библиотека C. Динамический компоновщик во время выполнения также исправляет записи в PLT, чтобы в конечном итоге вызвать код, отображаемый из /usr/lib/libc.so
.
Насколько я могу судить (я использую Arch linux), ваши вторые 3 инструкции - это запись PLT, которая gdb
вызывает "exit @ plt", когда я вхожу в нее. jmp 0x80482c0
переходит на другой адрес, который, наконец, переходит в код libc.so
.
Вы можете продемонстрировать это себе с помощью довольно сложного упражнения. Во-первых, у вас есть адрес записи в таблице PLT, независимо от того, что gdb
сообщает вам, это адрес jmp * 0x8049698
- это адрес «exit @ plt». На моем Linux-сервере с архитектурой x86:
(gdb) disassemble 0x8048310,+20
Dump of assembler code from 0x8048310 to 0x8048324:
0x08048310 <exit@plt+0>: jmp *0x80496e8
0x08048316 <exit@plt+6>: push $0x10
0x0804831b <exit@plt+11>: jmp 0x80482e0
Затем выполните readelf -e _program_> elf.headers
. Посмотрите файл elf.headers
. Вы найдете строку текста с надписью «Заголовки разделов:» Где-то в заголовках разделов вы увидите что-то вроде этого:
[ 9] .rel.dyn REL 08048290 000290 000008 08 A 5 0 4
[10] .rel.plt REL 08048298 000298 000020 08 AI 5 12 4
[11] .init PROGBITS 080482b8 0002b8 000023 00 AX 0 0 4
[12] .plt PROGBITS 080482e0 0002e0 000050 04 AX 0 0 16
«exit @ plt» находится по адресу 0x8048310. Это прямо в разделе ".rel.plt". ".rel.plt", вероятно, означает "таблица привязки программы перемещения".
Теперь мы переходим к той части, где int $ 0x80
может даже не существовать. Выполните ldd _program_
. Опять же, Arch linux x86 говорит следующее:
linux-gate.so.1 (0xb77d9000)
libc.so.6 => /usr/lib/libc.so.6 (0xb7603000)
/lib/ld-linux.so.2 (0xb77da000)
Видите "linux-gate.so.1"? Он содержит фактический код, выполняющий системный вызов. Это может быть int $ 0x80
, или это может быть инструкция sysenter
, либо что-то еще. Предполагается, что ядро Linux помещает «небольшую разделяемую библиотеку» в адресное пространство процесса с реальным кодом, а затем передает адрес этой небольшой разделяемой библиотеки в «вспомогательный вектор» ELF. У man vdso
некоторые подробности. Динамический компоновщик, /lib/ld-linux.so.2
знает детали вспомогательного вектора ELF и в конечном итоге помещает адрес linx-gate.so.1
в PLT где-то, поэтому фактические вызовы функций C могут в конечном итоге привести к эффективным системным вызовам.
Если вы выполните несколько вызовов ldd _program_
, вы увидите, что адрес linux-gate.so.1
отличается от вызова к вызову. На самом деле ядро не помещает вершину стека в один и тот же адрес каждый раз, чтобы попытаться запутать вредоносное ПО, которому необходимо знать расположение стека для выполнения своего собственного кода.