第三章 程序的机器级表示
p104, p105: X86 寻址方式经历三代
x86
80x86的寻址方式包括两方面的内容:一是与数据有关的寻址方式;另一个是与转移地址有关的寻址方式。狭义的讲,寻址方式就是指与数据有关的寻址方式,即寻找操作数的地址方式,根据操作数的地址找到操作数。
通常情况下,对初学者来说,第一种寻址方式更为常用,因此在这里只总结与数据有关的寻址方式。
p106: ISA的定义
ISA
英特尔32位元架构(英语:Intel Architecture, 32-bit,缩写为IA-32),常被称为i386、x86-32或是x86,由英特尔公司推出的
指令集架构,至今英特尔最受欢迎的处理器仍然采用此架构。它是x86架构的32位元延伸版本,首次应用在Intel 80386芯片中,用来取代之前的x86 16位元架构(x86-16),包括8086、80186与80286芯片。IA-32属于复杂指令集。 复杂指令集,也称为CISC指令集,英文名是CISC,(Complex Instruction Set Computer的缩写)。在CISC微处理器中,程序的各条指令是按顺序串行执行的,每条指令中的各个操作也是按顺序串行执行的。顺序执行的优点是控制简单,但计算机各部分的利用率不高,执行速度慢。其实它是英特尔生产的x86系列(也就是IA-32架构)CPU及其兼容CPU,如AMD、VIA的。即使是现在新起的X86-64(也被称为AMD64)都是属于CISC的范畴。要知道什么是指令集还要从当今的X86架构的CPU说起。X86指令集是Intel为其第一块16位CPU(i8086)专门开发的,IBM1981年推出的世界第一台PC机中的CPU—i8088(i8086简化版)使用的也是X86指令,同时电脑中为提高浮点数据处理能力而增加了X87芯片,以后就将X86指令集和X87指令集统称为X86指令集。虽然随着CPU技术的不断发展,Intel陆续研制出更新型的i80386、i80486直到过去的PII至强、PIII至强、Pentium 3,最后到今天的Pentium 4系列、至强(不包括至强Nocona),但为了保证电脑能继续运行以往开发的各类应用程序以保护和继承丰富的软件资源,所以Intel公司所生产的所有CPU仍然继续使用X86指令集,所以它的CPU仍属于X86系列。由于Intel X86系列及其兼容CPU(如AMD Athlon MP、)都使用X86指令集,所以就形成了今天庞大的X86系列及兼容CPU阵容。x86CPU目前主要有intel的服务器CPU和AMD的服务器CPU两类。
对于机器级编程来说,其中两种抽象尤为重要:
1、指令集体系结构(Instruction set architecture ISA)
它定义了处理器状态、指令的格式,以及每条指令对状态的影响。
IA32将程序的行为描述成好像每条指令时按顺序执行的,一条指令结束后,下一条再开始。(实际上处理器并发地执行许多指令,但是可以采取措施保证整体行为与ISA指定的顺序执行完全一致)
2、机器级程序使用的存储器地址是虚拟地址
提供的存储器模型看上去是一个非常大的字节数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。
程序存储器(program memory)包含:程序的可执行机器代码、操作系统需要的一些信息、栈、堆。程序存储器用虚拟地址来寻址(此虚拟地址不是机器级虚拟地址)。操作系统负责管理虚拟地址空间(程序级虚拟地址),将虚拟地址翻译成实际处理器存储器中的物理地址(机器级虚拟地址)。
p107:
gcc -S xxx.c -o xxx.s 获得汇编代码,也可以用objdump -d xxx 反汇编;
64位机器:gcc -m32 -S xxx.c
注意:MAC OS中没有objdump, 有个基本等价的命令otool
Ubuntu中 gcc -S code.c (不带-O1)
p108:
二进制文件可以用od 命令查看,也可以用gdb的x命令查看。
我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看
od code.o | more
od code.o > code.txt
p109-p111:
两种格式:ATT格式和Intel格式
表中不同数据的汇编代码后缀
p112:
EAX、ECX、EDX、EBX:為ax,bx,cx,dx的延伸,各為32位元
ESI、EDI、ESP、EBP:為si,di,sp,bp的延伸,32位元
eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。如果用C语言来解释,可以把这些寄存器当作变量看待。
比方说:add eax,-2 ; //可以认为是给变量eax加上-2这样的一个值。
这些32位寄存器有多种用途,但每一个都有“专长”,有各自的特别之处。
EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 则总是被用来放整数除法产生的余数。
ESI/EDI分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.
EBP是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:
push ebp ;保存当前ebp
mov ebp,esp ;EBP设为当前堆栈指针
sub esp, xxx ;预留xxx字节给函数临时变量.
...
这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.
ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。
esi edi可以用来操纵数组,esp ebp用来操纵栈帧。
p113:
一 、立即寻址方式(immediate addressing)
二 、寄存器寻址方式(register addressing)
三 、直接寻址方式(direct addressing)
四 、寄存器间接寻址方式(register indirect addressing)
五 、寄存器相对寻址方式(register relative addressing)
六 、基址变址寻址方式(based indexed addressing)
七 、相对基址变址寻址方式(relative based indexed addressing)
p114:
注意ATT格式中的方向,
不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下。能区分MOV,MOVS,MOVZ,掌握push,pop
p115/p116:
栈帧与push pop; 注意栈顶元素的地址是所有栈中元素地址中最低的。
p117:
指针就是地址;局部变量保存在寄存器中。
p119:
结合表理解一下算术和逻辑运算, 注意目的操作数都是什么类型
注意减法
p123:
结合C语言理解一下控制部分,也就是分支(if/switch),循环语句(while, for)如何实现的。
p124:
条件码寄存器
注意leal不改变条件码寄存器
p125:
SET指令根据t=a-b的结果设置条件码
p127:
跳转指令(导致执行切换到程序中一个全新的位置,跳转的目的地通常用一个标号指明)
无条件跳转:JMP 可以是直接跳转也可以是间接跳转(写法是*后面加操作数指示符)
有条件跳转:根据条件码的某个组合,或者跳转或者继续执行下一条指令
p130/p131:
if-else 的汇编结构
p132/p133:语句的使用
三种语句:
条件分支:if--else
循环结构:do--while、while、for
switch语句
p149:
IA32通过栈来实现过程调用。掌握栈帧结构,注意函数参数的压栈顺序.
p150/p151:
call/ret; 函数返回值存在%eax中
p174:
1.在栈帧之间切换
GDB中有很多针对调用堆栈的命令,都需要一个目标栈帧,例如打印局部变量值的命令。
frame args 将当前栈帧设置为args(编号或Address)指定的栈帧,并打印该栈帧的简要信息。
select-frame args 与frame args相同,但是不打印栈帧信息。
up n 向上回退n个栈帧(更外层),n默认为1.
down n 向下前进n个栈帧(更内层),n默认为1.
up-silently n 与up n相同,但是不打印信息。
down-silently n 与down n相同,但是不打印信息。
2.打印栈帧信息(不移动栈帧)
frame 打印当前栈帧的简要信息。
info frame 打印当前栈帧的详细信息。
info frame args 打印指定栈帧的详细信息。
info args 打印函数参数信息。
info locals 打印当前可访问的局部变量的信息。
3.打印调用堆栈
backtrace 打印全部栈帧的简要信息,按Ctrl-c可终止打印。
backtrace n 打印最内层的n个栈帧的简要信息。
backtrace -n 打印最外层的n个栈帧的简要信息。
backtrace full 打印全部栈帧的详细信息。
backtrace full n 打印最内层的n个栈帧的详细信息。
backtrace full -n 打印最外层的n个栈帧的详细信息。
4.一些配置项
set backtrace past-main on 对调用堆栈的打印可越过main函数。
set backtrace past-main off 对调用堆栈的打印止步于main函数。
bt/frame/up/down :关于栈帧的gdb命令