2

I have noticed that sometimes when I compile C code the assembly code is sometimes generated in method 1:

STR R11, [SP, #-4]!
ADD R11, SP, #0
SUB SP, SP, #4

and sometimes in method 2:

STMFD SP!, {R11, LR}
ADD R11, SP, #4
SUB SP, SP, #4

the difference between the first and the second methods is that the second method saves LR into the stack.

Right now I am facing a problem where my function, that starts like the first method, calls another function using the link register (BL), and since my function does not save the LR in the first place it causes a serious problem. If I could tell the compiler to use the second method It could solve my problem.

It may be connected to the fact that the function calls the inner function using inline assembly, and thus "not recognizes" that there is a call to another function and sees no point in saving the LR. Calling the inline assembly is obligated because the called function gets the value of SP as a parameter.

This is quite a catch-22 situation, hopefully someone could help me solve that. Thanks!

fuz
  • 88,405
  • 25
  • 200
  • 352
YouYou
  • 45
  • 3
  • Why did you tag the question "C" without having shown how your function looks in C? – user3078414 Jun 01 '16 at 08:11
  • 2
    It might be a good idea to replace the call done in inline assembly with a call done in C. – fuz Jun 01 '16 at 08:32
  • If you need assembly to get SP, then use it to get SP ; the call is unrelated, so do it in C. That shouldn't be hard, but you don't show your code so we can't know. – ElderBug Jun 01 '16 at 08:51
  • The equivalent in C would be writing "int func() {}" since I talk about function prelog. But since that gives no information I see no point in that. – YouYou Jun 01 '16 at 08:55

3 Answers3

1

Mark lr as clobbered to make the compiler save it:

extern void foo(void);
extern void bar(void)
{
    asm ( "bl foo" : : : "lr" );
}

generates the following code:

bar:
    str lr, [sp, #-4]!
    bl foo
    ldr lr, [sp], #4
    bx  lr

See the documentation for more details. Remember to mark all other registers you use as clobbered, too. Otherwise the compiler might put a variable in one of them. You may also need to mark all caller-saved registers as clobbered.

fuz
  • 88,405
  • 25
  • 200
  • 352
1

The easiest/direct answer is to add lr to the clobber, but you may need to add more!


WHY??

Compilers generate different prologue/epilogue depending on the type of function. They can be a generic function, a leaf function (calls no others) or a tail call. You haven't shown any code so it is difficult to know your particular circumstance. However, if you don't document that your inline assembler is calling another function, then gcc thinks it is a leaf function when it is not.

From the gcc assembler documentation,

Accessing data from C programs without using input/output operands (such as by using global symbols directly from the assembler template) may not work as expected. Similarly, calling functions directly from an assembler template requires a detailed understanding of the target assembler and ABI.

For this reason, I think that it is safest to add r0-r3 and lr to the clobbers as well. Neon/Floating point register may be required as well. The compiler should be made aware that these can change over the 'inline asm' statement.

You can use a tail call if there is no code after the assembler statement and you assume there is not a 'frame pointer' or other registers used. Ie, replace bl inline_asm_target with just b inline_asm_target, where the inline_asm_target function still does a bx lr (or whatever is appropriate for the compiler options/ABI in use).

Related:
- ARM link register and frame pointer
- ARM to C calling convention registers to save

Community
  • 1
  • 1
artless noise
  • 21,212
  • 6
  • 68
  • 105
0

There are several solutions to save LR.

1) You can save it manually, since you write inline assembly, you can save LR before calling your function and restore it after the call.

2) You can declare a dummy variable which will be put in LR with the following C code :

register int foo asm ("lr");

3) Use clobbers in your inline assembly

Note that the best solution is probably the third.

blatinox
  • 833
  • 6
  • 18