我有以下程序:
SECTION .text
main:
mov ebx, 10
mov ecx, 50
repeat:
inc ebx
loop repeat
mov eax, ebx
ret
当该程序运行时,它会按预期返回60。但是,如果删除最后的ret
语句,程序运行正常,但随后返回0.为什么会这样?
答案 0 :(得分:6)
当您退出“ret”时,计算机执行最后一次“move eax,ebx”,然后执行计算机内存中接下来发生的任何事情。
我很惊讶你没有得到非法指令/访问;这将是最常见的回应。在捣毁寄存器之后,垃圾指令就像回归一样。
它还有点不清楚你的意思是“返回60”。你的意思是作为命令提示符的值?很明显,你的程序无法防范非法指令陷阱。 当你在没有防御的情况下获得这样的陷阱时,Windows所做的事情对我来说并不清楚;我从经验中知道,当我这样做时,Windows往往只是终止我的进程,我得到一些随机的退出状态。 “0”可能是这样的状态。
尝试添加:
mov byte ptr[eax], 0
答案 1 :(得分:2)
因为它会通过并运行链接器后面的下一个函数。
请参阅我对Ira的答案的评论,为什么你的代码不会崩溃。如果您没有与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可执行文件中。文件的内存映射只发生在页面粒度上,因此它最终都被映射为可执行指令。 (机器代码不会从磁盘高速缓存中复制出来用于可执行文件;内存只是将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
剥离二进制文件仍会使其成为段错误,但使用不同的指令。耶?
# 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
指令是我在hexdump中包含的第一行中的b8 0a 00 00 00
。我认为以下00 2e ...
是一个ELF数据结构,可能是部分或某事的索引。作为x86指令,它是add %ch,(%rsi)
,因为%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)