GCC中的内嵌汇编语言

发布者:shmilyde最新更新时间:2023-06-27 来源: elecfans关键字:GCC  内嵌  汇编语言 手机看文章 扫描二维码
随时随地手机看文章

一.声明


  虽然Linux的核心代码大部分是用C语言编写的,但是不可避免的其中还是有一部分是用汇编语言写成的。有些汇编语言代码是直接写在汇编源程序中的,特别是Linux的启动代码部分;还有一些则是利用gcc的内嵌汇编语言嵌在C语言程序中的。这篇文章简单介绍了gcc中的内嵌式汇编语言,主要想帮助那些才开始阅读Linux核心代码的朋友们能够更快的入手。
写这篇文章的主要信息来源是GNU的两个info文件:as.info和gcc.info,如果你觉得这篇文章中的介绍还不够详细的话,你可以查阅这两个文件。当然,直接查阅这两个文件可以获得更加权威的信息。如果你不想被这两篇文档中的一大堆信息搞迷糊的话,我建议你先阅读一下这篇文章,然后在必要时再去查阅更权威的信息。

二.简介


  在Linux的核心代码中,还是存在相当一部分的汇编语言代码。如果你想顺利阅读Linux代码的话,你不可能绕过这一部分代码。在Linux使用的汇编语言代码中,主要有两种格式:一种是直接写成汇编语言源程序的形式,这一部分主要是一些Linux的启动代码;另一部分则是利用gcc的内嵌式汇编语言语句asm嵌在Linux的C语言代码中的。这篇文章主要是介绍第二种形式的汇编语言代码。
  首先,我介绍一下as支持的汇编语言的语法格式。大家知道,我们现在学习的汇编语言的格式主要是Intel风格的,而在Linux的核心代码中使用的则是AT&T格式的汇编语言代码,应该说大部分人对这种格式的汇编语言还不是很了解,所以我觉得有必要介绍一下。
  接着,我主要介绍一下gcc的内嵌式汇编语言的格式。gcc的内嵌式汇编语言提供了一种在C语言源程序中直接嵌入汇编指令的很好的办法,既能够直接控制所形成的指令序列,又有着与C语言的良好接口,所以在Linux代码中很多地方都使用了这一语句。

三.gcc的内嵌汇编语言语句asm 


  利用gcc的asm语句,你可以在C语言代码中直接嵌入汇编语言指令,同时还可以使用C语言的表达式指定汇编指令所用到的操作数。这一特性提供了很大的方便。
  要使用这一特性,首先要写一个汇编指令的模板(这种模板有点类似于机器描述文件中的指令模板),然后要为每一个操作数指定一个限定字符串。例如:
  extern __inline__ void change_bit(int nr,volatile void *addr) 
  {
  __asm__ __volatile__( LOCK_PREFIX
"btcl %1,%0"
  :"=m" (ADDR)
  :"ir" (nr));
  }
  上面的函数中:
  LOCK_PREFIX:这是一个宏,如果定义了__SMP__,扩展为"lock;",用于指定总线锁定前缀,否则扩展为""。
  ADDR:这也是一个宏,定义为(*(volatile struct __dummy *) addr)
"btcl %1,%0":这就是嵌入的汇编语言指令,btcl为指令操作码,%1,%0是这条指令两个操作数的占位符。后面的两个限定字符串就用于描述这两个操作数。
  : "=m" (ADDR):第一个冒号后的限定字符串用于描述指令中的“输出”操作数。刮号中的ADDR将操作数与C语言的变量联系起来。这个限定字符串表示指令中的“%0”就是addr指针指向的内存操作数。这是一个“输出”类型的内存操作数。
  : "ir" (nr):第二个冒号后的限定字符串用于描述指令中的“输入”操作数。这条限定字符串表示指令中的“%1”就是变量nr,这个的操作数可以是一个立即操作数或者是一个寄存器操作数。
  *注:限定字符串与操作数占位符之间的对应关系是这样的:在所有限定字符串中(包括第一个冒号后的以及第二个冒号后的所有限定字符串),最先出现的字符串用于描述操作数“%0”,第二个出现的字符串描述操作数“%1”,以此类推。
