为什么我会得到"地址边界错误"从这段汇编代码

时间:2016-08-13 03:16:39

标签: assembly compiler-construction x86 x86-64

代码由我正在处理的编译器生成。但是我不知道为什么它会给我这个错误。没有注册冲突。当我尝试调用callq *%rbx我的程序立即崩溃时,我收到一条错误,说#34; 被SIGSEGV信号终止(地址边界错误)"

我知道程序似乎试图访问非法内存,但我无法分辨它的错误。有人可以给我一个提示吗?

编辑:已完成的汇编代码为here,我的编译器尝试编译的代码为here。 runtime.c是here

    .globl _main
_main:
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %r12
    pushq   %rbx
    pushq   %r14
    pushq   %r13
    subq    $0, %rsp
    movq    $16384, %rdi
    movq    $16, %rsi
    callq   _initialize
    movq    _rootstack_begin(%rip), %r15

    leaq    o13352(%rip),   %rbx
    leaq    z13351(%rip),   %rcx
    movq    $1, %rdi
    callq   *%rcx              <------ As noted in comments: z13351() clobbers %rbx
    movq    %rax,   %rcx
    movq    $20,    %rdi
    movq    %rcx,   %rsi
    callq   *%rbx       <-------------      (Address boundary error)

    ... 

1 个答案:

答案 0 :(得分:1)

您的代码段错误,因为%rbx在第一个函数返回后不再指向正确的位置。因此,您不必调用o13352,而是跳转到某个未映射的地址。代码获取并将SIGSEGV加载/存储在无效地址上。

你可能需要修复%rbx反叛,但这是一个单独的问题。这个问题的最佳答案是使用直接call insn 发出对命名符号的调用,而不是使用RIP相对LEA和间接CALL的复杂函数调用序列。 / p>

...               # set up args
call    z13351
...               # set up args
call    o13352

编译器生成起来更简单,更容易让人跟踪(在调试其他问题时很有帮助),并且使代码显着提高效率:不使用#n;&#39;浪费一个寄存器,更小的代码大小,更少的指令,并避免来自间接分支的分支错误预测(你应该将其用于跳转表的函数指针,但不能用于此)。

请参阅标记wiki中的链接,以获取更多性能和其他内容。 ESP。 Agner Fog's guides是如何编写创造良好和正确的asm的绝佳资源。当然,玩具编译器不会产生良好的代码,但了解什么是好的和什么是坏的是个好主意。 Agner Fog的指南可能也会帮助您更好地理解事物,这将有助于生成正确的代码,无论效率如何。

如果你想通过函数指针继续使用间接调用,即使没有必要:

根据您的评论,z15953不会保留%rbx 。 (或z13351或在您的代码更新中调用的任何内容)。假设这是一个错误,修复它也会解决这个问题,因为你问题中的代码看起来是正确的(但很讨厌)。

您没有指定编译器尝试为其生成代码的ABI,但我认为它是一个调用约定,其中%rbx应该被调用保存(也就是被调用者保存,也称为非-volatile)。因此,即使您将函数调用序列更改为使用常规直接CALL,修复该错误也可能是出于其他原因所必需的。

看起来您在生成的函数中推送/弹出%rbx以保存/恢复它。将此减少到尽可能简单的情况,仍然可以证明这个问题。您在问题中链接的代码太大,甚至无法包含在线,因此它显然甚至不接近Minimal, Complete, and Verifiable example(不,我对此不感兴趣很抱歉。你写了你的编译器,所以你可能比我更容易识别膨胀代码所做的模式以及找到可能相关的部分。)

由于您的功能仍能成功ret成功,这意味着您没有打破堆栈(即沿着不包含结尾{{{}的路径到达ret 1}}为s)。但也许你在堆栈上覆盖了pop的保存值?在使用rsp相对地址存储临时值之前,您是否在%rbx预留了足够的空间?

愚蠢的创可贴修复:

sub $size, %rsp之后安排第二个地址的lea

call

# your original:
leaq    o13352(%rip),   %rbx
leaq    z13351(%rip),   %rcx
...
callq   *%rcx
...                             # set up args
callq   *%rbx

这会通过重复使用相同的寄存器来保存寄存器,而不是为您要调用的每个功能使用不同的寄存器。 (这显然不会扩展到调用许多其他函数的函数。)