CSAPP-第三章程序的机器级表示(上)

3.1程序编码

实际的汇编语言有多个版本根据处理器的指令集来决定有哪些命令这里讲述的是其中一种比较简单的汇编语言,汇编语言现在的应用有逆向工程和一些嵌入式硬件的编程。

3.1.1机器级代码

x86-64的机器代码和原始的C代码差别非常大。一些通常对C语言程序员隐藏的处理器状态是可见的:

  • 程序计数器:给出将要执行的下一条指令在内存中的地址。
  • 整数寄存器文件:包含16个命名的位置,分别存储64位的值。这些寄存器可以存储地址或整数数据。有的寄存器被用来记录某些重要的程序状态,而其它寄存器用来保存临时数据(参数,返回值,局部变量)。
  • 条件码寄存器:保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,比如用来实现if和while语句。
  • 一组寄存器可以存放一个或多个整数或浮点数的值。

 C语言的数据类型对于汇编语言来说只是一组连续的字节数,C语言中的数组和结构体在汇编语言上是一个很大的,按字节寻址的数据类型。

3.1.2代码示例 

C语言代码:

long mult2(long,long);
void mulstore(long x,long y,long *data){
    long t=mult2(x,y);
    *dest=t;
}
 

对应的汇编代码:

multstore:
    pushq  %rbx
    movq   %rdx, %rbx
    call   mult2
    movq   %rax, (%rbx)
    popq   %rbx
    ret

上面发代码每个缩进都对应于一条机器指令。比如pushq指令表示应该将寄存器%的内容压入程序栈。

3.2数据格式

C声明和本书所讲的汇编的数据类型对应如下表:

C声明汇编数据类型汇编代码后缀大小(字节)
char字节b1
shortw2
int双字l4
long四字q8
char*四字q8
float单精度s4
double双精度l8

注意:表中的l既是双字也是双精度是因为整型数据和浮点数所使用的指令和寄存器不同,图中的各数据类型都是32位的。

3.3访问信息

一个x86-64的CPU包含一组16个存储64位值的通用目的寄存器。这些寄存器用来存储整数数据和指针。下图显示了这16个寄存器。它们名字都以%r开头。规律可以自己看出

63311570
%rax%eax%ax%al返回值
%rbx%ebx%bx%bl被调用者保存
%rcx%ecx%cx%cl第四个参数
%rdx%edx%dx%dl第三个参数
%rsi%esi%si%sil第二个参数
%rdi%edi%di%dil第三个参数
%rbp

%ebp

%bp%bpl被调用者保存
%rsp%esp%sp%spl栈指针
%r8%r8d%r8w%r8b第五个参数
%r9%r9d%r9w%r9b第六个参数
%r10%r10d%r10w%r10b调用者保存
%r11%r11d%r11w%r11b调用者保存
%r12%r12d%r12w%r12b被调用者保存
%r13%r13d%r13w%r13b被调用者保存
%r14%r14d%r14w%r14b被调用者保存
%r15%r15d%r15w%r15b被调用者保存

表头的第一行表示操作的位数(注意是从0算其),最后一列表示寄存器的作用。

3.3.1操作数指示符

大多数指令有一个或多个操作数,指示出执行一个操作要使用的源数据值,以及放置结果的目的位置。x86-64支持多种操作数格式,包括三种,第一种是立即数,用来表示常数值,第二种是寄存器,它表示某个寄存器的内容。第三种是内存引用,它会根据计算出来的地址访问某个内存位置。操作数格式如下图;

类型格式操作数值名称
立即数$ImmImm立即数寻址
寄存器r1R[r1]寄存器寻址
存储器ImmM[Imm]绝对寻址
存储器(r1)M[R[r1]]间接寻址
存储器Imm(r2)M[Imm+R[r2]](基址+偏移量)寻址
存储器

(r1,r2)

M[R[r1]+R[r2]]变址寻址
存储器Imm(r1,r2)M[Imm+R[r1]+R[r2]]变址寻址
存储器(,r1,s)M[R[r1]*s]比例变址寻址
存储器Imm(,r1,s)M[Imm+R[r1]*s]比例变址寻址
存储器(r1,r2,s)M[R[r1]+R[r2]*s]比例变址寻址
存储器Imm(r1,r2,s)M[Imm+R[r1]+R[r2]*s]比例变址寻址

如下是个示例:

地址
0x1000xFF
0x1040xAB
0x1080x13
0x10C0x11
寄存器
%rax0x100
%rcx0x1
%rdx0x3

这是书上的一道题这里就作为一个示例:

操作数注释
%rax0x100寄存器
0x1040xAB绝对地址
$0x1080x108立即数
(%rax)0xFF地址0x100
4(%rax)0xAB地址0x104
9(%rax,%rdx)0x11地址0x10C
260(%rcx,%rdx)0x13地址0x108
0xFC(,%rcx,4)0xFF地址0x100
(%rax,%rdx,4)0x11地址0x10C

 3.3.2数据传送指令

最频繁使用的指令是将一个数据从一个位置复制到另一个位置的指令。数据传送指令就是用来实现赋值操作的具体命令如下表

指令效果描述
Mov  S,DD<-S传送
movb传送字节
movw传送字
movl传送双字
movq传送四字
movabsq I,RR<-I传送绝对的四字

这些mov指令都是由mov+数据类型后缀组成,mov指令其第一个操作数即S是其传送位置的值可以是一个立即数,存储在寄存器或内存中。第二个是目的操作数是其要赋值的位置可以是一个内存地址或是一个寄存器。