①汇编指令模板
  asm语句中的汇编指令模板主要由汇编指令序列和限定字符串组成。在一个asm语句中可以包括多条汇编指令。汇编指令序列中使用操作数占位符引用C语言中的变量。一条asm语句中最多可以包含十个操作数占位符:%0,%1,...,%9。汇编指令序列后面是操作数限定字符串,对指令序列中的占位符进行限定。限定的内容包括:该占位符与哪个C语言变量对应,可以是什么类型的操作数等等。限定字符串可以分为三个部分:输出操作数限定字符串(指令序列后第一个冒号后的限定字符串),输入操作数限定字符串(第一个冒号与第二个冒号之间),还有第三种类型的限定字符串在第二个冒号之后。同一种类型的限定字符串之间用逗号间隔。asm语句中出现的第一个限定字符串用于描述占位符“%0”,第二个用于描述占位符“%1”,以此类推(不管该限定字符串的类型)。如果指令序列中没有任何输出操作数,那么在语句中出现的第一个限定字符串(该字符串用于描述输入操作数)之前应该有两个冒号(这样,编译器就知道指令中没有输出操作数)。
  指令中的输出操作数对应的C语言变量应该具有左值类型,当然对于输出操作数没有这种左值限制。输出操作数必须是只写的,也就是说,asm对取出某个操作数,执行一定计算以后再将结果存回该操作数这种类型的汇编指令的支持不是直接的,而必须通过特定的格式的说明。如果汇编指令中包含了一个输入-输出类型的操作数,那么在模板中必须用两个占位符对该操作数的不同功能进行引用:一个负责输入,另一个负责输出。例如:
  asm ("addl %2,%0":"=r"(foo):"0"(foo),"g"(bar));
  在上面这条指令中,
