第五周学习任务报告

学习计时:共7小时

读书:2小时

代码:1小时

作业:2小时

博客:2小时

第三章程序的机器级表示

3.1 历史观点

Intel处理器系统:x86(加入了很多处理小整数和浮点数向量的格式和指令

指令集:IA32:Intel32位体系结构

Linux使用平坦寻址方式,将整个储存空间看做一个大的字节数组

3.2 程序编码

命令gcc指的是GCC C编译器

-01 使用第一级优化(提高优化级别会使最终程序运行更快,但编译时间可能会变长

第二级优化被认为是较好的选择

将源代码转化成可执行代码的过程以前学过,不再详述

3.2.1 机器级代码

两种抽象:指令集体系结构,虚拟地址

汇编代码的特点:用可读性更好的文本格式来表示

可见的处理器状态:程序计数器(PC),整数寄存器文件,条件码寄存器,浮点寄存器

汇编代码不区分有符号或无符号整数,不区分各种类型的指针,不区分指针和整数

程序存储器:程序的可执行机器代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,用户分配的存储器块

操作系统负责管理虚拟地址空间,将虚拟地址翻译成实际处理器存储器中的物理地址

3.2.2 代码示例

机器实际执行的程序是对一系列指令进行编码的字节序列,对源代码一无所知‘

反汇编器:根据目标代码产生一种类似于汇编代码的格式

链接器将代码的地址移到一段不同的地址范围中

链接器确定了存储全局变量accum的地址

3.2.3 关于格式的注解

所有以.开头的行都是指导汇编器和链接器的命令

3.3 数据格式

16位 字

32位 双字

64位 四字

大多数数据类型都是双字

指针:4字节的双字

单精度 4字节

双精度 8字节

扩展精度 10字节/12字节

字符后缀

具体见P111图3-1

数据传送指令三个变种:movb,movw,movl

3.4 访问信息

一个CPU包含一组8个存储32位值得寄存器

3.4.1 操作数指令符

大多数指令有一个或多个操作数,指示出执行一个操作中要引用的源数据值,以及放置结果的目标位置

不同操作数的可能性分为3种类型:立即数$,寄存器,存储器

寻址模式:见p113图3-3

底部的是最常用的形式,书上有解释

3.4.2 数据传送指令

把许多不同的指令分为指令类

见图P114 图3-4

MOV类中的指令将元操作数的值复制到目的操作数中

MOVS和MOVZ指令将一个较小的源数据复制到一个较大的数据位置,高位用符号位扩展或零扩展进行填充

Pushl指令:把数据压入到栈上

3.4.3 数据传送示例

过程体

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

局部变量通常保存在寄存器中,寄存器访问比存储器访问要快得多

3.5 算术和逻辑操作

加载有效地址、一元操作、二元操作、移位

3.5.1 加载有效地址

Leal:从存储器读数据到寄存器

目的操作数必须是一个寄存器

3.5.2 一元操作和二元操作

一元操作有一个操作数

二元操作有两个操作数

两个操作数不能同时是存储器的位置

3.5.3 移位操作

先给出移位量,再给出要移位的数值

左移:SAL.SHL效果一样

右移:SAR算术,SHR逻辑

3.5.4 讨论

编译器产生代码中,会用一个寄存器存放多个程序值,还会在寄存器之间传送程序值

3.5.5 特殊的算术操作

双操作数乘法指令,从两个32位操作数产生一个32位乘积

无符号除法使用divl指令,通常事先将寄存器%edx设置为0

3.6 控制

测试数据值,然后根据测试的结果来改变控制流或者数据流

Jump指令可以改变执行顺序

3.6.1 条件码

单个位的条件码寄存器

常用条件吗:CF,ZF,SF,OF

CF:无符号溢出

ZF:零

SF:负数

OF:有符号溢出

Leal进行地址运算所以不改变条件码

两种只会设置条件码的指令:CMP,SUB

3.6.2 访问条件码

三种方法:

  1. 根据条件码的某个组合,将一个字节设置为0或者1
  2. 条件跳转到程序的某个其他部分
  3. 有条件的传送数据

3.6.3          跳转指令及其编码

跳转指令会导致执行切换到程序中一个全新的位置

目的地通常用一个标号指明

当执行PC有关寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址

3.6.4          翻译条件分支

有条件和无条件的跳转

Goto语句,无条件跳转

3.6.5          循环

  1. do-while
  2. while
  3. for

3.6.6          条件传送代码

一种替代策略

3.6.7          switch语句

通过跳转表来访问代码位置

3.7     过程

3.7.1 栈帧结构

栈:传递过程参数、存储返回信息、保存寄存器用于以后恢复、本地存储

为单个过程分配的那部分栈成为栈帧

寄存器%ebp为帧指针

寄存器%esp为栈指针

3.7.2 转移控制

Call指令

目标:指明被调用过程起始的指令地址

效果:将返回地址入栈,并跳转到被调用过程的起始处

3.7.3 寄存器使用惯例

必须保证当一个过程调用另一个过程时,被调用者不会覆盖某个调用者稍后会使用的寄存器的值

3.7.4 过程示例

3.7.5 递归过程

使代码继续到完成部分,回复栈和被调用者保存寄存器,然后返回的两种情况:

终止条件,递归调用

3.8     数组分配和访问

3.8.1 基本原则

声明 T A[N];

3.8.2 指针运算

单操作数的操作符&和*可以产生指针和间接引用指针

3.8.3 嵌套的数组

其实就是二维数组

声明:T D[R][C]

3.8.4 定长数组

Define N n

3.8.5 变长数组

用malloc或calloc分配存储空间,显式的编码

3.9     异质的数据结构

结构体,联合体

3.9.1 结构

Struct:将不同类型的对象聚合到一个对象中

3.9.2 联合

允许以多种类型来引用一个对象

3.9.3 数据对齐

对齐限制简化了形成处理器和存储器系统之间接口的硬件设计

3.10综合:理解指针

每个指针都对应一个类型

每个指针都有一个值

指针用&运算符创建

运算符*用于指针的间接引用

数组与指针紧密联系

将指针从一种类型强制转换成另一种类型,只改变它的类型,不改变它的值

指针也可以指向函数

3.11  应用:使用GDB调试器

GDB:可以观察正在运行的程序,同时又对程序的执行有相当的控制

见P175图3-30

3.12  存储器的越界引用和缓存区溢出

常见的状态破坏:缓存区溢出

解决:使用fgets函数,它包括一个参数,限制待读入的最大字节数

攻击代码:让程序执行它本来不愿意执行的函数

两种攻击形式:使系统启用一个外壳程序,给攻击者提供一组操作系统函数;执行一些未授权的任务,修复对栈的破坏,第二次执行ret指令,正常返回给调用者

对抗缓存区溢出攻击

  1. 栈随机化:防止安全单一化
  2. 栈破坏检测:加入一种栈保护者
  3. 现在可执行代码区域

3.13            x86-64:将IA32扩展到64

数据类型:指针需要8个字节,long将整数变成64位

访问信息:寄存器16个,所以寄存器64位长,可以直接访问低32位

算术指令:在四字上进行运算的指令,后缀加q

控制:新增指令cmpq和testq,用于比较和测试四字

保存惯例:有些用来保存临时值的寄存器被指定为调用者保存,另外一些是被调用者保存

数据结构:数组作为同样大小的块的序列来分配,结构作为最长的块来分配,联合作为一个单独的块来分配。对任何K字节的标量数据类型来说,它的起始地址必须是K的倍数

3.14           浮点程序的机器级表示

浮点体系结构:存储模型,指令和传递规则的组合

两种:x87,SSE

遇到的问题:

  1. 和地址有关的地方有点弄不清,比如练习题3.1的倒数几个空就不大会
  2. 变长数组没看懂
  3. 很多题都是看着答案逆推会的,也就是说光看题很难做出来

心得:

这一次的学习任务乍一看很多,但里面的很多内容以前都或多或少接触过,所以这些部分的笔记写得也比较粗略,只列出了提纲,而没有接触过的部分的笔记就比较精细。但虽然学过,在这本书里,视角又不同了,又可以用新的视角去看这些知识,所以也并不容易,看了一遍书,掌握并不熟练,还是会有怎么看都看不明白的地方,有看不进去的地方,有虽然看起来很明白但是过目就忘的地方。

原文地址:https://www.cnblogs.com/javablack/p/4868729.html