下面给出一些示例:

movl $0x4050,%eax
movw %bp,%sp
movb (%rdi,%rcx),%al
movb %-17,(%rsp)
movq %rax,-12(%rbp)

第一行传送的是双字,传送的值是个立即数,目的地是寄存器

第二行传送的是字,传送的是寄存器的值,目的的是寄存器

第三行传送的是字节,传送的是对应内存地址的值,目的地是寄存器

第四行传送的是字节,传送的是个立即数,目的地是寄存器的值的对应内存地址

第五行传送的是四字,传送的是寄存器的值,目的地是内存地址

除了上述的传送指令还有零扩展和符号扩展的传送指令

零扩展mov指令
指令效果描述

MOVZ S,R

R<-零扩展(S)以零扩展进行传送
movzbw将做了零扩展的字节传送到字
movzbl将做了零扩展的字节传送到双字
movzwl将做了零扩展的字传送到双字
movzbq将做了零扩展的字节传送到四字
movzwq将做了零扩展的字传送到四字

符号扩展mov指令 

指令效果描述
MOVS S,RR<-符号扩展(S)传送符号扩展的字节
movsbw将做了符号扩展的字节传送到字
movsbl将做了符号扩展的字节传送到双字
movswl将做了符号扩展的字传送到双字
movsbp将做了符号扩展的字节传送到四字
movswp将做了符号扩展的字传送到四字
movslq将做了符号扩展的双字传送到四字
cltq%rax<-符号扩展(%eax)把%eax符号扩展到%rax

数据传送示例:

C语言代码:

long exchange(long *xp , long y)
{
    long x = *xp;
    *xp = y;
    return x;
}

汇编代码:

exchange:
    movq (%rdi),%rax
    movq %rsi, (%rdi)
    ret

其中xp在rdi,y在rsi

3.3.3压入和弹出栈数据

首先先说一下什么是栈

所谓栈就是内存上用来存放程序函数数据的内存区域即用来存放函数的局部变量和函数的代码段的一块内存地址。

所有的程序都是从man函数开始的才程序运行时首先会把man函数压入栈当调用其它函数时也会把调用的函数压入栈当调用完毕后就将该函数弹出栈在高级语言的编写中不需要考虑,但是汇编语言还是需要学习一下的其命令如下。

指令效果描述
pushq S

R[%rsp]<-R[%rsp]-8;

M[R[%rsp]<-S;

将四字压入栈
popq  D

D<-M[R[%rsp];

R[%rsp]<-R[%rsp]+8;

将四字弹出栈

pushq %rbp的行为等价于:

    subq $8 %rsp
    movq %rbp,(%rsp)

下图是一个栈操作的说明图:

 popq %rdx的行为等价于:

    movq (%rsp),%rax
    addq $8,%rsp

3.4算术和逻辑操作

下表列出了汇编语言的各种算术和逻辑运算的指令:

指令效果描述
leaq  S,DD<-&S加载有效地址

INC      D

DEC     D

 NEG    D

 NOT    D

 D<-D+1

 D<-D-1

D<- -D

 D<- ~D

加1

减1

取负

取补

ADD   S,D

SUB   S,D

IMUL  S,D

XOR   S,D

OR     S,D

AND   S,D

D<-D+S

D<-D-S

D<-D*S

D<-D^S

D<-D|S

D<-D&S

异或

SAL  k,D

SHL  k,D

SAR  k,D

SHR  k,D

D<-D<<k

D<-D<<k

D<-D>>k

D<-D>>k

左移

左移(等同于SAL)

算术右移

逻辑右移

3.4.1加载有效地址 

指令leap实际上是movq指令的变体,它的指令形式是从内存读数据到寄存器,但实际它根本没有引用内存。

3.5控制

3.5.1条件码

条件码可以看作是C语言的一些逻辑判断之后的结果不过这里有了分类在C语言里只有两种结果,而在汇编则是分为各种不同的寄存器来负责存放结果。

除了整数寄存器,CPU还维护着一组单个位的条件码寄存器,它们描述了最近的算术或逻辑操作的属性。可以检测这些寄存器来执行分支指令。最常用的条件码有:

CF:进位标志。最近操作使最高位产生了进位。可用来检测无符号的溢出。

ZF:零标志。最近的操作得出的结果为0。

SF:符号标志。最近的操作结果为负数。

OF:溢出标志。最近的操作导致一个补码溢出——正溢出或负溢出。

除了逻辑操作指令外还有如下指令(这些指令只负责设置条件码不会改变任何寄存器或内存,就类似于C语言中的逻辑表达式但是没有赋值操作)可以设置条件码:

指令基于描述

CMP       S1,S2

cmpb

cmpw

cmpl

cmpq

 S2-S1

比较

比较字节

比较字

比较双字

比较四字

TEST     S1,S2

testb

testw

testl

testq

S1&S2

测试

测试字节

测试字

测试双字

测试四字

CMP指令和TEXT指令只设置条件码不改变寄存器。CMP指令根据两个操作数之差来设置条件码。TEST指令根据两个操作数的与运算来设置条件码。典型用法是两个操作数是一样的(例如,testq %rax,%rax用来检查%rax是负数,零还是正数),或其中一个操作数是一个掩码,用来指示那些位应该被测试。

3.5.2 访问条件码

条件码通常不会直接读取,常用方法有三种: 1)可以根据条件码的某种组合,将一个字节设置为0或1,2)可以条件跳转到程序的某个其它部分,3)可以有条件地传送数据。

结尾:

至于后续内容将在下一篇文章介绍

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值