"%0”是一个输入-输出类型的操作数,
"=r"(foo)用于限定其输出功能,该指令的输出结果会存放到C语言变量foo中;
  指令中没有显式的出现“%1”操作数,但是针对它有一个限定字符串"0"(foo),事实上指令中隐式的“%1”操作数用于描述“%0”操作数的输入功能,它的限定字符串中的"0"限定了“%1”操作数与“%0” 
  具有相同的地址。可以这样理解上述指令中的模板:该指令将“%1”和“%2”中的值相加,计算结果存放回“%0”中,指令中的“%1”与“%0”具有相同的地址。注意,用于描述“%1”的"0"限定字符足以保证“%1”与“%0”具有相同的地址。
  但是,如果用下面的指令完成,这种输入-输出操作就不会正常工作: 
  asm ("addl %2,%0":"=r"(foo):"r"(foo),"g"(bar)); 
  虽然该指令中“%0”和“%1”同样引用了C语言变量foo,但是gcc并不保证在生成的汇编程序中它们具有相同的地址。
  还有一些汇编指令可能会改变某些寄存器的值,相应的汇编指令模板中必须将这种情况通知编译器。所以在模板中还有第三种类型的限定字符串,它们跟在输入操作数限定字符串的后面,之间用冒号间隔。这些字符串是某些寄存器的名称,代表该指令会改变这些寄存器中的内容。
  在内嵌的汇编指令中可能会直接引用某些硬件寄存器,我们已经知道AT&T格式的汇编语言中,寄存器名以“%”作为前缀,为了在生成的汇编程序中保留这个“%”号,在asm语句中对硬件寄存器的引用必须用“%%”作为寄存器名称的前缀。如果汇编指令改变了硬件寄存器的内容,不要忘记通知编译器(在第三种类型的限定串中添加相应的字符串)。还有一些指令可能会改变CPU标志寄存器EFLAG的内容,那么需要在第三种类型的限定字符串中加入"cc"。
  为了防止gcc在优化过程中对asm中的汇编指令进行改变,可以在"asm"关键字后加上"volatile"修饰符。
  可以在一条asm语句中描述多条汇编语言指令;各条汇编指令之间用“;”或者“n”隔开。
  ②操作数限定字符
  操作数限定字符串中利用规定的限定字符来描述相应的操作数,一些常用的限定字符有:(还有一些没有涉及的限定字符,参见gcc.info)
  1."m":操作数是内存变量。
  2."o":操作数是内存变量,但它的寻址方式必须是“偏移量”类型的,也就是基址寻址或者基址加变址寻址。
  3."V":操作数是内存变量,其寻址方式非“偏移量”类型。 
  4." ":操作数是内存变量,其地址自动增量。 
  6."r":操作数是通用寄存器。 
  7."i":操作数是立即操作数。(其值可在汇编时确定) 
  8."n":操作数是立即操作数。有些系统不支持除字(双字节)以外的立即操作数,这些操作数要用"n"而不是"i"来描述。
  9."g":操作数可以是立即数,内存变量或者寄存器,只要寄存器属于通用寄存器。
  10."X":操作数允许是任何类型。
  11."0","1",...,"9":操作数与某个指定的操作数匹配。也就是说,该操作数就是指定的那个操作数。例如,如果用"0"来描述"%1"操作数,那么"%1"引用的其实就是"%0"操作数。
  12."p":操作数是一个合法的内存地址(指针)。
  13."=":操作数在指令中是只写的(输出操作数)。
  14."+":操作数在指令中是读-写类型的(输入-输出操作数)。
  22."f":浮点数寄存器。 
  23."t":第一个浮点数寄存器。 
  24."u":第二个浮点数寄存器。 
  27."I":0-31之间的立即数。(用于32位的移位指令) 
  28."J":0-63之间的立即数。(用于64位的移位指令) 
  29."N":0-255之间的立即数。(用于"out"指令) 
  30."G":标准的80387浮点常数。 
  注:还有一些不常见的限定字符并没有在此说明,另外有一些限定字符,例如"%","&"等由于我缺乏编译器方面的一些知识,所以我也不是很理解它们的含义,如果有高手愿意补充,不慎感激!不过在核心代码中出现的限定字符差不多就是上面这些了。
对《gcc中的内嵌汇编语言》一文的补充说明
  初次接触到AT&T格式的汇编代码,看着那一堆莫名其妙的怪符号,真是有点痛不欲生的感觉,只好慢慢地去啃gcc文档,在似懂非懂的状态下过了一段时间。后来又在网上找到了灵溪写的《gcc中的内嵌汇编语言》一文,读后自感大有裨益。几个月下来,接触的源代码多了以后,慢慢有了一些经验。为了使初次接触AT&T格式的汇编代码的同志不至于遭受我这样的痛苦,就整理出该文来和大家共享.如有错误之处,欢迎大家指正,共同提高。
  本文主要以举例的方式对gcc中的内嵌汇编语言进行进一步的解释。

一、gcc对内嵌汇编语言的处理方式


  gcc在编译内嵌汇编语言时,采取的步骤如下:
  变量输入:根据限定符的内容将输入操作数放入合适的寄存器,如果限定符指定为立即数("i")或内存变量("m"),则该步被省略,如果限定符没有具体指定输入操作数的类型(如常用的"g"),gcc会视需要决定是否将该操作数输入到某个寄存器.这样每个占位符都与某个寄存器,内存变量,或立即数形成了一一对应的关系.这就是对第二个冒号后内容的解释.如::"a"(foo),"i"(100),"m"(bar)表示%0对应eax寄存器,%1对应100,%2对应内存变量bar. 
  生成代码:然后根据这种一一对应的关系(还应包括输出操作符),用这些寄存器,内存变量,或立即数来取代汇编代码中的占位符(则有点像宏操作),注意,则一步骤并不检查由这种取代操作所生成的汇编代码是否合法,例如,如果有这样一条指令asm("movl %0,%1"::"m"(foo),"m"(bar));如果你用gcc -c -S选项编译该源文件,那么在生成的汇编文件中,你将会看到生成了movl foo,bar这样一条指令,这显然是错误的.这个错误在稍后的编译检查中会被发现. 
  变量输出:按照输出限定符的指定将寄存器的内容输出到某个内存变量中,如果输出操作数的限定符指定为内存变量("m"),则该步骤被省略.这就是对第一个冒号后内容的解释,如:asm("mov %0,%1":"=m"(foo),"=a"(bar):);编译后为:
  #APP
  movl foo,eax
  #NO_APP
  movl eax,bar
  该语句虽然有点怪怪的,但它很好的体现了gcc的运作方式。
  再以arch/i386/kernel/apm.c中的一段代码为例,我们来比较一下它们编译前后的情况:
