上一篇文章学习了链接脚本的语法与相关概念:链接脚本的概念

在继续学习链接器的内容的同时,先学习一个新内容:内嵌汇编。

GCC编译器一般支持C/C++内嵌汇编语言,这样可以实现语言本身无法实现的内容。我们本文主要介绍C语言中的内嵌汇编,C++语言也是一样的规则。

首先要知道以下内容

x86汇编的两种语法:intel语法和AT&T语法
x86汇编一直存在两种不同的语法,在intel的官方文档中使
用intel语法,Windows也使用intel语法,而UNIX平台的汇编器一
直使用AT&T语法,所以本文是AT&T语法。 mov %edx,%eax 这条
指令如果用intel语法来写,就是 mov eax,edx ,寄存器名不加 % 号,
并且源操作数和目标操作数的位置互换。本文不详细讨论这两种
语法之间的区别,读者可以参考[AssemblyHOWTO]。
介绍x86汇编的书很多,UNIX平台的书都采用AT&T语法,例
如[GroudUp],其它书一般采用intel语法,例如[x86Assembly]。

1、C语言中的内嵌汇编

首先看一下C语言中的内嵌汇编语法格式:

  • 上述的volatile 关键字在以后会讲解具体作用,这里知道它是禁止编译器优化即可
  • 可选参数的意思是这些参数可以没有
  • 汇编指令是必须有的项

上述的解释还不是很好理解,我们可以看一个示例:


在以上的内嵌汇编中,我们可以来分析一下它的语法规则:

  • “movl %1, %0\n” 这句话中,%的意思是占位的意思。%1代表是一个input对应的寄存器,%0代表的是result对应的寄存器。他们所对应的寄存器是任意的,因为下面的限制符前面是 r ,代表编译器自动将通用寄存器与变量进行关联。当然这里也可以直接指定通用寄存器不用占位符。
  • 上面的图中说r这个限制符只是建议编译器用通用寄存器来与变量相关,但是编译器不一定听,比如如果在某个时候通用寄存器已经被被人占用,此时编译器就不会使用通用寄存器来关联我们的变量。
  • 带 = 号的是输出参数,即result是输出参数
  • input是输入参数
  • 上述汇编的意思是将输入参数input的值,传送给输出参数的值result。所以执行完上述汇编代码后,result与input的值都为1.

看了上面的解释,我们大概学会了内嵌汇编的组成,大致有汇编指令,这是必须存在的,可选参数,这是不必须存在的。

我们注意到上面的限制符 r 代表编译器指定一个通用寄存器关联变量,这是让编译器做主。但是我们也可以自己做主,自己指定一个寄存器来关联我们的变量。那么,都有哪些限制符以及他们对应的寄存器呢?常用的见下表:

上述的r代表通用寄存器,很明显与我们刚学习的一样。

1.1 代码实验

在知道了上述规则之后,我们来看看一个例子:

9-1.c

#include <stdio.h>int main()
{int result = 0;int input = 1;int a = 1;int b = 2;asm volatile ("movl %1, %0\n": "=r"(result): "r"(input));printf("result = %d\n", result);printf("input = %d\n", input);asm volatile ("movl %%eax, %%ecx\n""movl %%ebx, %%eax\n""movl %%ecx, %%ebx\n": "=a"(a), "=b"(b): "a"(a), "b"(b));printf("a = %d\n", a);printf("b = %d\n", b);return 0;
}

编译:

  • gcc 9-1.c

运行结果如下:

  • 分析上述代码
  1. 第一个汇编代码上述已经分析过,是将input值赋给result
  2. 第二个汇编代码块是交换a与b的值。
  • 分析第二个代码块

    • "=a"(a), "=b"(b) 代表输出参数,且将EAX寄存器与变量a关联,将EBX寄存器与b相连
    • "a"(a), "b"(b) 代表输入参数,且将EAX寄存器与变量a关联,将EBX寄存器与b相连
    • movl %%eax, %%ecx\n" 将EAX寄存器的值(也就是a的值)传送给ECX寄存器
    • movl %%ebx, %%eax\n" 将EBX寄存器的值(也就是b的值)传送给EAX,相当于将b赋给a
    • movl %%ecx, %%ebx\n" 将ECX寄存器的值(也就是a的值)传送给EBX,相当于将a赋给b

