Does GCC crash when you accept an argument address on ARM7TDMI?

My C code snippet takes the address of the argument and stores it in a memory location (pre-processed code):

void foo(unsigned int x) { *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&x); } int main() { foo(1); while(1); } 

I used the SVN version of GCC to compile this code. At the end of the foo function, I would expect the value 1 be stored on the stack, and at 0x40000d4 address pointing to this value. When I compile without optimization using the -O0 flag, I get the expected output of the ARM7TMDI assembly (commented out for your convenience):

  .align 2 .global foo .type foo, %function foo: @ Function supports interworking. @ args = 0, pretend = 0, frame = 8 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. sub sp, sp, #8 str r0, [sp, #4] @ 3. Store the argument on the stack mov r3, #67108864 add r3, r3, #212 add r2, sp, #4 @ 4. Address of the stack variable str r2, [r3, #0] @ 5. Store the address at 0x40000d4 add sp, sp, #8 bx lr .size foo, .-foo .align 2 .global main .type main, %function main: @ Function supports interworking. @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 stmfd sp!, {r4, lr} mov r0, #1 @ 1. Pass the argument in register 0 bl foo @ 2. Call function foo .L4: b .L4 .size main, .-main .ident "GCC: (GNU) 4.4.0 20080820 (experimental)" 

It explicitly saves the argument first on the stack and from there saves it to 0x40000d4 . When I compile with optimization using -O1 , I get something unexpected:

  .align 2 .global foo .type foo, %function foo: @ Function supports interworking. @ args = 0, pretend = 0, frame = 8 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. sub sp, sp, #8 mov r2, #67108864 add r3, sp, #4 @ 3. Address of *something* on the stack str r3, [r2, #212] @ 4. Store the address at 0x40000d4 add sp, sp, #8 bx lr .size foo, .-foo .align 2 .global main .type main, %function main: @ Function supports interworking. @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 stmfd sp!, {r4, lr} mov r0, #1 @ 1. Pass the argument in register 0 bl foo @ 2. Call function foo .L4: b .L4 .size main, .-main .ident "GCC: (GNU) 4.4.0 20080820 (experimental)" 

This time, the argument is never stored on the stack, even if something from the stack is still stored at 0x40000d4 .

Is this the expected / undefined behavior? Did I do something wrong or did I really find a compiler error and trade?

+4
source share
11 answers

As soon as you return from foo() , x will disappear, and any pointers to it are invalid. Subsequently, the use of such a pointer leads the C standard to like to call "undefined behavior", which means that the compiler is absolutely allowed to assume that you will not find it, or (if you insist on executing it anyway) you do not need to create code, who does something remotely, like what you expect. If you want the pointer to x remain valid after foo() returns, you should not allocate x on the foo stack, period - even if you know that there is basically nothing for this, because it is simply not allowed in C , no matter how often this happens, to do what you expect.

The simplest solution can be to make x local variable in main() (or any other function has a fairly long-lived area) and pass the address to foo. You can also make x global variable or allocate it on the heap using malloc() , or allocate memory for it in an even more exotic way. You can even try to figure out where the top of the stack is in some (hopefully) more portable form and explicitly stores your data in some part of the stack if you are sure that you will not need anything else and you "I I’m convinced that you really need to, but the method that you used for this is not reliable enough, as you discovered.

+7
source

So, you put the address of the local stack variable in the DMA controller that will be used, and then you return from the function in which the stack variable is available?

Although this may work with your main () example (since you no longer write on the stack), it will not work in the "real" program later - this value will be overwritten before or when the DMA calls it when another function is called and the stack is used again .

You need to have a structure or global variable that you can use to keep this value while the DMA accesses it, otherwise it will just be knocked down!

-Adam

+4
source

Actually, I don’t think the compiler is wrong, although this is an odd case.

From the point of view of code analysis, he sees that you store the address of the variable, but this address is never dereferenced, and you do not go beyond the function to an external code that could use the address that you saved. When you exit a function, the stack address is now considered fictitious, since its address is a variable that no longer exists.

The "volatile" keyword does really little in C, especially with respect to multiple threads or equipment. It simply tells the compiler that it should do the access. However, since there are no users of the x value according to the data stream, there is no reason to store "1" on the stack.

Maybe this will work if you wrote

 void foo(unsigned int x) { volatile int y = x; *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&y); } 

although it may still be illegal code, since the address y is considered invalid as soon as foo is returned, the nature of the DMA system will refer to this location regardless of the program flow.

+4
source

It should be noted that, in accordance with the standard, castings are r values. GCC used to allow this, but in recent versions has become a bit of a standard devotee.

I don't know if this will matter, but you should try the following:

 void foo(unsigned int x) { volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4); *ptr = (unsigned int)(&x); } int main() { foo(1); while(1); } 

In addition, I doubt that you intended to do this, but you store the address of the local x function (which is a copy of the int you passed). You probably want to make foo "unsigned int *" and pass in the address of what you really want to keep.

Therefore, I believe that the following would be a better solution:

 void foo(unsigned int *x) { volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4); *ptr = (unsigned int)(x); } int main() { int x = 1; foo(&x); while(1); } 

EDIT: finally, if you break code through optimization, this is usually a sign that your code is doing something wrong.

