1 chapter two: Representing and Manipulating Infomation
信息的表示和处理
1.1 Infomation Storage 信息存储
1.1.1 CONCEPTS
- byte , 最小的可寻址的存储器单元.
- virtual memory
- address , 存储器的每个字节都由一个唯一的数字来标识.
- virtual address space ,虚拟地址空间 即 所有可能的地址的集合.
- 二进制与十六进制的转换 :
每四位单独转换.
1.1.2 WORDS
word size : 指明整数和指针数据的标准大小 (nominal size) . 字长决定了虚拟地址空间的最大大小.
1.1.3 ENDIAN
- little endian
- big endian
- bi-endian
example: int x = 0×1234567; x’s address = 0×100 ;
address 0×100 0×101 0×102 0×103 big endian 01 23 45 67 little endian 67 45 23 01 - Notice :
C语言中的typedef声明提供了一种给数据类型命名的方式,可以极大的改善代码的可读性. 如 typedef unsigned char * byte_pointer ;
- Notice :
1.1.4 REPRESENTING STRING
1.1.5 REPRESENTING CODE
1.1.6 INTRODUCTION TO BOOLEAN ALGEBRA (布尔代数)
1.1.7 BIT-LEVEL OPERATIONS IN C
- Exclusive-OR (异或)
1.1.8 LOGICAL OPERATIONS IN C (逻辑运算)
1.1.9 SHIFT OPERATIONS IN C (移位运算)
1.2 Integer Representations 整数表示
1.2.1 INTEGER DATA TYPE (NORMAL)
type | mix | max |
---|---|---|
char | -128 | 127 |
unsigned char | 0 | 255 |
short (int) | -32768 | 32767 |
unsigned short | 0 | 65535 |
int | -2147483648 | 2147483647 |
- two’s-complement 补码
- ISO C99 中在stdint.h中引入 intN_t和uintN_t ,来标识N位有符号和无符号的整数,保证行为一致性.
1.3 Integer Arithmetic
1.4 Floating Point 浮点数表示
1.5 Summary
1.6 Homework Problems !!!!! hard
2 chapter three: Machine-Level Representation of Programs
程序的机器级表示 计算机执行机器代码,用字节序列编码低级的操作,包括处理数据,管理存储器,读写存储设备上的数据,以及利用网络通信.编译器基于编程语言的原则,目标机器的指令集和操作系统遵循的规则,经过一系列的阶段产生机器代码.
GCC C语言编译器以汇编代码的形式产生输出,汇编代码是机器代码的文本表示,给出程序中的每一条指令.然后GCC调用汇编器和连接器,从而根据汇编代码生成可执行的机器代码.
我们讨论的是IA32的中央处理器
2.1 Program Encodings 汇编
2.1.1 MACHINE-LEVEL CODE 机器级代码
简单的寄存器组
- PC 程序计数器 用%eip标识 指示将要执行的下一条指令所在存储器中的地址.
- xx寄存器 存储32位的值.可以存储地址(对应C语言中的指针)或整数数据. 用于记录某些重要的程序状态,保存临时数据,例如局部变量和程序的返回值.
- xx寄存器 保存最近执行的算术或逻辑指令的状态信息, 用于实现控制或者数据流中的条件变化,例如if while等语句
- 浮点寄存器
2.1.2 PROGRAM MEMORY
程序存储器:包含可执行的机器码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的存储器块(如malloc).程序存储器用虚拟地址寻址. 在任意给定的时刻,只认为有限的一部分虚拟地址是合法的.例如虽然IA32的32位地址可以寻址4GB的地址范围,但通常一个程序只会访问几兆的字节.操作系统负责管理虚拟地址空间,将虚拟地址翻译成实际处理器存储器中的物理地址.
2.2 Accessing Infomation
2.2.1 寄存器 REGISTER
- 一个IA32的CPU包含一组8个存储32位值的寄存器.用于存储整数数据和指针. 名字都以%e开头
- 前六个多数情况看成通用寄存器,
- 最后两个寄存器 %ebp , %esp 保存着指向程序栈中重要位置的指针.例如: 栈指针%esp保存着栈顶元素的地址.
2.2.2 操作数指示符 OPERAND
指示出执行一个操作中要引用的源数据值,以及放置结果的目标位置. 三种类型:
- 立即数寻址 (immediate) 即 常数值 |表示: ‘$’后跟一个标准C表示法表示的整数 |如 $Imm , $-577 , $0x1f .
- 寄存器寻址 (register) | 如 %eax , %ax .
- 存储器寻址 (memory) |寻址模式: 立即数偏移Imm,基址寄存器Eb,变址寄存器Ei,比例因子s.
2.2.3 数据传输指令 MOV
- 栈操作说明
根据惯例,栈是倒着画的,因而栈顶在底部,IA32的栈向低地址方向增长,所以压栈是减小栈指针(寄存器%esp)的值,并将数据存放在存储器中,而出栈是从寄存器中读,并增加栈指针的值. #+imgs/stack.png
2.2.4 算术和逻辑操作 ARITHMETIC AND LOGICAL OPERATIONS
- 加载有效地址
load effective address 指令 : leal . 从存储器读数据到寄存器.
- 一元操作与二元操作 Unary and Binary Operations
- 位移操作 Shift Operations
2.2.5 CONTROLL 流程控制
- Condition Codes
- condition code register : 条件代码寄存器
- Jump Instructions and Their Encodings 跳转指令及其编码
- Translating Conditional Branches 条件分支.
- Loops 循环
- Conditional Move Instructions 条件传送
- Switch *
2.2.6 PROCEDURES 过程
一个过程调用包括将数据(以过程参数和返回值的形式)和控制从代码的一部分传递到另一部分. 另外它还必须在进入时为过程的局部变量分配空间,并在退出时释放. 大多数机器,包括IA32,只提供转移控制到过程和从过程中转移出控制这种简单的指令. 数据传递,局部变量的分配和释放通过操纵程序栈来实现.
- Stack Frame Structure 栈帧结构
机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储. 为单个过程分配的那部分栈称为栈帧 . stack frame 栈帧的最顶端以两个指针界定:
- 寄存器 %ebp 为帧指针;
- 寄存器 %esp 为栈指针.
#+imgs/stack2.png 假设过程P(调用者)调用过程Q(被调用者),则Q的参数放在P的栈帧中. P的返回地址被压入栈中,形成P的栈帧的末尾,用于程序从Q中返回时继续执行的地方. Q的栈帧从保存的帧指针的值(%ebp)开始,后面是保存的其他寄存器的值.
过程Q也用栈来保存其他不能存放在寄存器中的局部变量,原因如下:
- 没有足够多的寄存器存放所有的局部变量;
- 有些局部变量是数组或者结构,因此必须通过数据或结构引用来访问;
- 要对一个局部地址使用地址操作符 ‘&’ , 所以要为它生成一个地址.
另外Q会用栈帧存放调用它的过程的参数. 被调用的过程中,第一个参数放在相对于%ebp偏移量8的位置处,剩下的参数(假设数据类型需要的字节不超过4个)存储在后续的4字节块中.较大的参数(比如结构)需要栈上更大的区域.
栈向低地址方向增长.
- Recursive Procedures 递归
2.2.7 ARRAY ALLOCATION AND ACCESS 数据的分配与访问
数组与指针的关系. * 讲的很详细
2.2.8 HETEROGENEOUS DATA STRUCTURES
- structure 结构 编译器维护结构类型的信息,指示每个字段(field)的字节偏移.以这些偏移作为存储器引用指令中的位移,从而产生对结构元素的引用.
- union 联合 以不同的字段来引用相同的存储器块.
- Data Alignment 很有趣的一节,许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(2/4/8)的倍数. 这种对齐限制简化了形成处理器和存储器系统之间接口的硬件设计.例如:
struct S1{ int i; char c; int j; };
offset | 0 | 4 | 5 | 8 | 12 |
value | i | c | - | j |
以上:虽然char类型的c只占一个字节,但是因为windows设计了数据对齐限制,则5-8的内存地址空置了.
2.2.9 UNDERSTANDING POINTERS – PUTTING IT TOGETHER
- 每个指针都有一个值,即某个指定类型对象的地址. 特殊的NULL(0)值表示该指针没有指向任何地方.
- 指针用&运算符创建. leal指令是设计用来计算存储器引用的地址的,&运算符的机器代码实现经常用这条指令来计算表达式的值.
- 操作符用于指针的间接引用. 其结果是一个值,类型与该指针的类型相关. 间接引用是通过存储器引用来实现的,要么是存储到一个指定的地址,要么是从指定的地址读取.
- 指针与数组紧密练习.一个数组的名字可以像一个指针变量一样引用.(但不能修改)
- 将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值.
- 指针也可以指向函数. 这提供了很强大的存储和向代码传递引用的功能. 函数指针的值是该函数机器代码表示中第一条指令的地址.
int fun (int x , int *p); (int) (*fp) (int, int* ); fp = fun; int y = 1; int result = fp(3,&y);
2.2.10 USING THE GDB
值得一个课题研究.
2.2.11 3.12 存储器的越界引用和缓冲区溢出. OUT-OF-BOUNDS MEMORY REFERENCES AND BUFFER OVERFLOW
- 值得反复阅读. important
详情参考书上的例子,在函数调用时分配char buf1; 而如果存储越界则会产生如下影响.
char num effect 0-7 none 8-11 破坏%ebx存储的值 12-15 破坏%ebp存储的值 16-19 返回地址 20+ callder中保存的状态 - 如何对抗溢出
2.3 x86-64 : Extending IA32 to 64 Bits .
全面总结 ,深度展望
2.4 c c++ java
早期编译C++与编译C 非常相似,实际上,C++的早期实现就只是简单的执行了从C++到C的源到源的转换,并对结果运行C编译器,产生目标代码.
C++的对象用结构来表示,类似C的struct. C++的方法是用指向实现方法的代码的指针来表示的.
相对而言,Java的实现方式完全不同,Java的目标代码是一种特殊的二进制表示,称为Java字节代码.这种代码可以看成虚拟机的机器级程序.
虚拟机并不是直接用硬件实现的,而是用软件解释器处理字节代码,模拟虚拟机的行为. 另外有一种称为及时编译 (Just-in-time compilation)的方法,动态地将字节代码序列翻译成机器指令.当代码要执行多次时(循环),这种方法执行起来更快.而且跨平台.