Почему не возвращается значение, если функция не имеет четкости, используйте '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)
Другие вопросы по тегам