zoukankan      html  css  js  c++  java
  • gcc c asm,C程序内嵌汇编

    gcc说明:

    https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Extended-Asm.html

    https://gcc.gnu.org/onlinedocs/gcc-7.3.0/gcc/Extended-Asm.html#Extended-Asm

    有时候,需要在C程序中嵌入汇编程序,比如rtems操作系统中有很多和底层处理器打交道的地方,就用到了很多内嵌汇编。

     https://github.com/yanhc519/rtems3.6.0-4.0.0/blob/master/c/src/exec/score/cpu/sparc/sparc.h

    #define sparc_get_psr( _psr ) 
      do { 
         (_psr) = 0; 
         asm volatile( "rd %%psr, %0" :  "=r" (_psr) : "0" (_psr) ); 
      } while ( 0 )
    #define nop() 
      do { 
        asm volatile ( "nop" ); 
      } while ( 0 )
    
    /*
     *  Get and set the PSR
     */
    
    #define sparc_get_psr( _psr ) 
      do { 
         (_psr) = 0; 
         asm volatile( "rd %%psr, %0" :  "=r" (_psr) : "0" (_psr) ); 
      } while ( 0 )
    
    #define sparc_set_psr( _psr ) 
      do { 
        asm volatile ( "mov  %0, %%psr " : "=r" ((_psr)) : "0" ((_psr)) ); 
        nop(); 
        nop(); 
        nop(); 
      } while ( 0 )
    
    /*
     *  Get and set the TBR
     */
    
    #define sparc_get_tbr( _tbr ) 
      do { 
         (_tbr) = 0; /* to avoid unitialized warnings */ 
         asm volatile( "rd %%tbr, %0" :  "=r" (_tbr) : "0" (_tbr) ); 
      } while ( 0 )
    
    #define sparc_set_tbr( _tbr ) 
      do { 
         asm volatile( "wr %0, 0, %%tbr" :  "=r" (_tbr) : "0" (_tbr) ); 
      } while ( 0 )
    View Code

    语法:asm 限定符(汇编语句模板: 输出部分: 输入部分: 破坏描述部分)

    (1)限定符可以为volatile,这可以禁止编译器对嵌入汇编进行优化。

    (2)汇编语句模板

    "rd %%psr, %0"为模板,其中用到了psr寄存器,由于有双引号,要输出%,需要使用两个%%,因此,写为%%psr;%0表示代表指令的操作数,称为占位符。占位符支持10个,可以为%0,%1,%2,%3.......%9,顺序对应后面C变量出现的顺序,C变量出现在圆括号()中。

    之后一个冒号后面是输出部分,另一个冒号后面是输入部分。

    (3)输出部分:

    输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串C语言变量(出现在圆括号())组成。每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数。

    (4)输入部分:

    输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串C语言表达式或者C语言变量组成。

    输出和输入部分的限制字符串可以用r限定使用通用寄存器,或m使用内存等,

    (5)匹配限制符

    匹配限制符是一位数字  "0"、"1" ....."9"  , 分别表示它限制的C表达式分别与占位符%0,%1,……%9对应的C变量匹配。例如使用“0”作为%1,的限制字符,那么 %0和%1表示同一个C变量。

    对于上面的例子,分别出现了两个圆括号,因此,有两个操作数,其中第一个的限定字符串有=,表示是输出操作数;第二个的限定字符串没有=,表示是输入操作数。对于占位符而言,两个操作数,分别对应%0和%1。而我们希望两个操作数是同一个数,因此,把第二个操作数的限定字符串用0限定,即表示第二个操作数与%0,即第一个操作数是同一个。

    上面的例子,相当于,先把_psr清零,然后,将%psr的数值读取到_psr中,输入和输出操作数都是_psr。

    关于volatile

    GCC’s optimizers sometimes discard asm statements if they determine there is no need for the output variables. Also, the optimizers may move code out of loops if they believe that the code will always return the same result (i.e. none of its input values change between calls). Using the volatile qualifier disables these optimizations.

    GCC如果认为输出变量没必要,就会将asm去掉。同时,gcc如果认为代码总是返回同样结果,那么,会将代码从循环中移除。使用volatile可以关闭这些优化。 

    https://blog.csdn.net/qq_26093511/article/details/79546321,这个文章解释的很清楚。

      1 AT&T手册里面的,我整理了下,方便阅读
      2 
      3  
      4 
      5 内核代码绝大部分使用C 语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码。GCC提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计。
      6 
      7  简单的内嵌汇编很容易理解
      8  例如:
      9 
     10 __asm____volatile__("hlt");
     11 
     12 “__asm__”表示后面的代码为内嵌汇编,“asm”是“__asm__”的别名。
     13 “__volatile__”表示编译器不要优化代码,后面的指令保留原样,
     14volatile”是它的别名。括号里面是汇编指令。
     15 
     16  
     17 
     18 使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。例如在下面的汇编语句:[下段解释一定要耐着性子看懂读懂!]
     19 
     20 __asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));
     21 “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”表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
     22 
     23 
     24 
     25 下面来看看一个内嵌汇编的小例子:
     26 
     27  
     28 
     29   extern     int    input,result;
     30    
     31   void test(void)
     32   {
     33          input= 1;
     34   __asm__ __volatile__ ("movl %1,%0" :
     35   "=r" (result) : "r" (input));
     36          return ;
     37   }
     38   
     39 
     40  对应的汇编代码如下;
     41 
     42   行号   代码 解释
     43 
     44   1
     45   7
     46   8 movl    $1, input                           对应C语言语句input = 1;
     47   9 movl     input, %eax
     48 10 #APP                                              GCC插入的注释,表示内嵌汇编开始
     49 11 movl     %eax,%eax                     我们的内嵌汇编语句
     50 12 #NO_APP                                      GCC  插入的注释,表示内嵌汇编结束
     51 13 movl      %eax, result                  将结果存入result变量
     52 
     53 从汇编代码可以看出,第9行和第13行是GCC,自动增加的代码,GCC 根据限定字符串决定如何处理C表达式,本例两个表达式都被指定为“r”型,所以先使用指令
     54 movl    input, %eax    将input读入寄存器%eax;
     55 
     56 GCC,也指定一个寄存器与输出变量result  相关,本例也是%eax,等得到操作结果后再使用指令:
     57 
     58 movl %eax, result
     59 
     60 
     61 将寄存器的值写回C变量result中。从上面的汇编代码我们可以看出与result 和input,相关连的寄存器都是%eax,GCC使用%eax,替换内嵌汇编指令模板中的 %0,%1 
     62 
     63 movl %eax,%eax
     64 显然这一句可以不要。但是没有优化,所以这一句没有被去掉。
     65 
     66 由此可见,C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC 如何处理即可。限制字符必须与指令对操作数的要求相匹配,否则产生的汇编代码将会有错,读者可以将上例中的两个“r”,都改为“m”(m,表示操作数放在内存,而不是寄存器中),编译后得到的结果是:
     67 
     68 movl input, result
     69 
     70 很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用“m”作为限定字符。
     71 
     72 由此我们可以总结出来内嵌汇编的格式:
     73 
     74 
     75 __asm__(
     76 汇编语句模板: 
     77 输出部分: 
     78 输入部分: 
     79 破坏描述部分)
     80 
     81 
     82 共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:”格开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空。
     83 
     84 我们来分别说明:
     85 
     86 输出部分
     87 
     88 输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和C语言变量组成。每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数。
     89 
     90 例:
     91 
     92   __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )
     93 
     94   
     95 描述符字符串表示对该变量的限制条件,这样GCC就可以根据这些条件决定如何分配寄存器,如何产生必要的代码处理指令操作数与C表达式或C变量之间的联系.
     96 
     97 输入部分
     98 
     99 输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成。
    100 
    101 例如:
    102 
    103  
    104 
    105  Static __inline__ void __set_bit(int nr, volatile void * addr)
    106   {
    107          __asm__(
    108          "btsl %1,%0"   :
    109          "=m"(addr)    :
    110          "Ir"(nr)
    111               );
    112   }
    113 
    114 该例子功能是将(*addr)的第nr位设为1。第一个占位符%0与C,语言变量ADDR对应,第二个占位符%1与C,语言变量nr对应。因此上面的汇编语句代码与下面的伪代码等价:
    115 btsl nr, ADDR,该指令的两个操作数不能全是内存变量,因此将nr的限定字符串指定"lr"(下文会有解释),与立即数或者寄存器相关联,这样两个操作数中只有ADDR为内存变量。
    116 
    117 现在继续看上面的例子:
    118 "=m" (ADDR)表示ADDR为内存变量(“m”),而且是输出变量(“=”);"Ir" (nr)表示nr,为 0-31之间的立即数(“I”)或者一个寄存器操作数(“r”)。
    119 
    120 
    121 匹配限制符是一位数字  "0""1" ....."9"  , 分别表示它限制的C表达式分别与占位符%0,%1,……%9对应的C变量匹配。例如使用“0”作为%1,的限制字符,那么 %0和%1表示同一个C变量。
    122 还是来看个例子吧:
    123 
    124  
    125 
    126   extern int input,result;
    127   void test_at_t()
    128   {
    129          result  = 0;
    130          input  = 1;
    131          __asm__
    132      __volatile__ ("addl  %2,%0":"=r"(result):"0"(result),"m"(input));
    133          
    134   }
    135 
    136 输入部分中的result用匹配限制符“0”限制,表示%1与%0,代表同一个变量,输入部分说明该变量的输入功能,输出部分说明该变量的输出功能,两者结合表示result, 是读写型。因为%0和%1,表示同一个C变量,所以放在相同的位置,无论是寄存器还是内存。
    137 
    138 寄存器破坏描述符
    139 
    140 通常编写程序只使用一种语言:高级语言或者汇编语言。高级语言编译的步骤大致如下:
    141         
    142 预处理;
    143 l        
    144 编译
    145 l        
    146 汇编
    147 l        
    148 链接
    149 
    150 
    151 我们这里只关心第二步编译(将C代码转换成汇编代码):因为所有的代码都是用高级语言编写,编译器可以识别各种语句的作用,在转换的过程中所有的寄存器都由编译器决定如何分配使用,它有能力保证寄存器的使用不会冲突;也可以利用寄存器作为变量的缓冲区,因为寄存器的访问速度比内存快很多倍。如果全部使用汇编语言则由程序员去控制寄存器的使用,只能靠程序员去保证寄存器使用的正确性。但是如果两种语言混用情况就变复杂了,因为内嵌的汇编代码可以直接使用寄存器,而编译器在转换的时候并不去检查内嵌的汇编代码使用了哪些寄存器(因为很难检测汇编指令使用了哪些寄存器,例如有些指令隐式修改寄存器,有时内嵌的汇编代码会调用其他子过程,而子过程也会修改寄存器),因此需要一种机制通知编译器我们使用了哪些寄存器(程序员自己知道内嵌汇编代码中使用了哪些寄存器),否则对这些寄存器的使用就有可能导致错误,修改描述部分可以起到这种作用。当然内嵌汇编的输入输出部分指明的寄存器或者指定为“r”,“g”型由编译器去分配的寄存器就不需要在破坏描述部分去描述,因为编译器已经知道了。
    152 
    153 下面看个例子就很清楚为什么需要通知GCC内嵌汇编代码中隐式(称它为隐式是因为GCC并不知道)使用的寄存器。
    154 
    155 在内嵌的汇编指令中可能会直接引用某些寄存器,我们已经知道AT&T格式的汇编语言中,寄存器名以“%”作为前缀,为了在生成的汇编程序中保留这个“%”号,在asm语句中对寄存器的引用必须用“%%”作为寄存器名称的前缀。原因是“%”在asm,内嵌汇编语句中的作用与“/”在C语言中的作用相同,因此“%%”转换后代表“%”。
    156 
    157 
    158  
    159 
    160 int main(void)   
    161 {
    162      int input, output,temp;    
    163     input = 1;
    164     __asm__ __volatile__  ("movl $0, %%eax;  
    165             movl %%eax, %1; 
    166             movl %2, %%eax; 
    167             movl %%eax, %0; "
    168             :"=m"(output),"=m"(temp)    /* output */            
    169             :"r"(input)     /* input */    
    170             );  
    171     return 0;
    172  }
    173  
    174 
    175 这段代码使用%eax作为临时寄存器,功能相当于C代码:“temp = 0;output=input”, 
    176 对应的汇编代码如下:
    177 
    178          movl  $1,-4(%ebp)
    179          movl -4(%ebp),%eax  /APP
    180          movl  $0, %eax;
    181         movl %eax, -12(%ebp);
    182          movl %eax, %eax;
    183          movl %eax, -8(%ebp);       /NO_APP
    184   
    185 显然GCC给input分配的寄存器也是%eax,发生了冲突,output的值始终为0,而不是input。
    186 
    187 使用破坏描述后的代码:
    188 
    189 
    190 int main(void)
    191  {
    192     int input, output,temp;
    193          input  = 1;
    194    __asm__ __volatile__
    195   (    "movl $0, %%eax; 
    196                          movl  %%eax, %1;     
    197                          movl  %2, %%eax; 
    198                          movl  %%eax, %0; "
    199                          :"=m"(output),"=m"(temp)    /* output */
    200                          :"r"(input)     /* input */
    201                          :"eax");  /* 描述符 */
    202   
    203    return 0;
    204  }
    205 
    206  对应的汇编代码:
    207 
    208 
    209          movl $1,-4(%ebp)
    210          movl  -4(%ebp),%edx            //APP
    211          movl  $0, %eax;
    212          movl  %eax, -12(%ebp);
    213          movl  %edx, %eax;
    214          movl  %eax, -8(%ebp);         /NO_APP
    215   
    216 
    217 通过破坏描述部分,GCC得知%eax已被使用,因此给input分配了%edx。在使用内嵌汇编时请记住一点:尽量告诉GCC尽可能多的信息,以防出错。
    View Code

     

  • 相关阅读:
    mysql中profile的使用
    6、MySQL索引种类
    MySql事务
    MySQL视图(view)
    MySql数据库命令基本操作
    2、MySQL常见数据库引擎及比较?
    1、列举常见的关系型数据库和非关系型都有那些?
    Python中的顺序表
    双端队列
    手持移动端特殊链接:打电话,发短信,发邮件
  • 原文地址:https://www.cnblogs.com/yanhc/p/13019640.html
Copyright © 2011-2022 走看看