1

Suppose I need to write to zero address (e.g. I've mmapped something there and want to access it, for whatever reason including curiosity), and the address is known at compile time. Here're some variants I could think of to obtain the pointer, one of these works and another three don't:

#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
}

I compile this with

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

and then objdump -d test.o -M intel --no-show-raw-insn gives me (alignment bytes are skipped here):

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

Here the testNullPointer obviously has UB since it dereferences what is null pointer by definition.

The principle of testAddressZero would give the expected code for any other than 0 address, e.g. 1, but for zero GCC appears to detect that address zero corresponds to null pointer, so also generates UD2.

The asm way of getting the zero address certainly inhibits the compiler's checks, but the price of that is that one has to write different assembly code for each architecture even if the principle of testAddressZero might have been successful (i.e. the same flat memory model on each arch) if not UD2 and similar traps. Also, the code appears not as terse as in the above two variants.

The way of volatile pointer would seem to be the best, but the code generated here appears to not dereference the address for some reason, so it's also broken.

The question now: if I'm targeting GCC, how can I seamlessly access zero address without any traps or other consequences of UB, and without the need to write in assembly?

Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • 1
    If you've `mmap`-ed, then presumably you already have a "valid" pointer, which means you wouldn't need to use *any* of these tricks. – Oliver Charlesworth Feb 21 '16 at 17:01
  • 2
    Related: http://stackoverflow.com/q/35537579/694576 – alk Feb 21 '16 at 17:09
  • @alk - Seems to be the *same* question, asked all of 2 hours ago. What's going on here? – Oliver Charlesworth Feb 21 '16 at 17:11
  • This question is about writing to address 0 the other about "just" reading. – alk Feb 21 '16 at 17:13
  • 2
    You cannot dereference a null pointer. Either your runtime system must disallow such mmap-ing, or your compiler/library must implement the null pointer as something other than all zeros bit pattern (yes such systems do exist). If neither is true, then you are firmly in the UB-land, and help you `deity[choice]`. – n. m. could be an AI Feb 21 '16 at 17:15
  • What using `-fdelete-null-pointer-checks` GCC option? – Frankie_C Feb 21 '16 at 17:42
  • @Frankie_C oh indeed, that seems to be the answer: use `-fno-delete-null-pointer-checks`. Feel free to post it as answer. – Ruslan Feb 21 '16 at 17:47
  • @n.m.: The last sentence invokes UB for atheists and agnostics at least ;-) – too honest for this site Feb 21 '16 at 17:59
  • While still UB by the standard, for some implementations `unsigned * volatile p=0` is basically the way to go. But you placed the `volatile` wrong. Do you want the pointer `volatile` or the object it points to? (No wonder the access has been optimized away). Your main problem might be your target does not support address `0x0` accesses anyway, thus your question is useless. – too honest for this site Feb 21 '16 at 18:01
  • @Olaf I made the pointer volatile, so that the compiler couldn't assume that it remains zero. Seems that indeed it should have instead been `volatile unsigned* volatile`, so that the dereference took place. – Ruslan Feb 21 '16 at 18:15
  • @Ruslan: You do not care about the pointer, but the object it points to! Once the object is accessed, the pointer has to have the correct value. Note it still is UB by the standard, but works reliably on many implementations. That's actually what I mean with "bending the rules". – too honest for this site Feb 21 '16 at 18:22
  • 2
    You should **edit your question to explain *why*** you need to dereference the 0 address. There are very few cases where that is actually useful. – Basile Starynkevitch Feb 21 '16 at 18:28
  • @Olaf if you rely on UB you ought to know your prayers... – n. m. could be an AI Feb 21 '16 at 20:13
  • @n.m.: I very well know about the problems. But my code does not rely on talking to some non-existent almighty entity. As an embedded engineer, you sometimes have to extend the rules a bit. For instance, a hardware register can very well be defined like `#define REG0 (*(volatile uint8_t *)0x0U)`. That just should not pop up at higher abstraction layers in the code.5 It is this kind of flexibility which made C that popular - for the good and the bad. – too honest for this site Feb 21 '16 at 20:26

3 Answers3

2

As a workaround you can use the GCC option -fno-delete-null-pointer-checks that refrain the compiler to actively check for null pointer dereferencing.
While this option is intended to be used to speed-up code optimization it can be used in specific cases as this.

