第3章“程序的机器级表示”:程序编码 和 数据格式

news2024/11/23 1:06:50

文章目录

  • 3.2 程序编码
    • 3.2.1 机器级代码
    • 3.2.2 代码示例
    • 3.2.3 关于格式的注解
  • 3.3 数据格式

3.2 程序编码

假设写一个 C 程序,有两个文件 p1.cp2.c。然后用 Unix 命令行编译这段代码:

unix> gcc -O2 -o p p1.c p2.c
  • 命令 gcc 表明的就是 GNU C 编译器 GCC。因为这是 Linux 上默认的编译器,也可以简单地用 CC 来启动它。
  • 编译选项 -O2 告诉编译器使用第二级优化。通常,提高优化级别会使最终程序运行得更快,但是编译时间可能会变长,对代码进行调试会更困难。第二级优化是性能优化和使用方便之间的一种很好的妥协。本书的所有代码都是用这个优化级别进行编译的。

这个命令实际上调用了一系列程序,将源代码转化成可执行代码。

  • 首先,C预处理器 会扩展源代码,插入所有用 #include 命令指定的文件,并扩展所有的宏。
  • 其次,编译器 产生两个源文件的汇编代码,名字分别为 p1.sp2.s
  • 接下来,汇编器 会将汇编代码转化成二进制目标代码文件 p1.op2.o
  • 最后,链接器 将两个目标文件与实现标准 Unix 库函数(例如 printf)的代码合并,并产生最终的可执行文件。

3.2.1 机器级代码

在整个编译过程中,编译器会完成大部分的工作,将把用 C 提供的相对比较抽象的执行模型表示的程序转化成处理器执行的非常基本的指令。汇编代码表示非常接近于机器代码。与目标代码的二进制格式相比,汇编代码的主要特点是用可读性更好的文本格式表示的。能够理解汇编代码以及它是如何与原始的 C 代码相对应的,是理解计算机如何执行程序的关键一步。

汇编程序员看到的机器与 C 程序员看到的机器差别很大。一些通常对 C 程序员屏蔽的处理器状态是可见的:

  • 程序计数器(称为%eip)表示将要执行的下一条指令在存储器中的地址。
  • 整数寄存器文件包含 8 个被命名的位置,分别存储 32 位的值。这些寄存器可以存储地址(对应于 C 的指针)或整数数据。有的寄存器用来记录某些重要的程序状态,而其他的寄存器用来保存临时数据,例如过程的局部变量。
  • 条件码寄存器保存着最近执行的算术指令的状态信息。它们用来实现控制流中的条件变化,比如说用来实现 ifwhile 语句。
  • 浮点寄存器文件包含 8 个位置,用来存放浮点数据。

虽然 C 提供了一种模型,可以在存储器中声明和分配各种数据类型的对象,但是汇编代码只是简单地将存储器看成一个很大的、按字节寻址的数组。C中的聚集数据类型,例如数组和结构,在汇编代码中是用连续的字节表示的。即使是对标量数据类型,汇编代码也不区分有符号或无符号整数,不区分各种类型的指针,甚至于不区分指针和整数。

程序存储器(program memory)包含程序的目标代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的存储器块(比如说用 malloc 库函数分配的)。

程序存储器是用虚拟地址来寻址的。在任意给定的时刻,只有有限的一部分虚拟地址是合法的。例如,虽然 IA32 的 32 位地址可以寻址 4GB 的地址范围,但是一个通常的程序只会访问几 M 字节。操作系统负责管理虚拟地址空间,将虚拟地址转换成实际处理器存储器(processor memory)中的物理地址。

一条机器指令只执行非常基本的操作。例如,将两个存放在寄存器中的数字相加,在存储器和寄存器之间传递数据,或是条件分支转移到新的指令地址。编译器必须产生这些指令序列,从而实现像算术表达式求值、循环或过程调用和返回这样的程序结构。

3.2.2 代码示例

