Assembly 为什么我会得到;地址边界错误“;来自这段汇编代码

Assembly 为什么我会得到;地址边界错误“;来自这段汇编代码,assembly,compiler-construction,x86,x86-64,Assembly,Compiler Construction,X86,X86 64,代码是由我正在处理的编译器生成的。但是我不知道为什么它会给我这个错误。没有注册冲突。当我试图调用callq*%rbx时,我的程序立即崩溃,我得到一个错误,错误是“被SIGSEGV信号终止(地址边界错误)” 我知道程序似乎试图访问非法内存,但我不知道哪里出错了。谁能给我一个提示吗 编辑:完成的汇编代码为,编译器尝试编译的代码为。runtime.c是 .globl\u main _主要内容: pushq%rbp movq%rsp,%rbp pushq%r12 pushq%rbx pushq%r14

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

我知道程序似乎试图访问非法内存,但我不知道哪里出错了。谁能给我一个提示吗

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

.globl\u main
_主要内容:
pushq%rbp
movq%rsp,%rbp
pushq%r12
pushq%rbx
pushq%r14
pushq%r13
次级Q$0,%rsp
movq$16384,%rdi
movq$16,%rsi
callq\u初始化
movq _根堆栈_开始(%rip),%r15
leaq o13352(%rip),%rbx
leaq z13351(%rip),%rcx
movq$1,%rdi

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


您可能需要修复
%rbx
碰撞,但这是一个单独的问题。最好的解决方法是使用直接
call
insns
发出对命名符号的调用,而不是使用RIP相对LEA和间接调用的卷积函数调用序列

...               # set up args
call    z13351
...               # set up args
call    o13352
编译器生成它更简单,更容易让人理解(在调试其他问题时很有帮助),并且使代码的效率大大提高:不浪费寄存器、更小的代码大小、更少的指令,并避免来自间接分支的分支预测失误(您应该将其用于跳转表的函数指针,但不用于此)

查看tag wiki中的链接以了解更多性能和其他内容。特别是对于如何编写创建好的和正确的asm来说,这是一个很好的资源。当然,玩具编译器不应该生成好的代码,但是了解什么是好的,什么是坏的是一个好主意。Agner Fog的指南可能也会帮助您理解b无论效率如何,它都有助于生成正确的代码


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

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

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

看起来您在生成的函数中按/pop
%rbx
来保存/还原它。请将其简化为一个尽可能简单的案例,以说明仍然存在此问题。您在问题中链接的代码太大,甚至无法包含内联代码,因此显然它甚至不接近于(不,对不起,我不想涉过这片混乱。你编写了你的编译器,因此你可能比我更容易识别臃肿代码的运行模式并找到可能相关的部分。)

由于您的函数仍然成功地
ret
,这意味着您没有中断堆栈(即沿着不包括尾声
pop
的路径到达
ret
)。但可能是您在堆栈上过度编写了
%rbx
的保存值?在使用rsp相对地址存储临时值之前,您是否使用
子$size,%rsp
保留了足够的空间


愚蠢的创可贴修复: 安排
呼叫后第二个地址的
lea

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


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

首先,为什么要使用register indirect
call
指令而不是编码?是否重复使用编译器在为全局函数发出加载/存储时使用的相同代码?当
调用发生时,
%rbx
中有什么内容?上一个函数调用是否可能是clobber
%rbx
(违反呼叫约定,至少如果您使用的是标准呼叫约定之一)。它是可执行内存的地址吗?如果这是一个JIT编译器,你记得确保内存是可执行的吗?在某些系统上,默认情况下malloc和静态分配不是来自可执行页面。如果这不是问题,你是否在我的编译器I ann中获得了RIP relative
lea
的正确编码?@PeterCordes Hi使用
function ref
标记对所有函数进行otate,这样我就可以将它们与局部变量区分开来。很抱歉,我不太理解你的第二个问题。你指的是什么全局变量?我试图猜测为什么你的编译器会生成如此愚蠢的代码,而不是发出
调用o15954
。一个寄存器间接
调用
将RIP相对地址加载到寄存器后完全是脑死的(这是对分支预测资源的浪费)。显然,您的编译器仍处于非常早期的阶段,因此脑死的代码是可以预料的,但值得指出。(请参阅中的性能链接,尤其是Agner Fog的资料。)但是任何
# 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