Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVR C compilers behavior. Memory management

Do AVR C compilers make program memorize the address in SRAM where function started to store its data (variables, arrays) in data stack in one of index registers in order to get absolute address of local variable by formula:

absoluteAdr = functionDataStartAdr + localShiftOfVariable.

And do they increase data stack point when variable declared by it's length or stack pointer increased in end/start of function for all it's variables lengths.

like image 374
Kuznetsov S.A. Avatar asked Oct 17 '22 23:10

Kuznetsov S.A.


2 Answers

Let's have a look at avr-gcc, which is freely available including its ABI:

Do AVR C compilers make program memorize the address in SRAM where function started to store its data (variables, arrays) in data stack in one of index registers in order to get absolute address of local variable by formula:

Yes, no, it depends:

Static Storage

For variables in static storage, i.e. variables as defined by

unsigned char func (void)
{
    static unsigned char var;
    return ++var;
}

the compiler generates a symbol like var.123 with appropriate size (1 byte in this case). The linker / locator will then assign the address.

func:
    lds  r24,var.1505
    subi r24,lo8(-(1))
    sts  var.1505,r24
    ret
    .local  var.1505
    .comm   var.1505,1,1

Automatic

Automatic variables are held in registers if possible, otherwise the compiler allocates space in the frame of the function. It may even be the case that variables are optimized out, and in that case they do not exist anywhere in the program:

int add (void)
{
    int a = 1;
    int b = 2;
    return a + b;
}

add:
    ldi r24,lo8(3)
    ldi r25,0
    ret

There are 3 types of entities that are stored in the frame of a function, all of which might be present or absent depending on the program:

  • Callee-saved registers that are saved (PUSH'ed) by the function prologue and restored (POP'ed) by the epilogue. This is needed when local variables are allocated to callee-saved registers.

  • Space for local variables that cannot be allocated to registers. This happens when the variable is too big to be held in registers, there are too many auto variables, or the address of a variable is taken (and taking the address cannot be optimized out). This is because you cannot take the address of a register1.

    void use_address (int*);
    
    void func (void)
    {
       int a;
       use_address (&a);
    }
    

    The space for these variables is allocated in the prologue and deallocated in the epilogue. Shrink-wrapping is not implemented:

    func:
        push r28
        push r29
        rcall .
        in r28,__SP_L__
        in r29,__SP_H__
        /* prologue: function */
        /* frame size = 2 */
        /* stack size = 4 */
        movw r24,r28
        adiw r24,1
        rcall use_address
        pop __tmp_reg__
        pop __tmp_reg__
        pop r29
        pop r28
        ret
    

    In this example, a occupies 2 bytes which are allocated by rcall . (it was compiled for a device with 16-bit program counter). Then the compiler initialized the frame-pointer Y (R29:R28) with the value of the stack pointer. This is needed because on AVR, you cannot access memory via SP; the only memory operations that involve SP are PUSH and POP. Then the address of that variable which is Y+1 is passed in R24. After the call of the function, the epilogue frees the frame and restores R28 and R29.

  • Arguments that have to be passed on the stack:

    void xfunc (int, ...);
    
    void call_xfunc (void)
    {
        xfunc (42);
    }
    

    These arguments are pushed and the callee is picking them up from the stack. These arguments are pushed / popped around the call, but can also be accumulated by means of -maccumulate-args.

    call_func:
        push __zero_reg__
        ldi r24,lo8(42)
        push r24
        rcall xfunc
        pop __tmp_reg__
        pop __tmp_reg__
        ret
    

    In this example, the argument has to be passed on the stack because the ABI says that all arguments of a varargs function have to be passed on the stack, including the named ones.

For a description on how exactly the frame is being layed out and arguments are being passed, see [Frame Layout and Argument Passing] (https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout).

1 Some AVRs actually allow this, but you never (like in NEVER) want to pass around the address of a general purpose register!

like image 189
emacs drives me nuts Avatar answered Oct 19 '22 15:10

emacs drives me nuts


Compilers are not managing the RAM, compilers at compilation time calculate the required size for each data sections like bss, data, text, rodata, .. etc and generate relocatable object file for each translation unit

The linker comes after and generate one object file and assign the relocatable addresses to absolute ones mapped according to the Linker configuration File LCF.

In run time, the mechanism depends on the architecture itself. normally, each function call has a frame in the stack where it's arguments, return address and local variables are defined. the stack extend with a creation of variables and for low cost AVR microcontrollers, there is no memory management protection regarding the stack increase or the overlapping between the stack and another memory section -normally the heap-. even if there is OS managing the protection from the tasks to exceed its allocated stack, without a memory management unit, all what OS can do is to assert a RESET with illegal memory access reason.

like image 30
Mo SAEED Avatar answered Oct 19 '22 14:10

Mo SAEED