假设有如下的 C 代码文件 code.c,包含下面这样的过程定义:

int accum = 0;

int sum(int x, int y)
{
	int t = x + y;
	accum += t;
	return t;
}

在命令行上使用 “-S” 选项,就能看到 C 编码器产生的汇编代码:

unix> gcc -O2 -S code.c

这会使编译器产生一个汇编文件 code.s,但是不做其他进一步的工作(通常情况下,它还会调用汇编器产生目标代码文件)。

GCC 是按照它自己的格式产生汇编代码的,这种格式称为 GAS(Gnu ASsembler,GNU 汇编器)。 后续的讲述是基于这种格式的,它同 Intel 文档中的格式以及微软编译器使用的格式差异很大。

汇编代码文件包含各种声明,包括下面所示:

sum:
	pushl  %ebp
	movl %esp,%ebp
	movl 12(%ebp), %eax
	addl 8(%ebp), %eax
	addl %eax, accum
	movl %ebp, %esp
	popl %ebp
	ret

上面代码中的每个缩进去的行都对应于一条机器指令。比如,pushl 指令表示应该将寄存器 %ebp 的内容压入程序栈中。这段代码中已经除去了所有关于局部变量名或数据类型的信息,但我们还是看到了一个对全局变量 accum 的引用,这是因为编译器还不能确定这个变量会放在存储器中的哪个位置。

如果使用 -c 命令行选项,GCC 会编译并汇编该代码:

unix> gcc -O2 -c code.c

这就会产生目标代码文件 code.o,它是二进制格式的,所以无法直接读。852字节的文件 code.o 中有一段 19 字节的十六进制表示的序列:
在这里插入图片描述
这就是对应于上面列出的汇编指令的目标代码。从中得到的重要信息就是,机器实际执行的程序只是对一系列指令进行编码的字节序列。机器对产生这些指令的源代码几乎一无所知。

如何找到程序的字节表示?

首先,用反汇编器来确定函数 sum 的代码长是 19 字节。

然后,在文件 code.o 上运行 GNU 调试工具GDB,输入命令:

(gdb) x/19xb sum

这条命令告诉 GDB 检查(简写为 “x”)19 个十六进制格式(也简写为 “x”)的字节(简写为“b”)。


要查看目标代码文件的内容,有一类称为反汇编器(disassembler)的程序的价值无法估量,这些程序根据目标代码生成一种类似于汇编代码的格式。【注:反汇编器根据目标代码生成汇编代码】。

在 Linux 系统中,带 “-d” 命令行选项的程序 OBJDUMP (代表 “object dump”)可以充当这个角色。【注:程序 OBJDUMP 用 “-d” 命令行选项可充当反汇编器。

unix> objdump -d code.o

结果是(这里,在左边增加了行号,在右边增加了注解):
在这里插入图片描述
在左边,看到按照前面给出的字节顺序排列的 19 个十六进制字节值,它们分成了一些组,每组有 1 ~ 6 个字节。每组都是一条指令,右边是等价的汇编语言。其中一些特性值得说明:

  • IA32 指令长度从1 ~ 15 个字节不等。指令编码被设计成使常用的指令以及操作数较少的指令所需的字节数少,而那些不太常用或操作数较多的指令所需字节数较多。
  • 指令格式是按照这样一种方式设计的,从某个给定位置开始,可以将字节唯一地解码成机器指令。例如,只有指令 pushl %ebp 是以字节值 55 开头的。
  • 反汇编器只是根据目标文件中的字节序列来确定汇编代码的。它不需要访问程序的源代码或汇编代码。
  • 反汇编器使用的指令命名规则与 GAS 使用的有些细微的差别。示例中,省略了很多指令结尾的 “l”。
  • code.s 中的汇编代码相比,结尾多了一条 nop 指令。这条指令根本不会被执行(它在过程返回指令之后),即使执行了也不会有任何影响(所以称之为 nop,是 “no operation” 的简写,通常读作“no op”)。编译器插入这样的指令是为了填充存储该过程的空间。