经过了上面的操作,就交换了a与b的值。

2、使用内嵌汇编进行系统调用

在程序中我们经常使用printf打印东西。中所周知,printf是一个系统函数,我们如何在不使用printf的前提下打印字符串?

使用内嵌汇编可以进行系统调用。

可以通过INT 0X80H 指令进行系统调用:

  • INT指令用于使用Linux内核服务(这是一个中断指令,众所周知中断会使执行流切换到内核)
  • 80H是一个中断向量号,用于执行系统调用

我们还是具体来看一个例子吧:

上面的解释是非常清楚的。注意区分传递的是立即数还是占位符即可。

同时,上面的例子 加了保留列表。它的意思是保留寄存器,不用于关联变量,因为这些寄存器已经被我们用于做系统调用以及传参数了。

那么上述汇编代码执行后参数s与参数l会被传给系统调用函数sys_write 。

下面再给出一个示例来看看与上面的示例有什么区别:

上面这个示例没有可选参数与保留列表。也就是没有输入输出参数,没有保留列表。

除了上述的区别,还有什么区别???

很明显,这个寄存器的前面是一个%,而刚刚那个是%%两个百分号。这是什么原因?

那么在内嵌汇编中就有如下注意事项:

  • 嵌入汇编时,除了汇编语言的指令不能省略,可选参数与保留列表都可以省略

  • 当省略的参数在中间时, 对应分隔符 “ : ”不可省略.如下图中的输出参数的分隔符:

  • 当省略保留列表时,对应的“ : ”可忽略

  • 当省略可选参数时,寄存器前使用 % 作为前缀

  • 当有可选参数时,寄存器前使用 %% 作为前缀

2.1 代码实验

在学习了上述的一系列原理后,我们写出如下代码:

9-2.c

#include <stdio.h>int main()
{char* s = "D.T.Software\n";int l = 13;printf("main begin\n");asm volatile("movl $4, %%eax\n""movl $1, %%ebx\n""movl %0, %%ecx\n""movl %1, %%edx\n""int $0x80     \n":: "r"(s), "r"(l): "eax", "ebx", "ecx", "edx");asm volatile("movl $1,  %eax\n""movl $42, %ebx\n""int $0x80     \n");printf("main end\n");return 0;
}

运行结果:

可以看出,我们使用内嵌汇编打印出了"D.T.Software\n"; 并且,在第二个汇编代码块中,进行系统调用调用了sys_exit 函数,直接退出进程运行了,所以第二个printf("main end\n"); 并没有执行。

当然我们也可以使用以下命令查看最近一次的一个进程退出时的退出状态码:

  • echo $?

得出:

很明显,退出状态码是42,正好与我们程序中写的一样。

3、 总结

  • C程序中支持内嵌汇编
  • 通过寄存器到汇编的关联,可以实现汇编到C程序的交互
  • 内嵌汇编代码时,可以使用占位符指定交互的变量
  • 限制符建议编译器将合适的寄存器关联到变量
  • 通过内嵌汇编可以直接进行系统调用

本文参考狄泰软件学院相关课程
想学习的可以加狄泰软件学院群,
群聊号码:199546072

学习探讨加个人(可以免费帮忙下载CSDN资源):
qq:1126137994
微信:liu1126137994

