x86_64在brk调用后打印segfault



当我尝试使用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_exitx86_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时就会死亡。

由于没有其他人指出:不,用非零参数调用sbrkbrk是不安全的,除非调用函数是当前可执行映像中malloc的唯一实现的一部分。更具体地说,如果从malloc实现之外更改brk区域的大小,则很可能损坏malloc的内部数据结构,这将导致程序在下次使用mallocfree时崩溃。此外,任何不在异步信号安全函数短列表中的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。

相关内容

  • 没有找到相关文章

最新更新