brk调用后x86_64 printf段错误

时间:2012-10-06 14:32:55

标签: segmentation-fault printf x86-64 brk

虽然我尝试使用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。

4 个答案:

答案 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的内部数据结构,这会导致程序崩溃下次使用mallocfree时。此外,任何 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。