3

I'm catching a link error when compiling and linking a source file with inline assembly.

Here are the test files:

via:$ cat test.cxx
extern int libtest();
int main(int argc, char* argv[])
{
    return libtest();
}

$ cat lib.cxx
#include <stdint.h>
int libtest()
{
    uint32_t rnds_00_15;    
    __asm__ __volatile__
    (
        ".intel_syntax noprefix         ;\n\t"
        "mov DWORD PTR [rnds_00_15], 1  ;\n\t"
        "cmp DWORD PTR [rnds_00_15], 1  ;\n\t"
        "je  done                       ;\n\t"
        "done:                          ;\n\t"
        ".att_syntax noprefix           ;\n\t"
        :
        : [rnds_00_15] "m" (rnds_00_15)
        : "memory", "cc"
    );

    return 0;
}

Compiling and linking the program results in:

via:$ g++ -fPIC test.cxx lib.cxx -c
via:$ g++ -fPIC lib.o test.o -o test.exe
lib.o: In function `libtest()':
lib.cxx:(.text+0x1d): undefined reference to `rnds_00_15'
lib.cxx:(.text+0x27): undefined reference to `rnds_00_15'
collect2: error: ld returned 1 exit status

The real program is more complex. The routine is out of registers so the flag rnds_00_15 must be a memory operand. Use of rnds_00_15 is local to the asm block. It is declared in the C code to ensure the memory is allocated on the stack and nothing more. We don't read from it or write to it as far as the C code is concerned. We list it as a memory input so GCC knows we use it and wire up the "C variable name" in the extended ASM.

Why am I receiving a link error, and how do I fix it?

jww
  • 97,681
  • 90
  • 411
  • 885
  • 1
    `uint32_t rnds_00_15;` is a local with automatic storage. Of course it there's no asm symbol with that name. Did you mean `%[rnds_00_15]`? And hopefully with a `"+m"` read/write operand if you're going to store `1` into it. Or simply `"=m"` if you want the compiler to reserve you some scratch space on the stack. – Peter Cordes Aug 18 '18 at 05:18
  • Thanks @Peter. I don't believe Intel syntax uses the percent sign. Perhaps I am missing something? – jww Aug 18 '18 at 05:30
  • The % is part of the "replacement pattern" in gcc - I don't use Intel syntax much, but I'm pretty sure Peter is right - or on the right track. – Mats Petersson Aug 18 '18 at 05:37
  • Probably close enough to be a duplicate of this: https://stackoverflow.com/questions/5397677/how-to-set-a-variable-in-gcc-with-intel-syntax-inline-assembly (Which uses %0 and %1 for substitutions. – Mats Petersson Aug 18 '18 at 05:39
  • OK, thanks. Using `%[rnds_00_15]` results in `Error: junk '(%ebp)' after expression`. This is why I avoid that crappy GCC inline assembly at nearly all costs. Man I despise this crappy tool. – jww Aug 18 '18 at 05:40
  • Thanks @EdMorton. You shouldn't be such a coward. You should comment on the downvote. I comment on the questions I hit. – jww Aug 26 '18 at 16:49
  • Once again - here now is the downvote and comment you requested. And you're absolutely full of crap claiming that you comment on the downvotes you give, half the time I just know it's you because I see every answer getting downvoted at the same time and then I see that you had logged in right before the downvote happened. Stop mass downvoting people and you probably won't accumulate so many downvotes in return. – Ed Morton Sep 06 '18 at 12:51
  • Thnaks @EdMorton. Not sure what you are talking about. I'll be responding in kind. – jww Sep 06 '18 at 12:55
  • I'm talking about you being so infamous for your behavior that people refer to mass downvoting as being jww-ed and someone (no, it wasn't me - I'd proudly claim it if it had been) even wrote a tool for people to know if they had been jww-ed (the kind where you do leave a comment which is maybe half the time). See http://data.stackexchange.com/stackoverflow/query/890812/have-i-been-jwwed. If you change your behavior then you won't have to worry about who's downvoting you. – Ed Morton Sep 06 '18 at 13:05
  • That's too funny. Thanks man! – jww Sep 06 '18 at 13:07

1 Answers1

6

Compile with gcc -masm=intel and don't try to switch modes inside the asm template string. AFAIK there's no equivalent before clang14 (Note: MacOS installs clang as gcc / g++ by default.)

Also, of course you need to use valid GNU C inline asm, using operands to tell the compiler which C objects you want to read and write.


I don't believe Intel syntax uses the percent sign. Perhaps I am missing something?

You're getting mixed up between %operand substitutions into the Extended-Asm template (which use a single %), vs. the final asm that the assembler sees.

You need %% to use a literal % in the final asm. You wouldn't use "mov %%eax, 1" in Intel-syntax inline asm, but you do still use "mov %0, 1" or %[named_operand].

See https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html. In Basic asm (no operands), there is no substitution and % isn't special in the template, so you'd write mov $1, %eax in Basic asm vs. mov $1, %%eax in Extended, if for some reason you weren't using an operand like mov $1, %[tmp] or mov $1, %0.


uint32_t rnds_00_15; is a local with automatic storage. Of course it there's no asm symbol with that name.

Use %[rnds_00_15] and compile with -masm=intel (And remove the .att_syntax at the end; that would break the compiler-generate asm that comes after.)

You also need to remove the DWORD PTR, because the operand-expansion already includes that, e.g. DWORD PTR [rsp - 4], and clang errors on DWORD PTR DWORD PTR [rsp - 4]. (GAS accepts it just fine, but the 2nd one takes precendence so it's pointless and potentially misleading.)

And you'll want a "=m" output operand if you want the compiler to reserve you some scratch space on the stack. You must not modify input-only operands, even if it's unused in the C. Maybe the compiler decides it can overlap something else because it's not written and not initialized (i.e. UB). (I'm not sure if your "memory" clobber makes it safe, but there's no reason not to use an early-clobber output operand here.)

And you'll want to avoid label name conflicts by using %= to get a unique number.

Working example (GCC and ICC, but not clang unfortunately), on the Godbolt compiler explorer (which uses -masm=intel depending on options in the dropdown). You can use "binary mode" (the 11010 button) to prove that it actually assembles after compiling to asm without warnings.

int libtest_intel()
{
    uint32_t rnds_00_15;
    // Intel syntax operand-size can only be overridden with operand modifiers
    // because the expansion includes an explicit DWORD PTR
    __asm__ __volatile__
    (  // ".intel_syntax noprefix \n\t"
        "mov %[rnds_00_15], 1  \n\t"
        "cmp %[rnds_00_15], 1  \n\t"
        "je  .Ldone%=                 \n\t"
        ".Ldone%=:                    \n\t"
        : [rnds_00_15] "=&m" (rnds_00_15)
        :
        : // no clobbers
    );
    return 0;
}

Compiles (with gcc -O3 -masm=intel) to this asm. Also works with gcc -m32 -masm=intel of course:

libtest_intel:
    mov DWORD PTR [rsp-4], 1  
    cmp DWORD PTR [rsp-4], 1  
    je  .Ldone8                 
.Ldone8:                    

    xor     eax, eax
    ret

I couldn't get this to work with clang: It choked on .intel_syntax noprefix when I left that in explicitly.


Operand-size overrides:

You have to use %b[tmp] to get the compiler to substitute in BYTE PTR [rsp-4] to only access the low byte of a dword input operand. I'd recommend AT&T syntax if you want to do much of this.



Using %[rnds_00_15] results in Error: junk '(%ebp)' after expression.

That's because you switched to Intel syntax without telling the compiler. If you want it to use Intel addressing modes, compile with -masm=intel so the compiler can substitute into the template with the correct syntax.

This is why I avoid that crappy GCC inline assembly at nearly all costs. Man I despise this crappy tool.

You're just using it wrong. It's a bit cumbersome, but makes sense and mostly works well if you understand how it's designed.

Repeat after me: The compiler doesn't parse the asm string at all, except to do text substitutions of %operand. This is why it doesn't notice your .intel_syntax noprefex and keeps substituting AT&T syntax.

It does work better and more easily with AT&T syntax though, e.g. for overriding the operand-size of a memory operand, or adding an offset. (e.g. 4 + %[mem] works in AT&T syntax).


Dialect alternatives:

If you want to write inline asm that doesn't depend on -masm=intel or not, use Dialect alternatives (which makes your code super-ugly; not recommended for anything other than wrapping one or two instructions):

Also demonstrates operand-size overrides

#include <stdint.h>
int libtest_override_operand_size()
{
    uint32_t rnds_00_15;
    // Intel syntax operand-size can only be overriden with operand modifiers
    // because the expansion includes an explicit DWORD PTR
    __asm__ __volatile__
    (
        "{movl $1, %[rnds_00_15] | mov %[rnds_00_15], 1}  \n\t"
        "{cmpl $1, %[rnds_00_15] | cmp %k[rnds_00_15], 1}  \n\t"
        "{cmpw $1, %[rnds_00_15] | cmp %w[rnds_00_15], 1}  \n\t"
        "{cmpb $1, %[rnds_00_15] | cmp %b[rnds_00_15], 1}  \n\t"
        "je  .Ldone%=                     \n\t"
        ".Ldone%=:                        \n\t"
        : [rnds_00_15] "=&m" (rnds_00_15)
    );
    return 0;
}

With Intel syntax, gcc compiles it to:

     mov DWORD PTR [rsp-4], 1  
     cmp DWORD PTR [rsp-4], 1  
     cmp WORD PTR [rsp-4], 1  
     cmp BYTE PTR [rsp-4], 1  
    je  .Ldone38                     
.Ldone38:                        

    xor     eax, eax
    ret

With AT&T syntax, compiles to:

    movl $1, -4(%rsp)   
    cmpl $1, -4(%rsp)   
    cmpw $1, -4(%rsp)   
    cmpb $1, -4(%rsp)   
    je  .Ldone38                     
.Ldone38:                        

    xorl    %eax, %eax
    ret
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks. Two percent signs results in `lib.cxx:21: Error: bad expression`. I think I am going to delete this question, close the bug report and move onto something more useful. I wasted four hours on GCC's broken tools. That's more time then they deserve. – jww Aug 18 '18 at 05:57
  • @jww: Did you even read the answer? Using `%%` will emit a literal `%[rnds_00_15]` into the asm output. I updated with working examples. (But I wasn't able to get Intel-syntax working with clang, only GCC and ICC. They are not broken, only clang is (if it ever intended to support intel-syntax.) – Peter Cordes Aug 18 '18 at 06:31
  • Thanks again Peter. Yes, I did read the answer and even tried to compile the code in my test example. I accepted the answer because I trust you and your answer. There's no need to waste any more time on it. – jww Aug 18 '18 at 06:33
  • By the way, Clang has been broken for years. LLVM can't even do a simple negate. Also see [Inline assembly operands don't work with .intel_syntax](http://llvm.org/bugs/show_bug.cgi?id=24232). – jww Aug 18 '18 at 06:57
  • @jww: I've found clang's optimizer often sucks with inline asm when you give it a choice like `"rm" (some_var)`. It tends to spill the variable to memory and pick the memory option, even if it already has it in registers. (Obviously I was using AT&T syntax so clang could compile it.) I'd just use intrinsics, or write whole functions in asm in a separate file if I cared about clang and really hated AT&T syntax. Otherwise just use AT&T syntax like everyone else does. It's not that bad once you learn to make a mental context-switch and start thinking in AT&T syntax. – Peter Cordes Aug 18 '18 at 07:09
  • *"Otherwise just use AT&T syntax like everyone else does..."* - We use Intel style because the code is shared between Windows, Unix and Linux. Microsoft's assembler needs Intel style. We gave up trying to support OS X. – jww Aug 18 '18 at 07:21
  • gcc and clang on Windows use AT&T syntax. You have to write the code twice for GNU C vs. MSVC inline asm syntax anyway. (Or do your macro hacks work around that? Can't you just have your macros reverse the operands for AT&T syntax? There are a lot of differences in GNU vs. MSVC inline asm syntax as far as getting data into / out of an asm statement.) – Peter Cordes Aug 18 '18 at 07:25
  • @MichaelPetch: oh, that does work for register operands. (https://godbolt.org/g/25fnxV). (And yeah, of course you should use `neg`, not `negl`, in Intel syntax. An unfortunate start to that bug report.) So clang knows how to format register operands, but not memory. The error messages show it substituting in `mov -4(%rsp), 1` with the "Intel" button clicked, which passes `-masm=intel`. – Peter Cordes Aug 18 '18 at 13:52
  • 1
    Thanks Michael. As Peter observed in a comment, we have macros which allow us to use the same ASM source for both Windows (MASM) and Linux (GAS). There is about 4000 lines of macro'd ASM source and I don't think it is feasible to rewrite it for Clang. It would be better for everyone if Clang fixed its integrated assembler. Fixing the assembler in one place is better/more economical then hundreds of developers modifying their source code to workaround bugs. – jww Aug 20 '18 at 06:50
  • So, is there any chance on most recent Clang to write in intel-syntax inline assembly? I managed successfully to write any intel asm but when using only `r` (register) type of inputs and outputs, with `m` (memory) inputs/outputs I never succeeded on Clang, although tried different ways. Also `-masm=intel` seems to be just ignored by Clang. – Arty Jul 14 '21 at 17:37
  • @Arty: Not that I know of, unfortunately. I don't know how to convince clang to format operands into Intel-syntax style, or even to default to `.intel_syntax` mode when parsing the asm template. – Peter Cordes Jul 14 '21 at 20:05