源程序 编译后的汇编代码 
__asm__ ( 
"pushl %%edint"
"pushl %%ebpnt"
"lcall %%cs:nt"
"setc %%alnt"
"addl %1,%2nt"
"popl %%ebpnt"
"popl %%edint"
:"=a"(ea),"=b"(eb), 
"=c"(ec),"=d"(ed),"=S"(es) 
:"a"(eax_in),"b"(ebx_in),"c"(ecx_in) 
:"memory","cc"); 
movl eax_in,%eax 
  movl ebx_in,%ebx 
  movl ecx_in,%ecx 
#APP 
  pushl %edi 
  pushl %ebp 
  lcall %cs: 
  setc %al 
  addl eb,ec 
  popl %ebp 
  popl %edi 
#NO_APP 
  movl %eax,ea 
  movl %ebx,eb 
  movl %ecx,ec 
  movl %edx,ed 
  movl %esi,es 

二、对第三个冒号后面内容的解释


  第三个冒号后面内容主要针对gcc优化处理,它告诉gcc在本段汇编代码中对寄存器和内存的使用情况,以免gcc在优化处理时产生错误。它可以是"eax","ebx","ecx"等寄存器名,表示本段汇编代码对该寄存器进行了显式操作,如 asm ("mov %%eax,%0",:"=r"(foo)::"eax");这样gcc在优化时会避免使用eax作临时变量,或者避免cache到eax的内存变量通过该段汇编码。
  下面的代码均用gcc的-O2级优化,它显示了嵌入汇编中第三个冒号后"eax"的作用
源程序 编译后的汇编代码 
int main() 

  int bar=1; 
  bar=fun(); 
  bar++; 
  return bar; 

pushl %ebp 
movl %esp,%ebp 
call fun 
incl %eax #显然,bar缺省使用eax寄存器 
leave 
ret 
  加了汇编后:
源程序 编译后的汇编代码 
int main() 

  int bar=1; 
  bar=fun(); 
  asm volatile("" : : : "eax"); 
  bar++; 
  return bar; 

pushl %ebp 
movl %esp,%ebp 
call fun 
movl %eax,%edx #bar改为使用edx寄存器 
incl %edx 
movl %edx,%eax #放入main()的返回值 
ret 
"merory"是一个常用的限定,它表示汇编代码以不可预知的方式改变了内存,这样gcc在优化时就不会让cache到寄存器的内存变量使用该寄存器通过汇编代码,否则可能会发生同步出错.有了上面的例子,这个问题就很好理解了 

三、对"&"限定符的解释

这是一个较常见用于输出的限定符,它告诉gcc输出操作数使用的寄存器不可再让输入操作数使用。

