当我尝试使用brk(int 0x80,45in%rax)在汇编中实现一个简单的内存管理器程序并按顺序打印块时,我一直在使用segfault。过了一段时间,我只能重现错误,但不知道为什么会发生这种情况:
.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
在这里的例子中,如果注释的行被取消注释,我有一个segfault,但一些命令(如de movq$0,%rax)工作得很好。在我的另一个程序中,第一对printf工作,但第三个崩溃了。。。在寻找其他问题时,我听说printf有时会分配一些内存,不应该使用brk,因为在这种情况下,它会破坏堆或其他东西。。。我很困惑,有人知道吗?
编辑:我刚刚发现printf要工作,你需要%rax=0。
您的直接问题是使用了错误的系统呼叫号码:45是i*86
上的SYS_brk
,但在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>
您的下一个问题是x86_64系统调用约定使用%rdi、%ri、%rdx、%r10、%r8和%r9按该顺序传递最多六个参数(而不是x86_32中使用的%ebx、%ecx、%edx、%esi、%edi、%ebp)。
因此,您必须将brk
的第一个参数放在%rdi:中
movq $12, %rax
movq $0, %rdi
syscall
在支持它的内核上,使用int 0x80
实际上会调用具有32位调用号和寄存器分配的32位系统调用(为了兼容性)。如果你有一个这样的内核,那么你的代码片段应该已经工作了。如果你不这样做,那么你的程序在执行int 0x80
时就会死亡。
由于没有其他人指出:不,用非零参数调用sbrk
或brk
是不安全的,除非调用函数是当前可执行映像中malloc
的唯一实现的一部分。更具体地说,如果从malloc
实现之外更改brk
区域的大小,则很可能损坏malloc
的内部数据结构,这将导致程序在下次使用malloc
或free
时崩溃。此外,任何不在异步信号安全函数短列表中的C库函数(列表在该文档的末尾)都可以在后台调用malloc
,尤其是printf
在Linux上肯定会这样做。
附言:即使你是用汇编语言编码,你也应该使用C库的垫片来进行系统调用;这将使您免受x86-32和x86-64之间系统调用号差异的影响,并在其他方面有所帮助,如设置errno
和确保您使用处理器可用的最有效的陷阱序列。
EDIT:我刚刚发现,要使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。