c - 需要弄清楚遵循内联汇编代码的含义


static int func_name (const uint8_t * address)
{
    int result;
    asm ("movl $1f, %0; movzbl %1, %0; 1:"
   : "=&a" (result) : "m" (*address));
    return result;
}

我已经通过互联网浏览了内联程序集引用。但是我无法弄清楚这段代码在做什么,例如。什么是 $1f ?"m"是什么意思?使用"=r"和"r"不是正常的内联约定吗?

代码在功能上与return *address相同,但并非绝对等同于生成的二进制/对象文件。

在 ELF 中,使用前向引用(即检索程序集本地标签地址mov $1f, ...(会导致创建所谓的重定位。重定位是指向链接器的指令(在创建可执行文件时或稍后在加载可执行文件/库时向动态链接器发送(,以插入仅在链接/加载时已知的值。在目标代码中,这看起来像:

反汇编 .text 部分:0000000000000000 :   0: b8 00 00 00 00 mov $0x0,%eax   5: 0f B6 07 movzbl (%rdi(,%eax   8: C3 RETQ

请注意,这里的值(在.text部分的偏移量 1 处(为零,即使这实际上不正确 - 这取决于函数在运行代码中的位置。只有(动态(链接器才能最终知道这一点,并且这段内存在加载时需要更新的信息实际上被放置在目标文件中:

$ readelf -a xqf.oELF 标头:[ ... ]节标题:  [nr]名称类型地址偏移量       大小 entSize 标志 链接 信息 对齐  [ 0] NULL 0000000000000000 000000000       0000000000000000  0000000000000000           0     0     0  [ 1] .text PROGBITS 000000000000000000 00000040       0000000000000009 0000000000000000 AX 0 0 16  [ 2] .rela.text RELA 000000000000000 000004e0       0000000000000018  0000000000000018          10     1     8[ ... ]偏移量0x4e0处的".rela.text"部分包含 1 个条目:  偏移信息 类型 符号 值 符号 名称 + 添加000000000001 000200000000a R_X86_64_32 0000000000000000 .text + 8[ ... ]

这个ELF部分条目说:

  • 查看偏移1.text部分
  • 有一个 32 位值将零扩展到 64 位 ( R_X86_64_32 (。 这可能旨在用于 32 位代码,但在 64 位非 PIE 可执行文件中,这仍然是将地址放入寄存器的最有效方法;小于 lea 1f(%rip), %0 对于R_X86_64_PC32 RIP 相对重定位。 是的,将RIP相对LEA转换为32位寄存器是合法的,如果您不关心截断地址,则可以节省一个字节的机器代码。
  • 您(作为链接器(需要放置的值是.text + 8的值(必须在链接/加载时计算(

此条目是根据mov $1f, %0指令创建的。如果你省略它(或者只是写return *address(,它就不会在那里。

我通过删除static限定符强制为上述内容生成代码;如果不这样做,一个简单的编译实际上根本不创建任何代码(如果不使用static代码就会被消除,而且,很多时候,如果使用,代码会内联(。

由于该函数是static的,如前所述,它通常会由编译器在调用站点内联。因此,使用它的信息通常会丢失,调试器检测它的能力也会丢失。但是此处显示的技巧可以(间接地(恢复这一点,因为每次使用该函数都会创建一个重新定位条目。除此之外,像这样的方法可用于在二进制文件中建立检测点;在可通过目标文件格式恢复的位置插入众所周知/严格定义但功能上没有意义的小汇编语句,然后让调试器/跟踪实用程序在需要时用"更有用"的东西替换它们。

$1f1标签的地址。f指定在正向方向上查找名为 1 的第一个标签。 "m" 是内存中的输入操作数。 "=&a" 是使用eax寄存器的输出操作数。 a指定要使用的寄存器,=使其成为输出操作数,&保证其他操作数不会共享同一寄存器。

在这里,%0将被替换为第一个操作数(eax寄存器(,%1替换为第二个操作数(address 指向的地址(。

所有这些以及更多内容都在GCC文档中关于内联汇编和asm约束的解释。

这段代码(除了由于两个拼写错误而不可编译(几乎没有用处。

这就是它变成的(使用 -S 开关(:

_func_name:
        movl 4(%esp), %edx ; edx = the "address" parameter
        movl $1f, %eax ; eax = the address of the "1" label
        movzbl (%edx), %eax; eax = byte from address in edx, IOW, "*address"
     1:
        ret

因此,函数的整个主体可以替换为

return *address;

这是来自PintOS项目的代码片段。

操作系统内核使用此处的函数从用户地址空间读取 address 处的字节。这是通过movzbl %1, %0完成的,0% result1% address。但在此之前,内核必须将$1f的地址(即紧随movzbl %1, %0之后的指令地址(移动到eax寄存器。此移动似乎毫无用处,因为缺少某些上下文信息。内核这样做是为了页面错误中断处理程序使用它。因为address可能是用户提供的无效,并且可能会导致页面错误。发生这种情况时,中断处理程序将接管,将eip设置为等于eax(这是$1f的内存地址(,并将eax设置为-1以指示读取失败。之后,内核能够从处理程序返回到$1f并继续前进。如果不保存$1f的地址,处理程序将不知道它应该返回到哪里,只能一次又一次地返回到movzbl %1, %0

相关内容

  • 没有找到相关文章

最新更新