生成实际可执行的代码需要对一组目标代码文件运行链接器,而这一组目标代码文件中必须含有一个 main 函数。假设在文件 main.c 中有下面这样的函数:

int main()
{
	return sum(1, 3);
}

然后,用如下方法生成可执行文件 test:

unix> gcc -O2 -o prog code.o main.c

文件 prog 变成了 11667 个字节,因为它不仅包含两个 过程的代码,还包含了用来启动和终止程序的信息,以及用来与操作系统交互的信息。也可以反汇编 prog 文件:

unix> objdump -d prog

反汇编器会抽取出各种代码序列,包括下面这段:
在这里插入图片描述
注意,这段代码与 code.o 反汇编产生的代码几乎一样。
一个主要的区别是左边列出的地址不同——链接器将代码的地址移到一段不同的地址范围。
第二个不同之处在于链接器终于确定存储全局变量 accum 的地址code.o 反汇编代码的第6行中,accum 的地址还是 0。prog 的反汇编代码中,地址就设成了 0x8049464。这可以从指令的汇编代码格式中看到,还可以从指令的最后四个字节中看出来,从最低位到最高位列出的就是64 94 04 08。

3.2.3 关于格式的注解

GCC 产生的汇编代码有点难读,它包含一些我们不需要关心的信息。另外,它不提供任何程序的描述或它是如何工作的描述。例如,假设文件 simple.c 包含下列代码:

int simple(int *xp, int y)
{
	int t = *xp + y;
	*xp = t;
	return t;
}

当带选项 “-S” 运行 GCC 时,它产生下面的文件 simple.s
在这里插入图片描述
文件包含的信息多于我们实际需要的。所有以“.” 开头的行都是指导汇编器和链接器的命令(directive),不过通常可以忽略这些行。 另一方面,也没有关于这些指令是干什么用的以及它们与源代码之间关系的解释说明。

为了更清楚地说明汇编代码,将给出汇编代码的格式,包括行号和解释性说明。对于上文的示例,带解释的汇编代码是如下这样的:

在这里插入图片描述在这里插入图片描述
通常我们只会给出与要讨论内容相关的代码行。每一行的左边都有编号供引用,右边是注释,简单地描述指令的效果以及它与原始 C 代码中的计算操作的关系。这是一种汇编语言程序员写代码的风格。

3.3 数据格式

由于是从 16 位体系结构扩展成 32 位的,Intel 用术语“字” 表示 16 位数据类型。 因此,称 32 位数为 “双字(double words)”,称 64 位数为 “四字(quad words)”。 我们将遇到的大多数指令都是对字节或双字操作的。

下图给出了对应 C 基本数据类型的机器表示。
在这里插入图片描述
注意,大多数常用数据类型都是作为双字存储的。 其中,包括普通整数(int)和长整数(long int),无论它们是否有符号。

此外,所有的指针(在此用 char * 表示)都是 4 字节的双字【注:32位系统指针占 4 字节,64位系统指针占 8 字节】。

处理字符串数据时,通常用到字节。

浮点数有三种形式:

  • 单精度(4字节)值,对应于 C 数据类型 float
  • 双精度(8字节)值,对应于 C 数据类型 double
  • 和扩展精度(10字节)值。

GCC 用数据类型 long double 来表示扩展精度的浮点值。为了提高存储器系统的性能,它将这样的浮点数存储成 12 字节数。虽然 ANSI C 标准包括 long double 数据类型,但是对大多数编译器和机器组合来说,它的实现和普通 double 的 8 字节格式是一样的。对 GCC 和 IA32 的组合来说,支持扩展精度是很少见的。

