GCC内联汇编入门
gcc内嵌汇编详解
gcc内嵌汇编详解有时候我们希望在C/C++代码中使⽤嵌⼊式汇编,因为C中没有对应的函数或语法可⽤。
⽐如我最近在ARM上写FIR程序时,需要对最后的结果进⾏饱和处理,但gcc没有提供ssat这样的函数,于是不得不在C代码中嵌⼊汇编指令。
1. ⼊门在C中嵌⼊汇编的最⼤问题是如何将C语⾔变量与指令操作数相关联。
当然,gcc都帮我们想好了。
下⾯是是⼀个简单例⼦。
asm(“fsinx %1, %0”:”=f”(result):”f”(angle));这⾥我们不需要关注fsinx指令是⼲啥的;只需要知道这条指令需要两个浮点寄存器作为操作数。
作为专职处理C语⾔的gcc编译器,它是没办法知道fsinx这条汇编指令需要什么样的操作数的,这就要求程序猿告知gcc相关信息,⽅法就是指令后⾯的”=f”和”f”,表⽰这是两个浮点寄存器操作数。
这被称为操作数规则(constraint)。
规则前⾯加上”=”表⽰这是⼀个输出操作数,否则是输⼊操作数。
constraint后⾯括号内的是与该寄存器关联的变量。
这样gcc就知道如何将这条嵌⼊式汇编语句转成实际的汇编指令了:fsinx:汇编指令名%1, %0:汇编指令操作数“=f”(result):操作数%0是⼀个浮点寄存器,与变量result关联(对输出操作数,“关联”的意思就是说gcc执⾏完这条汇编指令后会把寄存器%0的内容送到变量result中)“f”(angle):操作数%1是⼀个浮点寄存器,与变量angle关联(对输⼊操作数,“关联”的意思是就是说gcc执⾏这条汇编指令前会先将变量angle的值读取到寄存器%1中)因此这条嵌⼊式汇编会转换为⾄少三条汇编指令(⾮优化):1> 将angle变量的值加载到寄存器%12> fsinx汇编指令,源寄存器%1,⽬标寄存器%03> 将寄存器%0的值存储到变量result当然,在⾼优化级别下上⾯的叙述可能不适⽤;⽐如源操作数可能本来就已经在某个浮点寄存器中了。
GCC的内嵌汇编语法
GCC的内嵌汇编语法AT&T ASM Syntax1 Overview开发一个OS,尽管绝大部分代码只需要用C/C++等高级语言就可以了,但至少和硬件相关部分的代码需要使用汇编语言,另外,由于启动部分的代码有大小限制,使用精练的汇编可以缩小目标代码的Size。
另外,对于某些需要被经常调用的代码,使用汇编来写可以提高性能。
所以我们必须了解汇编语言,即使你有可能并不喜欢它。
如果我们选择的OS开发工具是GCC以及GAS的话,就必须了解AT&T汇编语言语法,因为GCC/GAS只支持这种汇编语法。
本文只讨论AT&T的汇编语法,以及GCC的内嵌汇编语法。
2 Syntax1.寄存器引用引用寄存器要在寄存器号前加百分号%,如"movl %eax, %ebx"。
80386有如下寄存器:8个32-bit寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp;8个16-bit寄存器,它们事实上是上面8个32-bit寄存器的低16位:%ax,%bx,%cx,%dx,%di,%si,%bp,%sp;8个8-bit寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh,%dl。
它们事实上是寄存器%ax,%bx,%cx,%dx的高8位和低8位;6个段寄存器:%cs(code),%ds(data),%ss(stack),%es,%fs,%gs;3个控制寄存器:%cr0,%cr2,%cr3;6个debug寄存器:%db0,%db1,%db2,%db3,%db6,%db7;2个测试寄存器:%tr6,%tr7;8个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7)。
2. 操作数顺序操作数排列是从源(左)到目的(右),如"movl %eax(源), %ebx(目的)"3. 立即数使用立即数,要在数前面加符号$, 如"movl $0x04, %ebx"或者:para = 0x04movl $para, %ebx指令执行的结果是将立即数04h装入寄存器ebx。
GCC Inline ASM GCC内联汇编
GCC Inline ASM GCC内联汇编GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。
这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写C/C++代码中使用汇编编写简洁高效的代码。
1.GCC中基本的内联汇编非常易懂,我们先来看两个简单的例子:__asm__("movl %esp,%eax"); // 看起来很熟悉吧!或者是__asm__("movl $1,%eax // SYS_exitxor %ebx,%ebxint $0x80");或__asm__("movl $1,%eax\r\t" \"xor %ebx,%ebx\r\t" \"int $0x80" \);基本内联汇编的格式是__asm__ __volatile__("Instruction List");1、__asm____asm__是GCC关键字asm的宏定义:#define __asm__ asm__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。
2、Instruction ListInstruction List是汇编指令序列。
它可以是空的,比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。
但并非所有Instruction List为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory"); 就非常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。
GCC中内嵌汇编语言.
#define barrier( __asm__ __volatile__("": : :"memory" 中的memory是gcc的东西gcc内嵌汇编简介在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C 变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可,GCC会自动插入代码完成必要的操作。
1、简单的内嵌汇编例:__asm__ __volatile__("hlt"; "__asm__"表示后面的代码为内嵌汇编,"asm"是"__asm__"的别名。
"__volatile__"表示编译器不要优化代码,后面的指令保留原样,"volatile"是它的别名。
括号里面是汇编指令。
2、内嵌汇编举例使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。
例如在下面的汇编语句:__asm__ __violate__ ("movl %1,%0" : "=r" (result : "m" (input;"movl %1,%0"是指令模板;"%0"和"%1"代表指令的操作数,称为占位符,内嵌汇编靠它们将C 语言表达式与指令操作数相对应。
指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:"result"和"input",他们按照出现的顺序分别与指令操作数"%0","%1"对应;注意对应顺序:第一个C 表达式对应"%0";第二个表达式对应"%1",依次类推,操作数至多有10 个,分别用"%0","%1"...."%9"表示。
GCC内联汇编基础
GCC内联汇编基础Sandeep.S (原作者)v0.1, 01 March 2003.翻译:fancylea 版权所有,禁止用作商业用途。
/fancyleaV0.1, 2008-08-19 Draft[翻译错误报告和交流:MSN: Jeffrey.lea@Email: fancylea@QQ: 7798018]这篇文章阐述内联汇编的使用方法。
显然,阅读这篇文章您需要具备X86汇编语言和C语言的基础知识。
Contents1. 简介 (3)2. 概要 (3)3. GCC汇编格式。
(3)1) 源操作数和目的操作数的方向 (3)2) 寄存器命名 (4)3) 立即数 (4)4) 操作数大小 (4)5) 内存操作数 (4)4. 基本形式的内联汇编 (4)5. 扩展形式的内联汇编 (5)5.1 汇编模板 (6)5.2 操作数 (6)5.3 Clobber List (7)5.4 Volatile...?.. (8)6. 深入constraints。
(8)6.1 常用constraints (8)6.2 constraint修改标记 (10)7.常用技巧 (10)8.结束语 (13)9. 参考文献 (13)1. 简介[主要是版权/反馈/勘误/感谢等信息。
没有翻译。
--译者注, 本文中方括号中的都是译者注]2. 概要我们现在学习GCC内联汇编,那么内联汇编到底是什么?[我们首先先来看看内联函数有什么好处]我们可以让编译器将函数代码插入到调用者代码中,指出函数在代码中具体什么位置被执行。
这种函数就是内联函数。
内联函数似乎很像一个宏?的确,他们之间有很多相似之处。
那么内联函数到底有什么好处呢?内联函数降低了函数调用的开销。
[不仅仅节省堆栈] 如果某些函数调用的实参相同,那么返回值一定是相同的,这就可能给编译器留下了简化的空间。
因为返回值相同了就不必把内联函数的代码插入到调用者的代码中[直接用这个返回值替换就好了]。
GCC扩展内联汇编简介
GCC扩展内联汇编简介基本内联汇编基本内联汇编格式⽐较直观,可以直接这样写:asm("assembly code");例如:asm("movl %ecx, %eax"); /* 把 ecx 内容移动到 eax */__asm__("movb %bh , (%eax)"); /* 把bh中⼀个字节的内容移动到eax指向的内存 */扩展内联汇编前⾯讨论的基本内联汇编只涉及到嵌⼊汇编指令,⽽在扩展形式中,我们还可以指定操作数,并且可以选择输⼊输出寄存器,以及指明要修改的寄存器列表。
对于要访问的寄存器,并不⼀定要显式指明,也可以留给GCC⾃⼰去选择,这可能让GCC更好去优化代码。
扩展内联汇编格式如下:asm 汇编限定符(汇编模板:"限定符1"(输出操作数1),"限定符2"(输出操作数2)....... //可选的:"限定符1"(输⼊操作数1),"限定符2"(输⼊操作数2)....... //可选的:已改写寄存器列表 //可选的);其中汇编模板为汇编指令部分。
括号内的操作数都是C语⾔表达式中常量字符串。
不同部分之间使⽤冒号分隔。
相同部分语句中的每个⼩部分⽤逗号分隔。
最多可以指定10个操作数,不过可能有的计算机平台有额外的⽂档说明可以使⽤超过10个操作数。
第⼀个关键字asm,有时为了避免命名冲突,gcc也⽀持写作__asm__汇编限定符,例如限定符可以是__volatile__汇编模板#include <stdio.h>void foo(void){int foo = 0; bar = 10;__asm__ __volatile__ ("movl %1, %%eax;\n""movl %%eax, %0;\n":"=r"(foo):"r"(bar):"%eax");printf("foo = bar; foo = %d\n", foo);}截取⾃上⾯冒号之前的两句就是汇编模板,他们可以⼀⾏写⼀句,甚⾄加上换⾏符\n,\r。
汇编语言---GCC内联汇编
汇编语⾔---GCC内联汇编GCC⽀持在C/C++代码中嵌⼊汇编代码,这些代码被称作是"GCC Inline ASM"(GCC内联汇编);⼀、基本内联汇编GCC中基本的内联汇编⾮常易懂,格式如下:__asm__ [__volatile__] ("instruction list");其中,1.__asm__:它是GCC定义的关键字asm的宏定义(#define __asm__ asm),它⽤来声明⼀个内联汇编表达式,所以,任何⼀个内联汇编表达式都以它开头,它是必不可少的;如果要编写符合ANSI C标准的代码(即:与ANSI C兼容),那就要使⽤__asm__;2.__volatile__:它是GCC关键字volatile的宏定义;这个选项是可选的;它向GCC声明"不要动我所写的instruction list,我需要原封不动地保留每⼀条指令";如果不使⽤__volatile__,则当你使⽤了优化选项-O进⾏优化编译时,GCC将会根据⾃⼰的判断来决定是否将这个内联汇编表达式中的指令优化掉;如果要编写符合ANSI C标准的代码(即:与ANSI C兼容),那就要使⽤__volatile__;3.instruction list:它是汇编指令列表;它可以是空列表,⽐如:__asm__ __volatile__("");或__asm__("");都是合法的内联汇编表达式,只不过这两条语句什么都不做,没有什么意义;但并⾮所有"instruction list"为空的内联汇编表达式都是没意义的,⽐如:__asm__("":::"memory");就是⾮常有意义的,它向GCC声明:"我对内存做了改动",这样,GCC在编译的时候,就会将此因素考虑进去;例如:__asm__("movl %esp,%eax");或者是__asm__("movl 1,1,0x80");或者是__asm__("movl 1,1,0x80");instruction list的编写规则:当指令列表⾥⾯有多条指令时,可以在⼀对双引号中全部写出,也可将⼀条或多条指令放在⼀对双引号中,所有指令放在多对双引号中;如果是将所有指令写在⼀对双引号中,那么,相邻俩条指令之间必须⽤分号";"或换⾏符(\n)隔开,如果使⽤换⾏符(\n),通常\n后⾯还要跟⼀个\t;或者是相邻两条指令分别单独写在两⾏中;规则1:任意两条指令之间要么被分号(;)或换⾏符(\n)或(\n\t)分隔开,要么单独放在两⾏;规则2:单独放在两⾏的⽅法既可以通过\n或\n\t的⽅法来实现,也可以真正地放在两⾏;规则3:可以使⽤1对或多对双引号,每1对双引号⾥⾯可以放1条或多条指令,所有的指令都要放在双引号中;例如,下⾯的内联汇编语句都是合法的:__asm__("movl %eax,%ebx sti popl %edi subl %ecx,%ebx");__asm__("movl %eax,%ebx; sti popl %edi; subl %ecx,%ebx");__asm__("movl %eax,%ebx; sti\n\t popl %edi subl %ecx,%ebx");如果将指令放在多对双引号中,则,除了最后⼀对双引号之外,前⾯的所有双引号⾥的最后⼀条指令后⾯都要有⼀个分号(;)或(\n)或(\n\t);⽐如,下⾯的内联汇编语句都是合法的:__asm__("movl %eax,%ebx sti\n" "popl %edi;" "subl %ecx,%bx");__asm__("movl %eax,%ebx; sti\n\t" "popl %edi; subl %ecx,%ebx");__asm__("movl %eax,%ebx; sti\n\t popl %edi\n" "subl %ecx,%ebx"); ⼆、带有C/C++表达式的内联汇编GCC允许你通过C/C++表达式指定内联汇编中"instruction list"中的指令的输⼊和输出,你甚⾄可以不关⼼到底使⽤哪些寄存器,完全依靠GCC来安排和指定;这⼀点可以让程序员免去考虑有限的寄存器的使⽤,也可以提⾼⽬标代码的效率;1.带有C/C++表达式的内联汇编语句的格式:__asm__ [__volatile__]("instruction list":Output:Input:Clobber/Modify);圆括号中的内容被冒号":"分为四个部分:A. 如果第四部分的"Clobber/Modify"可以为空;如果"Clobber/Modify"为空,则其前⾯的冒号(:)必须省略;⽐如:语句 __asm__("movl%%eax,%%ebx":"=b"(foo):"a"(inp):);是⾮法的,⽽语句__asm__("movl %%eax,%%ebx":"=b"(foo):"a"(inp));则是合法的;B.如果第⼀部分的"instruction list"为空,则input、output、Clobber/Modify可以为空,也可以不为空;⽐如,语句__asm__("":::"memory");和语句__asm__(""::);都是合法的写法;C. 如果Output、Input和Clobber/Modify都为空,那么,Output、Input之前的冒号(:)可以省略,也可以不省略;如果都省略,则此汇编就退化为⼀个基本汇编,否则,仍然是⼀个带有C/C++表达式的内联汇编,此时"instruction list"中的寄存器的写法要遵循相关规定,⽐如:寄存器名称前⾯必须使⽤两个百分号(%%);基本内联汇编中的寄存器名称前⾯只有⼀个百分号(%);⽐如,语句__asm__("movl %%eax,%%ebx"::);__asm__("movl %%eax,%%ebx":);和语句__asm__("movl %%eax,%%ebx");都是正确的写法,⽽语句__asm__("movl %eax,%ebx"::);__asm__("movl%eax,%ebx":);和语句__asm__("movl %%eax,%%ebx");都是错误的写法;D.如果Input、Clobber/Modify为空,但Output不为空,则,Input前⾯的冒号(:)可以省略,也可以不省略;⽐如,语句__asm__("movl%%eax,%%ebx":"=b"(foo):);和语句__asm__("movl %%eax,%%ebx":"=b"(foo));都是正确的;E. 如果后⾯的部分不为空,⽽前⾯的部分为空,则,前⾯的冒号(:)都必须保留,否则⽆法说明不为空的部分究竟是第⼏部分;⽐如,Clobber/Modify、Output为空,⽽Input不为空,则Clobber/Modify前⾯的冒号必须省略,⽽Output前⾯的冒号必须保留;如果Clobber/Modify不为空,⽽Input和Output都为空,则Input和Output前⾯的冒号都必须保留;⽐如,语句 __asm__("movl %%eax,%%ebx"::"a"(foo));和__asm__("movl %%eax,%%ebx":::"ebx");注意:基本内联汇编中的寄存器名称前⾯只能有⼀个百分号(%),⽽带有C/C++表达式的内联汇编中的寄存器名⾂前⾯必须有两个百分号(%%);2.Output:Output部分⽤来指定当前内联汇编语句的输出,称为输出表达式;格式为: "操作约束"(输出表达式)例如:__asm__("movl%%rc0,%1":"=a"(cr0));这个语句中的Output部分就是("=a"(cr0)),它是⼀个操作表达式,指定了⼀个内联汇编语句的输出部分;Output部分由两个部分组成:由双引号括起来的部分和由圆括号括起来的部分,这两个部分是⼀个Output部分所不可缺少的部分;⽤双引号括起来的部分就是C/C++表达式,它⽤于保存当前内联汇编语句的⼀个输出值,其操作就是C/C++赋值语句"="的左值部分,因此,圆括号中指定的表达式只能是C/C++中赋值语句的左值表达式,即:放在等号=左边的表达式;也就是说,Output部分只能作为C/C++赋值操作左边的表达式使⽤;⽤双引号括起来的部分就指定了C/C++中赋值表达式的右值来源;这个部分被称作是"操作约束"(Operation Constraint),也可以称为"输出约束";在这个例⼦中的操作约束是"=a",这个操作约束包含两个组成部分:等号(=)和字母a,其中,等号 (=)说明圆括号中的表达式cr0是⼀个只写的表达式,只能被⽤作当前内联汇编语句的输出,⽽不能作为输⼊;字母a是寄存器EAX/AX/AL的缩写,说明cr0的值要从寄存器EAX中获取,也就是说cr0=eax,最终这⼀点被转化成汇编指令就是:movl %eax,address_of_cr0;注意:很多⽂档中都声明,所有输出操作的的操作约束都必须包含⼀个等号(=),但是GCC的⽂档中却明确地声明,并⾮如此;因为等号(=)约束说明当前的表达式是⼀个只写的,但是还有另外⼀个符号:加号(+),也可以⽤来说明当前表达式是可读可写的;如果⼀个操作约束中没有给出这两个符号中的任何⼀个,则说明当前表达式是只读的;因此,对于输出操作来说,肯定必须是可写的,⽽等号(=)和加号(+)都可表⽰可写,只不过加号(+)同时也可以表⽰可读;所以, 对于⼀个输出操作来说,其操作约束中只要包含等号(=)或加号(+)中的任意⼀个就可以了;等号(=)与加号(+)的区别:等号(=)表⽰当前表达式是⼀个纯粹的输出操作,⽽加号(+)则表⽰当前表达式不仅仅是⼀个输出操作,还是⼀个输⼊操作;但⽆论是等号(=)还是加号(+),所表⽰的都是可写,只能⽤于输出,只能出现在Output部分,⽽不能出现在Input部分;在Output部分可以出现多个输出操作表达式,多个输出操作表达式之间必须⽤逗号(,)隔开;3、Input:Input部分⽤来指定当前内联汇编语句的输⼊;称为输⼊表达式;格式为: "操作约束"(输⼊表达式)例如:__asm__("movl %0,%%db7"::"a"(cpu->db7));其中,表达式"a"(cpu->db7)就称为输⼊表达式,⽤于表⽰⼀个对当前内联汇编的输⼊;Input同样也由两部分组成:由双引号括起来的部分和由圆括号括起来的部分;这两个部分对于当前内联汇编语句的输⼊来说也是必不可少的;在这个例⼦中,由双引号括起来的部分是"a",⽤圆括号括起来的部分是(cpu->db7);⽤双引号括起来的部分就是C/C++表达式,它为当前内联汇编语句提供⼀个输⼊值;在这⾥,圆括号中的表达式cpu->db7是⼀个C/C++语⾔的表达式,它不必是左值表达式,也就是说,它不仅可以是放在C/C++赋值操作左边的表达式,还可以是放在C/C++赋值操作右边的表达式;所以,Input可以是⼀个变量、⼀个数字,还可以是⼀个复杂的表达式(如:a+b/c*d);⽐如,上例还可以这样写:__asm__("movl %0,%%db7"::"a"(foo));__asm__("movl%0,%%db7"::"a"(0x12345));__asm__("movl %0,%%db7"::"a"(va:vb/vc));⽤双引号括起来的部分就是C/C++中赋值表达式的右值表达式,⽤于约束当前内联汇编语句中的当前输⼊;这个部分也成为"操作约束",也可以成为是"输⼊约束";与输出表达式中的操作约束不同的是,输⼊表达式中的操作约束不允许指定等号(=)约束或加号(+)约束,也就是说,它只能是只读的;约束中必须指定⼀个寄存器约束;例⼦中的字母a表⽰当前输⼊变量cpu->db7要通过寄存器EAX输⼊到当前内联汇编语句中;三、操作约束:Operation Constraint操作约束只会出现在带有C/C++表达式的内联汇编语句中;每⼀个Input和Output表达式都必须指定⾃⼰的操作约束Operation Constraint;约束的类型有:寄存器约束、内存约束、⽴即数约束、通⽤约束;操作表达式的格式:"约束"(C/C++表达式)即:"Constraint"(C/C++ expression)1.寄存器约束:当你的输⼊或输出需要借助于⼀个寄存器时,你需要为其指定⼀个寄存器约束;可以直接指定⼀个寄存器名字;⽐如:__asm__ __volatile__("movl %0,%%cr0"::"eax"(cr0));也可以指定寄存器的缩写名称;⽐如:__asm__ __volatile__("movl %0,%%cr0"::"a"(cr0));如果指定的是寄存器的缩写名称,⽐如:字母a;那么,GCC将会根据当前操作表达式中C/C++表达式的宽度来决定使⽤%eax、%ax还是%al;⽐如:unsigned short __shrt;__asm__ __volatile__("movl%0,%%bx"::"a"(__shrt));由于变量__shrt是16位⽆符号类型m⼤⼩是两个字节,所以,编译器编译出来的汇编代码中,则会让此变量使⽤寄存器%ax;⽆论是Input还是Output操作约束,都可以使⽤寄存器约束;常⽤的寄存器约束的缩写:r:I/O,表⽰使⽤⼀个通⽤寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl、%edx/%dx/%dl中选取⼀个GCC认为是合适的;q:I/O,表⽰使⽤⼀个通⽤寄存器,与r的意义相同;g:I/O,表⽰使⽤寄存器或内存地址;m:I/O,表⽰使⽤内存地址;a:I/O,表⽰使⽤%eax/%ax/%al;b:I/O,表⽰使⽤%ebx/%bx/%bl;c:I/O,表⽰使⽤%ecx/%cx/%cl;d:I/O,表⽰使⽤%edx/%dx/%dl;D:I/O,表⽰使⽤%edi/%di;S:I/O,表⽰使⽤%esi/%si;f:I/O,表⽰使⽤浮点寄存器;t:I/O,表⽰使⽤第⼀个浮点寄存器;u:I/O,表⽰使⽤第⼆个浮点寄存器;A:I/O,表⽰把%eax与%edx组合成⼀个64位的整数值;o:I/O,表⽰使⽤⼀个内存位置的偏移量;V:I/O,表⽰仅仅使⽤⼀个直接内存位置;i:I/O,表⽰使⽤⼀个整数类型的⽴即数;n:I/O,表⽰使⽤⼀个带有已知整数值的⽴即数;F:I/O,表⽰使⽤⼀个浮点类型的⽴即数;2.内存约束:如果⼀个Input/Output操作表达式的C/C++表达式表现为⼀个内存地址(指针变量),不想借助于任何寄存器,则可以使⽤内存约束;⽐如:__asm__("lidt %0":"=m"(__idt_addr));或__asm__("lidt %0"::"m"(__idt_addr));内存约束使⽤约束名"m",表⽰的是使⽤系统⽀持的任何⼀种内存⽅式,不需要借助于寄存器;使⽤内存约束⽅式进⾏输⼊输出时,由于不借助于寄存器,所以,GCC不会按照你的声明对其做任何的输⼊输出处理;GCC只会直接拿来使⽤,对这个C/C++ 表达式⽽⾔,究竟是输⼊还是输出,完全依赖于你写在"instruction list"中的指令对其操作的⽅式;所以,不管你把操作约束和操作表达式放在Input部分还是放在Output部分,GCC编译⽣成的汇编代码都是⼀样的,程序的执⾏结果也都是正确的;本来我们将⼀个操作表达式放在Input或Output部分是希望GCC能为我们⾃动通过寄存器将表达式的值输⼊或输出;既然对于内存约束类型的操作表达式来说,GCC不会为它做任何事情,那么放在哪⾥就⽆所谓了;但是从程序员的⾓度来看,为了增强代码的可读性,最好能够把它放在符合实际情况的地⽅;3.⽴即数约束:如果⼀个Input/Output操作表达式的C/C++表达式是⼀个数字常数,不想借助于任何寄存器或内存,则可以使⽤⽴即数约束;由于⽴即数在C/C++表达式中只能作为右值使⽤,所以,对于使⽤⽴即数约束的表达式⽽⾔,只能放在Input部分;⽐如:__asm__ __volatile__("movl %0,%%eax"::"i"(100));⽴即数约束使⽤约束名"i"表⽰输⼊表达式是⼀个整数类型的⽴即数,不需要借助于任何寄存器,只能⽤于Input部分;使⽤约束名"F"表⽰输⼊表达式是⼀个浮点数类型的⽴即数,不需要借助于任何寄存器,只能⽤于Input部分;4.通⽤约束:约束名"g"可以⽤于输⼊和输出,表⽰可以使⽤通⽤寄存器、内存、⽴即数等任何⼀种处理⽅式;约束名"0,1,2,3,4,5,6,7,8,9"只能⽤于输⼊,表⽰与第n个操作表达式使⽤相同的寄存器/内存;通⽤约束"g"是⼀个⾮常灵活的约束,当程序员认为⼀个C/C++表达式在实际操作中,⽆论使⽤寄存器⽅式、内存⽅式还是⽴即数⽅式都⽆所谓时,或者程序员想实现⼀个灵活的模板,以让GCC可以根据不同的C/C++表达式⽣成不同的访问⽅式时,就可以使⽤通⽤约束g;例如:#define JUST_MOV(foo) __asm__("movl %0,%%eax"::"g"(foo))则,JUST_MOV(100)和JUST_MOV(var)就会让编译器产⽣不同的汇编代码;对于JUST_MOV(100)的汇编代码为:#APPmovl $100,%eax #⽴即数⽅式;#NO_APP对于JUST_MOV(var)的汇编代码为:#APPmovl 8(%ebp),%eax #内存⽅式;#NO_APP像这样的效果,就是通⽤约束g的作⽤;5.修饰符:等号(=)和加号(+)作为修饰符,只能⽤于Output部分;等号(=)表⽰当前输出表达式的属性为只写,加号(+)表⽰当前输出表达式的属性为可读可写;这两个修饰符⽤于约束对输出表达式的操作,它们俩被写在输出表达式的约束部分中,并且只能写在第⼀个字符的位置;符号&也写在输出表达式的约束部分,⽤于约束寄存器的分配,但是只能写在约束部分的第⼆个字符的位置上;⽤符号&进⾏修饰时,等于向GCC声明:"GCC不得为任何Input操作表达式分配与此Output操作表达式相同的寄存器";其原因是修饰符&意味着被其修饰的Output操作表达式要在所有的Input操作表达式被输⼊之前输出;即:GCC会先使⽤输出值对被修饰符&修饰的Output操作表达式进⾏填充,然后,才对Input操作表达式进⾏输⼊;这样的话,如果不使⽤修饰符&对Output操作表达式进⾏修饰,⼀旦后⾯的Input操作表达式使⽤了与Output操作表达式相同的寄存器,就会产⽣输⼊输出数据混乱的情况;相反,如果没有⽤修饰符&修饰输出操作表达式,那么,就意味着GCC会先把Input操作表达式的值输⼊到选定的寄存器中,然后经过处理,最后才⽤输出值填充对应的Output操作表达式;所以, 修饰符&的作⽤就是要求GCC编译器为所有的Input操作表达式分配别的寄存器,⽽不会分配与被修饰符&修饰的Output操作表达式相同的寄存器;修饰符&也写在操作约束中,即:&约束;由于GCC已经规定加号(+)或等号(=)占据约束的第⼀个字符,那么& 约束只能占⽤第⼆个字符;例如:int __out, __in1, __in2;__asm__("popl %0\n\t""movl %1,%%esi\n\t""movl %2,%%edi\n\t":"=&a"(__out):"r"(__in1),"r"(__in2));注意: 如果⼀个Output操作表达式的寄存器约束被指定为某个寄存器,只有当⾄少存在⼀个Input操作表达式的寄存器约束为可选约束(意思是GCC可以从多个寄存器中选取⼀个,或使⽤⾮寄存器⽅式)时,⽐如"r"或"g"时,此Output操作表达式使⽤符号&修饰才有意义;如果你为所有的Input操作表达式指定了固定的寄存器,或使⽤内存/⽴即数约束时,则此Output操作表达式使⽤符号&修饰没有任何意义;⽐如:__asm__("popl%0\n\t" "movl %1,%esi\n\t" "movl %2,%edi\n\t" :"=&a"(__out) :"m"(__in1),"c"(__in2));此例中的Output操作表达式完全没有必要使⽤符号&来修饰,因为__in1和__in2都已经被指定了固定的寄存器,或使⽤了内存⽅式,GCC⽆从选择;如果你已经为某个Output操作表达式指定了修饰符&,并指定了固定的寄存器,那么,就不能再为任何Input操作表达式指定这个寄存器了,否则会出现编译报错;⽐如:__asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&a"(__out):"a"(__in1),"c"(__in2));对这条语句的编译就会报错;相反,你也可以为Output指定可选约束,⽐如"r"或"g"等,让GCC为此Output操作表达式选择合适的寄存器,或使⽤内存⽅式,GCC在选择的时候,会排除掉已经被Input操作表达式所使⽤过的所有寄存器,然后在剩下的寄存器中选择,或者⼲脆使⽤内存⽅式;⽐如:__asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&r" (__out):"a"(__in1),"c"(__in2));这三个修饰符只能⽤在Output操作表达式中,⽽修饰符%则恰恰相反,它只能⽤在Input操作表达式中;修饰符%⽤于向GCC声明:"当前Input操作表达式中的C/C++表达式可以与下⼀个Input操作表达式中的C/C++表达式互换";这个修饰符⼀般⽤于符合交换律运算的地⽅;⽐如:加、乘、按位与&、按位或|等等;例如:__asm__("addl %1,%0\n\t":"=r"(__out):"%r"(__in1),"0"(__in2));其中,"0"(__in2)表⽰使⽤与第⼀个Input操作表达式("r"(__in1))相同的寄存器或内存;由于使⽤符号%修饰__in1的寄存器⽅式r,那么就表⽰,__in1与__in2可以互换位置;加法的两个操作数交换位置之后,和不变;修饰符 I/O 意义= O 表⽰此Output操作表达式是只写的+ O 表⽰此Output操作表达式是可读可写的& O 表⽰此Output操作表达式独占为其指定的寄存器% I 表⽰此Input操作表达式中的C/C++表达式可以与下⼀个Input操作表达式中的C/C++表达式互换四、占位符每⼀个占位符对应⼀个Input/Output操作表达式;带C/C++表达式的内联汇编中有两种占位符:序号占位符和名称占位符;1.序号占位符:GCC 规定:⼀个内联汇编语句中最多只能有10个Input/Output操作表达式,这些操作表达式按照他们被列出来的顺序依次赋予编号0到9;对于占位符中的数字⽽⾔,与这些编号是对应的;⽐如:占位符%0对应编号为0的操作表达式,占位符%1对应编号为1的操作表达式,依次类推;由于占位符前⾯要有⼀个百分号%,为了去边占位符与寄存器,GCC规定:在带有C/C++表达式的内联汇编语句的指令列表⾥列出的寄存器名称前⾯必须使⽤两个百分号(%%),⼀区别于占位符语法;GCC对占位符进⾏编译的时候,会将每⼀个占位符替换为对应的Input/Output操作表达式所指定的寄存器/内存/⽴即数;例如:__asm__("addl %1,%0\n\t":"=a"(__out):"m"(__in1),"a"(__in2));这个语句中,%0对应Output操作表达式"=a"(__out),⽽"=a"(__out)指定的寄存器是%eax,所以,占位符%0被替换为%eax;占位符%1对应Input操作表达式"m"(__in1),⽽"m"(__in1)被指定为内存,所以,占位符%1被替换位__in1的内存地址;⽤⼀句话描述:序号占位符就是前⾯描述的%0、%1、%2、%3、%4、%5、%6、%7、%8、%9;其中,每⼀个占位符对应⼀个Input/Output的C/C++表达式;2.名称占位符:由于GCC中限制这种占位符的个数最多只能由这10个,这也就限制了Input/Output操作表达式中C/C++表达式的数量做多只能有10个;如果需要的C/C++表达式的数量超过10个,那么,这些需要占位符就不够⽤了;GCC内联汇编提供了名称占位符来解决这个问题;即:使⽤⼀个名字字符串与⼀个C/C++表达式对应;这个名字字符串就称为名称占位符;⽽这个名字通常使⽤与C/C++表达式中的变量完全相同的名字;使⽤名字占位符时,内联汇编的Input/Output操作表达式中的C/C++表达式的格式如下:[name] "constraint"(变量)此时,指令列表中的占位符的书写格式如下:%[name]这个格式等价于序号占位符中的%0,%1,$2等等;使⽤名称占位符时,⼀个name对应⼀个变量;例如:__asm__("imull %[value1],%[value2]":[value2] "=r"(data2):[value1] "r"(data1),"0"(data2));此例中,名称占位符value1就对应变量data1,名称占位符value2对应变量data2;GCC编译的时候,同样会把这两个占位符分别替换成对应的变量所使⽤的寄存器/内存地址/⽴即数;⽽且也增强了代码的可读性;这个例⼦,使⽤序号占位符的写法如下:__asm__("imull %1,%0":"=r"(data2):"r"(data1),"0"(data2));五、寄存器/内存修改标⽰(Clobber/Modify)有时候,当你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进⾏修改,希望GCC在编译时能够将这⼀点考虑进去;那么你就可以在Clobber/Modify部分声明这些寄存器或内存;1.寄存器修改通知:这种情况⼀般发⽣在⼀个寄存器出现在指令列表中,但⼜不是Input/Output操作表达式所指定的,也不是在⼀些Input/Output操作表达式中使⽤"r"或"g"约束时由GCC选择的,同时,此寄存器被指令列表中的指令所修改,⽽这个寄存器只供当前内联汇编语句使⽤的情况;⽐如:__asm__("movl %0,%%ebx"::"a"(__foo):"bx");//这个内联汇编语句中,%ebx出现在指令列表中,并且被指令修改了,但是却未被任何Input/Output操作表达式是所指定,所以,你需要在Clobber/Modify部分指定"bx",以让GCC知道这⼀点;因为你在Input/Output操作表达式中指定的寄存器,或当你为⼀些Input/Output操作表达式使⽤"r"/"g"约束,让GCC为你选择⼀个寄存器时,GCC对这些寄存器的状态是⾮常清楚的,它知道这些寄存器是被修改的,你根本不需要在Clobber/Modify部分声明它们;但除此之外,GCC对剩下的寄存器中哪些会被当前内联汇编语句所修改则⼀⽆所知;所以,如果你真的在当前内联汇编指令中修改了它们,那么就最好在 Clobber/Modify部分声明它们,让GCC针对这些寄存器做相应的处理;否则,有可能会造成寄存器不⼀致,从⽽造成程序执⾏错误;在Clobber/Modify部分声明这些寄存器的⽅法很简单,只需要将寄存器的名字⽤双引号括起来就可以;如果要声明多个寄存器,则相邻两个寄存器名字之间⽤逗号隔开;例如:__asm__("movl %0,%%ebx; popl %%ecx"::"a"(__foo):"bx","cx");这个语句中,声明了bx和cx,告诉GCC:寄存器%ebx和%ecx可能会被修改,要求GCC考虑这个因素;寄存器名称串:"al"/"ax"/"eax":代表寄存器%eax"bl"/"bx"/"ebx":代表寄存器%ebx"cl"/"cx"/"ecx":代表寄存器%ecx"dl"/"dx"/"edx":代表寄存器%edx"si"/"esi":代表寄存器%esi"di"/"edi":代表寄存器%edi所以,只需要使⽤"ax","bx","cx","dx","si","di"就可以了,因为他们都代表对应的寄存器;如果你在⼀个内敛汇编语句的Clobber/Modify部分向GCC声明了某个寄存器内存发⽣了改变,GCC在编译时,如果发现这个被声明的寄存器的内容在此内联汇编之后还要继续使⽤,那么,GCC会⾸先将此寄存器的内容保存起来,然后在此内联汇编语句的相关代码⽣成之后,再将其内容回复;另外需要注意的是,如果你在Clobber/Modify部分声明了⼀个寄存器,那么这个寄存器将不能再被⽤作当前内敛汇编语句的Input/Output操作表达式的寄存器约束,如果Input/Output操作表达式的寄存器约束被指定为"r"/"g",GCC也不会选择已经被声明在Clobber /Modify部分中的寄存器;例如:__asm__("movl %0,%%ebx"::"a"(__foo):"ax","bx");这条语句中的Input操作表达式"a"(__foo)中已经指定了寄存器%eax,那么在Clobber/Modify部分中个列出的"ax"就是⾮法的;编译时,GCC会报错;2.内存修改通知:除了寄存器的内容会被修改之外,内存的内容也会被修改;如果⼀个内联汇编语句的指令列表中的指令对内存进⾏了修改,或者在此内联汇编出现的地⽅,内存内容可能发⽣改变,⽽被改变的内存地址你没有在其Output操作表达式中使⽤"m"约束,这种情况下,你需要使⽤在Clobber/Modify部分使⽤字符串"memory"向GCC声明:"在这⾥,内存发⽣了,或可能发⽣了改变";例如:void* memset(void* s, char c, size_t count){__asm__("cld\n\d""rep\n\t""stosb":/*no output*/:"a"(c),"D"(s),"c"(count):"cx","di","memory");return s;}如果⼀个内联汇编语句的Clobber/Modify部分存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装⼊了寄存器,那么,在这个内联汇编之后,如果需要使⽤这个内存处的内容,就会直接到这个内存处重新读取,⽽不是使⽤被存放在寄存器中的拷贝;因为这个时候寄存器中的拷贝很可能已经和内存处的内容不⼀致了;3.标志寄存器修改通知:当⼀个内联汇编中包含影响标志寄存器eflags的条件,那么也需要在Clobber/Modify部分中使⽤"cc"来向GCC声明这⼀点;。
GCC内联汇编基础
GCC 内联汇编基础GCC 内联汇编在的实验中,有⼏处⽤到了很底层的函数,都以内联汇编的形式存在,例如因此这篇博客对于内联汇编的基本⽤法做⼀个总结。
⾸先是⼀般的形式,只能使⽤全局变量来传递数据,例如如下程序(插⼊在):运⾏结果如下这⾥就跟普通的汇编⼀样的使⽤⽅式,注意关键字是为了防⽌被优化,还有每⾏汇编语句后⾯的。
⼜或者如这段程序如果只能使⽤全局变量,必然会有很多不⽅便。
为了能使⽤局部变量,需要使⽤扩展的内联汇编。
扩展的内联汇编形式如下其中output location :输出的放哪⼉input operands :哪些输⼊changed registers :改变了哪些寄存器并且输⼊和输出部分都是如下形式MIT6.828static inline uint32_tread_esp(void){uint32_t esp;asm volatile("movl %%esp,%0" : "=r" (esp));return esp;}static inline uint32_tread_ebp(void){uint32_t ebp;asm volatile("movl %%ebp,%0" : "=r" (ebp));return ebp;}kern/monitor.c uint32_t test_val=0;int mon_backtrace(int argc, char **argv, struct Trapframe *tf){ uint32_t *ebp=(uint32_t*)read_ebp();cprintf("the ebp provided is :%8x and eip is %8x\n",ebp,ebp[1]);// you could use simple asm if you use global variablesasm volatile("push %eax \n" "mov %ebp,%eax \n" "mov %eax,test_val \n""popl %eax \n");cprintf("my ebp is %8x\n",(uint32_t*)test_val);return 0;}volatile \n int32_t eip=0;int main(){//basic inline assembly//global variable can be useasm volatile("push %eax \n""call .testpop \n"".testpop: \n""pop %ebx \n""movl %ebx,%eax \n""movl %eax,eip \n""pop %eax \n");printf("the eip is :%x\n",eip);asm volatile("push %eax \n""movl %ebp,%eax \n""movl %eax,eip \n""pop %eax \n");printf("the ebp is :%x\n",eip);return 0;}asm("assembly code":output location:input orperands:changed registers);"constraint"(variable)约束符主要是限制寄存器的使⽤同时可以加上修饰符来看⼀段程序这⾥意思是输出使⽤寄存器,并且把结果放到变量中。
gcc内联汇编语法解释
gcc内联汇编语法解释
GCC 的内联汇编语法是一种在 C 或 C++ 代码中嵌入汇编代码
的方法。
它允许开发人员直接在高级语言代码中插入汇编指令,以
实现对底层硬件的精细控制和优化。
内联汇编通常用于需要对性能
进行微调或需要直接访问底层硬件的场景。
内联汇编的语法由两部分组成,汇编模板和操作数列表。
汇编
模板是包含实际汇编指令的字符串,而操作数列表则包含了这些指
令所需的输入和输出操作数。
操作数列表中的操作数可以是 C 或
C++ 变量、寄存器、内存地址等。
GCC 的内联汇编语法使用 `asm` 关键字来标识内联汇编代码块。
在 `asm` 关键字后面,紧跟着汇编模板和操作数列表。
此外,GCC
还提供了一些扩展的语法来支持更复杂的操作,比如约束(constraint)、内联汇编函数等。
在使用内联汇编时,开发人员需要特别注意代码的可移植性和
安全性,因为汇编代码的可移植性较差,而且对硬件的直接操作可
能引入安全漏洞。
因此,在使用内联汇编时,应该仔细考虑其必要性,并确保代码的可读性和可维护性。
总之,GCC 的内联汇编语法提供了一种强大的工具,可以在 C 或 C++ 代码中直接插入汇编指令,以实现对底层硬件的精细控制和优化。
开发人员应该谨慎使用内联汇编,并充分了解其语法和使用限制。
[计算机软件及应用]GCC内嵌汇编介绍2
◦ 置字符串:
◦ 传送字符串:
字符串操作
◦ 输入字符串:
通用约束
◦ g:表示可以用通用寄存器,内存,立即数等任何一种 ◦ 0,1,2,3…:表示和第n个操作数使用相同的寄存器/内存 ◦ 一个带有C/C++表达式的内联汇编,其操作表达式被按 照列出的顺序编号,最多允许有10个操作表达式 ◦ 如果某个input操作表达式使用0-9中的数字作为它的约束, 则等于向GCC声明“我要使用和编号为1的output操作表 达式形同的寄存器或者内存地址”
◦ 带进位的循环移位
rcl:将CF加入移位的中间,移出的循环补上 rcr:将CF加入移位的中间,移出的循环补上
位操作:
◦ 位扫描
bsf:正向扫描,返回第一个1的位置 bsr:逆向扫描,返回第一个1的位置
◦ 位检测指令(对指定位的操作)
bt:传给CF btc:传给CF,取反 btr:传给CF,清零 bts:传给CF,置1
◦ 减法
算术运算
◦ 乘法
◦ 除法
mul:无符号乘法 imul:有符号乘法
◦ 类型转换
div:无符号除法 idiv:有符号除法
cbw:字节转字 cwd:字转双字 cwde:字转扩展双字 cdq:字转四字
逻辑运算:
◦ ◦ ◦ ◦ and:并 or:或 not:非 xor:异或
◦ 输出字符串:
gcc 编译 汇编
gcc 编译汇编gcc编译汇编:一门强大的编程工具随着计算机技术的不断发展,编程语言也愈发多样化。
在众多编程语言中,汇编语言是一门非常底层、高效且强大的语言,它直接操作计算机硬件,可以实现更精细的控制和优化。
而gcc编译器则是一款广泛使用的编译器,能够将汇编语言翻译成机器码,让计算机能够执行汇编代码。
本文将介绍如何使用gcc编译器来编译汇编代码,并讨论一些与gcc编译汇编相关的重要概念和技巧。
一、汇编语言的基本概念汇编语言是一种低级语言,它使用助记符来表示计算机指令和操作数。
与高级语言相比,汇编语言更接近计算机硬件,对程序员的要求更高。
在编写汇编代码时,需要了解计算机的指令集架构和寄存器等基本概念。
1.1 指令集架构指令集架构是计算机硬件的基本组成部分,它规定了计算机可以执行的指令种类和操作方式。
常见的指令集架构有x86、ARM等。
在编写汇编代码时,需要根据目标机器的指令集架构选择相应的指令。
1.2 寄存器寄存器是计算机中用于存储和处理数据的一种高速存储器。
在汇编语言中,寄存器是非常重要的概念,它们可以用于存储临时数据、地址等。
不同的指令集架构提供的寄存器数量和功能也会有所不同。
二、gcc编译器的基本用法gcc是一款强大的编译器,支持多种编程语言,包括C、C++、汇编等。
在编译汇编代码时,可以使用gcc来将汇编代码翻译成可执行文件。
2.1 编写汇编代码需要编写汇编代码文件,以.asm或.s为后缀。
在汇编代码中,可以使用汇编指令和伪指令来编写程序。
汇编指令用于执行具体的操作,而伪指令则用于辅助汇编过程。
2.2 使用gcc编译汇编代码编写完汇编代码后,可以使用gcc编译器来将其翻译成可执行文件。
打开终端或命令提示符窗口,进入汇编代码所在的目录,然后执行以下命令:gcc -o output input.asm其中,output是生成的可执行文件的名称,input.asm是汇编代码文件的名称。
执行完上述命令后,gcc会将汇编代码翻译成机器码,并生成可执行文件。
gcc中的内嵌汇编语言
gcc中的内嵌汇编语⾔gcc采⽤的是AT&T的汇编格式,MS采⽤Intel的格式.⼀ 基本语法语法上主要有以下⼏个不同.★寄存器命名原则AT&T: %eax Intel: eax★源/⽬的操作数顺序AT&T: movl %eax,%ebx Intel: mov ebx,eax★常数/⽴即数的格式AT&T: movl $_value,%ebx Intel: mov eax,_value把_value的地址放⼊eax寄存器AT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d★操作数长度标识AT&T: movw %ax,%bx Intel: mov bx,ax★寻址⽅式AT&T: immed32(basepointer,indexpointer,indexscale)Intel: [basepointer + indexpointer*indexscale + imm32)linux⼯作于保护模式下,⽤的是32位线性地址,所以在计算地址时不⽤考虑segment:offset的问题.上式中的地址应为:imm32 + basepointer + indexpointer*indexscale下⾯是⼀些例⼦:★直接寻址AT&T: _booga ; _booga是⼀个全局的C变量注意加上$是表⽰地址引⽤,不加是表⽰值引⽤.注:对于局部变量,可以通过堆栈指针引⽤.Intel: [_booga]★寄存器间接寻址AT&T: (%eax)Intel: [eax]★变址寻址AT&T: _variable(%eax)Intel: [eax + _variable]⼆ 基本的⾏内汇编基本的⾏内汇编很简单,⼀般是按照下⾯的格式asm("statements");例如:asm("nop"); asm("cli");asm 和 __asm__是完全⼀样的.如果有多⾏汇编,则每⼀⾏都要加上 "nt"例如:asm( "pushl %eaxnt""movl $0,%eaxnt""popl %eax");实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编⽂件中,所以格式控制字符是必要的.再例如:asm("movl %eax,%ebx");asm("xorl %ebx,%edx");asm("movl $0,_booga);在上⾯的例⼦中,由于我们在⾏内汇编中改变了edx和ebx的值,但是由于gcc的特殊的处理⽅法,即先形成汇编⽂件,再交给GAS去汇编,所以GAS并不知道我们已经改变了edx和ebx的值,如果程序的上下⽂需要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也存在⼀样的问题.为了解决这个问题,就要⽤到扩展的⾏内汇编语法.三 扩展的⾏内汇编扩展的⾏内汇编类似于Watcom.基本的格式是:asm ( "statements" : output_regs : input_regs : clobbered_regs);clobbered_regs指的是被改变的寄存器.下⾯是⼀个例⼦(为⽅便起见,我使⽤全局变量):int count=1;int value=1;int buf[10];void main(){asm("cld nt""rep nt""stosl":: "c" (count), "a" (value) , "D" (buf[0]): "%ecx","%edi" );}得到的主要汇编代码为:movl count,%ecxmovl value,%eaxmovl buf,%edi#APPcldrepstosl#NO_APPcld,rep,stos就不⽤多解释了.这⼏条语句的功能是向buf中写上count个value值.冒号后的语句指明输⼊,输出和被改变的寄存器.通过冒号以后的语句,编译器就知道你的指令需要和改变哪些寄存器,从⽽可以优化寄存器的分配.其中符号"c"(count)指⽰要把count的值放⼊ecx寄存器类似的还有:a eaxb ebxc ecxd edxS esiD ediI 常数值,(0 - 31)q,r 动态分配的寄存器g eax,ebx,ecx,edx或内存变量A 把eax和edx合成⼀个64位的寄存器(use long longs)我们也可以让gcc⾃⼰选择合适的寄存器.如下⾯的例⼦:asm("leal (%1,%1,4),%0": "=r" (x): "0" (x) );这段代码实现5*x的快速乘法.得到的主要汇编代码为:movl x,%eax#APPleal (%eax,%eax,4),%eax#NO_APPmovl %eax,x⼏点说明:1.使⽤q指⽰编译器从eax,ebx,ecx,edx分配寄存器.使⽤r指⽰编译器从eax,ebx,ecx,edx,esi,edi分配寄存器.2.我们不必把编译器分配的寄存器放⼊改变的寄存器列表,因为寄存器已经记住了它们.3."="是标⽰输出寄存器,必须这样⽤.4.数字%n的⽤法:数字表⽰的寄存器是按照出现和从左到右的顺序映射到⽤"r"或"q"请求的寄存器.如果我们要重⽤"r"或"q"请求的寄存器的话,就可以使⽤它们.5.如果强制使⽤固定的寄存器的话,如不⽤%1,⽽⽤ebx,则asm("leal (%%ebx,%%ebx,4),%0": "=r" (x): "0" (x) );注意要使⽤两个%,因为⼀个%的语法已经被%n⽤掉了。
GCC-Inline-ASM-GCC内联汇编
GCC Inline ASM GCC内联汇编GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。
这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写C/C++代码中使用汇编编写简洁高效的代码。
1。
GCC中基本的内联汇编非常易懂,我们先来看两个简单的例子:__asm__("movl %esp,%eax”); // 看起来很熟悉吧!或者是__asm__("movl $1,%eax // SYS_exitxor %ebx,%ebxint $0x80");或__asm__("movl $1,%eax\r\t” \"xor %ebx,%ebx\r\t” \”int $0x80" \);基本内联汇编的格式是__asm__ __volatile__("Instruction List");1、__asm____asm__是GCC关键字asm的宏定义:#define __asm__ asm__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的.2、Instruction ListInstruction List是汇编指令序列。
它可以是空的,比如:__asm__ __volatile__(”");或__asm__ (”");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。
但并非所有Instruction List为空的内联汇编表达式都是没有意义的,比如:__asm__ (””:::”memory");就非常有意义,它向GCC声明:&#8220;我对内存作了改动&#8221;,GCC在编译的时候,会将此因素考虑进去。
我们看一看下面这个例子:$ cat example1。
gcc汇编指令
gcc汇编指令GCC是一个广泛使用的C语言编译器,除了支持C语言,它还支持C++、Objective-C、Fortran语言等。
在GCC编译器中,C/C++的源代码首先要被转化成汇编语言,然后再被转化成机器语言。
因此,学习GCC汇编指令对于了解底层的编译和执行过程有很大的帮助。
本文将围绕GCC汇编指令进行阐述。
第一步:如何产生GCC汇编代码在GCC编译器中,将源文件转换成汇编文件可以采用以下命令行:$ gcc -S filename.c其中,filename.c是指C语言源代码文件。
这个命令行产生的是一个叫做filename.s的汇编文件,它包含了源代码的汇编语言版。
第二步:GCC汇编指令的标准语法GCC汇编指令的语法有以下几个部分:1. 操作码(opcode):指令的名称,例如mov、add等。
2. 操作数(operand):指令要操作的数据,可以是寄存器、内存地址或立即数(immediate value)等。
3. 注释(comment):对指令进行说明的文字,可以在指令后面添加注释。
GCC汇编指令的语法如下所示:opcode [操作数1 [, 操作数2] …] [注释]例如,下面是使用mov指令在寄存器中存储值的代码:mov %eax, 2 # 将2存储在eax寄存器中第三步:GCC汇编指令的语法细节GCC汇编语言的语法有很多细节,下面列出几个常见的:1. 操作数前要加上%符号,表示要使用的是寄存器。
2. 内存地址要用方括号[]括起来,例如:movl 4(%esp), %eax # 将esp+4处的内存值存储到eax寄存器中3. 立即数需要前缀$,例如:movl $0x16, %eax # 将立即数0x16存储到eax寄存器中4. 指令后面可以添加注释。
5. 操作数的数据类型可以使用后缀,例如:movb $0, %al # 将立即数0存储到al寄存器中,b表示一个字节movw $0, %ax # 将立即数0存储到ax寄存器中,w表示两个字节movl $0, %eax # 将立即数0存储到eax寄存器中,l表示四个字节第四步:GCC汇编指令实践下面是一个简单的例子,展示了如何在GCC中使用汇编指令。
gcc 内联汇编写法
gcc 内联汇编写法
GCC支持使用内联汇编,也就是直接在C代码中嵌入汇编代码。
下面是内
联汇编的基本语法:
```c
asm (汇编指令 : 输出操作数 : 输入操作数 : 内存操作数);
```
其中,`汇编指令`是要执行的汇编指令,`输出操作数`是该指令的输出操作数,`输入操作数`是该指令的输入操作数,`内存操作数`是该指令可能涉及的内存操作数。
例如,下面是一个使用内联汇编计算平方根的例子:
```c
int sqrt(int x) {
int result;
asm (" %1, %0" : "=r"(result) : "r"(x));
return result;
}
```
在这个例子中,``是平方根指令,`%1`表示输入操作数,`%0`表示输出操作数。
输出操作数和输入操作数都用占位符表示,并使用`:`分隔符将它们分隔开。
`:`后面是输出操作数的约束,它指定了输出操作数的寄存器或内存地址。
在这个例子中,输出操作数约束为`"=r"`,表示使用一个通用寄存器。
输入
操作数的约束为`"r"`,表示使用一个通用寄存器。
注意,内联汇编语法可能会因编译器版本和目标架构而有所不同。
因此,在使用内联汇编时,请务必参考GCC文档和相关资料。
GCC内嵌汇编
分类 限定符 描述
通用寄存器 "a" 将输入变量放入eax
(注:这里有一个问题:假设eax已经被使用,那怎么办?其实很简单:因为GCC 知道eax 已经被使用,它在这段汇编代码的起始处插入一条语句pushl %eax,将eax 内容保存到堆栈,然后在这段代码结束处再增加一条语句popl %eax,恢复eax的内容)
void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
5.2、C语言关键字volatile
C语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取不能缓存到寄存器,每次使用时需要重新存取。该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程,例如:
可以使用1对或多对引号,每1对引号里可以放任一多条指令,所有的指令都要被放到引号中。
在基本内联汇编中,汇编语句模板的书写的格式和你直接在汇编文件中写非内联汇编没有什么不同,你可以在其中定义Label,定义对齐(.align n ),定义段(.section name )。例如:
__asm__(".align器优化介绍
内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。
ARMGCC内嵌汇编手册.
转自:/u2/69404/showart_1922655.htmlARM GCC 内嵌(inline)汇编手册关于这篇文档这篇文章是本人为方便各位业界同仁而翻译,方便大家开发底层代码使用,转载请注明出处,谢谢。
要是你E文功底好,本人还是建议阅读E文版的。
http://www.ethernut.de/en/documents/arm-inline-asm.html对于基于ARM的RISC处理器,GNU C编译器提供了在C代码中内嵌汇编的功能。
这种非常酷的特性提供了C代码没有的功能,比如手动优化软件关键部分的代码、使用相关的处理器指令。
这里设想了读者是熟练编写ARM汇编程序读者,因为该片文档不是ARM汇编手册。
同样也不是C语言手册。
这篇文档假设使用的是GCC 4 的版本,但是对于早期的版本也有效。
GCC asm 声明让我们以一个简单的例子开始。
就像C中的声明一样,下面的声明代码可能出现在你的代码中。
/* NOP 例子 */asm("mov r0,r0");该语句的作用是将r0移动到r0中。
换句话讲他并不干任何事。
典型的就是NOP 指令,作用就是短时的延时。
请接着阅读和学习这篇文档,因为该声明并不像你想象的和其他的C语句一样。
内嵌汇编使用汇编指令就像在纯汇编程序中使用的方法一样。
可以在一个asm声明中写多个汇编指令。
但是为了增加程序的可读性,最好将每一个汇编指令单独放一行。
asm("mov r0, r0\n\t""mov r0, r0\n\t""mov r0, r0\n\t""mov r0, r0");换行符和制表符的使用可以使得指令列表看起来变得美观。
你第一次看起来可能有点怪异,但是当C编译器编译C语句的是候,它就是按照上面(换行和制表)生成汇编的。
到目前为止,汇编指令和你写的纯汇编程序中的代码没什么区别。
GCC内嵌汇编
内核代码绝大部分使用C语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码。
GCC提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计。
一、基本内嵌汇编GCC提供了很好的内嵌汇编支持,最基本的格式是:__asm__ __volatile__(汇编语句模板);1、__asm____asm__是GCC关键字asm的宏定义:#define __asm__ asm__asm__或asm用来声明一个内嵌汇编表达式,所以任何一个内嵌汇编表达式都是以它开头的,是必不可少的。
2、汇编语句模板“汇编语句模板”是一组插入到C程序中的汇编指令(可以是单个指令,也可以是一组指令)。
每条指令都应该由双引号括起,或者整组指令应该由双引号括起。
每条指令还应该用一个定界符结尾。
有效的定界符为换行符(\n)和分号(;)。
\n后可以跟一个制表符(\t)作为格式化符号,增加GCC在汇编文件中生成的指令的可读性。
上述原则可以归结为:①任意两个指令间要么被分号(;)分开,要么被放在两行;②放在两行的方法既可以通过\n的方法来实现,也可以真正的放在两行;③可以使用一对或多对双引号,每对双引号里可以放任意多条指令,所有的指令都必须放到双引号中。
在基本内嵌汇编中,“汇编语句模板”的书写的格式和你直接在汇编文件中使用汇编语言编程没有什么不同,你可以在其中定义标号(Label),定义对齐(.align n),定义段(.section name)。
例如:__asm__(".align 2\n\t""movl %eax, %ebx\n\t""test %ebx, %ecx\n\t""jne error\n\t""sti\n\t""error: popl %edi\n\t""subl %ecx, %ebx");建议大家都使用这种格式来写内嵌汇编代码。
GCC内嵌汇编
GCC内嵌汇编AT&T手册里面的,我整理了下,方便阅读内核代码绝大部分使用C 语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码。
GCC提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计。
简单的内嵌汇编很容易理解例如:“__asm__”表示后面的代码为内嵌汇编,“asm”是“__asm__”的别名。
“__volatile__”表示编译器不要优化代码,后面的指令保留原样,“volatile”是它的别名。
括号里面是汇编指令。
使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。
例如在下面的汇编语句:[下段解释一定要耐着性子看懂读懂!]“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇编靠它们将C语言表达式与指令操作数相对应。
指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:“result”和“input”,他们按照出现的顺序分别与指令操作数“%0”,“%1,”对应;注意对应顺序:第一个C表达式对应“%0”;第二个表达式对应“%1”,依次类推,操作数至多有10个,分别用“%0”,“%1”….“%9,”表示。
在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。
“result”前面的限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令。
“input”前面的“r”表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
GCC内联汇编入门分类:linux编程2008-12-21 15:48 507人阅读评论(0) 收藏举报目录(?)[-]1. 前言1. 版权与许可证2. 回馈与更正3. 感谢2. 简介3. GCC汇编语法4. 基本内联汇编5. 扩展内联汇编1. 汇编程序模板2. 操作数3. Clobber列表4. Volatile6. 更多关于约束条件1. 常用的约束2. 约束修饰符7. 一些有用的诀窍8. 结束语9. 参考原文为GCC-Inline-Assembly-HOWTO,在google上可以找到原文,欢迎指出翻译错误。
中文版说明由于译者水平有限,故译文出错之处,还请见谅。
C语言的关键字不译,一些单词或词组(如colbber等)由于恐怕译后词不达意,故并不翻译,由下面的单词表代为解释,敬请见谅。
英文原文中的单词和词组:operand:操作数,可以是寄存器,内存,立即数。
volatile:易挥发的,是C语言的关键字。
constraint:约束。
register:本文指CPU寄存器。
asm:“asm”和“__asm__”在C语言中是关键字。
原文中经常出现这个单词,是指嵌入到C语言(或者其它语言)的汇编程序片断。
basic inline assembly:指C语言中内联汇编程序的一种形式,和extended asm对应。
基本格式如下:asm("assembly code");extended assembly:和basic inline assembly对应,比它多了一些特性,如可以指明输入,输出等。
基本格式如下:asm ( assembler template: output operands: input operands: list of clobbered registers);clobber list:实际上就是被使用的寄存器的列表,用来告诉GCC它们已经被asm 使用了,不要在asm程序外使用它们。
不然可能带来不可预见的后果。
clobbered registers:它和clobber list对应。
assembler template:就是汇编模板,所有内联汇编代码都有按一定的格式。
见extended assembly的说明作者:Sandeep.S译者:吴遥版本号 v0.1 2003年3月01日翻译版更新日期 2008/06/11这篇HOWTO解释GCC提供的内联汇编特性的用途和用法。
学习这篇文章只须具备两个前提条件,显然那就是对x86汇编语言和C语言有基本的了解。
目录1.前言1.1版权与许可证1.2回馈与更正1.3感谢2.简介3.GCC汇编语法4.基本内联汇编5.扩展内联汇编5.1汇编程序模板5.2操作数5.3 Clobber列表5.4 Volatile … ?6.更多关于约束条件6.1 常用的约束6.2 约束修饰符7. 一些有用的诀窍8. 结束语9. 参考1.前言1.1版权与许可证版权所有 (c)2003 Sandeep S.这篇文档是免费的,你可以在依据自由软件组织GNU通用公共许可证条款下重新发布或者修改它。
无论是版本2的许可证还是后来的版本(由你自己选择)。
这份文档的发布是希望它有用,但是并没有任何保证。
1.2回馈与更正欢迎善意的回馈和批评,我感谢每一个指出本文错误的人并尽快地更正错误。
1.3感谢我向GNU开发者提供这个功能强大的特性表达最诚挚的感谢。
感谢Mr.Pramode C E的帮助。
感谢政府工程学院的朋友尤其是Nisha Kurur和Sakeeb S精神上的支持。
感谢政府工程学院老师对我的帮助。
另外,还要感谢 Phillip、Brennan、Underwood 和 colin@ ,他们解决了很多难题。
2.简介现在我们开始学GCC内联汇编。
内联意味着什么?我们可以指示编译器插入一个函数的代码到调用者的代码中,也就是实际上调用产生的地方。
这样的函数就是内联函数。
看上去很像宏?实际上它们很相似。
内联函数有什么好处呢?内联的方法减少了函数调用的额外开销。
而且如果有实际的参数值是常数,那么在编译的时候编译器知道可能充许参数值的单一化,所以并不是所有的内联函数的代码都要包含进来。
对可执行代码大小的影响是不可预测的,它视乎对特定的情况。
声明一个内联函数,我们声明中使用关键字inline。
现在我们站在一个位置来猜什么是内联汇编。
它只是一些写在函数内的汇编语言的程序。
在系统编程时候它们会显得很便利,快速,非常有用。
我们的主要目标是学习GCC内联汇编函数的基本格式和用法。
内联汇编之所以如此重要主要是因为它操作的能力和让它的输出在C语言变量中可见。
(这个句话译得不太好)因为这样的能力,“asm”(译者:asm 指内联函数)就像一个汇编指令和包含它的C语言程序之间的接口。
3.GCC汇编语法GCC,即Linux平台下的GNU C语言编译器,它使用AT&T&sol(译者:应该是指AT&T语法,但是sol就不知道是什么);UNIX汇编语法。
现在让我们使用AT&T语法来进行汇编编码。
如果你对AT&T语法不熟悉也不用担心,我将会教你。
这种语法和Intel语法有很大的不同。
以下我将给出主要的不同。
1、来源地-目的地定序AT&T语法和Intel语法在操作数的方向上是相反的。
Intel语法的第一个操作数是目的地,第二个是来源地。
然而AT&T语法的第一个操作数是来源地,第二的是目的地。
也即:“Op-code dst src”在Intel语法中变为“Op-code src dst”AT&T语法。
2、寄存器命名寄存器名字要有前缀“%”。
也即如果寄存器eax被使用,应写作%eax。
3、立即操作数AT&T立即操作数之前要有一个“$”符号。
对于静态C语言变量也要有前缀“$”。
在Intel语法里,十六进制常数要有“h”作为后缀。
在AT&T语法里我们用“0x”作为代替。
所以,对于十六进制的数,我们看到一个“$”,然后一个“0x”,最后才是常数本身。
4、操作数大小4.基本内联汇编基本内联汇编的格式是非常简单的,如下:asm("assembly code");例子如下:asm("movl %ecx %eax");/*将ecx的值传给eax了/__asm__("movb %bh (%eax)");/*将bh的值传到eax指向的内存处*/你可能已经注意到在这里我使用asm和__asm__两个关键字。
它们都是正确的。
如果asm关键字与程序里的某些程序发生冲突,那么你可以使用__asm__代替。
如果我们有不止一条指令,那么我们每行在双引号里写一条指令,并且在指令最后加上一个'/n'和'/t'。
这是因为gcc以字符串的形式发送每条指给as(GAS),并且通过使用newline&tab的方法发送正确的行格式给汇编器。
例子:__asm__ ("movl %eax, %ebx/n/t""movl $56, %esi/n/t""movl %ecx, $label(%edx,%ebx,$4)/n/t""movb %ah, (%ebx)");如果在我们的代码中我们改变了一些寄存器的值并且没有记下这些改变便返回,可能会导致错误的发生。
这是因为GCC并不知道寄存器的值改变了,这会给我们带来麻烦,尤其是编译器对程序进行一些优化处理时。
假设有这样的情况发生:某些寄存器保存着某些变量的值,而我们没有有告诉GCC便改变了它,程序会如常地运行。
这就是我们所的扩展功能的原因。
扩展asm提供我们这种的功能。
5.扩展内联汇编在基本汇编内联里,我们只使用了指令。
而在扩展汇编内联里,我们能够指定操作数。
它允许我们指定输入寄存器,输出寄存器和一列clobbered registers (译者注:实际就是指一些被内联汇编使用的寄存器,不知道如何翻译,所以下文也是以英文写出)。
没有强性规定一定要指定使用寄存器,我们可以把头痛的事情留给GCC,并且这样可能更有利于GCC对程序优化。
基本的格式如下:asm ( assembler template: output operands /* optional */: input operands /* optional */: list of clobbered registers /* optional */);汇编程序模板(The assembler template)由汇编指令组成。
每一个操作数由在括号内的C语言表达式后的操作数约束字符串描述。
第一个冒号把汇编程序模板和第一个输出操作数分开,第二个冒号则把最后一个输出操作数和第一个输入操作数分开,假设有这样的操作数。
逗号则在每组中分开操作数。
操作数的量最多为10,或者机器描述里最大操作数的的指令模式,这取决于那一个比较大。
如果没有输出操作数却有输入操作数,就要写上两个连续冒号,以说明没有输出操作数。
例子:asm ("cld/n/t""rep/n/t""stosl": /* no output registers */: "c" (count), "a" (fill_value), "D" (dest): "%ecx", "%edi");现在,看看这代码都做了什么。
上面的内联代码把fill_value的值写到edi指向的内存地址count次。
并且告诉GCC寄存器eax和edi不可以再用了(也则是说被使用了)。
让我们看多一个例子来更好地理解。
int a=10, b;asm ("movl %1, %%eax;movl %%eax, %0;":"=r"(b) /* output */:"r"(a) /* input */:"%eax" /* clobbered register */);这里我们使用汇编指令让“b”的值等于“a”的值。
下面是一些要点:“b”是一个输出操作数,通过%0联系起来;而“a”是一个输入操作数,通过%1联系起来。
“r”是对操作数的一个约束。
在后面我们将会谈到关于约束的细节。
“r”告诉GCC使用任何一个寄存器来存储操作数的值。
输出操作数的约束必须包含一个约束修饰符“=”。
这个修饰符说明输出操作数是只写的。
这里有两个“%”在寄存器之前。
这样可以帮助GCC辨别操作数和寄存器,操作数只有一个“%”作为前缀。