程序的机器级表示

本文深入探讨了程序的机器级表示,包括生成汇编语言、寄存器保存方式、汇编指令解析、寄存器发展历史以及操作数指示符。文章详细介绍了如何使用汇编指令如`pushq`, `movq`, `call`等,并讨论了如何在函数调用中保存和恢复寄存器状态。此外,还涵盖了数据传送、程序栈、加载有效地址、移位操作、条件码、跳转指令、循环、switch语句以及参数传递和局部存储的实现。通过对这些概念的解析,读者可以更好地理解计算机底层的工作原理。" 102616438,5882991,SVM深度解析:最大间隔、对偶问题与核函数,"['机器学习', 'SVM', '支持向量机', '线性分类', '核函数']

生成汇编语言

在这里插入图片描述
使用以下命令生成汇编文件mstore.s并用vim打开:
linux > gcc -Og -S mstore.c
在这里插入图片描述

· 开头的行可以忽略
pushq 这条指令是将寄存器的值压入程序栈进行保存

两种保存器

在这里插入图片描述
A称为调用者,B称为被调用者。由于调用了函数B,寄存器rbx在函数b中被修改,逻辑上寄存器rbx的内容在用函数b的前后应该保持一致。有两种解决方法

  • 函数A在调用B之前,先保存rbx的内容,执行完函数b之后,再回复rbx原来的内容,称为调用者保存, Caller
  • 函数b在使用寄存器rbx之前,先保存rbx的值,b返回之前先恢复rbx原来存储的内容,称为被调用者保存,Callee


不同的寄存器被定义成不同的策略

汇编拆解

在这里插入图片描述
1 mulstore三个参数分别保存在寄存器rdi, rsi, rdx中,
2 movq将rdx中的内容复制到rbx中,movq的q表示数据大小
3 call 指令对应函数调用,函数的返回值会保存到rax中
4 下一条指令将rax值送到内存中,内存的地址就存放在rbx中

在这里插入图片描述
gcc 数据传送指令四个变种,分别为movb, movw, movl, movq

寄存器发展史

直到现在,8个16为寄存器扩展成了64位,害增加了8个新的寄存器
在这里插入图片描述
在这里插入图片描述
rax保存目标函数的返回值,rsp保存程序栈结束位置,剩下的用来传递函数参数

在这里插入图片描述

操作数指示符

立即数以$符号开头,后跟一个C定义的整数
寄存器带小括号表示内存引用。我们通常将内存抽象成一个字节数组,当需要从内存中存取数据时,需要获得目的数据的起始地址addr, 以及数据长度b。为了简便,会省略下标b

I m m ( r b , r i , s ) ( 有 效 地 址 ) = I m m ( 立 即 数 ) + R [ r b ] + R [ r i ] ⋅ s Imm(r_b, r_i, s) (有效地址) =Imm(立即数) + R[r_b] + R[r_i]·s Imm(rb,ri,s)()=Imm+R[rb]+R[ri]s
r b r_b rb 基址寄存器
r i r_i ri 变址寄存器
s 比例因子 必须是1,2,4,8

在这里插入图片描述

mov指令

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当movq指令的原操作数是立即数时,该立即数只能是32位的补码表示,对该数符号位扩展后,将得到的64位数传送到目的位置。

在这里插入图片描述
movabsq指令的源操作数可以是任意的64位立即数,单目的操作数只能是寄存器。

例子:
在这里插入图片描述
此时寄存器rax内保存的数值如图所示:
在这里插入图片描述
8位(bit), 1的原码=0x01,取反=0xfe,再加1=0xff
同理,16位(short)时=0xffff, 32位(int32)时=0xffffffff
(每个数字表示4个二进制数字)

在这里插入图片描述
在这里插入图片描述
al的长度为8,-1表示为0xff,所以rax的低8位发生了改变

在这里插入图片描述
在这里插入图片描述
ax的长度是16,所以-1表示为0xffff,rax的低16位发生了改变

