所以对于任何熟悉汇编的人来说,这似乎是一个非常简单的问题,但是我希望有人可以向我解释下面两段代码之间的区别是什么,因为这会导致分段错误,另一个没有,但(对我来说)他们似乎应该在逻辑上等同。
工作正常:
char *src1; int esi_out, eax;
__asm__
__volatile__(
"lodsb\n\t;"
: "=&S" (esi_out), "=&a" (eax)
: "0" (src1)
);
printf("src1 %c @ %p, esi_out: %x, eax: %x\n", *src1, src1, esi_out, eax);
并打印:
src1 w @ 0x7fffce186959, esi_out: ce18695a, eax: ce186977
所以我理解这段代码应该将src1(这是一个地址)的值加载到ESI中,将该值复制到EAX中,将ESI中的地址递增1个字节,然后在退出时将这些值输出到本地C变量esi_out和eax。 src1和esi_out看起来是正确的,但是eax似乎已经关闭了。这是怎么回事?
代码的第二位是我们看到一个我无法理解的段错误:
__asm__
__volatile__(
"movl %%ebx, %%esi\n\t;"
//"lodsb\n\t;"
: "=&S" (esi_out), "=&b" (ebx), "=&a" (eax)
: "1" (src1)
);
printf("src1 %c @ %p, esi_out: %x, eax: %x, ebx: %x\n",
*src1, src1, esi_out, eax, ebx);
将lodsb命令注释掉后,它会产生:
src1 w @ 0x7ffff093b959, esi_out: f093b959, eax: f093b959, ebx: f093b959
并且没有注释掉lodsb命令,它就是段错误。根据我的想法,直接加载ESI值,如上面的第一种情况,并将其加载到EBX然后将其移入ESI应该是等价的,不是吗?
我错过了什么?为什么写入EAX的值会被看作?我将等效的程序直接编写到程序集中并使用gdb逐步完成它并且工作正常。
非常感谢任何见解。
答案 0 :(得分:3)
从printf中%p
的输出外观来看,你正在编译64位,但你的asm代码假定为32位。尝试
__asm__
__volatile__(
"movl %%rbx, %%rsi\n\t;"
"lodsb\n\t;"
: "=&S" (esi_out), "=&b" (ebx), "=&a" (eax)
: "1" (src1)
);
printf("src1 %c @ %p, esi_out: %x, eax: %x, ebx: %x\n",
*src1, src1, esi_out, eax, ebx);
您还应将esi_out
和ebx
声明为指针类型(void*
或char*
)或uintptr_t
。
发生的事情是lodsb在64位模式下使用RSI作为其源地址,但是你只将指针值的低32位放入RSI,因此它不包含有效地址,因此是段错误。正如Slagh所说,只有寄存器(al)的低8位被lodsb修改。您应该屏蔽其他位(eax和0xff),清除rax(xor%rax,%rax)或将eax声明为char
。
如果您感到意外,您还应该找到有关x86_64程序集的资源。
答案 1 :(得分:1)
你的第一个问题 - lodsb
正在修改AL但不修改EAX的剩余部分。您的EAX值ce186977
中的最后一个字节是0x77,对于小写的“w”是十六进制。
我很遗憾不熟悉GCC汇编语法 - 当您运行代码并跨过movl
时,哪个寄存器是目标?对我来说,看起来EBX正在编写ESI。你的代码之前在EBX中有什么用?
答案 2 :(得分:1)
您有一些错误:
您使用的是64位架构,int
不足以容纳指针。 esi_out
变量应为64位宽,或者更好地使用ptrdiff_t
。你应该将它命名为rsi_out
; - )
64位模式下的lodsb
指令隐含地引用rsi
而不是esi
。在movl %%ebx, %%esi
之前的指令中,您只设置rsi
的下半部分,上半部分被清除。将其更改为movq %%rbx, %%rsi
。