对于"g","r"等限定符,为了有效利用为数不多的几个通用寄存器,gcc一般会让输入操作数和输出操作数选用同一个寄存器。但如果代码没编好,会引起一些意想不到的错误,例如:
  asm("call fun;mov ebx,%1":"=a"(foo):"r"(bar));
  gcc编译的结果是foo和bar同时使用eax寄存器:
  movl bar,eax
  #APP
  call fun
  movl ebx,eax
  #NO_APP
  movl eax,foo
  本来这段代码的意图是将fun()函数的返回值放入foo变量,但半路杀出个程咬金,用ebx的值冲掉了返回值,所以这是一段错误的代码,解决的方法是加上一个给输出操作数加上一个"&"限定符:
  asm("call fun;mov ebx,%1":"=&a"(foo):"r"(bar));
  这样gcc就会让输入操作数另寻高就,不再使用eax寄存器了
补充说明:
  其实&对读代码根本没有意义,只有GCC或写汇编的才关心。 
  如果你非要知道&的作用,这里解释一下。
  1. GCC处理嵌入汇编时,如果两个输入操作数值相同,可能会分配到同一个寄存器,以减少寄存器的使用。 
  2. GCC把嵌入汇编看成一个整体,它并不知道是一条指令还是多条指令,通常它认为汇编指令输出结果时,输入操作数还没有改变,单条指令时,这种假设大多成立,但多条指令时可能不成立。比如定义如下:
  #define add1(a,b) asm("incl %0ntaddl %2,%0":"=r"(res):"0"(a),"r"(b))
  计算a+b+1。我们看看add1(a,a)生成的指令,假设%eax中包含a的值:
  #%0=eax,%2=eax
  #APP
  incl %eax
  addl %eax, %eax
  #NO_APP
  结果不对,读或写汇编的人很容易看出这点,但是GCC不知道,加个&,定义写成:
  #define add1(a, b) asm("addl %2,%0":"=&r"(res):"0"(a),"r"(b))
  告诉GCC参作数%0是earlyclobber,不要和%2分配到同一个寄存器,这样add1(a,a)生成指令:
  movl %eax, %edx
  #%0=edx, %2=eax
  #APP
  incl %edx
  addl %eax, %edx # edx = output
  #NO_APP

四、对%quot;&"限定符的解释(老铁补充)


  %:说明指令中可与下一操作数交换的那个操作数,这意味着编译可以交换这两个操作数以使得能以代价更小的方法来满足操作数约束,这常常用于真正只有两个操作数的加法指令的指令样板中,这种加法指令的结果必须存放在两个操作数之一中


关键字:GCC  内嵌  汇编语言 引用地址:GCC中的内嵌汇编语言

上一篇:Jlink烧写出错 : Unable to halt arm core
下一篇:TQ2440 LCD试验失败经验教训

推荐阅读最新更新时间:2024-11-13 20:29