在这里插入图片描述
eax的长度是32,但当movl的目的操作数是寄存器时,它会把该寄存器的高4字节设置为0
即任何长度的寄存器生成32位值的指令都会把该寄存器的高位部分置为0

在这里插入图片描述
源操作数的数位小于目的操作数时,需要对目的操作数剩余的字节进行零扩展或者符号位扩展
零扩展数据传送指令有5条,指令最后两个字符都是大小指示符,第一个字母表示源操作数的大小,第二个字母表示目的操作数的大小。
在这里插入图片描述
符号位扩展传送指令有6条,其中s是sign的缩写,同样最后两个字符也是大小指示符。
在这里插入图片描述
数据传送实例
在这里插入图片描述
第一条mov指令从内存中读取数值到寄存器,内存地址保存在寄存器rdi中,目的操作数是寄存器rax,由于最后函数exchange需要返回变量x的值,所以直接将变量x放到寄存器rax中。

第二条mov指令将变量y的值写道内存里,变量y存储在寄存器rsi中,内存地址保存至寄存器rdi中,也就是xp指向的内存地址。

程序栈

例如我们要保存寄存器rax内存储的数据0x123,可以使用pushq指令把数据压入栈内,该指令指令执行的过程可以分解为两步:
在这里插入图片描述
在这里插入图片描述

1 压栈之前,栈顶指针rsp指向栈顶的位置,此处的内存地址0x108, 压栈的第一步就是寄存器rsp的值减8,此时指向内存地址0x100
2 然后将需要保存的数据复制到新的栈顶地址,此时,内存地址0x100处将保存寄存器rax内存储的数据0x123。

pop指令也可以分为两步
1 首先从栈顶的位置读出数据,复制到寄存器rbx。此时,栈顶指针rsp指向的内存地址是0x100

2 然后将栈顶指针加8,pop后栈顶指针rsp指向的内存地址是0x108
在这里插入图片描述
pop指令是通过修改栈顶指针所指向的内存地址来实现数据删除的,内存地址0x100内所保存的数据0x123仍然存在,知道下次push操作,此处的数据才会被覆盖。

加载有效地址

leaq S, D ->Load Effective Address

leaq 7(%rdx, %rdx, 4), %rax
表示的含义是把有效地址复制到寄存器rax中

假设寄存器rdx内保存的数值为x, 那么有效地址的值为
7 + %rdx + %rdx *4 = 7 + 5x

对于leaq指令所执行的操作不是去内存地址(5x+7)处读取数据,而是将有效地址(5x + 7)这个值直接写入到目的寄存器rax.

leaq指令还可以用来表示加法和有限的乘法运算。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一条指令将x + 4y 保存到rax里
第二条指令将3
z 保存到rdx里
第三条指令将 x + 4y + 4z 保存到rax里
(由于比例因子的取值只能是1,2,4,8,所以把12进行分解)

一元和二元操作

一元操作数,该操作数既是原操作数,也是目的操作数,操作数可以是寄存器,也可以是内存地址
在这里插入图片描述
二元操作数,第一个是源操作数,第二个操作数既是源操作数,也是目的操作数,但不能是立即数
在这里插入图片描述
例子:
在这里插入图片描述
指令addq 是将rax内的数据与寄存器rcx内的数据相加,二者之和再存储到内存地址0x100处,该指令执行完毕后,内存地址0x100处所存储的数据由0xFF变成0x100
在这里插入图片描述

指令subq是将内存地址0x108内的数据减去寄存器rdx内的数据,再存储到内存0x108处,0x108的数据由0xAB变为0xA8
在这里插入图片描述
incq指令,将内存地址0x110内存储的数据+1,结果内存地址0x110处的数据由0x13变为0x14

最后一条加法指令,将寄存器rax内的值减去寄存器rdx内的值,最终寄存器rax的值由0x100变为0xFD
在这里插入图片描述
(rax)表示rax寄存器的地址,改变的是内存地址的值
rax表示rax寄存器内存储的值,改变的是寄存器的值,不改变内存

移位操作

在这里插入图片描述
算术右移补符号位,逻辑右移补0

