我正在使用类似汇编程序的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
只是一个调试注释,我说它是&#34;移动imm64到reg&#34;一种指导。
备注:sub
和add
是在16字节边界上对齐堆栈,我认为这是此ABI的必要条件。他们可以更好地写成push rax
和pop rax
(或pop rcx
如果返回值需要rax
,但忽略它,除非它这是搞乱调用(例如,如果没有正确修改rsp
)。
答案 0 :(得分:2)
是的,在System V ABI中,堆栈在每个call
指令之前与16字节边界对齐。因此,在函数输入时,需要另一个 8 字节(而非1)才能到达下一个16字节边界。请记住,在C中,指针差异由sizeof(type)
缩放,但在asm中它们不是。
是的,push rax
/ pop rcx
是一个不错的选择,如果clang / LLVM不需要推送奇数个调用保留寄存器或保留任何寄存器额外的堆栈空间。如果确实需要为本地保留任何堆栈空间,请使用将rsp
16字节对齐的偏移量。
顺便说一下,当立即符合符号扩展的8位值(即sub r/m64, imm8
)时,可以使用if ((int8_t)imm == imm)
编码来保存代码大小。此外,如果您需要添加/减去+128,请注意-128
适合imm8,因此您可以add 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-aligned是非常糟糕的,但我不希望它崩溃任何会因为8字节对齐(但不是16)而崩溃。