【问题标题】:How to dereference zero address with GCC? [duplicate]如何使用 GCC 取消引用零地址? [复制]
【发布时间】:2016-06-03 00:20:47
【问题描述】:

假设我需要写入零地址(例如,我在那里有 mmapped 的东西并想访问它,无论出于何种原因,包括好奇),并且该地址在编译时是已知的。以下是我可以想到的一些变体来获取指针,其中一个有效,另外三个无效:

#include <stdint.h>

void testNullPointer()
{
    // Obviously UB
    unsigned* p=0;
    *p=0;
}

void testAddressZero()
{
    // doesn't work for zero, GCC detects it as NULL
    uintptr_t x=0;
    unsigned* p=(unsigned*)x;
    *p=0;
}

void testTrickyAddressZero()
{
    // works, but the resulting assembly is not as terse as it could be
    unsigned* p;
    asm("xor %0,%0\n":"=r"(p));
    *p=0;
}

void testVolatileAddressZero()
{
    // p is updated, but the code doesn't actually work
    unsigned*volatile p=0;
    *p=0; // because this doesn't dereference p! // EDIT: pointee should also be volatile, then this will work
}

我用这个编译

gcc test.c -masm=intel -O3 -c -o test.o

然后objdump -d test.o -M intel --no-show-raw-insn 给我(此处跳过对齐字节):

00000000 <testNullPointer>:
   0:   mov    DWORD PTR ds:0x0,0x0
   a:   ud2a   

00000010 <testAddressZero>:
  10:   mov    DWORD PTR ds:0x0,0x0
  1a:   ud2a   

00000020 <testTrickyAddressZero>:
  20:   xor    eax,eax
  22:   mov    DWORD PTR [eax],0x0
  28:   ret    

00000030 <testVolatileAddressZero>:
  30:   sub    esp,0x10
  33:   mov    DWORD PTR [esp+0xc],0x0
  3b:   mov    eax,DWORD PTR [esp+0xc]
  3f:   add    esp,0x10
  42:   ret

这里 testNullPointer 显然有 UB,因为它根据定义取消引用空指针。

testAddressZero 的原则将给出除0 以外的任何地址的预期代码,例如1,但对于零,GCC 似乎检测到地址零对应于空指针,因此也会生成 UD2。

