0

I'm trying to call an external function in c by an asm inline with a branch. I'm compiling to an arm m0 instruction set but it returns bad expression.

The code is:

__asm volatile (
                "   cmp     r3,#0                   \n"                     
                "   b %[my_function]                \n" //Call function
                "   bx r14                          \n"
                : // no output
                : [my_function] "i" (my_function) // input
                : "r0" // clobber
            );

The return is:

/tmp/ccICkDIE.s: Assembler messages:
/tmp/ccICkDIE.s:152: Error: bad expression -- `b #my_function'

What we need to do?

artless noise
  • 21,212
  • 6
  • 68
  • 105
  • 1
    Why do you tag `my_function` as immediate argument? And how would `may_function` return, as you only branch? The following line will never be executed. – too honest for this site Nov 04 '15 at 18:04
  • Create a very short C program that calls `my_function`. Then compile with the `-S` option and take a look at the assembly that the compiler generates. – user3386109 Nov 04 '15 at 18:18
  • This code works at Cortex-M4 instruction set, but not at Cortex-M0. Olaf – Geancarlo Abich Nov 04 '15 at 18:30
  • This not solve my problem. user3386109 – Geancarlo Abich Nov 04 '15 at 18:37
  • 1
    Given that the `b` mnemonic takes a symbol as an argument, not an immediate, [the appropriate constraint](https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Machine-Constraints.html#Machine-Constraints) would appear to be `S`, if just writing `"b my_function\n"` in the asm isn't good enough. The fact that this code won't assemble is at least saving you the fun of debugging the "bogus clobber list" and/or "tail call with unknown return address and without popping the stack frame" issues... – Notlikethat Nov 04 '15 at 19:05

2 Answers2

0

You want the BL instruction. This is "branch and link". It does a jump and stores the return address in r14.

But, you've still got a problem ... When you do BL, it destroys the r14 that you need. You've still got more work to do, even after the following:

stmfd   sp!,{v1-v6,lr}              // preserve caller registers
bl      %[my_function]              // call function
ldmfd   sp!,{v1-v6,pc} @std         // restore caller registers and return

You'll need more investigation. You might want to disassemble the compiled function and see what wrapper goes around your inline asm and adjust accordingly. It may do the stmfd/ldmfd for you. Try marking r14 as a clobber.

You may be better off with just the BL. The BX without restore might produce an infinite loop or unpredictable results. I'd leave it off

Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • 1
    There should be no need for a _call site_ to ever preserve callee-saved registers, even when doing totally undefined craziness like transferring control outside of an inline asm ([asm goto](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels) excepted). In this case, it's all the registers the callee _won't_ preserve that need to be saved, but since you'd have to declare all of them and more on the clobber list anyway for this to have the remotest chance of working, it's effectively already taken care of. – Notlikethat Nov 04 '15 at 23:38
  • @Notlikethat I answered the primary about the bl. I pulled code from a standalone. I told OP to disassemble and check wrapper and it might do the save/restore for him. Also, at the end, I said the bl by itself might be sufficient. Overkill, maybe, OP will figure it out. But, with anything more complex: (e.g.) containing func takes 3 arguments: calls fA(a1), fB(a2), fC(a3). You'd need to save a1-a3 in v1-v3 first [and do a1 = v2, a1 = v3] before the calls. Then stmfd/ldmfd is just v1-v3. Maybe easier than trying to do it with constraints [if possible at all] – Craig Estey Nov 05 '15 at 00:09
  • This doesn't even address the actual question; you've just added an inappropriate boilerplate function prologue and epilogue around the still-broken line of code being asked about, and suggested the OP should generate their syntax error with a slightly different instruction in their quest to implement a fundamentally broken self-crashing code idea. Furthermore, even the "helpful suggestion" is actively harmful as there's no guarantee that r14 contains a return address at that point, and even if it did, popping it back into the PC _inside an inline asm_ is utterly wrong. – Notlikethat Nov 05 '15 at 00:38
  • @Notlikethat Read the _first_ sentence/paragraph again [_several_ times until _you_ get it]: need to use BL inst. Question answered. Beyond that, OP's use of BX was wrong--using unreliable r14 and trying to switch to thumb arch--not supported on most unixlike OSes [some chips don't even have it]. If called function didn't change r14/lr, he'd loop infinitely on the BX. There were more than enough caveats in what I said, I gave OP a roadmap on how to decide to use/not use. Remember, I said that wrapper may do stmfd/ldmfd for you [implying leave off]. Move on. – Craig Estey Nov 05 '15 at 03:25
  • 1
    The problem posed in the question is that `b %[my_function]` fails to assemble with a syntax error. Changing that to `bl %[my_function]` does nothing to resolve said syntax error. – Notlikethat Nov 05 '15 at 09:23
0

It is interesting that inline assembler from GCC has no way to deal with call frames. My answer attempts to address this by adding a function wrapper and giving the syntax to get an address. The same complaint by 'NotLikeThat' is comments to Craig Estey's answer. Indeed, if you need to save/restore floating point registers, etc. That answer will not work. You also need to clue the compiler that a stack frame needs to be emitted. I tried to do this through the wrapping function.

This answer is not generic to all cases, do not copy paste it. It tries to address the concepts of getting a function address to a register and transferring control. 'C' is a procedural programming language so the compiler performs many optimizations based on the context of the function.

No information is generic, but might get you to a specific solution. GCC's label variables and other concepts might be suitable (and more portable) depending on the use case. The code is meant to be informative. All of my answers are written for people who can think about a problem and never canned solutions.


After composing the below, I remembered the ethernut tutorial. He has virtually the same answer,

asm volatile(
    "mov lr, %1\n\t"
    "bx %0\n\t"
    : : "r" (main), "r" (JMPADDR)
    : // probably unsafe without "lr" clobber in most use-cases
   );
 // and also __builtin_unreachable() to tell the compiler execution doesn't leave

The OP would do well to read this tutorial; even though it is for traditional ARM as opposed to an 'm0'.

This method works for simple functions but is not a generic solution. However, it demonstrates getting an address to a register. The OP's original query. The use case here is in a start function where you are doing a tail call that will eventually call main().


You may use the 'r' constraint to place the address in a register and branch to it.

An example can be found on the online compiler godbolt.

extern int my_function(void);

// prevent inlining and optimization across functions, 
// so we can take advantage of the calling convention instead of using proper clobbers
void f(void) __attribute__((noinline,noipa))
{
__asm volatile (
                "   cmp     r3,#0                   \n"                     
// Below is a tail call, not setting LR, it will return to the caller of f()
// It relies on the caller of f() having populated the 'lr'.
// See further comments below.
                "   b %[my_function]                \n"
                "   bx r14 \n" // does nothing...
                : // no output
                : [my_function] "r" (my_function) // input
            );
 
 }

This example relies on the fact it is embedded in a function. The function will do the stack manipulation needed to prepare a call for the particular function you call from. It may not be possible to inline this generically as you can specify clobbers such as r0-r3, memory and 'cc', but it would not tell the compiler that a stack frame must be well formed at this point. Some answers will depend on what the called function does as you need to match the EABI (calling convention expected). However, this is much beyond the capabilities of the original poster and they were struggling with much more basic concepts.

__attribute__((noinline,noipa)) was added to prevent inlining of an external function. Since the answer was written, it is much more likely that GCC will perform global optimizations that may try to inline the routine.

With the output (using particular build options with a specific compiler version),

f():
    ldr r3, .L2
       cmp     r3,#0                   
   b r3                
   bx r14                          

    bx  lr
.L2:
    .word   my_function()

We can see several issues with the output. r14 is lr and the b r3 will transfer control directly and return to the caller of f. The cmp r3, #0 seems completely un-needed (given limited context of question).

The sample above answers the question and it could be used for a tail-call macro or other use, but it obviously needs some work. A function pointer like,

int (*g)(void) = my_function;

will also work as the parameter 'my_function' to the GCC extended assembler.

Another method is just to use 'C' macro string concatenation. Here is a starting sample,

#define xstr(s) str(s)
#define str(s) #s
#define TAIL_CALL(func) __asm volatile(" b  " str(func) "\n")

For most code sizes (jump distance), the branch will be able to resolve (4MB?). If you use the function pointer method, then there is no issue. This example (as on godbolt) needs to be embedded in a function wrapper to take care of stack frame. It is just meant as an example of how to get a function address to a register (the OPs original question).

artless noise
  • 21,212
  • 6
  • 68
  • 105
  • **NOTE!!** You can not use this mechanism for a complex function (with a stack frame). The `f()` function is extremely simple. If you wanted to actually return to `f()`, then you need to use `bl` and should add `lr` to the clobber list. However, the `b` might be useful for an advanced programmer; conditional `TAIL_CALL` with a `longjmp()` may implement some primitive exception handling for instance. – artless noise Nov 05 '15 at 14:40
  • This is missing clobbers on the call-clobbered registers (such r0..3 and lr, and FP regs, and `"cc"`). This could only be safe if the call target was `noinline`, and even then it's a nasty hack to depend on the calling convention and lack of inlining for correctness of `asm` statements. Also needs a `"memory"` clobber if the called function isn't pure. – Peter Cordes May 06 '22 at 17:37
  • For example, to somewhat safely call a function from inline asm on x86-64, when compiling without AVX-512 (which would introduce another 16 + 8 required clobber declarations), see [Calling printf in extended inline ASM](https://stackoverflow.com/a/37503773). ARM is less likely to have a problem with incorrect stack alignment on entry to the asm statement, since the compiler can always move it by multiples of the required alignment, but everything else is just as problematic. – Peter Cordes May 07 '22 at 04:39
  • Yes, this is correct (volatile does not mean clobbers). The ARM EABI says that R0-R3 are callee saved, so the function 'f()' is relying on this. It would be pedantically correct to add clobbers. The first example is not using the clobber as everything is specified. The main purpose of the 'f()' example is to demonstrate how to get the function address to assembler as this was the OPs issue. Also, the `cmp r3, #0` is directly from the OPs example; it does not make sense to me. If I was to correct this, I would remove that line. – artless noise May 07 '22 at 12:20
  • The first code block that does `mov lr, %1` / `bx %0` violates the GCC requirements in two ways: overwrites `lr`, and *jumps out of the inline asm statement without telling the compiler about it!!*. It's not even a call so execution won't come back. You can do that with `asm goto` (https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels) and tell the compiler about possible jump targets, or you can follow the asm statement with `__builtin_unreachable()` from which GCC infers that execution must (always) jump out of it, I think that's what the docs are saying: – Peter Cordes May 07 '22 at 12:35
  • GNU C inline asm is *very* easy to get wrong, and hard to test because things can easily happen to work (especially with optimization disabled.) IMO it's very important not to have unsafe examples of it in SO answers. Answering a specific question doesn't justify unsafe code that has undefined behaviour in other ways. It's also commonly misunderstood, e.g. [GCC code that seems to break inline assembly rules but an expert believes otherwise](https://stackoverflow.com/q/56142479) which IMO makes it even more important not to have misleading examples floating around. – Peter Cordes May 07 '22 at 12:58
  • Deleted some of my earlier comments now that they've been addressed in the answer. I don't know a good way to do this; I'd highly recommend against doing this, and dereference a C function-pointer variable so the compiler knows about the call. If you insist on not doing that, yes a non-inlinable wrapper can work as a hack. (But then why use inline asm in the first place instead of a separate `.s` or a `naked` function). – Peter Cordes May 07 '22 at 16:22
  • Without lying to the compiler at all, an ARM equivalent of [Calling printf in extended inline ASM](https://stackoverflow.com/a/37503773) (with a `"cc"` clobber added) is the only way I know that would be safe. As far as passing in the function name from outside the asm statement, getting GCC to expand the function name inside the template so you don't have to mess with function pointers can be done (on x86 at least) with an `"i"` constraint and a `%P0` modifier on the expansion to print the bare name: [Direct C function call using GCC's inline assembly](https://stackoverflow.com/q/3467180) – Peter Cordes May 07 '22 at 16:23
  • 1
    There are some uses such as a boot loader where you want to 'parent' the stack with main(). I agree this is not a standard thing to do. If you have functions that modify the stack, you might need to do this. That is inherently fraught with danger, but none the less possible. It maybe easier just to write it in an assembler only file. At least portions of the original post tried to make people aware of this. Thanks for making it more obvious. – artless noise May 07 '22 at 16:35