Max Sedlusch
Max Sedlusch

Reputation: 105

ARM V7 inline assembly - moving a C variable into a register

There are no questions on ARM V7 inline assembly that answer mine so I made a post. I want to move the value of C variables into r0-r2 and the other way round - from the registers into C variables. However, my code always loads the same value - 12 - into the register. Can anyone tell me how I can achieve my goal?

// syscalls.h
#ifndef SYSCALL_H
#define SYSCALL_H
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define SYSCALL_ID_EXIT          1
#define SYSCALL_ID_CREATE_THREAD 2
#define SYSCALL_ID_GETC          3
#define SYSCALL_ID_PUTC          4
#define SYSCALL_ID_SLEEP         5
#define SYSCALL_ID_UNDEFINED     6
#ifndef __ASSEMBLER__
//...
// syscalls.c
// ...
void syscall_create_thread(void (*f) (void *), void * args, unsigned int arg_size){
    unsigned int func = (unsigned int) f;
    unsigned int arguments = (unsigned int) args;
    unsigned int argument_size = (unsigned int) arg_size;
    asm("mov r0, %0" : "=r"(func)::"r0");
    asm("mov r1, %0" : "=r"(arguments)::"r1");
    asm("mov r2, %0" : "=r"(argument_size)::"r2");
    asm("mov r7, #" TOSTRING(SYSCALL_ID_CREATE_THREAD));
    asm("SVC #0");
}

Upvotes: 1

Views: 77

Answers (1)

Eugene Sh.
Eugene Sh.

Reputation: 18381

Your specific use-case can be done as simple as following:

__attribute__((naked))
void syscall_create_thread(void (*f) (void *), void * args, unsigned int arg_size) 
{
    asm("mov r7, #" TOSTRING(SYSCALL_ID_CREATE_THREAD) :::"r7");
    asm("SVC #0");
    asm("RET");
}

This relies on the fact that the calling convention for your architecture defines that the arguments being passed to a function are placed in the registers r0, r1... by the caller. When you make the function naked you are telling the compiler not to generate any prologue/epilogue for the function, that is not to create a stack frame, save the callee-saved registers or even add ret instruction. But it means that you need to be extra careful too and add the missing pieces yourself.

Here is the final example code and the generated assembly to convince you that it works:

#include <stdlib.h>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define SYSCALL_ID_CREATE_THREAD 2

__attribute__((naked))
void syscall_create_thread(void (*f) (void *), void * args, unsigned int arg_size) 
{
    asm("mov r7, #" TOSTRING(SYSCALL_ID_CREATE_THREAD) :::"r7");
    asm("SVC #0");
    asm("RET");
}


void callback(void* args)
{
    return;
}

void caller() {
    int arg = 8;
    syscall_create_thread(callback, &arg, sizeof (int));
}

And the generated assembly (annotated by me):

callback:
        bx      lr
syscall_create_thread:
        mov r7, #2
        SVC #0
        RET
caller:
        str     lr, [sp, #-4]!
        sub     sp, sp, #12
        mov     r3, #8
        str     r3, [sp, #4]
        mov     r2, #4              // r2=sizeof(int)
        add     r1, sp, r2          // r1 = address of the local `arg` variable
        ldr     r0, .L5             // r0 = address of callback function
        bl      syscall_create_thread
        add     sp, sp, #12
        ldr     lr, [sp], #4
        bx      lr
.L5:
        .word   callback

See this on Godbolt

EDIT: Apparently, when the function is "naked", there is no effect of the r7 being marked as clobbered, so it won't be "callee-saved" as it should, so some amendment is needed for the code above to actually make sure r7 value is saved and restored within the syscall_create_thread() function. The simplest would be to do so in one of the non-callee-saved registers, such as r3 or r4. Or it can be done on stack.

Upvotes: 2

Related Questions