+3
source

I'm damn if I can find the link for now, but I'm 99% sure that you should always be able to accept the address of the argument, and it's up to the compiler to refine the details of calls, using registers, etc.

In fact, I would think that it is such a general requirement that it is difficult to see that this could be a common problem. Interestingly, this is something like flying pointers that violated optimization.

Personally, I could try this to make sure it compiles better:

 void foo(unsigned int x) { volatile unsigned int* pArg = &x; *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg; } 
+1
source

Tomi Kyöstilä wrote

for Game Boy Advance. I read about my DMA system and I experimented with it, creating monochrome bitmap images. The idea was to have an indexed color passed as an argument to a function that would use DMA to fill the tiles with that color. The source address for DMA transmission is stored at 0x40000d4.

This is the right thing for you, and I can see how the (unexpected) code you received with the -O1 optimization will not work.

I see the (expected) code that you received with the -O0 optimization does what you expect - it pushes the value of the color you want on the stack and a pointer to that color in the DMA transfer register.

However, even the (expected) code that you received with the -O0 optimization will not work either. By the time the DMA hardware bypasses this pointer and uses it to read the desired color, this value on the stack has (probably) been overwritten for a long time by other routines or interrupt handlers, or both. And therefore, both expected and unexpected code lead to the same thing - DMA (probably) is going to get the wrong color.

I think you really wanted to keep the color value in some place where it remains safe until the DMA finishes reading it. So a global variable or a local static variable such as

// Warning: three-star programmer at work

 // Warning: untested code. void foo(unsigned int x) { static volatile unsigned int color = x; // "static" so it not on the stack volatile unsigned int** dma_register = (volatile unsigned int**)(0x4000000 + 0xd4); *dma_register = &color; } int main() { foo(1); while(1); } 

Does this work for you?

You see that I use "volatile" twice because I want the two values ​​to be written in this particular order.

+1
source

sparkes wrote

If you think you have found an error in the GCC, the mailing lists will be glad to see you, but in general they find some kind of hole in your blame and mock ruthlessness :(

I decided that I would try my luck first before going to the GCC mailing list to show my incompetence :)


Adam Davis wrote

Out of curiosity, what are you trying to accomplish?

I tested the development of Game Boy Advance. I read about my DMA system, and I experimented with it, creating monochrome bitmaps. The idea was that the indexed color be passed as an argument to a function that would use DMA to fill the tile with that color. The source address for DMA transmission is stored at 0x40000d4 .


Will Dean write

Personally, I could try to see this if it compiles better:

 void foo(unsigned int x) { volatile unsigned int* pArg = &x; *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg; } 

With -O0 , which works the same way, and with -O1 , which is optimized for the same assembly -O1 that I posted in my question.

0
source

Not an answer, but simply additional information for you. At my day job, we are launching 3.4.5 20051201 (Red Hat 3.4.5-2).

We also noticed that part of our code (which I cannot post here) stops working when we add the -O1 flag. Our solution was to remove the flag for now: (

0
source

In general, I would say that this is a true optimization. If you want to delve deeper into it, you can compile with -da. This generates the name .c.Number.Passname, where you can see rtl (intermediate representation in gcc). There you can see which driveway does which optimization (and, possibly, disable only the one you do not want)

0
source

I think that even T. has an answer. You passed a variable, you cannot take the address of this variable inside a function, you can take the address of a copy of this variable, although, btw, this variable is usually a register, so it does not have an address. As soon as you leave this function, all of it is gone, the calling function loses it. If you need an address in a function that you must follow the link, and not by value, send the address. It seems to me that the error is in your code, not gcc.

BTW, using * (volatile blah *) 0xabcd or any other method to try to program registers will eventually bite you. gcc and most other compilers have this supernatural way of finding out exactly the worst time to hit.

Tell me what day you are changing

 *(volatile unsigned int *)0x12345 = someuintvariable; 

to

 *(volatile unsigned int *)0x12345 = 0x12; 

A good compiler will understand that you save only 8 bits, and there is no reason to spend 32-bit storage on this, depending on the architecture you specified or the default architecture for this compiler that day, so it is inside its right to optimize this for strb instead str.

After these were burned dozens of times by gcc and others, I resorted to causing the problem:

 .globl PUT32 PUT32: str r1,[r0] bx lr PUT32(0x12345,0x12); 

The cost of a few additional clock cycles, but my code continues to work yesterday, today, and will work tomorrow with any optimization flag. Without having to re-visit the old code and sleep soundly at night, there are a few additional clock cycles here and there.

Also, if your code breaks when compiling for release instead of compiling for debugging, it also means that this is most likely an error in your code.

0
source

Is this only expected behavior? Did I do something wrong or did I really find the Error ™ compiler?

There are no errors of only a certain behavior that optimization parameters can create odd code that may not work :)

EDIT:

If you think you have found a mistake in GCC, the mailing lists will be happy if you fall, but as a rule, they find that some kind of hole in your knowledge is to blame and scoffs at ruthlessness :(

In this case, I think these are probably -O options trying to use shortcuts that break your code that needs to be bypassed.

-2
source

Source: https://habr.com/ru/post/1276439/


All Articles