Sandeep.S
v0.1, 01 March 2003.This HOWTO explains the use and usage of the inline assembly feature provided by GCC. There are only two prerequisites for reading this article, and that’s obviously a basic knowledge of x86 assembly language and C.
1. Introduction.
2. Overview of the whole thing.
3. GCC Assembler Syntax.
4. Basic Inline.
5. Extended Asm.
6. More about constraints.
7. Some Useful Recipes.
8. Concluding Remarks.
9. References.
1. Introduction.
1.1 Copyright and License.
1.2 Feedback and Corrections.
1.3 Acknowledgments.
2. Overview of the whole thing.
inline
in its declaration.
asm
.
3. GCC Assembler Syntax.
- Source-Destination Ordering.
The direction of the operands in AT&T syntax is opposite to that of Intel. In Intel syntax the first operand is the destination, and the second operand is the source whereas in AT&T syntax the first operand is the source and the second operand is the destination. ie,"Op-code dst src" in Intel syntax changes to"Op-code src dst" in AT&T syntax.
- Register Naming.
Register names are prefixed by % ie, if eax is to be used, write %eax.
- Immediate Operand.
AT&T immediate operands are preceded by ’$’. For static "C" variables also prefix a ’$’. In Intel syntax, for hexadecimal constants an ’h’ is suffixed, instead of that, here we prefix ’0x’ to the constant. So, for hexadecimals, we first see a ’$’, then ’0x’ and finally the constants.
- Operand Size.
In AT&T syntax the size of memory operands is determined from the last character of the op-code name. Op-code suffixes of ’b’, ’w’, and ’l’ specify byte(8-bit), word(16-bit), and long(32-bit) memory references. Intel syntax accomplishes this by prefixing memory operands (not the op-codes) with ’byte ptr’, ’word ptr’, and ’dword ptr’.Thus, Intel "mov al, byte ptr foo" is "movb foo, %al" in AT&T syntax.
- Memory Operands.
In Intel syntax the base register is enclosed in ’[’ and ’]’ where as in AT&T they change to ’(’ and ’)’. Additionally, in Intel syntax an indirect memory reference is likesection:[base + index*scale + disp], which changes tosection:disp(base, index, scale) in AT&T.One point to bear in mind is that, when a constant is used for disp/scale, ’$’ shouldn’t be prefixed.
+------------------------------+------------------------------------+
| Intel Code | AT&T Code |
+------------------------------+------------------------------------+
| mov eax,1 | movl $1,%eax |
| mov ebx,0ffh | movl $0xff,%ebx |
| int 80h | int $0x80 |
| mov ebx, eax | movl %eax, %ebx |
| mov eax,[ecx] | movl (%ecx),%eax |
| mov eax,[ebx+3] | movl 3(%ebx),%eax |
| mov eax,[ebx+20h] | movl 0x20(%ebx),%eax |
| add eax,[ebx+ecx*2h] | addl (%ebx,%ecx,0x2),%eax |
| lea eax,[ebx+ecx] | leal (%ebx,%ecx),%eax |
| sub eax,[ebx+ecx*4h-20h] | subl -0x20(%ebx,%ecx,0x4),%eax |
+------------------------------+------------------------------------+
4. Basic Inline.
asm("assembly code");
asm("movl %ecx %eax"); /* moves the contents of ecx to eax */
__asm__("movb %bh (%eax)"); /*moves the byte from bh to the memory pointed by eax */
asm
and __asm__
. Both
are valid. We can use __asm__
if the keyword asm
conflicts with
something in our program. If we have more than one instructions, we write one per
line in double quotes, and also suffix a ’\n’ and ’\t’ to the
instruction. This is because gcc sends each instruction as a string to as(GAS) and
by using the newline/tab we send correctly formatted lines to the assembler.
__asm__ ("movl %eax, %ebx\n\t"
"movl $56, %esi\n\t"
"movl %ecx, $label(%edx,%ebx,$4)\n\t"
"movb %ah, (%ebx)");
5. Extended Asm.
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);
asm ("cld\n\t"
"rep\n\t"
"stosl"
: /* no output registers */
: "c" (count), "a" (fill_value), "D" (dest)
: "%ecx", "%edi"
);
fill_value
count
times to the location pointed to by the register edi
. It
also says to gcc that, the contents of registers eax
and edi
are no
longer valid. Let us see one more example to make things more clearer.
int a=10, b;
asm ("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
- "b" is the output operand, referred to by %0 and "a" is the input operand, referred to by %1.
- "r" is a constraint on the operands. We’ll see constraints in detail later. For the time being, "r" says to GCC to use any register for storing the operands. output operand constraint should have a constraint modifier "=". And this modifier says that it is the output operand and is write-only.
- There are two %’s prefixed to the register name. This helps GCC to distinguish between the operands and registers. operands have a single % as prefix.
- The clobbered register %eax after the third colon tells GCC that the value of %eax is to be modified inside "asm", so GCC won’t use this register to store any other value.
5.1 Assembler Template.
5.2 Operands.
lea
.
asm ("leal (%1,%1,4), %0"
: "=r" (five_times_x)
: "r" (x)
);
asm ("leal (%0,%0,4), %0"
: "=r" (five_times_x)
: "0" (x)
);
asm ("leal (%%ecx,%%ecx,4), %%ecx"
: "=c" (x)
: "c" (x)
);
ecx
on the c
lobberlist, gcc knows it goes into x. Therefore, since it can know the value
of ecx
, it isn’t considered clobbered.
5.3 Clobber List.
eax
and ecx
.
asm ("movl %0,%%eax;
movl %1,%%ecx;
call _foo"
: /* no outputs */
: "g" (from), "g" (to)
: "eax", "ecx"
);
5.4 Volatile ...?
volatile
or
__volatile__
which follows an asm
or __asm__
. I mentioned
earlier about the keywords asm
and __asm__
. So what is this
volatile
?
volatile
after asm and before the ()’s. So to keep it from moving,
deleting and all, we declare it as
asm volatile ( ... : ... : ... : ...);
__volatile__
when we have to be verymuch careful.
volatile
. Avoiding it
helps gcc in optimizing the code and making it more beautiful.
Some Useful Recipes
, I have provided many examples for inline asm
functions. There we can see the clobber-list in detail.
6. More about constraints.
6.1 Commonly used constraints.
- Register operand constraint(r)
When operands are specified using this constraint, they get stored in General Purpose Registers(GPR). Take the following example:
asm ("movl %%eax, %0\n" :"=r"(myval));
Here the variable myval is kept in a register, the value in registereax
is copied onto that register, and the value ofmyval
is updated into the memory from this register. When the "r" constraint is specified, gcc may keep the variable in any of the available GPRs. To specify the register, you must directly specify the register names by using specific register constraints. They are:+---+--------------------+ | r | Register(s) | +---+--------------------+ | a | %eax, %ax, %al | | b | %ebx, %bx, %bl | | c | %ecx, %cx, %cl | | d | %edx, %dx, %dl | | S | %esi, %si | | D | %edi, %di | +---+--------------------+
- Memory operand constraint(m)
When the operands are in the memory, any operations performed on them will occur directly in the memory location, as opposed to register constraints, which first store the value in a register to be modified and then write it back to the memory location. But register constraints are usually used only when they are absolutely necessary for an instruction or they significantly speed up the process. Memory constraints can be used most efficiently in cases where a C variable needs to be updated inside "asm" and you really don’t want to use a register to hold its value. For example, the value of idtr is stored in the memory location loc:
asm("sidt %0\n" : :"m"(loc));
- Matching(Digit) constraints
In some cases, a single variable may serve as both the input and the output operand. Such cases may be specified in "asm" by using matching constraints.
asm ("incl %0" :"=a"(var):"0"(var));
We saw similar examples in operands subsection also. In this example for matching constraints, the register %eax is used as both the input and the output variable. var input is read to %eax and updated %eax is stored in var again after increment. "0" here specifies the same constraint as the 0th output variable. That is, it specifies that the output instance of var should be stored in %eax only. This constraint can be used:- In cases where input is read from a variable or the variable is modified and modification is written back to the same variable.
- In cases where separate instances of input and output operands are not necessary.
The most important effect of using matching restraints is that they lead to the efficient use of available registers.
- "m" : A memory operand is allowed, with any kind of address that the machine supports in general.
- "o" : A memory operand is allowed, but only if the address is offsettable. ie, adding a small offset to the address gives a valid address.
- "V" : A memory operand that is not offsettable. In other words, anything that would fit the `m’ constraint but not the `o’constraint.
- "i" : An immediate integer operand (one with constant value) is allowed. This includes symbolic constants whose values will be known only at assembly time.
- "n" : An immediate integer operand with a known numeric value is allowed. Many systems cannot support assembly-time constants for operands less than a word wide. Constraints for these operands should use ’n’ rather than ’i’.
- "g" : Any register, memory or immediate integer operand is allowed, except for registers that are not general registers.
- "r" : Register operand constraint, look table given above.
- "q" : Registers a, b, c or d.
- "I" : Constant in range 0 to 31 (for 32-bit shifts).
- "J" : Constant in range 0 to 63 (for 64-bit shifts).
- "K" : 0xff.
- "L" : 0xffff.
- "M" : 0, 1, 2, or 3 (shifts for lea instruction).
- "N" : Constant in range 0 to 255 (for out instruction).
- "f" : Floating point register
- "t" : First (top of stack) floating point register
- "u" : Second floating point register
- "A" : Specifies the `a’ or `d’ registers. This is primarily useful for 64-bit integer values intended to be returned with the `d’ register holding the most significant bits and the `a’ register holding the least significant bits.
6.2 Constraint Modifiers.
- "=" : Means that this operand is write-only for this instruction; the previous value is discarded and replaced by output data.
- "&" : Means that this operand is an earlyclobber operand,
which is modified before the instruction is finished using the input
operands. Therefore, this operand may not lie in a register that is used as
an input operand or as part of any memory address. An input operand can be
tied to an earlyclobber operand if its only use as an input occurs before the
early result is written.
The list and explanation of constraints is by no means complete. Examples can give a better understanding of the use and usage of inline asm. In the next section we’ll see some examples, there we’ll find more about clobber-lists and constraints.
7. Some Useful Recipes.
- First we start with a simple example. We’ll write a program to add two numbers.
int main(void) { int foo = 10, bar = 15; __asm__ __volatile__("addl %%ebx,%%eax" :"=a"(foo) :"a"(foo), "b"(bar) ); printf("foo+bar=%d\n", foo); return 0; }
Here we insist GCC to store foo in %eax, bar in %ebx and we also want the result in %eax. The ’=’ sign shows that it is an output register. Now we can add an integer to a variable in some other way.
__asm__ __volatile__( " lock ;\n" " addl %1,%0 ;\n" : "=m" (my_var) : "ir" (my_int), "m" (my_var) : /* no clobber-list */ );
This is an atomic addition. We can remove the instruction ’lock’ to remove the atomicity. In the output field, "=m" says that my_var is an output and it is in memory. Similarly, "ir" says that, my_int is an integer and should reside in some register (recall the table we saw above). No registers are in the clobber list. - Now we’ll perform some action on some registers/variables and compare the value.
__asm__ __volatile__( "decl %0; sete %1" : "=m" (my_var), "=q" (cond) : "m" (my_var) : "memory" );
Here, the value of my_var is decremented by one and if the resulting value is0
then, the variable cond is set. We can add atomicity by adding an instruction "lock;\n\t" as the first instruction in assembler template.In a similar way we can use "incl %0" instead of "decl %0", so as to increment my_var.Points to note here are that (i) my_var is a variable residing in memory. (ii) cond is in any of the registers eax, ebx, ecx and edx. The constraint "=q" guarantees it. (iii) And we can see that memory is there in the clobber list. ie, the code is changing the contents of memory. - How to set/clear a bit in a register? As next recipe, we are going to see it.
__asm__ __volatile__( "btsl %1,%0" : "=m" (ADDR) : "Ir" (pos) : "cc" );
Here, the bit at the position ’pos’ of variable at ADDR ( a memory variable ) is set to1
We can use ’btrl’ for ’btsl’ to clear the bit. The constraint "Ir" of pos says that, pos is in a register, and it’s value ranges from 0-31 (x86 dependant constraint). ie, we can set/clear any bit from 0th to 31st of the variable at ADDR. As the condition codes will be changed, we are adding "cc" to clobberlist. - Now we look at some more complicated but useful function. String copy.
static inline char * strcpy(char * dest,const char *src) { int d0, d1, d2; __asm__ __volatile__( "1:\tlodsb\n\t" "stosb\n\t" "testb %%al,%%al\n\t" "jne 1b" : "=&S" (d0), "=&D" (d1), "=&a" (d2) : "0" (src),"1" (dest) : "memory"); return dest; }
The source address is stored in esi, destination in edi, and then starts the copy, when we reach at 0, copying is complete. Constraints "&S", "&D", "&a" say that the registers esi, edi and eax are early clobber registers, ie, their contents will change before the completion of the function. Here also it’s clear that why memory is in clobberlist.We can see a similar function which moves a block of double words. Notice that the function is declared as a macro.
#define mov_blk(src, dest, numwords) \ __asm__ __volatile__ ( \ "cld\n\t" \ "rep\n\t" \ "movsl" \ : \ : "S" (src), "D" (dest), "c" (numwords) \ : "%ecx", "%esi", "%edi" \ )
Here we have no outputs, so the changes that happen to the contents of the registers ecx, esi and edi are side effects of the block movement. So we have to add them to the clobber list. -
In Linux, system calls are implemented using GCC inline assembly. Let us look how a system call is implemented. All the system calls are written as macros (linux/unistd.h). For example, a system call with three arguments is defined as a macro as shown below.
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \ type name(type1 arg1,type2 arg2,type3 arg3) \ { \ long __res; \ __asm__ volatile ( "int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \ "d" ((long)(arg3))); \ __syscall_return(type,__res); \ }
Whenever a system call with three arguments is made, the macro shown above is used to make the call. The syscall number is placed in eax, then each parameters in ebx, ecx, edx. And finally "int 0x80" is the instruction which makes the system call work. The return value can be collected from eax.Every system calls are implemented in a similar way. Exit is a single parameter syscall and let’s see how it’s code will look like. It is as shown below.
{ asm("movl $1,%%eax; /* SYS_exit is 1 */ xorl %%ebx,%%ebx; /* Argument is in ebx, it is 0 */ int $0x80" /* Enter kernel mode */ ); }
The number of exit is "1" and here, it’s parameter is 0. So we arrange eax to contain 1 and ebx to contain 0 and byint $0x80
, theexit(0)
is executed. This is how exit works.