[AVR]使用Gcc编译时各种数据的长度
创建项目时,芯片选择ATMEGA16 各种数据类型长度如下 char 8位 short 16位 int 16位 long 32位 long long 64位 有空再测试下double、float的长度 后来偶然发现gcc提供一个头文件 stdint.h 可以这样定义新的变量 1 uint32_t a;//a是一个32位长无符号整型 2 uint64_t b;//b是一个64位长无符号整型 3 int8_t c;//等价于signed char c 直观,易懂,可移植性强..
[单片机]
ARM GCC 内嵌汇编手册
关于这篇文档 对于基于ARM的RISC处理器,GNU C编译器提供了在C代码中内嵌汇编的功能。这种非常酷的特性提供了C代码没有的功能,比如手动优化软件关键部分的代码、使用相关的处理器指令。 这里设想了读者是熟练编写ARM汇编程序读者,因为该片文档不是ARM汇编手册。同样也不是C语言手册。 这篇文档假设使用的是GCC 4 的版本,但是对于早期的版本也有效。 GCC asm 声明 让我们以一个简单的例子开始。就像C中的声明一样,下面的声明代码可能出现在你的代码中。 /* NOP 例子 */ asm( mov r0,r0 ); 该语句的作用是将r0移动到r0中。换句话讲他并不干任何事。典型的就是NOP指令,作用就是短时的延时。 请接着阅
[单片机]
ARM <font color='red'>GCC</font> <font color='red'>内嵌</font>汇编手册
ARM汇编语言中的程序结构
在 ARM ( Thumb )汇编语言程序中,以程序段为单位组织代码。段是相对独立的指令或数据序列,具有特定的名称。段可以分为代码段和数据段,代码段的内容为执行代码,数据段存放代码运行时需要用到的数据。一个汇编程序至少应该有一个代码段,当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映象文件。 可执行映象文件通常由以下几部分构成: 一个或多个代码段,代码段的属性为只读。 零个或多个包含初始化数据的数据段,数据段的属性为可读写。 零个或多个不包含初始化数据的数据段,数据段的属性为可读写。 链接器根据系统默认或用户设定的规则,将各个段安排在存储器中的相应位置。因此源程序中段之间的相对位
[单片机]
ARM架构下GCC中progma编译指示字
ARM架构下GCC中progma编译指示字 主要说#pragma pack() 1.为什么要内存对齐 2.裸机代码验证ARM9(S3C2440)的默认对齐字节数 (还不能证实) 3.验证奇数地址访问的可行性 (经实现可以看出,没有对奇数地址有特别要求) 4.struct大小
[单片机]
关于GCC中断调用函数名的问题
问: 嗯。。最近学习AVR,我用的编译软件是GCC。。。 觉的网上这个方面的资料还是比较少的,用ICC的比较多。 如我用外部中断时,写中断函数用的中断函数名为 ISR(INTO_vect) 想知道用到其他函数时用的什么函数名!! 在库函数里,找了下没有找到。 答: 还可以按以下路径: WINAVR安装目录- avr- include- avr 可以找到对应各个型号的头文件,比如iom8.h对应mega8,里面有如下一段: #define INT0_vect _VECTOR(1) #define SIG_INTERRUPT0 _VECTOR(1) #define INT1_vect _VECTOR(2)
[单片机]
汇编语言阶段一总结
将数据代码放入不同的段 汇编语言程序可以将数据,栈和代码都放到一个段里面,但是也可以将程序,栈和代码分别放到不同的段里,下图就是定义多个段的程序 assume cs:codesg,ds:data,ss:stack data segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data ends stack segment dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 stack ends codesg segment start:mov ax,stack mov ss,ax mov sp,20h
[单片机]
<font color='red'>汇编语言</font>阶段一总结
基于内嵌Modbus协议的远程监控系统的设计
一.引言 在生产和生活中监控系统起到的作用越来重要,如在交通.银行.电力等部门对监控系统的要求越来越越高.需求也在不断增大.在远程监控系统中,作为一种分布式控制系统和工业通信协议Modbus协议在工业领域得到很好的应用,本文针对其含义.系统设计构架和系统性能进行详细的阐述. 二.浅析Modbus协议 (1)Modbus协议开发的Modbus协议是一种分布式控制系统和工业通信协议,在分布式控制系统和工业设备通讯中得到很好的应用,采用RS485方式作为物理接口.控制器经由网络.控制器相互之间.通讯协议等可以通讯,在工业生产中已经成为通用标准,可以连接不同的控制设备成为工业网络,实现集中控制和监控. (2)作为一种主从网络
[嵌入式]
单片机的软件
通常单片机开发中用的程序设计语言是汇编语言。编写程序后用PE、EDLIN等软件在计算机上编辑,然后编译成机器码文件,再由通信软件将机器码文件送入单片机联机调试。随着单片机系统规模的扩大和功能的复杂,用汇编语言编制程序的方法有明显的缺点。主要是效率低,程序不易维护,不能移植,很不适应要求。有必要寻求一种高效率的结构化的高级程序设计语言。这些语言现在有C、PL/M、Forth 、Pascal、Modula-2等〔9〕。   C语言是一种介于高级语言和汇编语言之间的适于单片机开发用的语言。它既有高级语言的特点,又易与汇编语言接口。原来用汇编语言写的程序现在可以用C语言编写。只是在体现速度的场合如信息的实时处理、实时控制,以及和硬件打交
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved