怎样在C语言嵌入汇编代码.抱歉,不知什么缘故,所有例子里的代码都排成了一行,我在编辑模式里换行正常,很漂亮的代码。请知道如何解决的朋友告诉我下。 源网页: >>WIKI<< 4. Basic Inline.The format of basic inline assembly is very much straight forward. Its basic form is 基本汇编嵌入格式如下:
Example.
You might have noticed that here I’ve used 可能注意到了这里使用了 asm 和 __asm__. 都是有效的.如果关键字asm在程序中有冲突, 则可以使用__asm__. 如果我们需要使用一条以上的汇编指令, 我们应该每条占用一行, 用双引号括起,并加上'\n'和'\t'后缀. 这是因为gcc把用字符串的格式把汇编指令传给as(GAS), 然后利用换行符, 把它们转换成正确的汇编格式.
Example.
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去做.
扩展汇编的格式如下:
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:
Now, what does this code do? The above inline fills the 上面这段代码做了什么? 这段内嵌汇编把 fill_value, count装入寄存器,同时告知GCC,clobber list目录中的寄存器eax,edi,已经改变. 我们来看下一个例子:
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'的值相等.
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 instruction 我们现在分析几个例子。我们想给一个数乘以5。因此,我们使用lea指令: (汇编语句leal(r1,r2,4),r3语句表示r1+r2*4→r3。这个例子可以非常快地将x乘5。)
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这样做(通过更改“限制符”内容)。
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。
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 put 上面三个例子,我们没有把任何寄存器放入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 registers clobber list中的寄存器可以反复读写。参考下面这个例子,代码子程序__foo用eax,ecx寄存器传递参数。则这俩寄存器的值不再可靠,所以加入到clobber list中。
5.4 Volatile ...?If you are familiar with kernel sources or some beautiful code like that, you must have seen many functions declared as 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 keyword
Use If our assembly is just for doing some calculations and doesn’t have any side effects, it’s better not to use the keyword In the section 如果我们的汇编代码仅仅做一些计算并且没有什么副作用,那么最好不用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. 有大量的限制符,我们常用使用其中很少一部份,现在来看看: When operands are specified using this constraint, they get stored in General Purpose Registers(GPR). Take the following example: 当操作指定了“r”限制符,那么操作数将会被存储在通用寄存器内。看下例:
Here the variable myval is kept in a register, the value in register 这里的变量myval被存储在一个寄存器内,代码将eax寄存器的值拷贝到myval占用的寄存器内,然后myval寄存
器的值将更新myval的内存值。当“r”限制符被指定,GCC可能分配任意一个通用寄存器来存储操作数。如果要确切使用某个寄存器,你应该指定这个寄存
器名称,通过下表的格式:
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:
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.
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个操作数使用相同的寄存器。这样,就指明了输出输入使用同一个寄存器。这个限
制符在如下地方可能用到:
The most important effect of using matching restraints is that they lead to the efficient use of available registers. 使用匹配限制符最重要的作用是:使得对有限寄存器资源使用更高效。 Some other constraints used are: 其他一些限制符:
Following constraints are x86 specific. 下面的限制符是x86特定的:
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提供给我们一些限制语句修饰符。最常用的修饰符有:“=”,“&”。
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). 我们已经接触过内嵌汇编的基本理论。现在我们专注几个例子。使用内嵌汇编来定义宏是非常精妙的,我们经常可以在内核代码中看到。
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内嵌汇编,所以我们从中可以找到大量的例子。她们会给我们很多帮助。
请留言,我将立刻改正翻译不妥之处,以免误导读者,为了锻炼自己阅读能力,我才干了这事儿。thanks a lot~ :-) |
|
来自: WUCANADA > 《embedded》