4. Basic Inline.
The format of basic inline assembly is very much straight forward. Its basic form is
基本匯編嵌入格式如下:
asm("assembly code");
Example.
- 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 */
You might have noticed that here I’ve used asm
and __asm__
. Both are valid. We can use__asm__
if the keywordasm
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 toas(GAS) and by using the newline/tab we send correctly formatted lines to the assembler.
可能注意到了這里使用了 asm 和 __asm__. 都是有效的.如果關鍵字asm在程序中有沖突, 則可以使用__asm__. 如果我們需要使用一條以上的匯編指令, 我們應該每條占用一行, 用雙引號括起,並加上'\n'和'\t'后綴. 這是因為gcc把用字符串的格式把匯編指令傳給as(GAS), 然后利用換行符, 把它們轉換成正確的匯編格式.
Example.
- __asm__ ("movl %eax, %ebx\n\t"
- "movl $56, %esi\n\t"
- "movl %ecx, $label(%edx,%ebx,$4)\n\t"
- "movb %ah, (%ebx)");
If in our code we touch (ie, change the contents) some registers and return from asm without fixing those changes, something bad is going to happen. This is because GCC have no idea about the changes in the register contents and this leads us to trouble, especially when compiler makes some optimizations. It will suppose that some register contains the value of some variable that we might have changed without informing GCC, and it continues like nothing happened. What we can do is either use those instructions having no side effects or fix things when we quit or wait for something to crash. This is where we want some extended functionality. Extended asm provides us with that functionality.
如果我們的代碼里使用了寄存器, 並且在返回的時候沒有還原它, 這將有壞的情況發生. 因為GCC並不知道寄存器的值改變了, 特別是編譯器對代碼進行優化的時候. 編譯器會認為,那些存放變量的寄存器,我們並沒有改變它,然后繼續自己的優化. 為了避免這種情況, 要么, 我們不改變寄存器的值, 要么, 匯編函數返回之前, 還原寄存器使用前的值, 或者 等着代碼崩潰(wait for something to crash). 正是由於存在這樣的問題,我們需要使用"Extended Asm". 它將提供給我們擴展功能, 解決上邊的問題.
5. Extended Asm.
In basic inline assembly, we had only instructions. In extended assembly, we can also specify the operands. It allows us to specify the input registers,output registers and a list of clobbered registers. It is not mandatory to specify the registers to use, we can leave that head ache to GCC and that probably fit into GCC’s optimization scheme better. Anyway the basic format is:
在基本嵌入匯編格式中,我們只使用了指令. 在擴展匯編中, 我們還可以指定更多操作. 它允許我們指定輸入寄存器, 輸出寄存器和變化表(clobber list). 我們並不一定要指定使用哪些寄存器. 我們可以把這件事情交給GCC去做. 擴展匯編的格式如下:
- asm ( assembler template
- : output operands /* optional */
- : input operands /* optional */
- : list of clobbered registers /* optional */
- );
The assembler template consists of assembly instructions. Each operand is described by an operand-constraint string followed by the C expression in parentheses. A colon separates the assembler template from the first output operand and another separates the last output operand from the first input, if any. Commas separate the operands within each group. The total number of operands is limited to ten or to the maximum number of operands in any instruction pattern in the machine description, whichever is greater.
這個模板由若干條匯編指令組成, 每個操作數(括號里C語言的變量)都有一個限制符(“”中的內容)加以描述. 冒號用來分割輸入的操作和輸出的操作. 如果每組內有多個操作數,用逗號分割它們. 操作數最多為10個, 或者依照具體機器而異 .
If there are no output operands but there are input operands, you must place two consecutive colons surrounding the place where the output operands would go.
如果沒有輸出操作, 但是又有輸入, 你必須使用連續兩個冒號, 兩個連續冒號中無內容, 表示沒有輸出結果的數據操作 .
Example:
- asm ("cld\n\t"
- "rep\n\t"
- "stosl"
- : /* no output registers */
- : "c" (count), "a" (fill_value), "D" (dest)
- : "%ecx", "%edi"
- );
Now, what does this code do? The above inline fills the fill_value
count
times to the location pointed to by the registeredi
. It also says to gcc that, the contents of registerseax
andedi
are no longer valid. Let us see one more example to make things more clearer.
上面這段代碼做了什么? 這段內嵌匯編把 fill_value, count裝入寄存器,同時告知GCC,clobber list目錄中的寄存器eax,edi,已經改變. 我們來看下一個例子:
- int a=10, b;
- asm ("movl %1, %%eax;
- movl %%eax, %0;"
- :"=r"(b) /* output */
- :"r"(a) /* input */
- :"%eax" /* clobbered register */
- );
Here what we did is we made the value of ’b’ equal to that of ’a’ using assembly instructions. Some points of interest are:
代碼目的是讓'b'的值與'a'的值相等.
- "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.
- 'b'是要輸出的數據,%0也指它。 'a'是輸入的數據,%1也指它。
- 'r' 是對操作數的約束。呆會在詳細了解。 暫時這樣理解,‘r’告訴GCC選擇一個可用的寄存器來保存這個操作數。 輸出操作數,應該使用‘=’, 表示這個數據只寫。
- 雙%%前綴,指明這是一個寄存器名。 單%指明操作數。 這幫組GCC辨別 操作數和寄存器。
- 第三個冒號后邊, 這個變化表(clobber list)里的寄存器%eax,告訴gcc聲明的寄存器值已經改變,這樣,GCC不會在其他地方使用這個寄存器了。
When the execution of "asm" is complete, "b" will reflect the updated value, as it is specified as an output operand. In other words, the change made to "b" inside "asm" is supposed to be reflected outside the "asm".
當這段匯編代碼執行完畢,'b'變量將會存儲這個結果,,正如例子里聲明這個變量為輸出。 換句話說, 'b'用來反映匯編程序里值的變化。
Now we may look each field in detail.
現在,深入的理解每一塊,看看細節。
5.1 Assembler Template.
The assembler template contains the set of assembly instructions that gets inserted inside the C program. The format is like: either each instruction should be enclosed within double quotes, or the entire group of instructions should be within double quotes. Each instruction should also end with a delimiter. The valid delimiters are newline(\n) and semicolon(;). ’\n’ may be followed by a tab(\t). We know the reason of newline/tab, right?. Operands corresponding to the C expressions are represented by %0, %1 ... etc.
這個匯編模板包含一套完整的匯編指令,幫助在c語言內嵌入匯編語言。具體格式如下:每條指令應該加上雙括號,或者給整套匯編指令加上雙括號(如,最后一個例子)。每條指令結尾都應加上結束符,合法的結束符有(\n)和(;),或許還應該在 \n后邊加上一個 \t,我們應該了解原因吧? 括號里的若干操作數,依次對應%0,%1。。。等。
5.2 Operands.
C expressions serve as operands for the assembly instructions inside "asm". Each operand is written as first an operand constraint in double quotes. For output operands, there’ll be a constraint modifier also within the quotes and then follows the C expression which stands for the operand. ie,
"constraint" (C expression) is the general form. For output operands an additional modifier will be there. Constraints are primarily used to decide the addressing modes for operands. They are also used in specifying the registers to be used.
內嵌的匯編指令需要C變量為其提供一個操作數,這個操作數應加上括號。以輸出操作為例,首先會有一個限制符,然后跟上C變量,運算結果將存入這個變量。
雙引號內的“限制符”是一個規定的格式。在輸出操作中,這個限制符會額外多一個符號(=)。限制符主要用來決定操作數的尋址方式。同時還可指定使用某一個寄存器。
If we use more than one operand, they are separated by comma.
In the assembler template, each operand is referenced by numbers. Numbering is done as follows. If there are a total of n operands (both input and output inclusive), then the first output operand is numbered 0, continuing in increasing order, and the last input operand is numbered n-1. The maximum number of operands is as we saw in the previous section.
Output operand expressions must be lvalues. The input operands are not restricted like this. They may be expressions. The extended asm feature is most often used for machine instructions the compiler itself does not know as existing ;-). If the output expression cannot be directly addressed (for example, it is a bit-field), our constraint must allow a register. In that case, GCC will use the register as the output of the asm, and then store that register contents into the output.
As stated above, ordinary output operands must be write-only; GCC will assume that the values in these operands before the instruction are dead and need not be generated. Extended asm also supports input-output or read-write operands.
如果我們不止一個操作(有輸入,有輸出),就必須使用冒號將他們分開。
在標准匯編模板中,每個操作數會有一個Number與之對應。如果我們一共使用了n個操作數,那么輸出操作里的第一個操作數就排0號,之后遞增,所以最后一個輸出操作的操作數編號為n-1。操作數的最多個數,前邊已經提到過了。(一般最多10個或者某些機器指令支持更多)
輸出操作的表達式必須是數值,輸入操作沒有這個限制,他可能是表達式。擴展匯編常常用於實現機器平台自身特殊的指令,編譯器可能並不能識別他們:-)。如果輸出表達式不能直接被尋址(比如,他是一個位字段),我們就應該使用“限制符”指定一個寄存器。這樣,GCC會使用此寄存器存儲輸出結果,然后再將寄存器的值存入輸出操作數。
So now we concentrate on some examples. We want to multiply a number by 5.For that we use the instructionlea
.
我們現在分析幾個例子。我們想給一個數乘以5。因此,我們使用lea指令: (匯編語句leal(r1,r2,4),r3語句表示r1+r2*4→r3。這個例子可以非常快地將x乘5。)
- asm ("leal (%1,%1,4), %0"
- : "=r" (five_times_x)
- : "r" (x)
- );
Here our input is in ’x’. We didn’t specify the register to be used. GCC will choose some register for input, one for output and does what we desired.If we want the input and output to reside in the same register, we can instruct GCC to do so. Here we use those types of read-write operands. By specifying proper constraints, here we do it.
這里,輸入一個變量x,我們並沒指定特定的寄存器來存儲它,GCC會選擇一個(“r”表示gcc選擇)。如我們所要求的,gcc會自動選擇兩個寄存器,一個給input,一個給output。如果我們想給input和output指定同一個寄存器,我們可以要求GCC這樣做(通過更改“限制符”內容)。
- asm ("leal (%0,%0,4), %0"
- : "=r" (five_times_x)
- : "0" (x)
- );
Now the input and output operands are in the same register. But we don’t know which register. Now if we want to specify that also, there is a way.
上例,我們就讓input和output使用同一個寄存器,但是不知道具體哪一個。(如果輸入操作的限制符為0或為空,則說明使用與相應輸出一樣的寄存器。)如果,我們想指定使用具體一個寄存器,可以看看如下代碼:“c”表示使用寄存器ecx。
- asm ("leal (%%ecx,%%ecx,4), %%ecx"
- : "=c" (x)
- : "c" (x)
- );
In all the three examples above, we didn’t put any register to the clobber list. why? In the first two examples, GCC decides the registers and it knows what changes happen. In the last one, we don’t have to putecx
on the clobberlist, gcc knows it goes into x. Therefore, since it can know the value ofecx
, it isn’t considered clobbered.
上面三個例子,我們沒有把任何寄存器放入clobber list,為什么?前兩個例子,由GCC選擇寄存器,所以它知道那些寄存器值改變了。最后一個例子,我們沒有把ecx寄存器放入clobber list,GCC知道它的值變成x了。因此,既然GCC知道ecx寄存器的值,就沒必要加入到clobber list。
5.3 Clobber List.
Some instructions clobber some hardware registers. We have to list those registers in the clobber-list, ie the field after the third ’:’ in the asm function. This is to inform gcc that we will use and modify them ourselves. So gcc will not assume that the values it loads into these registers will be valid. We shoudn’t list the input and output registers in this list. Because, gcc knows that "asm" uses them (because they are specified explicitly as constraints). +If the instructions use any other registers, implicitly or explicitly (and the registers are not present either in input or in the output constraint list), then those registers have to be specified in theclobbered list.
一些指令改變了硬件寄存器的值。這時需要在clobber list中列舉出這些寄存器,位置所在匯編代碼的最后一個“:”之后。這是為了告知GCC,我們將使用和更改列舉出的寄存器。那么,GCC就知道之前裝載到寄存器里的值已經無效了,不會使用寄存器的舊值進行錯誤操作。我們不必把input,output所使用的寄存器列入clobber list,因為GCC知道匯編代碼已經使用和改變了那些寄存器。
If our instruction can alter the condition code register, we have to add "cc" to the list of clobbered registers.
如果匯編代碼將改變條件碼寄存器,我們需要在clobber list中加入“cc”。
If our instruction modifies memory in an unpredictable fashion, add "memory" to the list of clobbered registers. This will cause GCC to not keep memory values cached in registers across the assembler instruction. We also have to add thevolatile keyword if the memory affected is not listed in the inputs or outputs of the asm.
如果匯編指令更改了內存值,需在clobber list中加入“memory”。這樣,在 匯編語句執行過程中,GCC不再使用寄存器內的值。我們還需要加入volatile關鍵字,如果匯編的輸出輸入操作影響到了內存值,而且並沒有將這種變化加入到clobber list。
We can read and write the clobbered registers as many times as we like. Consider the example of multiple instructions in a template; it assumes the subroutine _foo accepts arguments in registerseax
andecx
.
clobber list中的寄存器可以反復讀寫。參考下面這個例子,代碼子程序__foo用eax,ecx寄存器傳遞參數。則這倆寄存器的值不再可靠,所以加入到clobber list中。
- asm ("movl %0,%%eax;
- movl %1,%%ecx;
- call _foo"
- : /* no outputs */
- : "g" (from), "g" (to)
- : "eax", "ecx"
- );
5.4 Volatile ...?
If you are familiar with kernel sources or some beautiful code like that, you must have seen many functions declared asvolatile
or__volatile__
which follows anasm
or__asm__
. I mentioned earlier about the keywordsasm
and__asm__
. So what is thisvolatile
?
If our assembly statement must execute where we put it, (i.e. must not be moved out of a loop as an optimization), put the keywordvolatile
after asm and before the ()’s. So to keep it from moving, deleting and all, we declare it as
asm volatile ( ... : ... : ... : ...);
如果你熟悉內核代碼或者像她一樣漂亮的代碼,你一定見到過許多函數被volatile或__volatile__修飾,通常緊跟在 asm或__asm__后邊。我先前提到過asm和__asm__的區別。那volatile呢?
Use __volatile__
when we have to be verymuch careful.
If our assembly is just for doing some calculations and doesn’t have any side effects, it’s better not to use the keywordvolatile
. Avoiding it helps gcc in optimizing the code and making it more beautiful.
In the section Some Useful Recipes
, I have provided many examples for inline asm functions. There we can see the clobber-list in detail.
如果我們的匯編代碼僅僅做一些計算並且沒有什么副作用,那么最好不用volatile。不使用它,可以幫助GCC優化代碼,讓代碼更漂亮。
下邊Some Useful Recipes部分,我提供了許多內嵌匯編代碼的例子,我們可以看到更多細節。
6. More about constraints.
By this time, you might have understood that constraints have got a lot to do with inline assembly. But we’ve said little about constraints. Constraints can say whether an operand may be in a register, and which kinds of register;whether the operand can be a memory reference, and which kinds of address; whether the operand may be an immediate constant, and which possible values (ie range of values)it may have.... etc.
看到這里,你應該知道匯編里的限制符(constraint)做了很多的事。但,我們只花了很少的篇幅敘述限制符。比如,限制符可以指定一個寄存器,限制符可以指向一塊內存空間,限制符可以是一個立即數。。。等。
6.1 Commonly used constraints.
There are a number of constraints of which only a few are used frequently. We’ll have a look at those constraints.
有大量的限制符,我們常用使用其中很少一部份,現在來看看:
- Register operand constraint(r)
When operands are specified using this constraint, they get stored in General Purpose Registers(GPR). Take the following example:
當操作指定了“r”限制符,那么操作數將會被存儲在通用寄存器內。看下例:
asm ("movl %%eax, %0\n" :"=r"(myval));
Here the variable myval is kept in a register, the value in register
eax
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:這里的變量myval被存儲在一個寄存器內,代碼將eax寄存器的值拷貝到myval占用的寄存器內,然后myval寄存器的值將更新myval的內存值。當“r”限制符被指定,GCC可能分配任意一個通用寄存器來存儲操作數。如果要確切使用某個寄存器,你應該指定這個寄存器名稱,通過下表的格式:
- +---+--------------------+
- | 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:
如果限制符“m”后的操作數在內存中,任何對它們的操作都會直接更改內存值。與“r”限制符不同,“r”首先將操作數保存在寄存器內,然后在寄存器里進行數據操作,接着把數據寫回內存區域。使用“r”限制符,通常是由於某些指令必須使用,或者為了加快程序運行,所以占用寄存器。“m”限制符運用更頻繁,當我們希望在匯編執行過程中就更新內存,或者不希望額外占用一個寶貴的寄存器來裝載變量值,就使用“m”限制符。如下:idtr的值就被保存在loc那塊內存。
asm("sidt %0\n" : :"m"(loc));
- Matching(Digit) constraints
In some cases, a single variable may serve as both the input and theoutput 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:
上邊見到過類似的例子,此例“0”使用了匹配限制符,寄存器eax同時供input,output使用。輸入變量var被讀入到eax,運算結束后,再被存儲到eax。“0”這個限制符表示:與第0個操作數使用相同的寄存器。這樣,就指明了輸出輸入使用同一個寄存器。這個限制符在如下地方可能用到:
- 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.
使用匹配限制符最重要的作用是:使得對有限寄存器資源使用更高效。
Some other constraints used are:
其他一些限制符:
- "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.
- “m”:對內存的操作被允許,用一個合法內存空間來做操作數。
- “o”:對內存的操作被允許,但是必須支持地址偏移值,即,對於給出的地址,加上一個偏移量,此時也是一個合法的地址。
- “V”:對內存的操作被允許,但是不支持偏移量。也就是說,支持“m”限制符,但不支持“o”的那些地址。
- “i”:對立即整數(const,常值)的操作被允許,這個常值可以是運行到匯編內才被賦值。
- “n”:對立即整數的操作被允許。許多系統不支持匯編中的操作數小於一個字寬,對於這些操作數,應該使用“n”而非“i”。
- “g”:任意寄存器,內存,立即數都被允許。除了非通用寄存器。
Following constraints are x86 specific.
下面的限制符是x86特定的:
- "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.
While using constraints, for more precise control over the effects of constraints, GCC provides us with constraint modifiers. Mostly used constraint modifiers are
在使用限制符的時候,為了更准確的利用限制符的功能,GCC提供給我們一些限制語句修飾符。最常用的修飾符有:“=”,“&”。
- "=" : 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.
- “=”表示此操作數類型是只寫。之前的值會被輸出數據值替代。
- “&”表示此操作數是一個很早更變的(earlyclobber)操作數。在指令執行過程中,輸出操作數產生之前,輸入操作數還未使用完成,所以輸出操作數不能與該指令的任何輸入操作數公用同一寄存器。這個聲明就為防止這種合並寄存器的優化。因此,這個輸入操作數可能沒有被保存到寄存器。
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.
上邊對限制符的解釋絕不完全,下邊的例子可以讓我們更好的理解嵌入匯編的用法。下邊一節我們將看一些例子,那里我們將遇到更多的colbber list和限制符。
7. Some Useful Recipes.
Now we have covered the basic theory about GCC inline assembly, now we shall concentrate on some simple examples. It is always handy to write inlineasm functions as MACRO’s. We can see many asm functions in the kernel code.(/usr/src/linux/include/asm/*.h).
我們已經接觸過內嵌匯編的基本理論。現在我們專注幾個例子。使用內嵌匯編來定義宏是非常精妙的,我們經常可以在內核代碼中看到。
-
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.
這里我們指定GCC使用eax寄存器來存儲foo,bar存放在ebx寄存器,並且結果存放到eax中。這個“=”表示這是輸出結果的寄存器。接下來,我們用其他方法把一個整數存放進一個變量中。
- __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.
這是一個(atomic addition)。我們可以去掉‘lock’來去除這個(atomicity)。在輸出區域,“=m”是說my_var是輸出操作數,並且它在內存里。“ir”是說my_int是一個整數,並且會選擇一個通用寄存器存放它。沒有寄存器在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 is
0
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.
這里,my_var的值遞減到1,如果結果值為0。那么,cond就確定了。
要點:1.my_var存在內存中。2.cond被“=q”限制,所以它占用 eax,ebx,ecx,edx中的一個。3.memory在clobber list中,即,代碼改變了內存。
-
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 to
1
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.內存變量ADDR的'pos'字位被置1。我們可以使用‘btrl’指令來清除這位。限制符“Ir”表示pos存放到寄存器內,並且pos的范圍是0~31(x86專用限制符,上邊有提及)。隨着條件代碼的改變,我們把“cc”加入到clobber list。
-
Now we look at some more complicated but useful function. String copy.
現在我們看實現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 willchange 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.
源地址存放在esi中,目的地址在edi,接下來開始拷貝。當到達0時,程序完成。限制符“&S”,“&D”,“&a”分別指定esi,edi,eax,並且他們是早期改變的寄存器,即,他們的內容在程序結束前,就會有變化。同樣memroy出現在clobber list。下例,類似的功能,移動兩個字節的內存塊,注意,它是以宏的方式實現的。
- #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.
這里沒有輸出,使用兩個連續的::。ecx,esi,edi的變化實現內存塊搬移。所以,把他們也加到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.
Linux中,系統調用用內嵌匯編實現。我們一起看看系統調用怎樣實現的。所有的系統調用都用匯編書寫。例如,系統調用3個參數,被定義成如下宏:
- #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.每當系統調用一個3參數的函數時,就使用上邊這個宏來調用。系統調用的名稱name被放置在eax,其后的參數分別占用ebx,ecx,edx。最后“int 0x80”這條指令實現了調用,而且返回值存入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.
每個系統調用的實現都相似,Exit()系統函數只有一個參數,我們來看看它的代碼長什么樣子。如下:
- {
- 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 by
int $0x80
, theexit(0)
is executed. This is how exit works.exit的編號是1,它的參數是0。所以我們安排eax存放1,ebx存放2,然后指令“int $0x80”,exit(0)就執行了。這就是它的工作方式。
8. Concluding Remarks.
This document has gone through the basics of GCC Inline Assembly. Once you have understood the basic concept it is not difficult to take steps by your own. We saw some examples which are helpful in understanding the frequently used features of GCC Inline Assembly.
GCC Inlining is a vast subject and this article is by no means complete. More details about the syntax’s we discussed about is available in the official documentation for GNU Assembler. Similarly, for a complete list of theconstraints refer to the official documentation of GCC.
And of-course, the Linux kernel use GCC Inline in a large scale. So we can find many examples of various kinds in the kernel sources. They can help us a lot.
If you have found any glaring typos, or outdated info in thisdocument, please let us know.
這個文檔縱覽了GCC內嵌匯編的基本內容。一旦你理解了這些基本概念,那麽修行靠個人。
GCC內嵌匯編是一個巨大的工程,並且這樣的藝術之作絕不會完成。更多細節請參見GNU Assembler官方文檔。
當然,Linux大規模使用了GCC內嵌匯編,所以我們從中可以找到大量的例子。她們會給我們很多幫助。