【问题标题】:Loading registers in gcc inline assembly? (simple?)在 gcc 内联汇编中加载寄存器? (简单的?)
【发布时间】:2012-01-30 19:12:24
【问题描述】:

所以对于任何精通汇编的人来说,这似乎都是一个非常简单的问题,但我希望有人可以向我解释以下两段代码之间的区别,因为其中一段会导致分段错误并且另一个没有,但(对我而言)它们似乎在逻辑上应该是等价的。

工作正常:

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,然后movl'ing到ESI应该是等价的吧?

我错过了什么?为什么写入 EAX 的值看起来不对?我将等效程序直接编写到汇编中,并使用 gdb 逐步执行它,它工作正常。

任何见解将不胜感激。

【问题讨论】:

  • 这确实是一个关于使用 64 位指针但仅将低 32 位移动到寄存器中的问题。谢谢大家。

标签: c linux gcc assembly x86


【解决方案1】:

从 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_outebx 声明为指针类型(void*char*)或uintptr_t

发生的情况是 lodsb 在 64 位模式下使用 RSI 作为其源地址,但您只将指针值的低 32 位放入 RSI,因此它不包含有效地址,因此出现了段错误。正如 Slagh 所说,只有 a 寄存器 (al) 的低 8 位被 lodsb 修改。您应该屏蔽掉其他位 (eax & 0xff),清除 rax (xor %rax,%rax) 或将 eax 声明为 char

如果您对此感到惊讶,您还应该找到有关 x86_64 汇编的资源。

【讨论】:

    【解决方案2】:

    您的第一个问题 - lodsb 正在修改 AL 而不是 EAX 的其余部分。 ce186977 EAX 值中的最后一个字节是 0x77,它是小写“w”的十六进制。

    不幸的是,我不熟悉 GCC 汇编语法 - 当您运行代码并跳过 movl 时,哪个寄存器是目标?在我看来,EBX 正在编写 ESI。 EBX 在您的代码之前有什么?

    【讨论】:

    • 谢谢,斯拉格。 EBX 使用 src1 的值初始化。 gcc 中的内联 asm 指令的格式是 __asm__(.data : outputs : inputs (: clobbered)),输出和输入控制进出 asm 部分的内容。在这种情况下,输出是从 ESI 读取的 esi_out (=&S)、从 EBX 读取的 ebx (=&b) 和从 EAX 读取的 eax (=&a);并且输入是读入 EBX 的 src1,其中“1”表示参数 $1,编译器将其分配给从 $0 开始的所有输入和输出。在这种情况下:{$0 = esi_out、1 = ebx 等}
    【解决方案3】:

    你有一些错误:

    您使用的是 64 位架构,int 不足以容纳指针。 esi_out 变量应该是 64 位宽,或者最好只使用 ptrdiff_t。你应该把它命名为rsi_out ;-)

    64 位模式下的lodsb 指令隐式引用rsi 而不是esi。在movl %%ebx, %%esi 之前的指令中,您只设置了rsi 的下半部分,上半部分被隐式清除。将其更改为movq %%rbx, %%rsi

    【讨论】:

    • ptrdiff_t 不是在这里使用的正确类型,它是为了两个指针之间的差异,不需要足够宽来存储整个指针
    猜你喜欢
    • 2021-03-21
    • 2015-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-23
    • 2013-06-14
    • 1970-01-01
    • 2014-09-19
    相关资源
    最近更新 更多