Frankie_C
  • 4,764
  • 1
  • 13
  • 30
  • This is a bad idea, as it does not solve the actual problem, but is mostly a hacky work-around. There is no guarantee it works under all circumstances. – too honest for this site Feb 21 '16 at 18:04
  • @Frankie_C actually the option should be `-fno-delete-null-pointer-checks`, since the form you mention is on by default, and "delete" refers to checks, i.e. by default GCC removes checks which are obviously false unless UB occurs. – Ruslan Feb 21 '16 at 18:17
  • @Olaf The OP is specifically about GCC, and this answer seems to give what I asked. In what cases would it not work? – Ruslan Feb 21 '16 at 18:19
  • @Ruslan: Please do not reverse my statements. An implication is not a bijection! "Is not guaranteed to work" doe not imply it will not work. It is just a very bad idea to rely on this, especially as there are better ways. First is to think **why** you think you need to reference an address with the same representation as a _null pointer_. This is typically useless for hosted environments and for freestanding bare-metal it normally is burried deep in a low-level module. – too honest for this site Feb 21 '16 at 18:39
  • @Olaf I didn't reverse. I just assumed that you have some particular scenarios in mind. – Ruslan Feb 21 '16 at 18:45
  • I believe that answer is wrong, if you forget the `-ffreestanding` option – Basile Starynkevitch Feb 21 '16 at 18:53
  • @BasileStarynkevitch do you mean the option in this answer won't work unless `-ffreestanding` is on? I've checked, and does work even without `-ffreestanding`. And `gcc(1)` doesn't say this these options have one or another as dependencies. – Ruslan Feb 21 '16 at 19:16
  • You cannot be sure that GCC won't optimize (as my answer explains) if you omit `-ffreestanding`. – Basile Starynkevitch Feb 21 '16 at 19:32
-1

I would put the pointer into a global variable:

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

Provided you expose the address beyond the scope of optimization (so the compiler can't figure out you don't set it somewhere else), that should do the trick, albeit slightly less efficiently.

Edit: make this code independent of implicit zero to null conversion.

Rob L
  • 2,351
  • 13
  • 23
  • That clearly dereferences a _null pointer_. Why do you think the scope and linkage change that? – too honest for this site Feb 21 '16 at 17:21
  • Ummmm, yes. That's what he asked for. He has something actually at memory address zero. – Rob L Feb 21 '16 at 17:22
  • Memory address zero is not the same as a _null pointer_. There is no requirement for null pointer to have a representation with all bits zero. `zeroAddress = 0` uses a _null pointer constant_, thus the variable is a _null pointer_ afterwards. And yes, that is some of the awkward things in C. – too honest for this site Feb 21 '16 at 17:23
  • I didn't use nullptr. I used 0. And, yes, this sample will crash if you haven't mapped memory to zero. I'm not questioning whether that is possible or not, just taking it as the premise that it has been done. If so, the code I showed should prevent the compiler from out smarting you. – Rob L Feb 21 '16 at 17:25
  • @RobL: By definition, a literal `0` in this context *is* a null pointer constant. – Oliver Charlesworth Feb 21 '16 at 17:26
  • Yes, I know, i just didn't want to argue that point. This question is about accessng memory address zero and how to make the compiler do it. The definition of null is irrelevant. – Rob L Feb 21 '16 at 17:28
  • The point is that this code doesn't guarantee that `zeroAddress` has all bits zero. – Ruslan Feb 21 '16 at 17:49
  • No, that's incorrect. Try it. zeroAddress will point to process space address 0 because it it set to 0. It might or not point to the same place as nullptr or NULL because those aren't guaranteed to be zero. It might or might not point to physical memory address zero on a modern processor. – Rob L Feb 21 '16 at 17:53
  • @RobL: Not sure what you mean with "nullptr". That is a C++11 name and not supported by C (sadly). Problem is, many people, including yourself confuse the terms `NULL` (a macro with a) _null pointer constant_ (the integer literal `0` in pointer context or explicitly cast to `void *`), _null pointer_ (a pointer object holding an implementation defined), representation which is guaranteed to be valid and not contain a valid address. I just tried to shed some light on that. My appologies if I failed; in that case, please refer to the C standard (which is C11 only, btw.). – too honest for this site Feb 21 '16 at 17:54
  • This is all offtopic. The question and answer have nothing to do with null. It is about memory address zero. I took the questioner's function name, so I'll rename it for clarity. – Rob L Feb 21 '16 at 17:56
  • 1
    @RobL: Point is, a _null pointer_ is guaranteed not to point to a valid address. So, **iff** your _null pointers_ have a representation of "all bits zero", **that** cannot be a valid address by the standard. See the proposed dup for additional comments. – too honest for this site Feb 21 '16 at 17:56
  • No, that's not quite correct. C++ memory allocation will never return a null pointer as a valid address. That is not quite the same thing as saying that there isn't valid memory there. It just means that the c/c++ library won't hand you that address as a valid address. – Rob L Feb 21 '16 at 18:00
  • I see what point you're making about zero since the compiler is allowed to make a conversion from zero to null. Fixing the answer. – Rob L Feb 21 '16 at 18:02
  • `(unsigned *)zero` is still a null pointer, which cannot access any memory by definition. – Bo Persson Feb 21 '16 at 18:18
  • The above answers were right, but this is not. The compiler won't convert zero to nullptr like it would convert '0'. See [link](http://stackoverflow.com/a/2761494) – Rob L Feb 21 '16 at 18:23
  • @RobL: The question is about C, not C++. Please stick to the language discussed! I will refrain from bringing Python here, too. And you shortend my comment too much. Please re-read it carefully! I clearly added some prerequisites (notice the "iff"). As all has been said and this is the second question about that same subject today, I'll leave it at that. Feel free to read the **C** standard carefully and do further research on your own. – too honest for this site Feb 21 '16 at 18:26
  • @RobL: `const uintptr_t zero` still is a variable. C does not support symbolic constants like C++ (again: different languages!), except _enum-constants_. – too honest for this site Feb 21 '16 at 18:29
  • Other than using uintptr_t (and nullptr in comments) for clarity, nothing is C++. And, the question isn't specific to C or C++, except that the author said he used -c for a command line option. – Rob L Feb 21 '16 at 18:29
  • @Olaf: In *practice* memory address zero is *very often* the null pointer. In principle, it could not be. – Basile Starynkevitch Feb 21 '16 at 18:55
  • @BasileStarynkevitch: I never said the opposite (and very well know). Problem is, gcc can generate code for a plethora of targets. Point is the answer is not the solution. It also does not state clear why that he thinks it is. – too honest for this site Feb 21 '16 at 19:00
