Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C with inline assembly - recursive function that calls within the asm statement

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.

like image 282
Steven Vanhaeren Avatar asked Sep 03 '25 04:09

Steven Vanhaeren


1 Answers

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 inline asm this way.

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.

like image 123
Peter Cordes Avatar answered Sep 04 '25 18:09

Peter Cordes