Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 897
LooPer Avatar asked Aug 21 '11 15:08

LooPer


1 Answers

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".

like image 52
user786653 Avatar answered Sep 21 '22 00:09

user786653