简体   繁体   中英

The different between with GCC inline assembly and VC

I am migrate VC inline assembly code into GCC inline assembly code.

#ifdef _MSC_VER
  // this is raw code.
  __asm
  {
    cmp cx, 0x2e
    mov dword ptr ds:[esi*4+0x57F0F0], edi
    jmp BW::BWFXN_RefundMin4ReturnAddress
  }
#else
  // this is my code.
  asm
  (
    "cmp $0x2e, %%cx\n\t"
    "movl %%edi, $ds:0x57F0F0(0, %%esi, 4)\n\t"
    "jmp %0"
    : /* no output */
    : "i"(BW::BWFXN_RefundGas3ReturnAddress)
    : "cx"
  );
#endif

But I got error

Error:junk `:0x57F0F0(0,%esi,4)' after expression
/var/.../cckr7pkp.s:3034: Error:operand type mismatch for `mov'
/var/.../cckr7pkp.s:3035: Error:operand type mismatch for `jmp'

Refer the Address operand syntax

segment:displacement(base register, offset register, scalar multiplier)

is equivalent to

segment:[base register + displacement + offset register * scalar multiplier]

in Intel syntax.

I don't know where is the issue.

This is highly unlikely to work just from getting the syntax correct, because you're depending on values in registers being set to something before the asm statement, and you aren't using any input operands to make that happen. (And for some reason you need to set flags with cmp before jumping?)

If that fragment worked on its own somehow in MSVC, then your code depends on the choices made by MSVC's optimizer (as far as which C value is in which register), which seems insane.

Anyway, the first answer to any inline asm question is https://gcc.gnu.org/wiki/DontUseInlineAsm if you can avoid it. Now might be a good time to rewrite your thing in C (maybe with some __builtin functions if needed).


You should use asm volatile and a "memory" clobber at the very least . The compiler assumes that execution continues after the asm statement, but at least this will make sure it stores everything to memory before the asm , ie it's a full memory barrier (against compile-time reordering). But any destructors at the end of the function (or in the callers) won't run, and no stack cleanup will happen; there's really no way to make this safe.

You might be able to use asm goto , but that might only work for labels within the same function.


As far as syntax goes , leave out %%ds: because it's the default segment anyway. (Everything after $ds was considered junk because $ds is the address of the symbol ds . Register names start with % .) Also, just leave out the base entirely, instead of using zero. Use

"movl %%edi, 0x57F0F0( ,%%esi, 4)  \n\t"

You could have got a disassembler to tell you how to write that, by assembling the Intel version and disassembling in AT&T syntax.

You can probably implement that store in pure C easily enough, eg int32_t *p = (int32_t *)0x57F0F0; p[foo]=bar; .


For the jmp operand , use %c0 to get the address with no $ so the compiler's asm output is jmp 0x12345 instead of jmp $0x12345 . See also https://stackoverflow.com/tags/inline-assembly/info for more links to guides + docs.

You can and should look at the gcc -O2 -S output to see what the compiler is feeding to the assembler. ie how exactly it's filling in the asm template.

I tested this on Godbolt to make sure it compiles, and see the asm output + disassembly output

void ext(void);
long foo(int a, int b) { return 0; }
static const unsigned my_addr = 0x000045678;

//__attribute__((noinline))
void testasm(void)
{
    asm volatile( // and still not safe in general
        "movl %%edi, 0x57F0F0( ,%%esi, 4) \n\t"
        "jmp   %c[foo]       \n\t"
        "jmp   foo           \n\t"
        "jmp   0x12345       \n\t"
        "jmp   %c[addr]      "
    : // no outputs
    :  // "S" (value_for_esi), "D" (value_for_edi)
    [foo] "i" (foo),
    [addr] "i" (my_addr)
    : "memory"  // prevents stores from sinking past this
    );

    // make sure gcc doesn't need to call any destructors here
    // or in our caller
    // because jumping away will mean they don't run
}

Notice that an "i" (foo) constraint and a %c[operand] (or %c0 ) will produce a jmp foo in the asm output, so you can emit a direct jmp by pretending you're using a function pointer.

This also works for absolute addresses. x86 machine code can't encode a direct jump, but GAS asm syntax will let you write a jump target as an absolute numeric address. The linker will fill in the right rel32 offset to reach the absolute address from wherever the jmp ends up.

So your inline asm template just needs to produce jmp 0x12345 as input to the assembler to get a direct jump.

asm output for testasm :

    movl %edi, 0x57F0F0( ,%esi, 4) 
    jmp   foo               #
    jmp   foo           
    jmp   0x12345        
    jmp   284280        # constant substituted by the compiler from a static const unsigned C variable
    ret

disassembly output:

 mov    %edi,0x57f0f0(,%esi,4)
 jmp    80483f0 <foo>
 jmp    80483f0 <foo>
 jmp    12345 <_init-0x8035f57>
 jmp    45678 <_init-0x8002c24>
 ret

Note that the jump targets decoded to absolute addresses in hex. (Godbolt doesn't give easy access to copy/paste the raw machine code, but you can see it on mouseover of the left column.)

This only works in position-dependent code (not PIC) , otherwise absolute relocations aren't possible. Note that many recent Linux distros ship gcc set to use -pie by default to enable ASLR for 64-bit executables, so you may need -no-pie -fno-pie to make this work , or else ask for the address in a register ( r constraint and jmp *%[addr] ) to actually do an indirect jump to an absolute address instead of a relative jump.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM