GCC内嵌汇编

C语言ASM(GCC Inline ASM)汇编内嵌语法 带有C/C++表达式的内联汇编GCC允许你通过C/C++表达式指定内联汇编"Instrcuction List"中指令的输入和输出,你甚至可以不关心到底使用哪个寄存器被使用,完全靠GCC来安排和指定。这一点可以让程序员避免去考虑有限的寄存器的使用,也可以提高目标代码的效率。 阅读详情

转自:http://blog.csdn.net/littlehedgehog/article/details/2259665

核代码绝大部分使用C 语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码。GCC提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计。

 简单的内嵌汇编很容易理解
 例如:

__asm____volatile__("hlt");


“__asm__”表示后面的代码为内嵌汇编,“asm”是“__asm__”的别名。
“__volatile__”表示编译器不要优化代码,后面的指令保留原样,
“volatile”是它的别名。括号里面是汇编指令。

 

使用内嵌汇编,要先编写汇编指令模板,然后将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,”表示。在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。“result”前面的限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令。“input”前面的“r”表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。



下面来看看一个内嵌汇编的小例子:


  extern     int    input,result;
   
  
void test(void)
  
{
         input
= 1;
  __asm__ __volatile__ (
"movl %1,%0" :
  
"=r" (result) : "r" (input));
         
return
 ;
  }

  

 对应的汇编代码如下;

  行号   代码 解释

  1
  7
  8 movl    $1, input                           对应C语言语句input = 1;
  9 movl     input, %eax
10 #APP                                              GCC插入的注释,表示内嵌汇编开始
11 movl     %eax,%eax                     我们的内嵌汇编语句
12 #NO_APP                                      GCC  插入的注释,表示内嵌汇编结束
13 movl      %eax, result                  将结果存入result变量

从汇编代码可以看出,第9行和第13行是GCC,自动增加的代码,GCC 根据限定字符串决定如何处理C表达式,本例两个表达式都被指定为“r”型,所以先使用指令
movl    input, %eax    将input读入寄存器%eax;

GCC,也指定一个寄存器与输出变量result  相关,本例也是%eax,等得到操作结果后再使用指令:

movl %eax, result


将寄存器的值写回C变量result中。从上面的汇编代码我们可以看出与result 和input,相关连的寄存器都是%eax,GCC使用%eax,替换内嵌汇编指令模板中的 %0,%1 

movl %eax,%eax
显然这一句可以不要。但是没有优化,所以这一句没有被去掉。

由此可见,C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC 如何处理即可。限制字符必须与指令对操作数的要求相匹配,否则产生的汇编代码将会有错,读者可以将上例中的两个“r”,都改为“m”(m,表示操作数放在内存,而不是寄存器中),编译后得到的结果是:

movl input, result

很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用“m”作为限定字符。

由此我们可以总结出来内嵌汇编的格式:


__asm__(
汇编语句模板: 
输出部分: 
输入部分: 
破坏描述部分)


共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:”格开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空。例如:

  __asm__ __volatile__(
  "cli":
  :
  :"memory")

我们来分别说明:

输出部分

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

例:

  __asm__ __volatile__( "pushfl ; popl %0 ; cli":"=g" (x) )


  
描述符字符串表示对该变量的限制条件,这样GCC就可以根据这些条件决定如何分配寄存器,如何产生必要的代码处理指令操作数与C表达式或C变量之间的联系.

输入部分

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

例如:

 

 Static __inline__ void __set_bit(int nr, volatile void * addr)
  
{
         __asm__(
         
"btsl %1,%0"   :
         
"=m"(addr)    :
         
"Ir"
(nr)
              );
  }

该例子功能是将(*addr)的第nr位设为1。第一个占位符%0与C,语言变量ADDR对应,第二个占位符%1与C,语言变量nr对应。因此上面的汇编语句代码与下面的伪代码等价:
btsl nr, ADDR,该指令的两个操作数不能全是内存变量,因此将nr的限定字符串指定"lr"(下文会有解释),与立即数或者寄存器相关联,这样两个操作数中只有ADDR为内存变量。