【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)相关推荐

  1. 【软件开发底层知识修炼】二十七 C/C++中的指针与数组是不同的

    上几篇文章学习了ABI-应用程序二进制接口:[软件开发底层知识修炼]二十六 ABI-应用程序二进制接口 学习总结文章目录 本篇文章就指针与数组的联系与区别来学习学习 文章目录 1 疑问 2 指针与数组 ...

  2. 【软件开发底层知识修炼】二十八 C/C++中volatile的作用

    上一篇文章学习了C/C++中的指针与数组的区别,点击链接进行查看:[软件开发底层知识修炼]二十七 C/C++中的指针与数组是不同的 本篇文章将学习volatile关键字在C/C++中的作用 文章目录 ...

  3. 【软件开发底层知识修炼】二十六 ABI-应用程序二进制接口 学习总结文章目录

    前面学习了ABI的知识,感觉受益良多.对底层与编译器有更加深刻的认识,为此这里将前面写过的关于ABI 的文章给列出来,方便学习与翻阅. [软件开发底层知识修炼]二十一 ABI-应用程序二进制接口一 [ ...

  4. 【软件开发底层知识修炼】二十五 ABI之函数调用约定二之函数返回值为结构体时的约定

    上一篇文章学习了几种函数调用约定的区别,点击链接查看上一篇文章:[软件开发底层知识修炼]二十四 ABI之函数调用约定 本篇文章继续学习函数调用约定中,关于函数返回值的问题.当函数返回值为结构体时,函数 ...

  5. 【软件开发底层知识修炼】二十四 ABI之函数调用约定

    上一篇文章学习了Linux环境下的函数栈帧的形成与摧毁.点击链接查看相关文章:软件开发底层知识修炼]二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁 本篇文章继续学习ABI接口相关的 ...

  6. 【软件开发底层知识修炼】二十二 ABI-应用程序二进制接口 二

    上一篇文章学习了ABI的相关内容,具体最后分析了不同ABI下结构体的对齐方式的不同.点击链接查看上一篇文章:[软件开发底层知识修炼]二十一 ABI-应用程序二进制接口一 本篇文章继续学习ABI相关内容 ...

  7. 【软件开发底层知识修炼】二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁

    上两篇文章我们初步接触了ABI-应用程序二进制接口的概念,点击链接查看上一篇文章:[软件开发底层知识修炼]二十二 ABI-应用程序二进制接口 二.了解了为什么会有ABI的存在.本篇文章继续学习ABI ...

  8. 【软件开发底层知识修炼】二十 深入理解可执行程序的结构

    上一篇文章记录了GDB调试从入门到熟练掌握的学习全过程.点击链接查看:[软件开发底层知识修炼]十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录 还记得在以前的学习Binutils工具的时候,学 ...

  9. 【软件开发底层知识修炼】二 深入浅出处理器之二 中断的概念与意义

    学习交流加 个人qq: 1126137994 个人微信: liu1126137994 学习交流资源分享qq群: 962535112 上一篇文章我们学习了微处理器与微控制器的区别.点击链接查看上一篇文章 ...

最新文章

  1. signature=cc0735b80de74e294c47d2b8d527fd10,Fungal Transposable Elements
  2. BundleFusion
  3. linux dev alloc name,深入理解Linux网络技术内幕-设备注册和初始化(二)
  4. Highcharts数据可视化工具功能效果图详解
  5. java面向接口编程详解
  6. 子进程 已安装 pre-removal 脚本 返回了错误号 1或2 与 子进程 已安装 post-installation 脚本 返回了错误号 1或2
  7. tushare基本用法
  8. DirectX9初步
  9. 扁平卡通风毕业论文答辩PPT模板
  10. 牛顿柯特斯求积公式matlab,牛顿-柯特斯求积公式总结.ppt
  11. 小学生如何用计算机写字,小学生练字笔顺电脑文章大全短文
  12. 2019年我能变强组队训练赛第十场 C Criss-Cross Cables(优先队列模拟)
  13. 单片机diy作品鉴赏,初学者进来膜拜
  14. 【软考笔记】1. 计算机原理与体系结构
  15. 小白踩坑记-Redis的安装与使用
  16. 对于算法工程师职业生涯规划的考虑
  17. 计算机系统(八):网络层(上篇)
  18. TokenGazer 深度研究 | Harmony:技术层面有一定创新 生态发展仍需时间验证
  19. 365技术网址导航源码附加交易系统
  20. 《单片机原理及应用》——概述

热门文章

  1. 玩转oracle 11g(37):rman备份-数据库指定文件恢复
  2. 整理一下网上看到的几个巧妙小电路
  3. ftp改为sftp_科普!一文详解 FTP、FTPS 与 SFTP 的原理
  4. [05] Session概要
  5. 轻快的VIM(三):删除
  6. 一个自动生成关键字索引页面的比处理文件
  7. 联想乐Pad_A1获取root权限
  8. 百度新闻的索引机制(二):智能聚类
  9. 导出sql文件_(一)SQL基本知识
  10. jQuery Validate 前端校验