虽然我尝试使用brk(int 0x80与45 in%rax)在程序集中实现一个简单的内存管理器程序并按顺序打印块,但我一直在遇到段错误。过了一会儿我只能重现错误,但不知道为什么会发生这种情况:
.section .data
helloworld:
.ascii "hello world"
.section .text
.globl _start
_start:
push %rbp
mov %rsp, %rbp
movq $45, %rax
movq $0, %rbx #brk(0) should just return the current break of the programm
int $0x80
#incq %rax #segfault
#addq $1, %rax #segfault
movq $0, %rax #works fine?
#addq $1, %rax #segfault again?
movq $helloworld, %rdi
call printf
movq $1, %rax #exit
int $0x80
在这里的例子中,如果注释行被取消注释,我有一个段错误,但是一些命令(比如de movq $ 0,%rax)工作得很好。在我的另一个程序中,第一个printf工作,但第三个崩溃...寻找其他问题,我听说printf有时会分配一些内存,并且不应该使用brk,因为在这种情况下它会破坏堆什么......我很困惑,有没有人知道这件事?
编辑:我刚刚发现,对于printf来说,你需要%rax = 0。答案 0 :(得分:3)
您当前的问题是您使用了错误的系统电话号码:45是SYS_brk
上的i*86
,但x86_64
上的{45}是SYS_recvfrom
。同样,SYS_exit
上的x86_64
为60。您可以找到正确的数字:
echo "#include <syscall.h>" | gcc -xc - -E -dD | egrep '__NR_(brk|exit) '
#define __NR_brk 12
#define __NR_exit 60
echo "#include <syscall.h>" | gcc -xc - -E -dD -m32 | egrep '__NR_(brk|exit) '
#define __NR_exit 1
#define __NR_brk 45
您的第二个问题是int $0x80
不是在x86_64
上调用系统调用的标准方法;你应该使用syscall
代替。
正如nneonneo正确指出的那样,你的第三个问题是x86_64
上的系统调用参数在%rdi
,%rsi
等传递,而不是%rbx
通过上述更改,我得到(使用printf注释掉):
strace ./a.out
execve("./a.out", ["./a.out"], [/* 68 vars */]) = 0
brk(0) = 0x15b9000
_exit(0) = ?
将其与原始程序(不带printf)进行比较:
execve("./a.out", ["./a.out"], [/* 68 vars */]) = 0
recvfrom(0, NULL, 0, 0, NULL, NULL) = 29184000
write(6291768, NULL, 0 <unfinished ... exit status 0>
答案 1 :(得分:2)
你的下一个问题是x86_64系统调用调用约定使用%rdi,%rsi,%rdx,%r10,%r8和%r9按顺序传递最多六个参数(而不是%ebx,%ecx ,%edx,%esi,%edi,x86_32中使用的%ebp。
因此,您必须将brk
的第一个参数放在%rdi:
movq $12, %rax
movq $0, %rdi
syscall
在支持它的内核上,使用int 0x80
实际上会调用具有32位调用号和寄存器分配的32位系统调用(为了兼容性)。如果您有类似的内核,那么您的代码片段应该有效。如果你没有,那么你的程序会在执行int 0x80
后立即死亡。
答案 2 :(得分:2)
由于没有其他人指出这一点:不,使用非零参数或sbrk
来调用brk
是不安全的,除非调用函数是(的一部分)和仅在当前可执行映像中实现malloc
。更具体地说,如果从brk
实现之外更改malloc
区域的大小,则很可能会损坏malloc
的内部数据结构,这会导致程序崩溃下次使用malloc
或free
时。此外,任何 C库函数不在async-signal-safe functions的短列表上(列表接近该文档的末尾),允许在引擎盖下调用malloc
,并且printf
尤其肯定会在Linux上做到这一点。
P.S。即使您使用汇编语言编写代码,也应该使用C库的填充程序进行系统调用;这样可以使您免受x86-32和x86-64之间系统调用数量的差异,并且还可以帮助其他方式,例如设置errno
并确保使用最有效的陷阱序列处理器。
答案 3 :(得分:1)
编辑:我刚刚发现,对于printf来说,你需要%rax = 0。 “
这就达到了目的。除了x86_64兼容问题外,必须将%rax设置为printf()的正确值才能正常工作。
根据x86_64 ABI文档(http://x86-64.org/documentation/abi.pdf),第3.5.7节变量参数列表:
当调用带有变量参数的函数时,%rax必须设置为向量寄存器中传递给函数的浮点参数的总数。
由于printf是一个var_arg函数,你需要明确地告诉C库你不想传递任何浮点类型,也就是说。 mov 0,%rax。