Обертка вокруг GNU ld, которая не поддерживает скрипты компоновщика
. Некоторые .so-файлы на самом деле являются GNU ld ссылками-компоновщиками , например. Файл libtbb.so является текстовым файлом ASCII с этим содержимым:
INPUT (libtbb.so.2)
Некоторые более сложные сборки могут не поддерживать это. Например, если вы включаете -v в параметры компилятора, вы можете видеть, что mainwin gcc wrapper mwdip отбрасывает файлы команд сценария компоновщика в подробном списке результатов библиотек для ссылки. Простая работа вместо файла командной строки ввода сценария компоновщика вместо копии (или символической ссылки), например
cp libtbb.so.2 libtbb.so
Или вы можете заменить аргумент -l полным путем .so, например вместо -ltbb
сделать /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
Когда вы покидаете «ret», компьютер выполняет последний «move eax, ebx», а затем выполняет все, что будет дальше в памяти компьютера.
Я удивлен, что вы не получить незаконную инструкцию / доступ; это будет самый общий ответ. Как-то инструкция мусора действует как возврат, после разгрома регистров.
Его также немного непонятно, что вы подразумеваете под «возвращает 60». Вы имеете в виду значение командной строки? Понятно, что ваша программа не защищает от незаконных ловушек. Что делает Windows, когда вы получаете такую ловушку без защиты, мне непонятно; Я знаю по опыту, когда я делаю, что Windows имеет тенденцию просто прекратить мой процесс, и я получаю некоторый случайный статус выхода. «0» может быть таким статусом.
Попробуйте добавить:
mov byte ptr[eax], 0
перед командой «ret»; это приведет к нелегальной памяти. Вы сообщаете, какой статус вы получаете. Это не удивило бы меня, если бы вы получили нулевой результат в этом случае.
Поскольку он проваливается и запускает следующую функцию, компоновщик помещает после нее.
См. мои комментарии к ответу Ира, почему ваш код не просто сбой. Если вы не связывались с кодом запуска библиотеки времени выполнения C (т. Е. У вас только _start
вместо main
), выполнение ударит по нескольким некодерам, а также к ошибкам в незаконной инструкции или к попытке получить доступ к немаркированной памяти. См. Ниже.
Разберите свой последний бинарный файл, чтобы узнать, что произошло. Когда я попробовал это, я обнаружил, что компоновщик помещает main
между стандартными запускаемыми функциями запуска C frame_dummy
и __libc_csu_init
. Это
00000000004004f6 <main>:
4004f6: b8 0a 00 00 00 mov $0xa,%eax
4004fb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000400500 <__libc_csu_init>:
400500: 41 57 push %r15
400502: 41 56 push %r14
400504: 41 89 ff mov %edi,%r15d
400507: 41 55 push %r13
... a bunch more code that eventually returns.
Вы могли узнать, что происходит с отладчиком, инструкциями с одним шагом.
BTW, если вы сделали автономный двоичный файл, либо с gcc -static -nostartfiles
или путем сборки (as foo.s
) / linking (ld foo.o
), вы получите файл размером 888 байт, содержащий одну команду, а остальные будут заголовками ELF и т. д.
$ cat > fallthrough.s <<EOF
.globl main
main:
.globl _start
_start:
mov $10, %eax
# fall through
EOF
$ gcc -g -static -nostartfiles fallthrough.s -o fallthrough
$ gdb fallthrough
(gdb) b _start # breakpoint
(gdb) r # run the prog
(gdb) disassemble /r _start, _start+40
Dump of assembler code from 0x4000d4 to 0x4000fc:
=> 0x00000000004000d4 <main+0>: b8 0a 00 00 00 mov $0xa,%eax
0x00000000004000d9: 00 00 add %al,(%rax)
0x00000000004000db: 00 00 add %al,(%rax)
0x00000000004000dd: 00 00 add %al,(%rax)
0x00000000004000df: 00 2c 00 add %ch,(%rax,%rax,1)
0x00000000004000e2: 00 00 add %al,(%rax)
0x00000000004000e4: 02 00 add (%rax),%al
0x00000000004000e6: 00 00 add %al,(%rax)
0x00000000004000e8: 00 00 add %al,(%rax)
0x00000000004000ea: 08 00 or %al,(%rax)
0x00000000004000ec: 00 00 add %al,(%rax)
0x00000000004000ee: 00 00 add %al,(%rax)
0x00000000004000f0: d4 (bad)
0x00000000004000f1: 00 40 00 add %al,0x0(%rax)
...
(gdb) layout asm #text-window mode. layout reg is great for single-stepping, BTW.
(gdb) si # step instruction
0x00000000004000d9 in ?? ()
(gdb) si
Program received signal SIGSEGV, Segmentation fault.
0x00000000004000d9 in ?? ()
(gdb) c
Continuing.
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
00
байты, которые следуют за вашим кодом в памяти, также находятся в исполняемом элементе ELF. сопоставление памяти с файлами происходит только с гранулярностью страницы, поэтому все они отображаются в виде исполняемых инструкций. (машинный код не копируется из кэша диска для исполняемых файлов, а память просто отображается с разрешением read + execute в процесс, который execve(2)
является двоичным.)
$ objdump -s a.out
a.out: file format elf64-x86-64
Contents of section .note.gnu.build-id:
4000b0 04000000 14000000 03000000 474e5500 ............GNU.
4000c0 db31c97d 55481b9a 57110753 1786dd1a .1.}UH..W..S....
4000d0 11679958 .g.X
Contents of section .text:
4000d4 b80a0000 00 .....
Contents of section .debug_aranges:
0000 2c000000 02000000 00000800 00000000 ,...............
0010 d4004000 00000000 05000000 00000000 ..@.............
0020 00000000 00000000 00000000 00000000 ................
...
$ size a.out
text data bss dec hex filename
41 0 0 41 29 a.out
Удаление двоичного кода все еще делает это segfault, но с другой инструкцией. Yay?
# b _start would be b *0x4000d4 without symbols.
(gdb) r
...
Program received signal SIGSEGV, Segmentation fault.
0x00000000004000d9 in ?? ()
(gdb) disassemble /r $rip-5, $rip +15
Dump of assembler code from 0x4000d4 to 0x4000e8:
0x00000000004000d4: b8 0a 00 00 00 mov $0xa,%eax
=> 0x00000000004000d9: 00 2e add %ch,(%rsi)
0x00000000004000db: 73 68 jae 0x400145
0x00000000004000dd: 73 74 jae 0x400153
0x00000000004000df: 72 74 jb 0x400155
0x00000000004000e1: 61 (bad)
0x00000000004000e2: 62 (bad)
0x00000000004000e3: 00 2e add %ch,(%rsi)
0x00000000004000e5: 6e outsb %ds:(%rsi),(%dx)
0x00000000004000e6: 6f outsl %ds:(%rsi),(%dx)
0x00000000004000e7: 74 65 je 0x40014e
$ hexdump -C a.out
...
000000d0 11 67 99 58 b8 0a 00 00 00 00 2e 73 68 73 74 72 |.g.X.......shstr|
000000e0 74 61 62 00 2e 6e 6f 74 65 2e 67 6e 75 2e 62 75 |tab..note.gnu.bu|
000000f0 69 6c 64 2d 69 64 00 2e 74 65 78 74 00 00 00 00 |ild-id..text....|
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
Наша команда mov
- это b8 0a 00 00 00
в первой строке, включенной в hexdump. Я думаю, что следующая 00 2e ...
- это структура данных ELF, вероятно, индекс разделов или что-то еще. Как инструкция x86, это add %ch,(%rsi)
, который segfaults, потому что %rsi
не указывает на записываемую память. (ABI говорит, что регистры, отличные от указателя стека, не определены в записи процесса, но Linux решает их обнулить в загрузчике ELF, чтобы избежать утечки данных ядра. %rsi
не указывает на записываемую память, и процесс, вероятно, не есть.)
Итак, что, если вы добавили сюда возврат? Нет, нет ничего, чтобы вернуть в . Стек содержит указатели на переменные среды arg процесса. Вы должны сделать системный вызов exit
.
.section .text
.globl _start
_start:
xor %edi, %edi
mov $231, %eax # exit(0)
syscall
# movl $1, %eax # The 32bit ABI works even for processes in long mode, BTW.
# int $0x80 # exit(edx)