1

In some project, I must access the machine code instructions of a function for debugging reasons.

My first approach (I decided to do it differently) was to convert a function pointer to the function to a data pointer using a union like this:

void exampleCode(void)
{
    volatile union {
        uint8_t ** pptr;
        void (* pFunc)(void);
    } u;
    uint8_t ** copyPptr;
    uint8_t * resultPtr;

#if 1
    u.pptr = (uint8_t **)0x10000000;
#endif
    u.pFunc = &myFunction;
    copyPptr = u.pptr;

    /* The problem is here: */
    resultPtr = copyPptr[109];
    *resultPtr = 0xA5;
}

If I remove the line u.pptr = (uint32_t **)0x10000000, the compiler assumes that copyPptr two lines below has an undefined value (according to the C standard, converting function pointers to data pointers is not allowed).

For this reason, it "optimizes" the last two lines out (which is of course not wanted)!

However, the volatile keyword means that the C compiler must not optimize away the read operation of u.pptr in the line copyPptr = u.pptr.

From my understanding, this also means that the C compiler must assume that it is at least possible that a "valid" result is read in this read operation, so copyPptr contains some "valid" pointer.

(At least this is how I understand the answers to this question.)

Therefore, the compiler should not optimize away the last two lines.

Is this a compiler bug (GCC for ARM) or is this behavior allowed according to the C standard?

EDIT

As I have already written, I already have another solution for my problem.

I asked this question because I'm trying to understand why the union solution did not work to avoid similar problems in the future.

In the comments, you asked me for my exact use case:

For debugging reasons, I need to access some static variable in some library that comes as object code.

The disassembly looks like this:

.text
.global myFunction
myFunction:
    ...
.L1:
    .word .theVariableOfInterest

.data:
# unfortunately not .global!
.theVariableOfInterest:
    .word 0

What I'm doing is this:

I set copyPtr to myFunction. If K is (.L1-myFunction)/4, then copyPtr+K points to .L1 and copyPtr[K] points to .theVariableOfInterest.

So I can access the variable using *(copyPtr[K]).

From the disassembly of the object file, I can see that K is 109.

All this works fine if copyPtr contains the correct pointer.

EDIT 2

It's much simpler to reproduce the problem with gcc -O3:

char a, c;
char * b = &a;

void testCode(void)
{
    char * volatile x;
    c = 'A';
    *x = 'X';
}

It's true that uninitialized variables cause undefined behavior.

However, if I understand the description of volatile in the draft "ISO/IEC 9899:TC3" of the C99 standard correctly, a volatile variable would not be "uninitialized" if I stop the program in the debugger (using a breakpoint) at the line c = 'A' and copy the value of b to x in the debugger.

On the other hand, some other statement was saying that using volatile for local variables in current C++ (not C) versions already causes undefined behavior ...

Now I'm wondering what is true for modern C (not C++) versions.