-1

The 0 address is the C99 NULL pointer (actually the "implementation" of the null pointer, which you can often write as 0....) on all the architectures I know about.

The null pointer has a very specific status in hosted C99: when a pointer can be (or was) dereferenced, it is guaranteed (by the language specification) to not be NULL (otherwise, it is undefined behavior).

Hence, the GCC compiler has the right to optimize (and actually will 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; };

In your case, you might want to compile for the freestanding subset of the C99 standard. So compile with gcc -ffreestanding (beware, this option can bring some infelicities).

BTW, you might declare some extern char strange[] __attribute__((weak)); (perhaps even add asm("0") ...) and have some assembler or linker trick to make that strange have a 0 address. The compiler would not know that such a strange symbol is in fact at the 0 address...

My strong suggestion is to avoid dereferencing the 0 address.... See this. If you really need to deference the address 0, be prepared to suffer.... (so code some asm, lower the optimization, etc...).

(If you have mmap-ed the first page, just avoid using its first byte at address 0; that is often not a big deal.)

(IIRC, you are touching a grey area of GCC optimizations - and perhaps even of the C99 language specification, and you certainly want the free standing flavor of C; notice that -O3 optimization for free standing C is not well tested in the GCC compiler and might have residual bugs....)

You could consider changing the GCC compiler so that the null pointer has the numerical address 42. That would take some work.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • `NULL` is a macro with a _null pointer constant_. And the C standard very well allows for more than a single _null pointer_ representation. – too honest for this site Feb 21 '16 at 18:32
  • Didn't know of `-ffreestanding`, +1. – Ruslan Feb 21 '16 at 18:32
  • @Ruslan: Read the rest carefully! That option can (and will) have other impact, e.g. `main` is not implicitly declared anymore. I think the very last sentence "My strong suggestion is to avoid dereferencing the 0 address ..." is the far by most important part here! – too honest for this site Feb 21 '16 at 18:34
  • @Olaf if the code has to access e.g. interrupt vector table for real mode environment or do whatever else lowlevel stuff, the suggestion to avoid dereferencing null address is useless at best. The example of `mmap`ped zero address in the OP was just _an_ example. – Ruslan Feb 21 '16 at 18:39
  • 1
    @Ruslan: Please read what the term "freestanding environment (C) or bare-metal (more global) mean. You exactly describe such an environment! But x86 real-mode has some more nastynesses, due to its rubbish segmentation where a pointer actually consists of two fields. Note that for the old x86, you could access physical memory address `0x0` with multiple ways exploiting segment:offset wrap-around. Search for "gate A20" to see how broken the x86 architecture actually is. – too honest for this site Feb 21 '16 at 18:45
  • @Olaf I do know exactly how, well, not broken — interesting — x86 architecture is. If A20 is enabled, that `0x100000` trick won't work. I was just curious how to do whatever lowlevel stuff I like still using GCC instead of resorting to assembly. – Ruslan Feb 21 '16 at 18:47
  • Well, I'd call using the keyboard-controller (a seperate MCU) to gate an address-signal of the CPU or the idea itself very well broken. But actually, it is the fault of MS (once more) who saved few bytes of code by exploiting that wrap-around to address the lower memory. Anyway, there are many other such quirks. I'd be more forgiving if there had not been other architectures that time with clean and modern hardware and meory models already. – too honest for this site Feb 21 '16 at 19:04