3

当我尝试使用 brk (int 0x80 with 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

4 回答 4

3

您的直接问题是您使用了错误的系统调用号: 45 是SYS_brkon i*86,但 on x86_64 45 是SYS_recvfrom。同样,SYS_exit是 60 on x86_64。您可以像这样找到正确的数字:

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>
于 2012-10-07T21:43:13.980 回答
2

您的下一个问题是 x86_64 系统调用调用约定使用 %rdi、%rsi、%rdx、%r10、%r8 和 %r9 以该顺序最多传递六个参数(而不是 %ebx、%ecx、%edx , %esi, %edi, %ebp 在 x86_32 中使用)。

因此,您必须将brk' 的第一个参数放在 %rdi 中:

movq    $12, %rax  
movq    $0, %rdi
syscall

在支持它的内核上, usingint 0x80实际上会调用具有 32 位调用号和寄存器分配的 32 位系统调用(为了兼容性)。如果您有这样的内核,那么您的代码片段应该可以工作。如果你不这样做,那么你的程序一执行就会死掉int 0x80

于 2012-10-07T21:53:08.343 回答
2

sbrk由于没有其他人指出这一点:不,使用非零参数调用是不安全的,或者根本不安全brk,除非调用函数是malloc当前可执行映像中唯一且唯一的实现。更具体地说,如果您brk从实现外部更改区域的大小malloc,您很可能会损坏malloc的内部数据结构,这将导致您的程序在下次使用malloc或时崩溃free。此外,任何不在异步信号安全函数短列表中的 C 库函数(列表在该文档的末尾附近)都可以malloc在后台调用,printf尤其是在 Linux 上确实可以调用。

PS 即使您使用汇编语言进行编码,您也确实应该使用 C 库的 shim 进行系统调用;这将使您免受 x86-32 和 x86-64 之间系统调用号的差异的影响,并且在其他方​​面也有帮助,例如设置errno和确保您使用处理器可用的最有效的陷阱序列。

于 2012-11-28T22:53:33.223 回答
1

编辑:我刚刚发现要让 printf 工作,你需要 %rax=0。”</p>

这恰到好处。除了 x86_64 兼容问题之外,必须将 %rax 设置为正确的值才能使 printf() 正常工作。

根据 x86_64 ABI 文档(http://x86-64.org/documentation/abi.pdf),第 3.5.7 节变量参数列表:

当调用采用变量参数的函数时,%rax 必须设置为向量寄存器中传递给函数的浮点参数的总数。

由于 printf 是一个 var_arg 函数,因此您需要明确告诉 C 库您不想将任何浮点类型传递给它,也就是。移动 0,%rax。

于 2014-07-28T05:48:41.110 回答