只是好奇是否可以在程序中将所有
MOV
替换为 PUSH/POP
?
我知道这种替代方法不切实际且效率低下。
这个 godbolt 示例展示了使用 MOV 的标准 printf 调用和另一个使用 PUSH/POP 的 printf 调用,以进行比较。我的直觉告诉我这是可能的,但一路上可能会遇到一些问题?
#include <stdio.h>
char format_string[] asm("format_string") = "%d %d %d %d %d\n";
void MOV_printf() {
__asm__ (
"subq $128, %%rsp\n\t"
"lea format_string(%%rip), %%rdi\n\t"
"movq $1, %%rsi\n\t"
"movq $2, %%rdx\n\t"
"movq $3, %%rcx\n\t"
"movq $4, %%r8\n\t"
"movq $5, %%r9\n\t"
"call printf\n\t"
"addq $136, %%rsp\n"
::: "rdi", "rsi", "rdx", "rcx", "r8", "r9", "rsp"
);
}
void PUSH_POP_printf() {
__asm__ (
"subq $128, %%rsp\n\t"
"lea format_string(%%rip), %%rdi\n\t"
"pushq $1\n\t"
"popq %%rsi\n\t"
"pushq $2\n\t"
"popq %%rdx\n\t"
"pushq $3\n\t"
"popq %%rcx\n\t"
"pushq $4\n\t"
"popq %%r8\n\t"
"pushq $5\n\t"
"popq %%r9\n\t"
"call printf\n\t"
"addq $136, %%rsp\n"
::: "rdi", "rsi", "rdx", "rcx", "r8", "r9", "rsp"
);
}
int main() {
MOV_printf();
PUSH_POP_printf();
return 0;
}
解决方案
MOV r64, imm64
--- 替换为 4 个 pushw
和一个 popq
。 示例
MOV AH,DL
--- 可能的解决方法。 示例
陷阱
这些需要
mov
mov %al, (%rdi)
无法与 push
实现。任何加载/存储包含的单词或 qword 并将其存储回来的模拟都不是线程安全的;包含字的非原子 RMW 可以通过另一个线程存储到另一个字节。
如果您愿意接受这一点,那么也许部分重叠的
pop m16
操作可以用您在静态缓冲区中查找的值构造一个单词,您可以 pop m16
/ push m16
将其复制到原始字节。
但是您不知道
(%rdi)
处的字节是包含它的 16 位字的低字节还是高字节,因此您不知道可以访问 -1(%rdi)
或 0(%rdi)
中的哪一个,而无需进入下一页会出现段错误。只有“对齐”的 16 位加载/存储才能保证不会跨越任何更宽的边界(例如 4k 页),因此如果该字包含您知道有效的任何字节,则不会出现页错误。 (在 x86 和 x64 上读取同一页面内的缓冲区末尾是否安全?)
单独使用push
/
pop
无法检查%rdi
的低位并进行相应的分支。(x86-64 使得不可能有段限制,在 32 位模式下,段限制可能是奇数个字节,一般情况下不假设平面内存模型。但实际上,x86-64(仍然?)我认为 FS 和 GS 可能有奇数段基,所以 mov %al, %fs:(%rdi)
更加未知;即使你可以
test %1, %dil
; jnz
,仍然无法告诉你线性地址是奇数还是偶数。)
此外,除了调试和控制寄存器之外,x86-64 还删除了 FS/GS 以外的段寄存器的压入/弹出操作码。 。所以 mov ds, eax
也是不可模仿的。