我正在学习amd64汇编程序,并试图实现一个简单的Unix过滤器。由于未知的原因,甚至简化为最低版本(如下代码),它也会随机崩溃。
我试图在GNU调试器(gdb)中调试该程序。在gdb的默认配置下,该程序运行良好,但是如果启用地址随机化(set disable-randomization off
),该程序将开始崩溃(SIGSEGV)。清单中标记了有问题的说明:
format ELF64 executable
sys_read = 0
sys_write = 1
sys_exit = 60
entry $
foo:
label .inbuf at rbp - 65536
label .outbuf at .inbuf - 65536
label .endvars at .outbuf
mov rbp, rsp
mov rax, sys_read
mov rdi, 0
lea rsi, [.inbuf]
mov rdx, 65536
syscall
xor ebx, ebx
cmp eax, ebx
jl .read_error
jz .exit
mov r8, rax ; r8 - count of valid bytes in input buffer
xor r9, r9 ; r9 - index of byte in input buffer, that is being processed.
xor r10, r10 ; r10 - index of next free position in output buffer.
.next_byte:
cmp r9, r8
jg .exit
mov al, [.inbuf + r9]
mov [.outbuf + r10], al ;; SIGSEGV here in GDB
inc r10
inc r9
jmp .next_byte
.read_error:
mov rax, sys_exit
mov rdi, 1
syscall
.exit:
mov rax, sys_write
mov rdi, 1
lea rsi, [.outbuf]
mov rdx, r10
syscall
mov rax, sys_exit
xor rdi, rdi
syscall
该程序旨在从stdin读取最多64kB的数据,将其存储在堆栈中的缓冲区中,将读取的数据逐字节复制到输出缓冲区中,并将输出缓冲区的内容写入标准输出流。本质上,它应该表现为cat
的受限版本。
在我的计算机上,它要么按预期工作,要么因SIGSEGV崩溃,大约成功运行1次至崩溃4次。
答案 0 :(得分:5)
sub rsp, <size>
要在触摸堆栈之前保留堆栈空间,如果您使用的RSP低于128字节。
当它崩溃时,请查看您的进程内存映射。您可能使用的内存远低于RSP,以至于内核不会增加堆栈映射,因此,这是对未映射页面的普通访问=无效页面错误=>内核提供了SIGSEGV。
(ABI仅定义了一个128字节的红色区域,但实际上,唯一可以破坏该内存的是信号处理程序(您没有安装)或使用程序的运行print some_func()
的GDB堆栈以调用程序中的函数。)
通常,Linux非常愿意在不接触中间页面的情况下增加堆栈映射,但是显然可以检查RSP的值。通常,您移动RSP而不是仅仅使用远低于堆栈指针的内存(因为无法保证它的安全性)。参见How is Stack memory allocated when using 'push' or 'sub' x86 instructions?
另一个重复项:Which exception can be generated when subtracting ESP or RSP register? (stack growing),在接触新的堆栈存储器之前使用sub rsp, 5555555
就足够了。
Stack ASLR可能在相对于页面边界的不同位置启动RSP ,因此有时您可能只是勉强摆脱了它。 Linux最初会映射132kiB的堆栈空间,其中包括环境空间和进入_start
时堆栈上的args。您的128kiB非常接近此值,因此有时随机工作是完全合理的。
顺便说一句,实际上没有理由在用户空间中复制内存,尤其是一次不复制1个字节。只需将相同的地址传递到write
。
或者如果可能的话至少进行过滤,这样您的缓存占用空间就会较小。
此外,加载字节的常规方法是movzx eax, byte [mem]
。仅当您特别想与RAX的旧值合并时,才使用mov al, [mem]
。在某些CPU上,mov
到al
对旧值有错误的依赖关系,您可以通过写入完整的寄存器来破坏旧的值。
顺便说一句,如果您的程序总是使用此空间,则最好在BSS中静态分配它。如果您选择汇编位置相关的(非PIE)可执行文件,则可以实现更高效的索引寻址。
答案 1 :(得分:4)
amd64中的红色区域只有128个字节长,但是您使用的rsp以下是131072个字节。向下移动堆栈指针,以包含要存储在堆栈上的缓冲区。