如上图所示,GAS 中的每个操作都有一个字符后缀,表明操作数的大小。例如,mov(传送数据)指令有三种形式:movb(传送字节),movw(传送字)和 movl(传送双字)。后缀 “l” 用来表示双字,因为在许多机器上,32 位数都称为 “长字(long word)”,这是沿用以 16 位字为标准的时代的习惯造成的。注意,GAS 使用后缀 “l” 来同时表示 4 字节的整数和 8 字节的双精度浮点数。这不会产生歧义,因为浮点数使用的是一组完全不同的指令和寄存器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式 Linux 入门(十一、make 和 MakeFile)

嵌入式 Linux 入门第十一课,Make 工具和 Makefile 的引入...... 矜辰所致目录 前言一、Linux 下多文件编译二、make 工具和 Makefile2.1 make 和 Makefile 是什么?2.2 通过 STM32 提前熟悉 Makefile2.3 GCC 与 make 的关系/区别? 三、一个简单的 Makefi…

〖Python网络爬虫实战㉙〗- Selenium案例实战(三)

订阅:新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列(零基础小白搬砖逆袭) 说明:本专栏持续更新中,目前专栏免费订阅,在转为付费专栏前订阅本专栏的,可以免费订阅付…

OpenGL之着色器

文章目录 什么是着色器数据类型输入与输出Uniform三角形渐变色例子从文件中读取 什么是着色器 着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。着色器的开头总是要声明版本,接着是输入和输…

攻击面管理有多重要?从一个社工钓鱼的仿冒网站说起

2023年4月中旬,A企业紧锣密鼓地展开了重保前期的筹备。A企业是一家集团公司,业务范围广,资产众多,为了提前了解自身安全情况,探知未知风险,公司通过自身资产清单及配套手段对自身资产暴露情况进行了梳理。 …

总结springboot项目中一些后端接收前端传参的方法

文章目录 1、java方法入参里面什么注解都没有2、不使用&#xff1f;&来拼接参数&#xff0c;在参数中添加PathVariable注解3、RequestBody 先创建一个springboot项目&#xff0c;并在pom文件中添加web依赖&#xff1a; <dependency><groupId>org.springframewo…

Linux:LVM动态磁盘管理

Linux中的LVM是什么 LVM&#xff08;Logical Volume Manager&#xff09;是Linux系统中的一种动态分区技术&#xff0c;它允许将多个物理硬盘上的存储空间组合成一个或多个逻辑卷&#xff08;Logical Volume&#xff09;&#xff0c;并且可以在运行时对逻辑卷进行调整。LVM的设…

Unity UI -- (7) 创建世界空间UI

目前为止&#xff0c;我们已经设计了一个屏幕空间UI&#xff08;Screen Space UI&#xff09;。一个屏幕空间UI会在屏幕上平坦放置&#xff0c;它会被渲染到环境中所有东西的上面&#xff0c;无论相机位置在哪里。 而一个世界空间UI&#xff08;World Space UI&#xff09;能够…

什么是半实物仿真平台自动驾驶半实物仿真平台有哪些?

文章目录 半实物仿真平台介绍自动驾驶半实物仿真平台介绍1.CARLA2.AirSim3.LGSVL Simulator 半实物仿真平台介绍 半实物仿真平台是一种综合利用虚拟仿真和实际硬件设备的仿真系统。它将虚拟环境和真实硬件设备结合起来&#xff0c;旨在提供更真实、更准确的仿真体验。 在半实…

Hack The Box真实靶机环境搭建教程

Hack The Box真实靶机环境搭建教程 1.开启测试靶机的方法2.Windows连接HTB3.Kali连接HTB 1.开启测试靶机的方法 在机器列表中选择一台主机&#xff1a; 选择加盟主机&#xff1a; 靶机开启成功&#xff1a; 2.Windows连接HTB 下载安装OpenVPN&#xff1a; 下载VPN&#xff08…

Linux---文本处理命令(grep、wc、管道符 |)

1. grep命令 grep命令能够在一个或多个文件中&#xff0c;搜索某一特定的字符模式&#xff08;也就是正则表达式&#xff09;&#xff0c;此模式可以 是单一的字符、字符串、单词或句子。 注意&#xff1a;在基本正则表达式中&#xff0c;如通配符 *、、{、|、( 和 )等&#…