对于移位量k,可以是一个立即数,或是放在寄存器cl中的数,其他寄存器不行。

salb移位量由寄存器cl低3位决定,
salw移位量由寄存器cl低4位决定
以此类推,双字对应低5位,四字对应低6位

在这里插入图片描述
在这里插入图片描述
第一条 R[%rdx] + R[%rdx]2 = 3z -> %rax
第二条将rax进行左移4位,等效于乘以2的四次方,24R[%rax] = 48z

其他算数指令操作

在这里插入图片描述

条件码

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
mov指令对寄存器al进行零扩展,最后返回判断结果

另一个例子
在这里插入图片描述
在这里插入图片描述
sete变成了setl,指令setl是如果a小于b,将寄存器al设置为1, l是less的缩写
在这里插入图片描述
判断小于的情况,根据SF(符号标志)和OF溢出标志的异或来判定。
两个有符号数相减,当没有发生溢出时,OF是0,
负数时,SF置为1,正数时,SF不会被置1

负溢出,差值变为正数
在这里插入图片描述
正溢出,差值变为负数
在这里插入图片描述
通过组合,可以表示各种情况
在这里插入图片描述
对于无符号数的比较情况,采用的是进位标志和零标志的组合
在这里插入图片描述

跳转指令

在这里插入图片描述
在这里插入图片描述
条件语句x小于y由指令cmp来实现
跳转指令jl,根据SF和OF的异或结果来判断究竟是顺序执行,还是跳转到L4处执行。跳转指令会根据条件寄存器的某种组合来决定是否进行跳转。
在这里插入图片描述
用数据的条件转移来代替控制的条件转移,效率会提高
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

循环

C语言由三种循环结构,do-while,while,for,汇编语言的循环语句是通过条件测试导跳转的结合来实现的。
在这里插入图片描述
cmpq与跳转指令的组合实现了循环操作。

在这里插入图片描述
do-while循环是先执行循环体的内容,然后再进行循环测试,while循环是先进行循环测试,根据测试结果判断是否执行循环体内容。

switch 语句

在这里插入图片描述
对应的汇编指令
在这里插入图片描述
在这里插入图片描述

运行时栈

当函数执行所需要的存储空间超出寄存器能够存放的大小时,就会借助栈上的存储空间,我们把这部分存储空间称为函数的栈帧。

返回地址

假设函数P调用函数Q, 函数Q执行完返回函数P
在这里插入图片描述
当函数P调用Q时,会把返回地址压入栈中,该地址指明了当Q执行结束返回时,要从函数P的哪个位置继续执行,这个return address的操作是由函数调用call来实现的。

参数传递

如果一个函数的参数数量大于6,超出部分就要通过栈来传递
在这里插入图片描述
在这里插入图片描述
参数7和参数8是通过栈来传递的。
在这里插入图片描述
通过栈来传递参数时,所有的数据大小都是向8的倍数对齐,a4是char,只占一个字节,但分配了8个字节
在这里插入图片描述
寄存器名字的使用取决于传递参数的大小

栈上的局部储存

在这里插入图片描述
在这里插入图片描述
caller的汇编代码
在这里插入图片描述
subq指令,栈顶指针减去16,在栈上分配16个字节的空间
根据两条mov指令推断出,arg1和arg2存储在caller的栈帧上,
接下来分别计算arg1 arg2存储的地址
执行call指令调用swap函数
最后通过栈顶指针+16的操作来释放栈帧

例子
在这里插入图片描述
在这里插入图片描述
可以看出x1占8个字节,x2占4个字节,x3占2个字节,x4占1个字节,局部变量不需要对齐
proc函数需要8个参数,7,8需要通过栈帧传递,参数需要8个字节对齐

寄存器中的局部存储空间

在这里插入图片描述
由于函数Q需要rdi来传递参数x,因此函数P需要保存rdi中的x;保存x使用寄存器rbp,rbp定义为调用者保存寄存器,于是有开头的pushq %rbp指令,pushq %rbx是同样的道理。
在函数P返回之前,使用pop指令恢复寄存器rbp和rbx的值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值