限制字符
限制字符有很多种,有些是与特定体系结构相关,此处仅列出常用的限定字符和i386中可能用到的一些常用的限定符。它们的作用是指示编译器如何处理其后的C语言变量与指令操作数之间的关系,例如是将变量放在寄存器中还是放在内存中等,下表列出了常用的限定字母。

“b”将输入变量放入ebx
  “c”将输入变量放入ecx
  “d”将输入变量放入edx
  “s”将输入变量放入esi
  “d”将输入变量放入edi
  “q”将输入变量放入eax,ebx  ,ecx  ,edx中的一个
  “r”将输入变量放入通用寄存器,也就是eax ,ebx,ecx,edx,esi,edi中的一个
  “A”把eax和edx,合成一个64位的寄存器(uselong longs) 
  “m”内存变量
  “o”操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址
  “V”操作数为内存变量,但寻址方式不是偏移量类型
  “,” 操作数为内存变量,但寻址方式为自动增量
  “p”操作数是一个合法的内存地址(指针)
  
  
  寄存器或内存
  
  “g” 将输入变量放入eax,ebx,ecx  ,edx中的一个或者作为内存变量 
  “X”操作数可以是任何类型

  
  立即数
  “I” 0-31 之间的立即数(用于32位移位指令)
  “J” 0-63 之间的立即数(用于64 位移位指令)
  “N” 0-255  ,之间的立即数(用于out  指令)
  “i” 立即数
  “n” 立即数,有些系统不支持除字以外的立即数,这些系统应该使用“n”而不是“i”

  
  匹配

  "0"、"1" ....."9"
  表示用它限制的操作数与某个指定的操作数匹配,也即该操作数就是指定的那个操作数,例如用“0  ”去描述“%1”操作数,那么“%1”引用的其实就是“%0”操作数,注意作为限定符字母的0-9 ,与指令中的“%0”-“%9”的区别,前者描述操作数,后者代表操作数。

操作数类型
  “=” 操作数在指令中是只写的(输出操作数)
  “+” 操作数在指令中是读写类型的(输入输出操作数)
  
   浮点数
  “f”
  
  浮点寄存器
  “t”第一个浮点寄存器
  “u”第二个浮点寄存器
  “G”标准的80387


现在继续看上面的例子:
"=m" (ADDR)表示ADDR为内存变量(“m”),而且是输出变量(“=”);"Ir" (nr)表示nr,为 0-31之间的立即数(“I”)或者一个寄存器操作数(“r”)。


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

 

  extern int input,result;
  
void test_at_t()
  
{
         result  
= 0;
         input  
= 1;
         __asm__
     __volatile__ (
"addl  %2,%0":"=r"(result):"0"(result),"m"
(input));
         
  }

输入部分中的result用匹配限制符“0”限制,表示%1与%0,代表同一个变量,输入部分说明该变量的输入功能,输出部分说明该变量的输出功能,两者结合表示result, 是读写型。因为%0和%1,表示同一个C变量,所以放在相同的位置,无论是寄存器还是内存。

寄存器破坏描述符

通常编写程序只使用一种语言:高级语言或者汇编语言。高级语言编译的步骤大致如下:
        
预处理;
l        
编译
l        
汇编
l        
链接


我们这里只关心第二步编译(将C代码转换成汇编代码):因为所有的代码都是用高级语言编写,编译器可以识别各种语句的作用,在转换的过程中所有的寄存器都由编译器决定如何分配使用,它有能力保证寄存器的使用不会冲突;也可以利用寄存器作为变量的缓冲区,因为寄存器的访问速度比内存快很多倍。如果全部使用汇编语言则由程序员去控制寄存器的使用,只能靠程序员去保证寄存器使用的正确性。但是如果两种语言混用情况就变复杂了,因为内嵌的汇编代码可以直接使用寄存器,而编译器在转换的时候并不去检查内嵌的汇编代码使用了哪些寄存器(因为很难检测汇编指令使用了哪些寄存器,例如有些指令隐式修改寄存器,有时内嵌的汇编代码会调用其他子过程,而子过程也会修改寄存器),因此需要一种机制通知编译器我们使用了哪些寄存器(程序员自己知道内嵌汇编代码中使用了哪些寄存器),否则对这些寄存器的使用就有可能导致错误,修改描述部分可以起到这种作用。当然内嵌汇编的输入输出部分指明的寄存器或者指定为“r”,“g”型由编译器去分配的寄存器就不需要在破坏描述部分去描述,因为编译器已经知道了。

下面看个例子就很清楚为什么需要通知GCC内嵌汇编代码中隐式(称它为隐式是因为GCC并不知道)使用的寄存器。

在内嵌的汇编指令中可能会直接引用某些寄存器,我们已经知道AT&T格式的汇编语言中,寄存器名以“%”作为前缀,为了在生成的汇编程序中保留这个“%”号,在asm语句中对寄存器的引用必须用“%%”作为寄存器名称的前缀。原因是“%”在asm,内嵌汇编语句中的作用与“/”在C语言中的作用相同,因此“%%”转换后代表“%”。


 

int main(void)   
{
     
int input, output,temp;    
    input 
= 1;
    __asm__ __volatile__  (
"movl $0, %%eax;  

            movl %%eax, %1
            movl 
%2%%eax; 
            movl 
%%eax, %0"
            :"=m"(output),"=m"(temp)    /* output */            
            :
"r"(input)     /* input */    
            );  
    
return 0
;
 }

 

这段代码使用%eax作为临时寄存器,功能相当于C代码:“temp = 0;output=input”, 
对应的汇编代码如下:

         movl  $1,-4(%ebp)
         movl -4(%ebp),%eax  /APP
         movl  $0, %eax;
        movl %eax, -12(%ebp);
         movl %eax, %eax;
         movl %eax, -8(%ebp);       /NO_APP
  
显然GCC给input分配的寄存器也是%eax,发生了冲突,output的值始终为0,而不是input。

使用破坏描述后的代码:

int main(void)
 
{
    
int input, output,temp;
         input  
= 1;
   __asm__ __volatile__
  (    
"movl $0, %%eax; 

                         movl  %%eax, %1;     
                         movl  
%2%%eax; 
                         movl  
%%eax, %0"
                         :"=m"(output),"=m"(temp)    /* output */

                         :
"r"(input)     /* input */
                         :
"eax");  /* 描述符 */
  
   
return 0;
 }

 对应的汇编代码:


         movl $1,-4(%ebp)
         movl  -4(%ebp),%edx            //APP
         movl  $0, %eax;
         movl  %eax, -12(%ebp);
         movl  %edx, %eax;
         movl  %eax, -8(%ebp);         /NO_APP

  

通过破坏描述部分,GCC得知%eax已被使用,因此给input分配了%edx。在使用内嵌汇编时请记住一点:尽量告诉GCC尽可能多的信息,以防出错。

 

TI-RTOS SYS/BIOS HAL实战:手把手教你配置中断与定时器(附完整代码示例) 本文详细介绍了TI-RTOS SYS/BIOS HAL在嵌入式开发中的实战应用,重点讲解如何配置中断(Hwi模块)与定时器(Timer模块)。通过完整代码示例演示了从环境搭建到系统集成的全过程,包括中断优先级管理、定时器精度优化等关键技术,帮助开发者构建高效的实时控制系统。 阅读详情

相关推荐

Android 13 系统定制:实现第三方Launcher预置与原生Launcher3的共存切换

本文详细介绍了在Android 13系统中实现第三方Launcher预置与原生Launcher3共存切换的技术方案。通过修改ResolverActivity逻辑、处理首次启动设置以及优化用户切换机制,开发者可以灵活定制默认Launcher,同时保留用户自由选择权。文章还提供了常见问题排查指南和性能优化建议,帮助提升双Launcher方案的稳定性和用户体验。

