I am fairly new to assembly.
After compiling, when I run the code below, I get a segmentation fault.
I am trying inline assembly with recursion. I am compiling this code with cxxdroid.
int sum_with_rec(int k)
{
int res =0;
__asm ("sum_with_rec_:\n"
"CMP %[input_i], #0\n"
"BEQ endrec\n"
"rec:\n"
"ADD %[result], %[input_i]\n"
"PUSH {%[result]}\n"
"SUB %[input_i], #1\n"
"BL sum_with_rec_\n"
"endrec:\n"
:[result] "+r" (res)
:[input_i] "r" (k)
);
return res;
}
int main(void)
{
int d = 10;
int e = 0;
e = sum_with_rec(d);
printf("Result of %d = %d\n", d, e);
}
Edit The target system is armv7. The compiler I use is cxxdroid.
Normal inline assembly compiles fine. When trying recursion in inline assembly it faults.
You have an unbalanced push
. (Which is doing what? Is that supposed to be passing an arg? But your "callee", sum_with_rec_:
, is looking for input in the registers picked by the compiler for the "r"
and "+r"
constraints. Look at disassembly or gcc -S
asm output, e.g. on https://godbolt.org/, to see how GCC filled in your template with the operands it picked, and to see the compiler-generated code around your block.)
But more importantly, you let execution fall out of the inline asm statement into the compiler-generated epilogue with a modified lr
(return address), or worse into main
if this inlines!! (Which it will with normal optimization levels.)
If sum_with_rec
doesn't inline into main
(which it won't with the default -O0
), the recursive callees will return back into the asm
statement (after the bl
, to endrec:
), then fall out again, running the compiler-generated epilogue multiple times. (Because as far as the compiler knows, this is a leaf function, and you didn't declare a clobber on "lr"
, but your asm statement's bl
instruction sets lr
to its return address). The epilogue may include some stack-pointer manipulation; depends on optimization options, among many other problems, which is part of why this is not a supported way to use GNU C inline assembly. (See https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html / https://stackoverflow.com/tags/inline-assembly/info)
The details of how it breaks depend on build options and details of what the compiler did, but this entire approach is a non-starter. You can call a C function from inside an asm
statement, although it's a pain. (You need to declare clobbers on every call-clobbered register in the ABI, including "cc"
which your cmp
also sets, and r0-r3
and others, and make sure your own asm is expecting that. And make sure the stack pointer is sufficiently aligned in ABIs where SP on call is aligned by more than 1 stack slot. e.g. x86-64 requires 16-byte stack alignment, but pushes are in units of 8.)
You also can call a chunk of hand-written asm code which return (bx lr
) without ever leaving hand-written asm. (e.g. in a global-scope asm
statement outside any function, or separate .S
file, or in part of your asm statement inside a function that you jump over so its only ever reached by bl
, not by falling into it.)
Then you still have to write clobbers for your asm
statement which correctly describes all the registers you modify. For example, you don't declare a clobber on "lr"
, the link register, so the compiler's prologue/epilogue won't have saved/restored it. (If they did, then your very first recursive return would return to main
, not recursively back up the call-tree. If not, then you'll have an infinite loop.)
Don't use it at all when you're trying to learn assembly; you have to already know assembly and how compilers "think" in order to write correct constraints to describe your asm
statement to the compiler, and to understand everything that's going on and what's supported vs. what isn't. It's a delicate dance between your asm and the compiler-generated asm, and you need to not step on each other's toes.
This would be much easier to understand and debug if you just wrote the whole asm function yourself, where you push a return address. It's not supported to jump back into the middle of an asm statement from C.
You can use a separate .S
file, or put a Basic asm
statement (no constraints) inside an __attribute__((naked))
function. In both cases you still have to write everything yourself, including push {r0, lr}
/ ... / pop {r1, pc}
or whatever. (Pushing LR and popping it back into the program counter is a traditional way to return on ARM, when you don't need to interoperate between ARM and Thumb mode. bx lr
is the normal way to just return.)
Thanks for thé help Eugene and Peter. Changing BL to B solved thé problem. I could have looked hours for thos
With b
it's not recursion, just a while()
loop. Your code is missing several pieces to actually be a recursive function call.
Look at compiler-generated code for a pure-C recursive function, compiled with -Og
or something, on https://godbolt.org/ (At higher optimization levels it will inline into itself and convert recursion to iteration; -Og -fno-inline-functions
should avoid that.)
For example, in your current code, res
isn't really passed / returned, it's just a global register variable across calls. So you're using recursion to loop, but not to get data between callers and callees.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With