Home > Enterprise >  What happens when a 64-bit value is passed as a parameter in a function on a 32-bit architecture?
What happens when a 64-bit value is passed as a parameter in a function on a 32-bit architecture?

Time:10-05

I am facing a weird issue. I am passing a uint64_t offset to a function on a 32-bit architecture(Cortex R52). The registers are all 32-bit.

This is the function. :

void read_func(uint8_t* read_buffer_ptr, uint64_t offset, sizeof(read_buffer)
main(){
   // read_buffer : memory where something is read.
   // read_buffer_ptr : points to read_buffer structure where value should be stored after reading value.
   read_func(read_buffer_ptr, 0, sizeof(read_buffer);
}

In this function, the value stored in offset is not zero but some random values which I also see in the registers(r5, r6). Also, when I use offset as a 32-bit value, it works perfectly fine.

Can you please let me know why this could be happening? Are registers not enough?

CodePudding user response:

  1. Cortex-R52 has 32 bits address bus and offset cannot be 64 bits. In calculations only lower 32bits will be used as higher ones will not have any effect.

example:

uint64_t foo(void *buff, uint64_t offset, uint64_t size)
{
    unsigned char *cbuff = buff;
    
    while(size--)
    {
        *(cbuff     offset) = size & 0xff;
    }
    return offset   (uint32_t)cbuff;
}

void *z1(void);
uint64_t z2(void);
uint64_t z3(void);

uint64_t bar(void)
{
    return foo(z1(), z2(), z3());
}
foo:
        push    {r4, lr}
        ldr     lr, [sp, #8]    //size
        ldr     r1, [sp, #12]   //size
        mov     ip, lr
        add     r4, r0, r2     // cbuff   offset calculation r3 is ignored as not needed - processor has only 32bits address space.
.L2:
        subs    ip, ip, #1     //size--
        sbc     r1, r1, #0     //size--
        cmn     r1, #1
        cmneq   ip, #1
        bne     .L3
        add     r0, r0, lr
        adds    r0, r0, r2
        adc     r1, r3, #0
        pop     {r4, pc}
.L3:
        strb    ip, [r4], #1
        b       .L2
bar:
        push    {r0, r1, r4, r5, r6, lr}
        bl      z1
        mov     r4, r0      // buff
        bl      z2
        mov     r6, r0      // offset
        mov     r5, r1      // offset
        bl      z3
        mov     r2, r6      // offset
        strd    r0, [sp]    // size passed on the stack
        mov     r3, r5      // offset
        mov     r0, r4      // buff
        bl      foo
        add     sp, sp, #8
        pop     {r4, r5, r6, pc}

As you see resister r2 & r3 contain the offset, r0 - buff and size is on the stack.

CodePudding user response:

The prototype posted is invalid, it should be:

void read_func(uint8_t *read_buffer_ptr, uint64_t offset, size_t size);

What happens when you pass a 64-bit argument on a 32-bit architecture is implementation defined:

  • either 8 bytes of stack space are used to pass the value
  • or 2 32-bit registers are used to pass the least significant part and the most significant part
  • or a combination of both depending on the number of arguments
  • or some other scheme appropriate for the target CPU

In your code you pass 0 which has type int and presumably has only 32 bits. This is not a problem if the prototype for read_func was correct and parsed before the function call, otherwise the behavior is undefined and a C99 compiler should not even compile the code, but may compilers will just issue a warning and generate bogus code.

  • Related