weixin_30275415的博客 99

OpenClaw工程实战

OpenClaw工程实战总览摘要 OpenClaw是一款面向AI Agent开发的全栈式解决方案,采用四层模块化架构(基础设施层、核心引擎层、生态组件层和应用适配层)。文档全面覆盖从环境配置到企业落地的九大技术板块,包含MCP协议实战、多Agent编排、商业级项目实践等核心内容。系统支持全场景部署(本地/云端/边缘设备),集成3286个市场技能,通过Gateway网关实现智能路由与多模型调度。典型应用场景包括企业数字化、开发者提效和全渠道智能助手。

lza_csdn2019的博客 566

Redhawk解析PAD / IOPAD的实现方法

Redhawk解析IOPAD的实现方法1. IOPAD DEF的概念说明2. 生成 IOPAD GDS文件3. 生成 IOPAD DEF文件4. Redhawk解析过程中读取IOPAD DEF 本文主要简述【Redhawk解析IOPAD】的实现原理方法,具体脚本没有放在这里。 1. IOPAD DEF的概念说明 2. 生成 IOPAD GDS文件 注: getinfo2IODEF.tcl 是基于innovus获取IOPAD的摆放信息以及库信息。 run_cerate_iopadgds.csh 是基于c

kobayashiyou的博客 3126

linux 64位操作系统环境下用gcc、g++编译32位程序

linux 64位操作系统环境下,默认编译生成的可执行文件是64位的。今天由于测试验证需要,需要将程序编译成32位的可执行文件,碰到了一些问题,下面做一下记录。我们有一个test.c,有一个test.cpp,功能都是打印:Hello World。

天道酬勤 6722

gcc内嵌汇编语法分析

简单来讲是gcc优化的需要,gcc只要保证一个符号在他使用前将其准备好就可以了,如果使用**“=”**修饰一个符号则是告诉编译器,内嵌汇编中不会用到这个值,所以gcc也不用在这段内嵌汇编前把这个值准备好,又因为gcc在编译阶段并不会分析内嵌汇编程序,所以即使在内嵌汇编中使用了这个值,编译器也不会报错,但这个值可能不是预期的。编译阶段仅仅会解析“输出部”,“输入部”,“破坏部”, “GotoLabels”,程序员也是同步这四个部分告诉编译器执行这段内嵌汇编可能发生的情况,如可能破坏寄存器,或者修改内存。

dmf_fff的博客 2015

linuxgcc编译时默认是32位还是64位,怎么指定为32or64?

这时我发现,在win32和win64平台下,long都是4字节的,我记得有些情况下long是8字节,所以我就继续进行验证,我在电脑上装上了xshell,登上了服务器,试图在linux平台下找到这种情况。然后再vim编辑器下写了同样的代码,准备使用gcc进行编译时想到:linux下怎么指定32or64位平台下编译代码?因为需要配置一些东西,这篇文章中都有提到,感兴趣的话可以点进去看一下这篇文章。然后找到了long打印为8字节的情况。

qq_63179783的博客 1792

note

CPU,此处CPU与BL芯片断开,没有连背光芯片。屏,此处屏与BL芯片断开,没有连背光芯片。虽然signal函数更简单些。行 3744: 07-22 23:56:01.139 <6>[ 5.577037][ T919] [2024:07:22 15:55:51](7)[919:modprobe][drm] [lcdkit /I]: type:66, name:[qcom,mdss-dsi-apl-bl-mode-off-command], length:16, pkt-count=1。

m0_73462935的博客 8万+

OpenClaw+腾讯云企业级AI助手部署实战指南