asm 获取零地址的方式肯定会抑制编译器的检查,但其代价是即使testAddressZero 的原理可能成功(即如果不是 UD2 和类似的陷阱,每个拱上的相同平面内存模型。此外,代码看起来不像上述两个变体那样简洁。

volatile 指针的方式似乎是最好的,但是这里生成的代码由于某种原因似乎没有取消引用地址,所以它也被破坏了。

现在的问题:如果我以 GCC 为目标,我如何才能无缝访问零地址而不会出现任何陷阱或 UB 的其他后果,并且无需编写汇编代码?

【问题讨论】:

  • 如果你有mmap-ed,那么大概你已经有一个“有效”的指针,这意味着你不需要使用这些技巧中的任何
  • @alk - 似乎是 same 问题,在 2 小时前问了所有问题。这是怎么回事?
  • 您不能取消引用空指针。您的运行时系统必须禁止此类 mmap-ing,或者您的编译器/库必须将空指针实现为非全零位模式(是的,此类系统确实存在)。如果两者都不是,那么你就在 UB 领域,并帮助你deity[choice]
  • 您应该编辑您的问题以解释为什么您需要取消引用 0 地址。真正有用的情况很少。

标签: c pointers gcc


【解决方案1】:

我会将指针放入一个全局变量中:

const uintptr_t zero = 0;
unsigned* zeroAddress= (unsigned *)zero;
void testZeroAddressPointer()
{
    *zeroAddress=0;
}

如果您将地址暴露在优化范围之外(因此编译器无法确定您没有在其他地方设置它),那么应该可以解决问题,尽管效率稍低。

编辑:使此代码独立于隐式零到空转换。

【讨论】:

  • 这显然取消了 空指针 的引用。为什么你认为范围和联系会改变这一点?
  • 嗯,是的。这就是他所要求的。他实际上在内存地址零处有一些东西。
  • 内存地址零与空指针不同。没有要求空指针具有所有位为零的表示。 zeroAddress = 0 使用空指针常量,因此变量之后是空指针。是的,这就是 C 语言中的一些尴尬之处。
  • 我没有使用 nullptr。我使用了 0。是的,如果你没有将内存映射到零,这个示例将会崩溃。我不质疑这是否可能,只是把它作为已经完成的前提。如果是这样,我展示的代码应该可以防止编译器欺骗你。
  • @RobL:点是,空指针保证不指向有效地址。因此,iff 您的 null 指针 具有“所有位为零”的表示,that 按照标准不能是有效地址。有关其他 cmets,请参阅建议的 dup。
【解决方案2】:

作为一种解决方法,您可以使用 GCC 选项 -fno-delete-null-pointer-checks 来阻止编译器主动检查空指针取消引用。
虽然此选项旨在用于加速代码优化,但它可以在特定情况下使用。

【讨论】:

  • 这是一个坏主意,因为它不能解决实际问题,但主要是一种 hacky 解决方法。不能保证它在所有情况下都有效。
  • @Frankie_C 实际上选项应该是-fno-delete-null-pointer-checks,因为您提到的表单默认情况下是打开的,并且“删除”是指检查,即默认情况下,GCC 会删除明显错误的检查,除非发生 UB .
  • @Olaf OP 专门针对 GCC,这个答案似乎满足了我的要求。在什么情况下它不起作用?
  • @Ruslan:请不要颠倒我的说法。蕴涵不是双射! “不保证工作”并不意味着它不会工作。依靠这个只是一个非常糟糕的主意,尤其是当有更好的方法时。首先是想为什么你认为你需要引用一个与空指针具有相同表示的地址。这对于托管环境通常是无用的,对于独立的裸机,它通常深埋在低级模块中。
  • @Olaf 我没有反转。我只是假设你有一些特定的场景。
【解决方案3】:

0 地址是我所知道的所有架构上的 C99 NULL 指针(实际上是空指针的“实现”,您通常可以写成 0....)。

空指针在托管 C99 中具有非常特殊的状态:当指针可以(或曾经)取消引用时,保证(由语言规范)不是NULL(否则, 是undefined behavior)。

因此,GCC 编译器有权使用optimize(实际上会优化)

int *p = something();
int x = *p;
/// the compiler is permitted to skip the following
/// because p has been dereferenced so cannot be NULL
if (p == NULL) { doit(); return; };

在您的情况下,您可能希望为 C99 标准的独立子集进行编译。所以用gcc -ffreestanding 编译(注意,这个option 会带来一些不便)。

顺便说一句,您可以声明一些 extern char strange[] __attribute__((weak));(甚至可能添加 asm("0") ...)并使用一些 assemblerlinker 技巧来实现 strange有一个 0 地址。编译器不会知道这样的strange 符号实际上是在 0 地址...

我的强烈建议是避免取消引用 0 地址....参见this。如果您确实需要尊重地址 0,请准备好受苦......(所以编码一些 asm,降低优化等......)。

(如果您有mmap-ed 第一页,请避免在地址 0 处使用其第一个字节;这通常没什么大不了的。)

(IIRC,您正在触及 GCC 优化的灰色区域 - 甚至可能是 C99 语言规范,您当然想要 C 的独立风格;请注意,独立 C 的 -O3 优化是在GCC 编译器中没有经过很好的测试,可能有残留的错误......)

您可以考虑更改 GCC 编译器,使空指针的数字地址为 42。这需要一些工作。

【讨论】:

  • NULL 是一个带有 空指针常量 的宏。并且 C 标准很好地允许多个空指针表示。
  • 不知道-ffreestanding,+1。
  • @Ruslan:请仔细阅读其余部分!该选项可以(并且将会)产生其他影响,例如main 不再隐式声明。我认为最后一句话“我的强烈建议是避免取消引用 0 地址......”是这里最重要的部分!
  • @Olaf 如果代码必须访问,例如用于实模式环境的中断向量表或做任何其他低级的东西,避免取消引用空地址的建议充其量是无用的。 OP中mmapped零地址的例子只是一个例子。
  • @Ruslan:请阅读术语“独立环境(C)或裸机(更全局)的含义。您准确地描述了这样的环境!但是 x86 实模式有一些更讨厌的,由于到它的垃圾分段,其中一个指针实际上由两个字段组成。请注意,对于旧的 x86,您可以通过多种方式访问​​物理内存地址0x0,利用段:偏移环绕。搜索“gate A20”以查看如何损坏x86 架构实际上是。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 2011-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-22
相关资源
最近更新 更多