【问题标题】:ASM to C: how to dereference a pointer and add an offset?ASM 到 C:如何取消引用指针并添加偏移量?
【发布时间】:2018-10-26 12:57:28
【问题描述】:

我觉得有点愚蠢,但我正在努力在 C 中取消引用指针(+ 添加偏移量)。 我想在 C 中重新创建的是这种行为:

movabs rax, 0xdeadbeef
add rax, 0xa
mov rax, QWORD PTR [rax]

所以最后 rax 应该是:*(0xdeadbeef+0xa) 尤其是与mov rax, QWORD PTR [rax] 等效的值会很重要,因为我需要使用计算出的值并检索当时存储的数据(=不同的地址)。

我尝试了很多东西,但这是我目前的阶段:

void *ptr = (void*)0xdeadbeef;
void *ptr2 = *(void*)(ptr+0xa);

翻译成这样:

   0x7ffff7fe6050:      mov    QWORD PTR [rbp-0x38],rax
   0x7ffff7fe6054:      mov    rax,QWORD PTR [rbp-0x38]
   0x7ffff7fe6058:      add    rax,0xa

编辑:它实际上并没有编译,我在此处提供的 C 代码中犯了一个错误,无法弄清楚哪些代码实际编译为此。无论如何,这并不重要,因为主要目标是将 ASM 转换为 C 并且现在问题已解决。感谢大家的参与。

所以前两行基本上没有用,只是将值添加到我的地址中,仅此而已。我需要将其解释为地址并在此时检索值。

此时存储在这些位置的数据无关紧要。基本上我想做的是在内存中找到一个特定的值,并且我知道一种添加偏移量和取消引用指针以达到我的目标的方法。最后一步只是从我的地址到当时的实际数据类型的类型转换。

我知道这对你们中的一些人来说可能看起来微不足道,但我对 C 不是很熟悉,所以我在这里苦苦挣扎......

【问题讨论】:

  • 这是不可能的,因为void *ptr2 = *(void*)(ptr+0xa);不编译,所以不可能生成代码。
  • 你的 C 代码有问题,你正在计算一个新指针,然后当它是一个 void* 时取消引用它,最终得到一个(坏的)void 类型并将它分配给一个 void*。
  • 为什么要使用 10 字节 movabs rax, 0xdeadbeef 而不是 5 字节 mov eax, 0xdeadbeef?两者都将 RAX 设置为相同的值。为什么不用一条指令来做呢? movabs rax, [0xdeadbeef + 0xa] 可以使用从 64 位绝对地址 (felixcloutier.com/x86/MOV.html) 加载的 mov 的仅 AL/AX/EAX/RAX 形式。 (它不适合 32 位符号扩展 disp32,因为设置了低 32 的高位,这与位置相关代码中的普通静态地址不同)。具有 32 位地址大小覆盖的常规 mov 也可以工作,大约 7 个字节。
  • @Jabberwocky 和 ​​Matthieu 哦!很抱歉,一定是添加了错误的 C 代码 - 我尝试了很多强制转换、取消引用和括号...我将更新示例,但我的实际问题仍然存在。
  • 在 C 语言中,您也可以用一条语句完成整个操作。喜欢void *ptr = *(void**)(0xdeadbeef + 0xa);。除了避免取消引用未按 8 = alignof(void*) 对齐的 void** 的未定义行为外,您还想使用 memcpy

标签: c pointers assembly x86-64 intel


【解决方案1】:

您可以将 asm 简化为一条指令,并在汇编时完成数学运算。 movabs rax, [0xdeadbeef + 0xa] 可以使用从 64 位绝对地址 (https://felixcloutier.com/x86/MOV.html) 加载的 mov 的仅 AL/AX/EAX/RAX 形式。 (它不适合 32 位符号扩展的 disp32,因为设置了低 32 的高位,这与位置相关代码中的普通静态地址不同)。具有 32 位地址大小覆盖的常规 mov 也可以在大约 7 个字节内工作,因为您的地址确实适合零扩展的 32 位整数。

在 C 语言中,您也可以用一条语句完成全部操作。无需过于复杂:您的地址是一个指针的指针,因此您需要将整数转换为x ** 类型。

void *ptr = *(const void**)(0xdeadbeefUL + 0xa);

在 asm 中,指针只是整数,因此使用整数而不是 char* 进行数学运算是有意义的。使其无符号保证它零扩展为指针宽度而不是符号扩展。

(但是,C 中的数字文字具有足够宽的类型来表示该值,因此 x86-64 编译器上的 0xdeadbeef 将是 int64_tlong long)。您实际上不会得到 @987654331 @ 是一个负 32 位 int,符号扩展为 0xffffffffdeadbeef。)

由于void 没有大小,您不能对void* 添加/减去整数。 void ** 上的指针数学将是 sizeof(void*) 的块。

为了避免取消引用未与8 = alignof(void*) 对齐的void**(在两个主流 x86-64 ABI 中)的未定义行为,您需要使用 memcpy。但我假设你的示例地址只是一个假的例子。像 gcc 这样的主流 x86 编译器不会对未对齐的地址做任何奇怪的事情来惩罚程序员的 UB,因此编译器输出将包含在 x86 上正常工作的未对齐负载。但是在自动矢量化时,您可能会遇到这种 UB 的问题。 Why does unaligned access to mmap'ed memory sometimes segfault on AMD64?


但是,如果您出于某种原因想要将内容分解为多个 asm 语句,您可以将其音译为多个 C 语句,如下所示:

uintptr_t wheres_the_beef = 0xdeadbeef;    // mov eax, 0xdeadbeef
wheres_the_beef += 0xa;                    // add eax, 0xa
void **address = (void**)wheres_the_beef;  // purely a cast, no asm instructions;
void *ptr = *address;                      // mov rax, [rax]

如果你想为指针添加字节偏移量,你可以乱用char*,但这里真的没有意义。

同样,这在大多数 C 实现中仍然存在未定义行为,其中 alignof(void*) 大于 1,因此 void **address = (void**)wheres_the_beef 创建了一个未对齐的指针。

(有趣的事实:即使创建未对齐的指针也是 ISO C 中的 UB。但是所有支持 Intel 内在函数的 x86 编译器都必须支持创建未对齐的指针,以便将它们传递给像 _mm_loadu_ps() 这样的内在函数,因此只有实际取消引用它们才是潜在的问题在 x86 编译器上。)

【讨论】:

  • 我不认为我会在堆栈溢出答案中写“牛肉在哪里”。我有时会自暴自弃。 :)
  • 哇,这比我需要的要多得多。谢谢,它确实帮助我更好地理解了我的问题并避免了将来的错误!赞成并接受。 :)
猜你喜欢
  • 2021-06-07
  • 2021-07-22
  • 1970-01-01
  • 1970-01-01
  • 2018-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多