使用arm混合汇编计算两个64位的和_《深入理解计算机系统》读书笔记 —— 第三章 程序的机器级表示...

本文是《深入理解计算机系统》读书笔记的一部分,详细介绍了计算机程序的机器级表示,特别是使用ARM混合汇编进行64位和计算。内容涵盖汇编代码中的寄存器、机器代码示例、反汇编简介、数据格式、访问信息、算数和逻辑操作、过程控制、数组分配和访问。强调了学习汇编语言对于理解计算机底层运作、优化代码和处理程序安全问题的重要性。
摘要由CSDN通过智能技术生成

本章主要介绍了计算机中的机器代码——汇编语言。当我们使用高级语言(C、Java等)编程时,代码会屏蔽机器级的细节,我们无法了解到机器级的代码实现。既然有了高级语言,我们为什么还需要学习汇编语言呢?学习程序的机器级实现,可以帮助我们理解编译器的优化能力,可以让我们了解程序是如何运行的,哪些部分是可以优化的;当程序受到攻击(漏洞)时,都会涉及到程序运行时控制信息的细节,很多程序都会利用系统程序中的漏洞信息重写程序,从而获得系统的控制权(蠕虫病毒就是利用了gets函数的漏洞)。特别是作为一名嵌入式软件开发的从业人员,会经常接触到底层的代码实现,比如Bootloader中的时钟初始化,重定位等都是用汇编语言实现的。虽然不要求我们使用汇编语言写复杂的程序,但是要求我们要能够阅读和理解编译器产生的汇编代码。

@[toc]

程序编码

计算机的抽象模型

在之前的《深入理解计算机系统》(CSAPP)读书笔记 —— 第一章 计算机系统漫游文章中提到过计算机的抽象模型,计算机利用更简单的抽象模型来隐藏实现的细节。对于机器级编程来说,其中两种抽象尤为重要。第一种是由指令集体系结构或指令集架构( Instruction Set Architecture,ISA)来定义机器级程序的格式和行为,它定义了处理器状态指令的格式,以及每条指令对状态的影响。大多数ISA,包括x86-64,将程序的行为描述成好像每条指令都是按顺序执行的,一条指令结束后,下一条再开始。处理器的硬件远比描述的精细复杂,它们并发地执行许多指令,但是可以采取措施保证整体行为与ISA指定的顺序执行的行为完全一致。第二种抽象是,机器级程序使用的内存地址是虚拟地址,提供的内存模型看上去是一个非常大的字节数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。

汇编代码中的寄存器

程序计数器(通常称为“PC”,在x86-64中用号%rip表示)给出将要执行的下一条指令在内存中的地址。