Martin Rosenau
  • 17,897
  • 3
  • 19
  • 38
  • 2
    There are some differing opinions in the question you link to, both in comments and in answers. With that said the compiler shouldn't optimize those two assignments away anyway (even without the `volatile` qualifier), since type-punning through unions is allowed. – Some programmer dude Jul 22 '22 at 15:58
  • Also, perhaps you have simplified your real code a bit too much, because with the code, as currently shown, there's no need for the `copyPtr` or the `resultPtr` variables. You could just use `u.pptr[109][0] = 0xa5;` – Some programmer dude Jul 22 '22 at 15:59
  • Apparently compiler optimized away something https://godbolt.org/z/xE8qMrW8x – Marek R Jul 22 '22 at 15:59
  • It happens since this code is using undefined behavior. `ISO C forbids conversion of function pointer to object pointer type` so you are fighting with an optimizer. – Marek R Jul 22 '22 at 16:05
  • _Side note:_ As posted, because you're using a `union` [vs. a `struct`], `copyPptr` will point to `myFunction` and _not_ `0x10000000` [or whatever else you want to set `u.pptr` to]. That's because you're setting `pFunc` _after_ you set `pptr` so you'll get the later value because of the `union` – Craig Estey Jul 22 '22 at 16:25
  • 1
    I think this is all a red herring. Surely the problem here is the use of `uint8_t** pptr`, which is a pointer to an array of byte pointers, rather than `uint8_t* pptr`, which would be a pointer to an array of bytes. Whatever a function pointer points to (which might or might not be the assembled code), it is almost certainly not an array of byte pointers. – rici Jul 22 '22 at 16:34
  • @Someprogrammerdude I added the `copyPtr` to make the question more understandable. I compiled exactly the code shown in the question with my current compiler settings to ensure that the problem is still occuring with this "simplified" code - and it still occurs. If I leave out the `volatile`, the compiler optimizes the complete function away, so it only consists of the return instruction (`bx lr`). – Martin Rosenau Jul 22 '22 at 16:36
  • @CraigEstey Exactly this is what I want: I want `copyPptr` to point to the entry point of the function `myFunction`. The value `0x10000000` could be any other value (but `NULL`). It is only there because the compiler does some stupid optimizations if I leave this line out. – Martin Rosenau Jul 22 '22 at 16:39
  • On my system, for `-O2`, neither `clang` 7.0.1 nor `gcc` 8.3.1 optimizes the code away. And they both generate: `movq $268435456, -8(%rsp) # imm = 0x10000000 movq $myFunction, -8(%rsp) movq -8(%rsp), %rax movq 872(%rax), %rax movb $-91, (%rax) retq` – Craig Estey Jul 22 '22 at 16:42
  • @rici The code I've posted is very simplified. (In reality, I would have to do some more calculations that I have removed to simplify my question.) In the end, I want to access two 32-bit words in the `.text` segment that both contain addresses (pointers) of variables. – Martin Rosenau Jul 22 '22 at 16:44
  • 1
    @MartinRosenau: Perhaps you need to post a more realistic example, then. Regardless, trying to use whatever a function pointer points to as an array of pointers seems to me to be piling undefined behaviour on top of undefined behaviour. You can probably get away with treating it as an array of bytes, then constructing a pointer to the particular data values you are interested in, and then treating those specific bytes as pointers. (Although there's probably a better solution.) – rici Jul 22 '22 at 16:56
  • If you're [just] trying to read/probe `.text`, although you can use one, I don't think you _need_ a `union` to do this. Just a cast. And, I don't think you need need double pointers – Craig Estey Jul 22 '22 at 17:03
  • @CraigEstey Unfortunately, by casting the function pointer to a data pointer I have the same problem: The C compiler assumes that the result of casting is undefined (it is according to C language specs!) and optimizes the instructions away, too. – Martin Rosenau Jul 22 '22 at 17:20
  • @MarekR The fact that conversion from function pointers to data pointers is not supported, may have the effect that the pointer `u.pptr` will contain some non-sense value (e.g. when running on a DSP with separate code and data address space). However, although the value is non-sense, it should be well-defined in this case. And in my case, `u.pptr` definitely contains the pointer that I'm interested in. So the conversion worked in the way I wanted it. – Martin Rosenau Jul 22 '22 at 20:40
  • It is not problem of "support" C standard defines this as "Undefined Behavior" and compiler optimizer assumes: "code must not contain any UBs, so if I've found one then there is no path which will lead to such behavior, so I will remove such code". You need something which will convince compiler there is no UB - for example by using some compiler extension. – Marek R Jul 22 '22 at 20:59
  • @MarekR I just had a look at the C99 draft "ISO/IEC 9899:TC3": If I understand the description of `volatile` in chapter 6.7.3 correctly, the C standard defines that you are allowed to put a breakpoint to the line `copyPtr = u.pptr`, set `u.pptr` to `&foo` with the debugger, continue the program and the program must then behave the same way as if there was a line `u.pptr = &foo` immediately before the breakpoint. This is definitely not the case here. – Martin Rosenau Jul 22 '22 at 21:23
  • @CraigEstey That's correct: 0x10000000 is just a dummy value. The only reason for the line is: If I don't initialize `u.pptr` with any non-`NULL` value, the compiler optimizes the code away... – Martin Rosenau Jul 22 '22 at 21:39
  • Create a function: `void *liar(void *addr) { return addr; }` Put it in a _separate_ `.c` file [so the compiler can't "inspect" it]. This function is like a cast that the compiler won't detect. You're already doing UB [sort of] by trying the fetch memory from the code segment, so don't worry. Then, do: `void *code = liar(myFunction); code += offsetof(.L1); void **ptr1 = addr; int *ptr2 = *ptr1; int myvalue = *ptr2;` – Craig Estey Jul 22 '22 at 21:55

1 Answers1

3

I'd personally try compiling with the no optimization flag -O0 (capital o, zero) to avoid the compiler "optimizing" out your code and seeing if it works then. If that's still acting problematic, consider adding the assembly -S flag so you can see what it's actually doing translating those instructions as and retranslating them yourself using the inline assembly __asm__ function.

JohnAlexINL
  • 615
  • 7
  • 15
  • 1
    I'd say that was a good suggestion. You can experiment here: https://godbolt.org/ – Paul Sanders Jul 22 '22 at 16:22
  • 1
    Thanks for your answer. But as I already wrote, I already have a working solution (indeed, it is based on inline assembly). The goal of my question was not finding a solution that works but the goal was understanding why the first approach did not work. – Martin Rosenau Jul 23 '22 at 06:06