What I learnt today…

… is that on the AVR, the “Data Memory Usage” information given by AVRGCC  only refers to memory allocated at compile time, and does not take the stack into account.1 2 The stack is not to be underestimated: it holds not only the return addresses from function calls, but also all local variables3 of the function(s) called.

So you might feel safe and sound reading something like “Data Memory Usage : 66 bytes 51,6% Full”, thinking there’s plenty of space left for future features; and then the stack still swoops down and corrupts your precious data because of a couple of nested function calls.

Took me a while to figure out what happened here. Then again, I have never worked on a machine with just 128 Bytes of RAM before.

The good news is that this can be reproduced reliably in the AtmelStudio simulator: Find the memory locations of the data being corrupted, set data breakpoints, and when the simulator stops at the end of something like this

000000F5 PUSH R11 Push register on stack 
000000F6 PUSH R12 Push register on stack 
000000F7 PUSH R13 Push register on stack 
000000F8 PUSH R14 Push register on stack 
000000F9 PUSH R15 Push register on stack 
000000FA PUSH R16 Push register on stack 
000000FB PUSH R17 Push register on stack 
000000FC PUSH R18 Push register on stack 
000000FD PUSH R19 Push register on stack 
000000FE PUSH R20 Push register on stack 
000000FF PUSH R21 Push register on stack 
00000100 PUSH R22 Push register on stack 
00000101 PUSH R23 Push register on stack 
00000102 PUSH R24 Push register on stack 
00000103 PUSH R25 Push register on stack 
00000104 PUSH R26 Push register on stack 
00000105 PUSH R27 Push register on stack 
00000106 PUSH R28 Push register on stack 
00000107 PUSH R29 Push register on stack 
00000108 PUSH R30 Push register on stack 
00000109 PUSH R31 Push register on stack

you know Bob’s your proverbial male relative.

  1. Nor does it take into account dynamically allocated memory. But you’d rarely use that on an AVR, at least on the smaller ones.
  2. But could the compiler even know how large the stack will grow at runtime? Not exactly, obviouly, as that depends on the actual control flow path take when the program runs, which will usually depend on external inputs that the compiler cannot know about. But through static code analysis, the compiler could build a call graph, and if this graph is non-cyclic (i.e., there’s no recursive calling), it could provide an upper bound for the stack size. Based on this, it could issue a warning that the stack might grow into the memory allocated for variables.
  3. albeit often indirectly, as the compiler pushes register contents onto the stack to make room for local variables in the AVR’s registers

Leave a Reply

Your email address will not be published. Required fields are marked *