我正在使用类似汇编程序的 API(它不是真正的汇编程序,但它可以发出机器代码),我正在调试和玩弄它。它专门用于 System V x86_64 ABI,因此我将只讨论 SysV 调用约定等。
出于某种原因,当我为了测试目的发出一些像这样的人为设计的代码时
builder.emit_sub(rsp, 1);
builder.emit_movq_vr(reinterpret_cast<uint64_t>(&hello_world), rax);
builder.emit_call(rax);
builder.emit_add(rsp, 1);
builder.emit_ret();
在调用时发生段错误(在运行时,而不是在组装时),但是
builder.emit_movq_vr(reinterpret_cast<uint64_t>(&hello_world), rax);
builder.emit_jmp(rax);
成功就好了。失败点似乎在 call
指令处,但我不知道伪汇编程序出了什么问题。它可能会发出错误的操作码操作数或其他东西,但我不确定。原始发出的机器代码对于错误代码看起来像这样,以及它应该表示的操作码,如一些简单的调试语句打印的那样
sub 48 81 EC 01 00 00 00
movqvr 48 B8 63 80 AA 01 01 00 00 00
call FF D0
add 48 81 C4 01 00 00 00
ret C3
备注:movqvr
不是真正的指令[助记符];最后的 vr
对我来说只是一个调试注释,它是一种“将 imm64 移至 reg”的指令。
备注:sub
和add
是将堆栈对齐到 16 字节边界上,我认为这在这个 ABI 中是必需的。它们可以更好地写成 push rax
和 pop rax
(或者 pop rcx
如果 rax
是需要一个返回值),但忽略它,除非它是搞乱调用的(例如,如果 rsp
没有被正确修改)。
最佳答案
是的,在 System V ABI 中,堆栈在每个call
指令之前 与 16 字节边界对齐。因此,在函数入口处,它需要另外 8 个字节(而不是 1 个字节)才能到达下一个 16 字节边界。请记住,在 C 中,指针差异按 sizeof(type)
缩放,但在 asm 中它们不是。
是的,push rax
/pop rcx
将是一个不错的选择,如果它还不需要推送奇数,这就是 clang/LLVM 所做的调用保留寄存器或保留任何额外的堆栈空间。如果您确实需要为局部变量保留任何堆栈空间,请使用将使 rsp
保持 16 字节对齐的偏移量。
顺便说一句,当立即数适合符号扩展的 8 位值(即 if ((int8_t) imm == imm)
).此外,如果您需要添加/减去 +128,请注意 -128
适合 imm8,因此您可以添加 rsp,-128
(例如在奇数之后push
指令)。
如果您知道代码运行的地址,您应该使用call rel32
编码,而不是寄存器间接调用。但是你是对的,跳转到任意 64 位地址需要这个 mov r64, imm64
序列,而不是直接的 call
。
您是否使用调试器找出 hello_world
崩溃的地方?也许如果它调用 printf
(而不是 puts
),它会忘记将 al
归零(使用 xor eax,eax
) 以指示 XMM 寄存器中没有 FP args,所以 printf 可能使用了一些 16 字节 SSE 对齐所需的存储到堆栈?
RSP 甚至没有 qword 对齐是非常糟糕的,但我不希望它崩溃任何会崩溃的东西 8 字节对齐(但不是 16)。
关于c++ - 调用但不跳转到 rax 中的地址时出现段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47730624/