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");
}
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.