为什么我从这段汇编代码中得到 "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
编译器生成更简单,人类更容易理解(在调试其他问题时很有帮助),并使代码效率显着提高:不是浪费寄存器,更小的代码大小,更少的指令,并避免来自间接分支的分支错误预测(您应该将其用于跳转表的函数指针,但不用于此)。
查看 x86 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
这通过重复使用同一个寄存器来设置函数指针来节省一个寄存器,而不是为您要调用的每个函数使用不同的寄存器。 (这显然不能扩展到调用许多其他函数的函数。)
代码是由我正在使用的编译器生成的。但是我不知道为什么它会给我这个错误。没有寄存器冲突。当我尝试调用 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
编译器生成更简单,人类更容易理解(在调试其他问题时很有帮助),并使代码效率显着提高:不是浪费寄存器,更小的代码大小,更少的指令,并避免来自间接分支的分支错误预测(您应该将其用于跳转表的函数指针,但不用于此)。
查看 x86 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
这通过重复使用同一个寄存器来设置函数指针来节省一个寄存器,而不是为您要调用的每个函数使用不同的寄存器。 (这显然不能扩展到调用许多其他函数的函数。)