为什么我从这段汇编代码中得到 "address boundary error"

Why do I get "address boundary error" from this piece of assembly code

代码是由我正在使用的编译器生成的。但是我不知道为什么它会给我这个错误。没有寄存器冲突。当我尝试调用 callq *%rbx 时,我的程序立即崩溃,并收到一条错误消息:“ 被信号 SIGSEGV 终止(地址边界错误)

我知道程序似乎试图访问非法内存,但我不知道哪里错了。任何人都可以给我提示吗?

编辑:完成的汇编代码为here, and the code my compiler tries to compile is here. The runtime.c is here

    .globl _main
_main:
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %r12
    pushq   %rbx
    pushq   %r14
    pushq   %r13
    subq    [=11=], %rsp
    movq    384, %rdi
    movq    , %rsi
    callq   _initialize
    movq    _rootstack_begin(%rip), %r15

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

    ... 

您的代码出现段错误,因为 %rbx 在第一个函数 returns 之后不再指向正确的位置。因此,您没有调用 o13352,而是跳转到某个未映射的地址。代码获取和 loads/stores 都 SIGSEGV 在无效地址上。


您可能需要修复 %rbx 破坏,但这是一个单独的问题。对此的最佳答案是 使用直接 call insns 发出对命名符号的调用,而不是使用相对于 RIP 的 LEA 和间接函数的复杂函数调用序列呼叫。

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

编译器生成更简单,人类更容易理解(在调试其他问题时很有帮助),并使代码效率显着提高:不是浪费寄存器,更小的代码大小,更少的指令,并避免来自间接分支的分支错误预测(您应该将其用于跳转表的函数指针,但不用于此)。

查看 tag wiki for more performance and other stuff. esp. Agner Fog's guides 中的链接是关于如何编写创建良好且正确的 asm 的重要资源。当然,玩具编译器不会生成好的代码,但了解什么是好的什么是坏的是个好主意。 Agner Fog 的指南可能还会帮助您更好地理解事物,这将有助于生成正确的代码,而不管效率如何。


如果您希望通过函数指针继续使用间接调用,即使在不需要时也是如此:

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

您没有指定您的编译器试图为其生成代码的 ABI,但我假设这是一个调用约定,其中 %rbx 应该是调用保留的(又名被调用者保存又名非易失性) .因此,由于其他原因,修复该错误可能是必要的,即使您将函数调用顺序更改为使用正常的直接调用也是如此。

看起来你 push/pop %rbx 在你生成的函数中 save/restore 它。将其简化为尽可能简单的案例,但仍能证明该问题。您在问题中链接到的代码太大,甚至无法包含在线,所以它显然甚至不接近 Minimal, Complete, and Verifiable example (不,我不想费力地解决这个问题,对不起。你写了你的编译器,所以你可能比我更容易识别臃肿代码的模式并找到可能相关的部分。)

由于您的函数仍然设法 ret 成功,这意味着您没有破坏堆栈(即沿着不包含尾声 [=25= 的路径到达 ret ])。但是也许你在堆栈上覆盖了 %rbx 的保存值?在使用相对于 rsp 的地址存储临时地址之前,您是否使用 sub $size, %rsp 预留了足够的 space?


愚蠢的创可贴修复:

安排call之后第二个地址的lea

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

# what you should do instead:
...
leaq    z13351(%rip),   %rcx
callq   *%rcx
...                             # set up args
leaq    o13352(%rip),   %rcx
callq   *%rcx

这通过重复使用同一个寄存器来设置函数指针来节省一个寄存器,而不是为您要调用的每个函数使用不同的寄存器。 (这显然不能扩展到调用许多其他函数的函数。)