20135304刘世鹏——信息安全系统设计基础第四周学习总结
第三章 程序的机器级表示
1.历史观点
GCC C语言编译器以汇编代码的形式产生输出,汇编代码是机器代码的文本表示,给出程序中的每一条指令。x86架构于1978年推出的Intel 8086中央处理器中首度出现,它是从Intel 8008处理器中发展而来的,而8008则是发展自Intel 4004的。8086在三年后为IBM PC所选用,之后x86便成为了个人计算机的标准平台,成为了历来最成功的CPU架构
2.程序编码
- 机器级程序的格式和行为,定义为指令集体系结构(ISA),它定义了处理器状态、指令的格式、以及每条指令对状态的影响。将程序的行为描述成好像每条指令是按顺序执行的。
- 提供的存储器模型看上去是一个非常大的字节数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。
- 指令集体系结构它定义了处理器状态、指令的格式,以及每条指令对状态的影响。IA32将程序的行为描述成好像每条指令时按顺序执行的,一条指令结束后,下一条再开始。
-
二进制文件可以用od 命令查看,也可以用gdb的x命令查看。 有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看。
od code.o | more od code.o > code.txt
- 程序存储器(program memory)包含:程序的可执行机器代码、操作系统需要的一些信息、栈、堆。程序存储器用虚拟地址来寻址(此虚拟地址不是机器级虚拟地址)。操作系统负责管理虚拟地址空间(程序级虚拟地址),将虚拟地址翻译成实际处理器存储器中的物理地址(机器级虚拟地址)。
3.数据格式
b-字节 ——8位
w-字 ——16位
l-双字 ——32位
语言基本数据类型对应的IA32表示:大多数常用数据类型都是以双字形式存储的。
单精度(4字节)float
双精度(8字节)double
扩展精度(10字节)long double
访问信息
-
全局变量存储在一个固定的地址上(至少对于程序来说,他们是固定的)。访问这些变量的最通常的方式是在指令中明确指出那个固定的地址。 MOV EAX,[1234134H] ; loads EAX with value stored at location 12341234H
INC DWORD PTR TEST2!_nCount ; increments DWORD variable nCount
-
堆变量存在于堆上,他们是通过指针来访问的。典型情况下需要不只一条指令来访问堆变量。 MOV ESI, TEST2!mpFileList ; load the pointer
MOV EAX, [Esi+4] ; read second DWORD (pszName) in heap
- 堆栈遵循“先出后进”的原则。栈顶元素的地址是所有栈中元素地址中最低的。
- 注意不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下。
- 有效地址的计算方式 Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
-
JMP命令
这个命令简单的设置EIP寄存器为下一条指令的地址。没有任何数据会被存储到栈上,并且不会设置任何标志位。JMP被用在固定的指令分支上。大多数的if-then-else语句族至少需要一条JMP指令。
-
INT命令
当INT命令的操作数是一个中断号的时候,该指令会引发一个软件中断。这个与CALL指令差不多,不同之处是EFLAGS寄存器被压入栈中。还有,如果是在user mode中被调用,在切换到kernel mode时也会发生将EFLAG寄存器压栈的操作。中断函数结束的时候,随着RETI指令的执行,EFLAGS寄存器和EIP都会从栈中恢复。
-
条件跳转指令
-
LOOP指令被用来实现高级语言中的循环。直到ECX(计数器)的值为0的时候,它才会走向分支地址。如果ECX不是0,那么ECX会被减一,然后继续循环操作。
XOR EAX,EAX ; clear EAX register
MOV ECX, 5 ; load loop count
START:
ADD EAX,1 ; add one to eax
LOOP START
-
根据条件来跳转的指令会去判断所指定的条件是否为真,若果是就执行跳转。比如,JNZ(jump not zero),操作数中指定的地址直到ZERO标志位被设置为1的时候才会被转过去。这些指令主要被用在if语句块中。
XOR EAX,EAX ; clear eax
MOV ECX,5
START:
ADD EAX,1 ; add one to EAX
DEC ECX ; decrement loop counter
JNZ START
-
5.算术和逻辑操作
- 四组操作:一元操作:只有一个操作数,可以是寄存器也可是存储器位置。 二元操作:源操作数是第一个,可以是立即数、寄存器、存储器。目的操作数是第二个,可以是寄存器、存储器。两个不能同时为存储器。 移位:第一个是移位量,用单个字节编码(只允许0-31位的移位),可以是立即数或者放在单字节寄存器%cl中。
6.控制
-
控制中最核心的是跳转语句:
有条件跳转(实现if,switch,while,for)
无条件跳转jmp(实现goto)
- 条件码寄存器:描述了最近的算术或逻辑操作的属性,可以检测这些寄存器来执行条件分支指令。常用条件码有:
CF:进位标志
ZF:零标志
SF:符号标志
OF:溢出标志
- 循环
- do-while循环
- while循环
- for循环
- Switch语句:根据一个整数索引值进行多重分支。通过使用跳转表这种数据结构实现更加高效。跳转表是一个数组,表项i是一个代码段的地址,这个代码段实现当开关索引值为i时程序该做的。
-
leal不改变任何条件码。访问条件码:
1、根据条件码的某个组合,将一个字节设置为0或1。 SET指令根据t=a-b的结果设置条件码 2、可以条件跳转到程序的某个其他部分 3、可以有条件的传送数据
7.过程
- 栈用来传递参数、存储返回信息、保存寄存器,以及本地存储。
- 最顶端的栈帧以两个指针界定,寄存器%ebp为帧指针,寄存器%esp为栈指针。程序执行时,栈指针可以移动,大多数信息的访问都是相对于帧指针的。
-
程序寄存器组是唯一能被所有过程共享的资源。
根据惯例寄存器%eax,%edx,%ecx被划分为调用者保存寄存器。 %ebx,%esi,%edi被划分为被调用者保存寄存器。 %ebp,%esp 惯例保持 %eax用来保存返回值
- call指令有一个目标,即指明被调用过程起始的指令地址,效果是将返回地址入栈,并跳转到被调用过程的起始处。
- ret指令从栈中弹出地址,并跳转到这个位置,使用这个指令栈指针要指向call指令存储返回地址的位置。