assembly 需要弄清楚以下内联汇编代码的含义

qpgpyjmq  于 2022-11-13  发布在  其他
关注(0)|答案(4)|浏览(185)
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”吗?

des4xlb0

des4xlb01#

该代码在功能上与return *address相同,但并不绝对等效于此生成的二进制/目标文件。
在ELF中,* 前向引用 * 的用法(例如,mov $1f, ...撷取组件区域标签的位址)会导致建立所谓的 * 重新配置 *。重新配置是 * 连接器 * 的指令(在创建可执行文件时或稍后在加载可执行文件/库时插入到动态链接器中),以 * 插入 * 一个仅在链接/加载时才知道的值。在目标代码中,如下所示:

Disassembly of section .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.o
ELF Header:
[ ... ]
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000009  0000000000000000  AX       0     0     16
  [ 2] .rela.text        RELA             0000000000000000  000004e0
       0000000000000018  0000000000000018          10     1     8
[ ... ]
Relocation section '.rela.text' at offset 0x4e0 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000001  00020000000a R_X86_64_32       0000000000000000 .text + 8
[ ... ]

此ELF部分条目表示:

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

这个条目是由mov $1f, %0指令创建的,如果你不写这个条目(或者只写return *address),它就不会在那里。
我通过删除static限定符强制生成上述代码;如果不这样做,一个简单的编译实际上 * 根本不会创建任何代码 *(如果不使用static代码,则会将其删除;如果使用了static代码,则会将其内联)。
由于函数是static,正如前面所说,编译器通常会在调用点内联它。因此,使用它的信息通常会丢失,调试器检测它的能力也会丢失。但这里显示的技巧可以恢复这一点(间接),因为每次使用该函数都会创建 * 一个重定位入口 *。除此之外,类似这样的方法可以用于在二进制文件中建立 * 插桩点 *;在可通过目标文件格式恢复的位置处插入公知的/严格定义的但功能上无意义的小汇编语句,然后在需要时让例如调试器/跟踪实用程序用“更有用”的东西来替换它们。

x33g5p2x

x33g5p2x2#

$1f1标签的地址。f指定正向查找名为1的第一个标签。"m"是内存中的输入操作数。"=&a"是使用eax寄存器的输出操作数。a指定要使用的寄存器,=使其成为输出操作数。并且&保证其它操作数将不共享同一寄存器。
这里,%0将被第一个操作数(eax寄存器)替换,%1将被第二个操作数(address指向的地址)替换。
GCC文档中关于Inline assembly和asm约束的部分对所有这些以及更多内容进行了解释。

h7wcgrx3

h7wcgrx33#

这段代码(除了由于两个错别字而不可编译之外)几乎没有什么用处。
这是它的转换结果(使用-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;
tez616oj

tez616oj4#

这是来自PintOS项目的代码片段。
这里的函数由操作系统内核使用,用于从用户地址空间**读取address**处的字节。这是由movzbl %1, %0完成的,其中0%result1%address。但在此之前,内核必须移动$1f的地址(movzbl %1, %0后面的指令地址)添加到eax寄存器。这个动作看起来没有用,因为一些上下文信息丢失了。内核这样做是为了让页面错误中断处理程序使用它。因为address可能是用户提供的无效的,它可能会导致页面错误。当这种情况发生时,中断处理程序会接管。将eip设置为等于eax(这是1f%的内存地址),并且还将eax设置为-1以指示读取失败。在此之后,内核能够从处理程序返回到$1f并继续前进。如果不保存$1f的地址,处理程序将不知道它应该返回到哪里,并且只能一次又一次地返回到movzbl %1, %0

相关问题