【STM32G431RBTx】备战蓝桥杯嵌入式→决赛试题→第十三届

文章目录 前言一、题目二、模块初始化三、代码实现interrupt.h:interrupt.c:main.h:main.c: 四、完成效果五、总结 前言 无 一、题目 二、模块初始化 1.LCD这里不用配置&#xff0c;直接使用提供的资源包就行 2.ADC:开启ADCsingle-ended 3.LED:开启PC8-15,PD2输出模式就行了…

从前序与中序遍历序列构造二叉树(java)

从前序与中序遍历序列构造二叉树 leetcode 105 题-原题链接题目描述解题思路往期经典二叉树递归题目&#xff1a; leetcode 105 题-原题链接 从前序与中序遍历序列构造二叉树 题目描述 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&a…

【论文阅读系列】NWD-Based Model | 小目标检测新范式,抛弃IoU-Based暴力涨点(登顶SOTA) 计算机视觉

NWD-Based Model | 小目标检测新范式&#xff0c;抛弃IoU-Based暴力涨点(登顶SOTA) 计算机视觉 参考&#xff1a;博客1 知乎2 在这里进行纪录分享&#xff0c;这是有用的资料&#xff0c;避免之后再寻找相当麻烦。 小目标检测是一个非常具有挑战性的问题&#xff0c;因为小目…

监控易:信创工程,几十万台终端设备桌面集中监控运维方案​

监控易&#xff1a;信创工程&#xff0c;几十万台终端设备桌面集中监控运维方案 从2019年开始,我国因国际国内形势的迫切要求,在信息和网络安全方面启动 “安全可靠工程”,全面深入推进信创运维及相关产品国产化。时至今日&#xff0c;已取得令世人瞩目的成果。 过去&#xff…

learn_C_deep_14 (条件编译的基本使用与理解)

目录 条件编译 1.条件编译如何使用&#xff1f; 2.为何要有条件编译? 3. 条件编译都在哪些地方用? 条件编译 1.条件编译如何使用&#xff1f; C语言的条件编译是一种在程序编译时根据条件选择不同代码段进行编译的技术。条件编译可以用于实现代码跨平台&#xff0c;开启…

C++小知识点(auto关键字)

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

猿创征文|Spring系列框架之面向切面编程AOP

⭐️前面的话⭐️ 本篇文章将介绍一种特别重要的思想&#xff0c;AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff0c;即面向切面编程&#xff0c;可以说是OOP&#xff08;Object Oriented Programming&#xff0c;面向对象编程&#xff09;的补充和完善。 …

Springcloud1---->Zuul网关

目录 简介加入zuul后的架构快速入门添加Zuul依赖编写zuul启动类编写zuul配置文件编写路由规则 面向服务的路由添加Eureka客户端依赖开启Eureka客户端发现功能添加Eureka配置&#xff0c;获取服务信息修改映射配置&#xff0c;通过服务名称获取 简化的路由配置过滤器使用场景自定…

这个 堆排序详解过程 我能吹一辈子!!!

文章目录 堆排序的概念堆的分类堆排序的算法思想堆排序的实现 堆排序的概念 堆是一种叫做完全二叉树的数据结构&#xff0c;可分为大根堆、小根堆&#xff0c;而堆排序就是基于这种结构产生的一种排序的算法。 堆的分类 大根堆&#xff1a;每个节点的值都大于或者等于它的左…

SpringBoot 读取 yml 文件属性值常用法总结

开发过程中有一些常量配置一般会写在application.yml文件中&#xff0c;而Spring Boot读取yml文件的主要方式有以下几种: 一、使用Value注解 在bean的属性上使用Value注解,直接读取yml中的值,如: 但这里面写法也有一些情况&#xff1a;其实这种写法对于 String 字符串其实没有…