《在汇编语言中使用asm》由会员分享,可在线阅读,更多相关《在汇编语言中使用asm(6页珍藏版)》请在金锄头文库上搜索。
1、在汇编语言中使用在汇编语言中使用 asmasm在汇编语言中使用 asm,你也可以使用 C 语句表达。也就是说你不需要知道你要使用的数据在寄存器或存储器中的位置。你需要描述汇编指令象出现在机器中的那样,为每一个操作数加上一个强制的约束。例如,这是使用 68881s fsinx 指令的方法: asm (“fsinx %1,%0“ : “=f“ (result) : “f“ (angle);这是用 C 语言表达当结果为输出操作数时的输入操作数。每个都有“f”如它的操作数约束,也就是说需要浮点记录器。显示了操作数为输出;所有的输出操作数必须使用“=” 。约束在机器中使用相同的语句(见约束)。每个操作数
2、都被括号中在 C 语句之后的操作约束字符串所描述。一个冒号把第一个输出操作数分开还有如果存在最后一个的话同样这样处理。逗号在每个组中分隔开操作数。操作数的总和一般被限制在 301 以内;这个限制可能在 GCC 的将来版本中被打破。如果没有输出而只有输入的话,那么需要在输出处加两个连续的冒号。 如 GCC 3.1 版,它也可能使用在汇编码中可以参考的符号名来指定输入和输出操作数。这些名字在在约束字符串前的方括号内被指定,并且可以在汇编码中使用%name代替操作数后的百分标记。使用命名操作数的例子如下:asm (“fsinx %angle,%output“: output “=f“ (result
3、): angle “f“ (angle);注意符号操作数的名字与其他 C 语言识别符没有什么联系。你可以使用任何你喜欢的名字,甚至那些现在有的标号,但是必须确保在同一个汇编程序中没有两个相同的符号名出现。输出操作表达必须为左值; 编译器才能识别它。输入操作数则不需要这样。编译器不能识别操作数对指令是否有合理的数据类型。它不能解析汇编程序指令并且不知道汇编指令的含义,甚至不知道是否为有效的汇编指令输入。asm 经常被用在自身不知道是否存在的机器指令编译中。如果输出操作表达不能直接寻址(例如:bit-field) ,那么约束中必须允许寄存器。在这种情况下,GCC 将使用寄存器作为 asm 的输出,
4、然后存储那个寄存器到输出内。当约束为读写操作(或操作数在只有一些位元需要转化)时,需要允许寄存器, 你可能二者选一,理论上把函数一分我二,一个读操作和一个写操作。在指令执行时他们之间的关系通过他们需要在相同的位置表达出来。你可以对两个操作都使用 C 表达,或不同的表达。例如,这是我们写的指令作为读操作源并且 foo 作为读写的目的地址:asm (“combine %2,%0“ : “=r“ (foo) : “0“ (foo), “g“ (bar);对操作数 1 的约束“0”要与操作数 0 占用相同的存储地址。在约束中的数必须在输入中被允许并且它必须提及输出操作数。只有在约束中的一个操作数目可以
5、保证一个操作数与另一个在相同的位置。事实上 foo 两个操作数的值不能保证他们在汇编代码中相同的位置。以下语句是不可靠的:asm (“combine %2,%0“ : “=r“ (foo) : “r“ (foo), “g“ (bar);不同最佳化或重载将引起操作数 0 和 1 在不同的寄存器中;GCC 不知道不这样做的原因。例如,编译器可能在寄存器中找到 foo 值的一个副本( 然后到自己地址复制它)。当然因为对操作数 1 寄存器在汇编代码中不被提及,结果将不会工作,但是 GCC 不会提示错误。如 GCC 3.1 版,可以为了匹配约束写name来代替操作数。例如:asm (“cmoveq %1
6、,%2,%result“: result “=r“(result): “r“ (test), “r“(new), “result“(old);一些指令破坏硬件寄存器。为了描述这个情况,在输入运算元之后写第三个冒号,在硬件寄存器的名字后面(如字符串所给。这是一个为VAX 例子:asm volatile (“movc3 %0,%1,%2“: /* no outputs */: “g“ (from), “g“ (to), “g“ (count): “r0“, “r1“, “r2“, “r3“, “r4“, “r5“);你可能在一定程度上对输入或输出操作数不会写描述。例如,如果没有在目录中提及寄存器你
7、可能不会描述操作数。在没有指定输出操作时没有方法来指定输入操作。注意如果你叙述的所有输出运算元为这个给目的 ( 并且因此不用), 然后将需要为 asm 描述构造,同样地在下面描述, 阻止 GCC 删除 asm。如果你在汇编码中提及一个特殊的硬件寄存器,你可能需要在第三个冒号之后向编译器指定寄存器记录器的数值。在汇编程序中,寄存器的名字从“%“开始;在汇编代码中创建“%“,必须在输入中写“%“.如果你的汇编指令能够改变条件码寄存器,则把 cc 加入寄存器列表。在一些机器上 GCC 表现为一个特殊的硬件寄存器条件码; cc 为寄存器命名。在其他的机器上,条件码不同方式地被处理,并且指定cc 此时是
8、没有作用的。但无论对于什么机器它是正确的。 如果你的汇编程序以不可预知方式修改存储器,把存储器加入寄存器列表。这将会导致 GCC 不保存在汇编指令对面的寄存器中被贮藏的存储器的值。如果改变的存储器不在 asm 的输入或输出中列出,你可能想要增加可变关键字,如存储器忽略 asm 的边际效应一样。你可以在一个单一 asm 中将多重寄存器指令集合起来, 并使用系统中汇编代码的正常字符将其分隔开。联合大多数都回在新的一行开始工作,加一个“tab “将其移到指令的位置( 写作: n t). 有时可以使用分号,如果汇编程序允许分号作为换行字符。注意有些汇编程序使用分号作为注释的开始.输入操作要保证不使用寄
9、存器,还有输出操作数的地址,所以你可以不限次数的读写寄存器.这是一个多重指令的例子;它假定子程序_foo 接受寄存器 9 和 10 的独立变量:asm (“movl %0,r9ntmovl %1,r10ntcall _foo“: /* no outputs */: “g“ (from), “g“ (to): “r9“, “r10“);除非一个输出运算元包含这假定了你的汇编程序支持本地标志,就如 GNU 汇编程序和大多数的 Unix 程序一样。 说到标志, 从一个 asm 跳转到另外一个是不被支持的。编译器的优化不识别这些跳转,所以在优化的时候不会考虑这些跳转。通常使用这些 asm 指令最方便的
10、方法是象函数一样压缩它们,如下:#define sin(x) ( double _value, _arg = (x); asm (“fsinx %1,%0“: “=f“ (_value): “f“ (_arg); _value; )这里变量_arg 用来保证指令操作一个 double 值,并且只承认可以自动转换的那些独立变量 x。另一种确定指令操作正确数据类型的方法是在 asm 中使用一个计算。它不同与使用变量_arg 的地方是它转换更多的不同数据类型。例如,如果需要的是 int,int 函数中的独立变量会无条件地承认一个指针,除非调用者明确地指定它否则在分配一个 int 变量名的时候会出现警
11、告。如果 asm 有输出运算, GCC 承认指令除了改变输出运算元之外没有其他的操作。这并不是说有边际效应的指令就不能使用了,但是必须小心的使用,因为如果在不用是输出运算时, 或把他们移出循环时,或他们有一个 sub 表达用一个代替了两者时,编译器可能消除他们。同时, 如果你的指令在变量上没有边际效应也不出现转换,如果巧合在寄存器中发现变量,那么它的初始值可能稍后被重新使用。 你可以通过在 asm 后写关键字来阻止 asm 指令被删除、移动、或联合,例如:#define get_and_set_priority(new) ( int _old; asm volatile (“get_and_s
12、et_priority %0, %1“ : “=g“ (_old) : “g“ (new); _old; )如果你写了一条没有输出的 asm 指令,GCC 将会识别有其他作用并且不会在循环外删除或移动它。关键字指出指令有重要的作用。如果它是可得到的, GCC 将不删除可变的 asm ( 如果 GCC 能确定程序将不会执行到指令的位置,指令仍然会被删除)。除此之外,GCC 将不会在可变 asm 指令中重置指令。例如:*(volatile int *)addr = foo;asm volatile (“eieio“ : : );假定地址中包含了寄存器列表中存储器的地址。 addr 包含存储器图表装
13、置记录器的位址。PC 的 eieio 指令(按顺序地执行 I/O 操作)将指定 CPU 确保在其他的 I/O 之前将它存储到设备寄存器中。 注意,如果对编译器无意义的话可变 asm 指令也可能移动,如跳转指令。你不能认为可变 asm 指令的顺序性会保持地非常连续。 如果你想要连续的输出,可以使用单一 asm 。 同时, GCC 将会为 asm 指令执行一些优化; 当 GCC 遇到一个可变 asm 指令方式如一些其他的编译器不会忘记每个操作。 一条没有没有任何操作的指令(“旧风格“的 asm)将会同样地被处理成可变 asm 指令。 这是寻找被汇编指令留下的条件码的一个很自然的想法。然而,当我们试
14、图实现的时候,发现没有任何方法能确保工作的可靠性。问题在于输出可能需要重载,从而导致需要额外的存储指令。大多数的机器上,测试它的时间之前,这些指令会改变条件码。问题的原因并不是普通的“ 测试 “和“ 比较 “指令,因为他们没有任何的输出操作。如上述的原因一样,不可能将先前指令留下的条件码传给汇编指令。如果你正在写一个头文件,应该包括ISO C 程序,使用_asm_ 代替 asm。见预备关键字。i386 浮点指针 asm 操作有 asm_operands 中使用堆栈有一些规则。这些规则只适用于如堆栈一样的运算元为普通型的:在 asm_operands 中给予一组输出规则,有必要知道哪些是被 as
15、m 退栈的,还有哪写是被 gcc 明确退栈的。被 asm 捕获的输入规则必需被明确的识别,除非它是强制与输出操作匹配的。对于任何被 asm 退栈输入信息,必需知道该如何调整堆栈为退栈补偿。如果任何的非退栈输入比退栈信息更靠近栈顶,将不会知道堆栈如何就象不清楚堆栈是如何检测的一样。所有的非退栈输入要比退栈信息更靠近栈顶。在 insn 输入可能被消除,可能使用输入为输出重载,考虑下面的例子:asm (“foo“ : “=t“ (a) : “f“ (b);程序中输入 B 不被出栈,并且 asm 将结果如栈,也就是说栈比以前更深了一位。但是如果输入 B 在 insn 中消除的话,重载可能考虑为输入和输
16、出使用相同的信息。如果任何的输入操作使用 f 强制约束, 那么普所有输出强制约束必须使用一些运算元需要在堆栈中特殊位置中。所有的输出都属于这个范畴-没有其他方法可以知道输出的出现除非用户强制地指定它。输出运算必须明确地指出哪一个输出在 asm 之后出现。 “=f”不承认: 操作数必须选择一个类。输出运算不可能在现有的堆栈之间被嵌套。因为没有 387 运算码使用读/写操作,在 asm_operands 之前所有的输出运算都被消除,并且被 asm_operands 压入堆栈,放在栈顶。输出运算必须在栈顶开始,它不可能跳过 reg。一些 asm 声明可能因内部的计算需要额外的堆栈空间。这可以通过与输入和输出武官的寄存器来保证。 以下为两个合理的写法:这个有一个输入,被出栈,产生两个输出: asm (“fsincos“ : “=t“ (cos), “=u“ (sin) : “0“ (inp);这个有两个输入,被 fyl2xp1 操作码出栈,并且将他们与一个输入做交换。用户必须为 reg-st