整数寄存器文件包含16个命名的位置,分别存储64位的值。这些寄存器可以存储地址(对应于C语言的指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他的寄存器用来保存临时数据,例如过程的参数和局部变量,以及函数的返回值。

条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,比如说用来实现if和 while语句

一组向量寄存器可以存放个或多个整数或浮点数值

关于汇编中常用的寄存器建议看我整理的嵌入式软件开发面试知识点中的ARM部分,里面详细介绍了Arm中常用的寄存器和指令集。

机器代码示例

假如我们有一个main.c文件,使用 gcc -0g -S main.c可以产生一个汇编文件。接着使用gcc -0g -c main.c就可以产生目标代码文件main.o。通常,这个.o文件是二进制格式的,无法直接查看,我们打开编辑器可以调整为十六进制的格式,示例如下所示。

53 48 89 d3 e8 00 00 00 00 48 89 03 5b c3

这就是汇编指令对应的目标代码。从中得到一个重要信息,即机器执行的程序只是一个字节序列,它是对一系列指令的编码。机器对产生这些指令的源代码几乎一无所知。

反汇编简介

要查看机器代码文件的内容,有一类称为反汇编器( disassembler)的程序非常有用。这些程序根据机器代码产生一种类似于汇编代码的格式。在 Linux系统中,使用命令 objdump -d main.o可以产生反汇编文件。示例如下图。

485774bb4381afe7c62af25cc1eb0e4c.png

在左边,我们看到按照前面给出的字节顺序排列的14个十六进制字节值,它们分成了若干组,每组有1~5个字节。每组都是一条指令,右边是等价的汇编语言

其中一些关于机器代码和它的反汇编表示的特性值得注意

  • x86-64的指令长度从1到15个字节不等。常用的指令以及操作数较少的指令所需的字节数少,而那些不太常用或操作数较多的指令所需字节数较多

  • 设计指令格式的方式是,从某个给定位置开始,可以将字节唯一地解码成机器指令。例如,只有指令 push%rbx是以字节值53开头的

  • 反汇编器只是基于机器代码文件中的字节序列来确定汇编代码。它不需要访问该程序的源代码或汇编代码

  • 反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些细微的差别。在我们的示例中,它省略了很多指令结尾的‘q’。这些后缀是大小指示符,在大多数情况中可以省略。相反,反汇编器给ca11和ret指令添加了‘q’后缀,同样,省略这些后缀也没有问题。

数据格式

Intel用术语“字(word)”表示16位数据类型。因此,称32位数为“双字( double words)”,称64位数为“四字( quad words)。下表给出了C语言基本数据类型对应的x86-64表示。

访问信息

操作数指示符
整数寄存器

不同位的寄存器名字不同,使用的时候要注意。

00ecfd4002c6792ab6561a2719096064.png
三种类型的操作数

1.立即数,用来表示常数值,比如,$0x1f 。不同的指令允许的立即数值范围不同,汇编器会自动选择最紧凑的方式进行数值编码。

2.寄存器,它表示某个寄存器的内容,16个寄存器的低位1字节、2字节、4字节或8字节中的一个作为操作数,这些字节数分别对应于8位、16位、32位或64位。在图3-3中,我们用符号${r_a}$来表示任意寄存器a,用引用$R[{r_a}]$来表示它的值,这是将寄存器集合看成一个数组R,用寄存器标识符作为索引

3.内存引用,它会根据计算出来的地址(通常称为有效地址)访问某个内存位置。因为将内存看成一个很大的字节数组,我们用符号${M_b}[Addr]$表示对存储在内存中从地址Addr开始的b个字节值的引用。为了简便,我们通常省去下标b。

操作数的格式

看汇编指令的时候,对照下图可以读懂大部分的汇编代码。

8ba9619f3622ef8a5bd6b50237efe514.png
数据传送指令

8439523c0acd06373388c83e2c4a74cf.png

不同后缀的指令主要区别在于它们操作的数据大小不同。

源操作数:寄存器,内存

目的操作数:寄存器,内存。

注意:传送指令的两个操作数不能都指向内存位置。将一个值从一个内存位置复制到另一个内存位置需要两条指令—第一条指令将源值加载到寄存器中,第二条将该寄存器值写入目的位置。

movl $0x4050,%eax         Immediate--Register,4 bytes p,1sp  move 
movw %bp,%sp              Register--Register, 2 bytes
movb (%rdi. %rcx),%al     Memory--Register  1 bytes
movb $-17,(%rsp)          Immediate--Memory 1 bytes
movq %rax,-12(%rpb)       Register--Memory, 8 bytes

将较小的源值复制到较大的目的时使用如下指令。

70248f7713949461fb04509aa1eda8fa.png

9cf450c1e6e866f01a52e8ee206a453d.png

举例

2359a26a1bc9e1ff50a92afc5e8b1392.png

过程参数xp和y分别存储在寄存器%rdi和%rsi中(参数通过寄存器传递给函数)。

第二行:指令movq从内存中读出xp,把它存放到寄存器%rax中(像x这样的局部变量通常是保存在寄存器中,而不是在内存中)。

第三行:指令movq将y写入到寄存器%rdi中的xp指向的内存位置。

第四行:指令ret用寄存器 %rax从这个函数返回一个值。

总结:

间接引用指针就是将该指针放在一个寄存器中,然后在内存引用中使用这个寄存器。

像x这样的局部变量通常是保存在寄存器中,而不是内存中。访问寄存器比访问内存要快得多。

压入和弹出栈数据

0acb3b8df8c54947385631049282d351.png

pushq指令的功能是把数据压入到栈上,而popq指令是弹出数据。这些指令都只有一个操作数——压入的数据源和弹出的数据目的。

pushq %rbp等价于以下两条指令:

subq $8,%rsp             Decrement stack
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值