Почему не возвращается значение, если функция не имеет четкости, используйте 'ret'
У меня есть следующая программа:
SECTION .text
main:
mov ebx, 10
mov ecx, 50
repeat:
inc ebx
loop repeat
mov eax, ebx
ret
Когда эта программа запускается, она возвращает 60, как и ожидалось. Однако, если вы бросите финал ret
Заявление, программа работает нормально, но затем возвращает 0. Почему это?
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.
Вы могли бы узнать, что происходит с отладчиком, пошаговые инструкции.
Кстати, если вы сделали автономный бинарный файл, либо с gcc -static -nostartfiles
или путем сборки (as foo.s
) / ссылка (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. Отображение памяти в файлах происходит только с гранулярностью страниц, поэтому все это отображается как исполняемые инструкции. (машинный код не копируется из дискового кэша для исполняемых файлов; память просто отображается с разрешением на чтение + выполнение в процессе, который 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, но с другой инструкцией. Ура?
# 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
не указывает на доступную для записи память, и процесс, вероятно, не имеет.)
Так что, если вы добавили сюда возврат? Нет, возвращаться не к чему. Стек содержит указатели на переменные среды args процесса. Вы должны сделать 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)