这是现有 SO 帖子的延续这里。
我在 x86_64 平台的虚拟内核模块中有以下函数。
static void dummy_function_1(int arg1, char arg2) {
printk(KERN_INFO "dummy_function_1: arg1=%d, arg2=%c\n", arg1, arg2);
dummy_function_2("Hello", (struct dummy_struct){.a = arg1, .b = arg2});
}
我使用
-r
对其进行了反汇编以显示重定位:
0000000000000120 <dummy_function_1>:
120: 55 push %rbp
121: 48 89 e5 mov %rsp,%rbp
124: 41 54 push %r12
126: 53 push %rbx
127: 89 fb mov %edi,%ebx
129: 41 89 f4 mov %esi,%r12d
12c: 40 0f b6 d6 movzbl %sil,%edx
130: 89 fe mov %edi,%esi
132: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
135: R_X86_64_32S .rodata.str1.8+0x118
139: e8 00 00 00 00 call 13e <dummy_function_1+0x1e>
13a: R_X86_64_PLT32 _printk-0x4
13e: 45 0f b6 e4 movzbl %r12b,%r12d
142: 49 c1 e4 20 shl $0x20,%r12
146: 89 de mov %ebx,%esi
148: 4c 09 e6 or %r12,%rsi
14b: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
14e: R_X86_64_32S .rodata.str1.1+0x24
152: e8 79 ff ff ff call d0 <dummy_function_2>
我们可以清楚地看到地址 0x135 和 0x13a 处有 4 个字节的零。我在正在运行的系统上对该函数进行了内存转储,我可以看到这 4 个零字节被动态链接器替换了:
crash> rd ffffffffc0000110 16
ffffffffc0000110: 9090909090909090 9090909090909090 ................
ffffffffc0000120: 89535441e5894855 d6b60f40f48941fb UH..ATS..A..@...
ffffffffc0000130: 004148c7c748fe89 0f45c1100e72e8c0 ..H..HA...r...E.
ffffffffc0000140: de8920e4c149e4b6 4024c7c748e6094c ..I.. ..L..H..$@
ffffffffc0000150: 00ffffff79e8c000 0000000000000000 ...y............
ffffffffc0000160: 9090909090909090 9090909090909090 ................
ffffffffc0000170: e5894855fa1e0ff3 e8c0004000c7c748 ....UH..H...@...
ffffffffc0000180: ccccc35dc1100e2c 000000000000cccc ,...]...........
因此,我们可以得出结论,0x135 被替换为 0x484100c0,0x13a 被替换为 0x720e10c1。这就是我所能得出的结论。我想进一步了解这些新字节意味着什么?是否真的可以确定存储该字符串的虚拟地址,以便我可以进行内存转储并确认?
编辑
我突然想到使用在线工具将内存转储中的字节转换回汇编:
48 c7 c7 48 41 00 c0 e8 72 0e 10 c1
我得到了:
0: 48 c7 c7 48 41 00 c0 mov rdi,0xffffffffc0004148
7: e8 72 0e 10 c1 call 0xffffffffc1100e7e
现在如果我转储位置
0xffffffffc0004148
:
crash> rd 0xffffffffc0004148 10
ffffffffc0004148: 5f796d6d75643601 6e6f6974636e7566 .6dummy_function
ffffffffc0004158: 31677261203a315f 677261202c64253d _1: arg1=%d, arg
ffffffffc0004168: 0000000a63253d32 75646f4d794d3601 2=%c.....6MyModu
ffffffffc0004178: 74696e49203a656c 676e697a696c6169 le: Initializing
ffffffffc0004188: 4d794d2065687420 ee000a656c75646f the MyModule...
crash>
完美,一些熟悉的字符串。但我仍然不确定在实际字符串开始之前,
0x0136
处的前两个字节0xffffffffc0004148
做什么?
所以,你这里有几个问题混在一起。
我想进一步了解这些新字节意味着什么?
这些地址是根据搬迁记录的要求搬迁到正确位置的。
例如
135: R_X86_64_32S .rodata.str1.8+0x118
意味着加载器必须用绝对加载时地址
.rodata.str1.8+0x118
替换该记录,但需要将其截断为 32 位,并且必须可符号扩展为其正确的 64 位值(因为 mov imm32, %rdi
命令需要这样)。在您的例子中,该地址是 0xffffffffc0004148
,其截断的 32 位值是 0xc0004148
。
R_X86_64_PLT32
重定位的工作原理类似,但它是与 PC 相关的,因为 call
指令需要这种方式;因此,确切的值不仅取决于 _printk
内核中的加载时位置,还取决于重定位点本身的位置。
您可以在谷歌上搜索它们,以更好地了解它们的工作原理(事实上,有很多关于搬迁的问题,并且有很好的答案)。
真的有可能确定存储该字符串的虚拟地址,以便我可以进行内存转储并确认吗?
嗯,是的。正如您自己所发现的,
R_X86_64_32S
类型的搬迁在搬迁时包含最终地址。人们还可以在知道模块中符号的偏移量及其加载位置的情况下找出内核模块中的任何和所有符号。 /sys/modules/<module>/sections
文件显示每个模块部分的加载位置。
但我仍然不确定 0xffffffffc0004148 处的前两个字节 0x0136 在实际字符串开始之前做了什么?
那就是 KERN_INFO。它并不像某些人想象的那样是 _printk 的参数。请参阅 KERN_INFO 到底扩展了什么,以及它在哪里实现?了解更多信息