Search code examples
cassemblygccinline-assembly

GNU C inline asm "m" constraint with a pointer: address vs. pointed-to value?


I am trying to understand some things about inline assembler in Linux. I am using following function:

void test_func(Word32 *var){
   asm( " addl %0, %%eax" : : "m"(var) );
   return;
}

It generates following assembler code:

.globl test_func
.type   test_func, @function
test_func:
        pushl %ebp
        movl %esp, %ebp
#APP
# 336 "opers.c" 1
        addl 8(%ebp), %eax
# 0 "" 2
#NO_APP
        popl %ebp
        ret
        .size   test_func, .-test_func

It sums var mem address to eax register value instead var value.

Is there any way to tell addl instruction to use var value instead var mem address without copying var mem address to a register?

Regards


Solution

  • It sums var mem address to eax register value instead var value.

    Yes, the syntax of gcc inline assembly is pretty arcane. Paraphrasing from the relevant section in the GCC Inline Assembly HOWTO "m" roughly gives you the memory location of the C-variable.

    It's what you'd use when you just want an address you can write to or read from. Notice I said the location of the C variable, so %0 is set to the address of Word32 *var - you have a pointer to a pointer. A C translation of the inline assembly block could look like EAX += *(&var) because you can say that the "m" constraint implicitly takes the address of the C variable and gives you an address expression, that you then add to %eax.

    Is there any way to tell addl instruction to use var value instead var mem address without copying var mem address to a register?

    That depends on what you mean. You need to get var from the stack, so someone has to dereference memory (see @Bo Perssons answer), but you don't have to do it in inline assembly

    The constraint needs to be "m"(*var) (as @fazo suggested). That will give you the memory location of the value that var is pointing to, rather than a memory location pointing to it.

    The generated code is now:

    test_func:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
    #APP
    # 2 "test.c" 1
        addl    (%eax), %eax
    # 0 "" 2
    #NO_APP
        popl    %ebp
        ret
    

    Which is a little suspect, but that's understandable as you forgot to tell GCC that you clobbered (modified without having in the input/output list) %eax. Fixing that asm("addl %0, %%eax" : : "m"(*var) : "%eax" ) generates:

        movl    8(%ebp), %edx
        addl    (%edx), %eax
    

    Which isn't any better or more correct in this case, but it is always a good practice to remember. See the section on the clobber list and pay special attention to the "memory" clobber for advanced usage of inline assembly.

    Even though you don't want to (explicitly) load the memory address into a register I'll briefly cover it. Changing the constraint from "m" to "r" almost seems to work, the relevant sections gets changed to (if we include %eax in the clobber list):

        movl    8(%ebp), %edx
        addl    %edx, %eax
    

    Which is almost correct, we have loaded the pointer value var into a register, but now we have to specify ourselves that we're loading from memory. Changing the code to match the constraint (usually undesirable, I'm only showing it for completeness):

    asm("addl (%0), %%eax" : : "r"(var) : "%eax" );
    

    Gives:

    movl    8(%ebp), %edx
    addl    (%edx), %eax
    

    The same as with "m".