As Michael has said, your problem probably comes from your 4th asm statement being written incorrectly.
The first thing you need to understand when writing inline asm is what registers are and how they are used. Registers are a fundamental concept in x86 assembler programming, so if you don't know what they are, it's time for you to find an x86 assembly language primer.
Once you've got that, you need to understand that when compiler runs, it is using those registers in the code it generates. For example if you do for (int x=0; x<10; x++)
, x is (probably) going to end up in a register. So what happens if gcc decides to use ebx to hold the value of 'x', and then your asm statement stomps on ebx, putting some other value in it? gcc doesn't 'parse' your asm to figure out what you are doing. The only clue it has about what your asm does are those constraints listed after the asm instructions.
That's what Michael means when he says "the 4th ASM block doesn't list "EBX" in the clobber list (but its contents are destroyed)". If we look at your asm:
asm ("movl $0x0, %%edx;"
"movl %2, %%eax;"
"movl %3, %%ebx;"
"idivl %%ebx;"
: "=a" (quo), "=d" (rem)
: "g" (arg1), "g" (arg2));
You see that the 3rd line is moving a value into ebx, but there's nothing in the constraints that follow to say that it is going to be changed. The fact that your program is crashing is probably due to gcc using that register for something else. The simplest fix might be to "list EBX in the clobber list":
asm ("movl $0x0, %%edx;"
"movl %2, %%eax;"
"movl %3, %%ebx;"
"idivl %%ebx;"
: "=a" (quo), "=d" (rem)
: "g" (arg1), "g" (arg2)
: "ebx");
This tells gcc that ebx may be changed by the asm (aka it 'clobbers' it), and that it doesn't need to have any particular value when the asm statement begins, and won't have any particular value in it when the asm exits.
However, while that may be 'simplest,' it isn't necessarily the best. For example instead of using the "g"
constraint for arg2, we can use the "b"
constraint:
asm ("movl $0x0, %%edx;"
"movl %2, %%eax;"
"idivl %%ebx;"
: "=a" (quo), "=d" (rem)
: "g" (arg1), "b" (arg2));
This lets us get rid of the movl %3, %%ebx
statement, since gcc will ensure the value is in ebx before calling the asm, and we don't need to clobber it anymore.
But why use ebx? idiv doesn't require any particular register there, and maybe gcc is already using ebx for something else. How about letting gcc just pick some register it isn't using? We do this using the "r"
constraint:
asm ("movl $0x0, %%edx;"
"movl %2, %%eax;"
"idivl %3;"
: "=a" (quo), "=d" (rem)
: "g" (arg1), "r" (arg2));
Notice that the idiv now uses %3, which means "use the thing that is in the (zero-based) parameter #3." In this case, that's the register that contains arg2.
However, we can still do better. As you have already seen in your previous asm statements, you can use the "a"
constraint to tell gcc to put a particular variable into the eax register. Which means we can do this:
asm ("movl $0x0, %%edx;"
"idivl %3;"
: "=a" (quo), "=d" (rem)
: "a" (arg1), "r" (arg2));
Again, 1 fewer instruction since we don't need to move the value into eax anymore. So how about that movl $0x0, %%edx
thing? Well, we can get rid of that too:
asm ("idivl %3"
: "=a" (quo), "=d" (rem)
: "a" (arg1), "r" (arg2), "d" (0));
This uses the "d"
constraint to put 0 into edx before executing the asm. That brings us to my final version:
asm ("idivl %3"
: "=a" (quo), "=d" (rem)
: "a" (arg1), "r" (arg2), "d" (0)
: "cc");
This says:
- On input, put arg1 into eax, arg2 into some register (that we'll refer to using %3), and 0 into edx.
- On output, eax will contain the quotient, edx will contain the remainder. This is how the idiv instruction works.
- The "cc" clobber tells gcc that your asm modifies the flags registers (eflags), which idiv does as a side effect.
Now, despite having described all this, I usually think using inline asm is a bad idea. It's cool, it's powerful, it gives interesting insight into how the gcc compiler works. But look at all the weird things you "just have to know" in order to work with this. And as you have noticed, if you get any of them wrong, weird things can happen.
It's true all these things are documented in gcc's docs. The simple constraints (like "r"
and "g"
) are doc'ed here. The specific register constraints for the x86 are in the 'x86 family' here. And the detailed description of all the asm features is here. So if you must use this stuff (for example if you are supporting some existing code that uses this), the information is out there.
But there's a much shorter read here that gives you a whole list of reasons not to use inline asm. That's the read I'd recommend. Stick with C, and let the compiler handle all that register junk for you.
PS While I'm at this:
asm ( "addl %2, %0" : "=r" (add) : "0" (arg1) , "r" (arg2) : "cc");
asm ( "subl %2, %0" : "=r" (sub) : "0" (arg1) , "r" (arg2) : "cc");
asm ( "imull %2, %0" : "=r" (mul) : "0" (arg1) , "r" (arg2) : "cc");
Check out the gcc docs to see what it means to use a digit in an input operand.