大模型API网关是企业AI落地的核心中间件,其本质是连接前端应用与云厂商托管模型服务的协议适配层。理解OpenClaw这类开源框架与腾讯云混元、DeepSeek等API之间的契约关系,是保障服务稳定性、成本可控性与安全合规性的前提。本文聚焦云原生环境下的生产级部署实践,涵盖API密钥安全轮换、Kubernetes容器化网关、双模型智能路由、COS对象存储集成及CLB+WebSocket高可用接入等关键技术环节,特别解析`api error: 400`、`context window limit`、`sock

weixin_34295316的博客 454

[二进制学习笔记]Linux通过GCC编译生成32位程序

​ 我所使用的是ubuntu20.04,在该环境下GCC默认生成的是64位的程序,如果希望编译生成32为程序,我们需要通过以下操作。(ia32-libs已经被lib32z1取代)GCC指定编译生成程序位数。

hide_in_darkness的博客 1714

Linux:用gcc编译为32位程序

gcc默认可能将程序编译为64为,若想强制让其将程序编译为32位,则: yum install glibc-devel.i686 gcc -m32 t.c file a.out ./a.out

Android framework 1869

如何在Linux 64位系统下编译32位程序?

随着计算机的不断发展,64位系统渐渐成为了主流,但是我们在编写程序的时候,仍有时候需要编译出32位程序来兼容旧的系统或者是其他32位程序的库,那么在Linux 64位系统下如何编译32位程序呢?

m0_66338176的博客 4359

使用gcc编译.s汇编

使用gcc编译.s汇编

xiaozhi 1832

使用C编译器产生清晰的二进制文件 (i386+)

Cornelis Frank April 10, 2000 翻译:湖北武汉华中师范大学(CCNU) 城市与环境科学学院 陈斌 Email:sunwen@aspcn.com 邮编:430079 引言: 这是一篇深入剖析GCC编译器产生的汇编码的文章,非常精彩.对于初学者来说,这篇文章是进阶的好材料,它可以让你深刻地体会到C的内部机制.对于高级开发者和系统开发人员来说,这是非常好的资料,因为它可以让你

魔芋的专栏 3747

Mac下安装i386编译工具

在学习《Orange‘S:一个操作系统的实现》时,作者使用nasm和gcc生成ELF文件,然后使用ld命令链接。但是他是在Linux上做的,Mac系统的gcc(clang)只能生成Mac自己的macho64格式的C中间文件,所以需要安装i386编译工具。由于书中是在IA32上开发的,所以所有的汇编和C语言都必须编译为32位的ELF,所以我选择了i386系列工具。如果有想要其他架构和平台的,过程也差不多。参考博客为(好像要翻墙)

tsunho's blog 793

linux 内核编译错误 gcc: error: elf_i386: No such file or directory

编译内核出现以下错误 CC arch/x86/mm/mmio-mod.o LD arch/x86/mm/mmiotrace.o LD arch/x86/mm/built-in.o CC arch/x86/crypto/crc32c-intel.o LD arch/x86/crypto/built-in.o LDS ...

whatday的专栏 1236

Linux下64位GCC编译生成32位程序

Linux下64位GCC编译生成32位程序

爱就是是恒久忍耐的专栏 1530

Linux Kernel 源码学习必备知识之:GCC 内联汇编(AT&T格式)

原因是即使寄存器只变动一部分,它的整体也会全跟着受影响,所以在 clobber/modiy 句中声明寄存器时,可以用低 8 位名称、低 16 位名称或低 32 位或全 64 位名称,如"al""ax""eax""rax" 都是指 rax 寄存器,其他通用寄存器也是一样的。单独强调一下,以上的 output() 和 intput() 括号中的是 C 代码中的变量,output(C变量)和input(C变量) 就像 C 语言中的函数,将 C 变量(值或变量地址)转换成汇编代码的操作数。

嵌入式Linux内核的博客 3311

asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范

asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范asp设计文档规范

上一篇: 下载安装和使用kdump的经历(一)
下一篇: 下载安装和使用kdump的经历(二)
ajige
博客等级 码龄16年 58粉丝 56原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值