3.1 历史观点
X86 寻址方式经历三代:
1 DOS时代的平坦模式,不区分用户空间和内核空间,很不安全
2 8086的分段模式
3 IA32的带保护模式的平坦模式
3.2程序编码
程序计数器,是用来计数的,指示指令在存储器的存放位置,也就是个地址信息
gcc -S xxx.c -o xxx.s 获得汇编代码,也可以用objdump -d xxx 反汇编(P107,108)
注意函数前两条和后两条汇编代码,所有函数都有,建立函数调用栈帧,应该理解、熟记。
注意: 64位机器上想要得到32代码:gcc -m32 -S xxx.c
MAC OS中没有objdump, 有个基本等价的命令otool
Ubuntu中 gcc -S code.c (不带-O1) 产生的代码更接近教材中代码(删除"."开头的语句)
使用‘-c’命令行选项,GCC会编译并汇编该代码:gcc -o1 -c code.c
产生目标代码文件code.o,为二进制格式文件。
*二进制文件可以用od 命令查看,也可以用gdb的x命令查看。
有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看
od code.o | more
od code.o > code.txt
- Intel代码省略了指示大小的后缀
- Intel代码省略了寄存器名字前的“%”符号
- Intel代码用不同的方式来描述存储器中的位置。
- 在带有多个操作数的指令情况下,列出操作数的顺序相反
esi edi可以用来操纵数组,esp ebp用来操纵栈帧。 只有根据 栈管理的标准惯例 才能修改这两个寄存器中的值。
对于寄存器,特别是通用寄存器中的eax,ebx,ecx,edx,大家要理解32位的eax,16位的ax,8位的ah,al都是独立的,我们通过下面例子说明:
假定当前是32位x86机器,eax寄存器的值为0x8226,执行完addw $0x8266, %ax指令后eax的值是多少?
解析:0x8226+0x826=0x1044c, ax是16位寄存器,出现溢出,最高位的1会丢掉,剩下0x44c,不要以为eax是32位的不会发生溢出.
操作数的三种类型:
1.立即数,也就是常数值。
在ATT格式的汇编代码中,立即数的书写方式是“”后跟一个用标准C表示法表示的整数。任何一个能放进32 位字中的数值都可以用做立即数,不过汇编器在可能时会使用一个或两个字节的编码。
2.寄存器
表示某个寄存器的内容
3.存储器
根据计算出来的地址(有效地址)访问某个存储器的位置。
有效地址的计算方式: Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
MOV相当于C语言的赋值”=“,注意ATT格式中的方向:第一个是源操作数,第二个是目的操作数。
指令类:一类中的指令执行异议的操作,只不过操作数大小不同。
注意:不能从内存地址直接MOV到另一个内存地址,
将一个值从一个存储器复制到另一个存储器中需要两个指令:1.第一条指令将源值加载到寄存器中。 2.将该寄存器值写入目的位置。
区分MOV,MOVS,MOVZ
MOV:将源操作数值复制到目的操作数中。
MOVS:将较小的源数据复制到一个较大的数据位置高位使用符号位扩展。
MOVZ:将较小的源数据复制到一个较大的数据位置高位使用零扩展。
栈是一个数据结构,可以添加或者删除数据,总是遵循“先进后出”原则。
栈顶:总是从栈的这端插入和删除元素。(栈顶元素的地址是所有栈中元素地址中最低的)
指针就是地址。
间接引用指针:将该指针放在一个寄存器中,然后在储存器中使用这个寄存器。
局部变量保存在寄存器中。
3.5算术和逻辑操作
指令类中的操作被分为四组:加载有效地址,一元操作数,二元操作数,移位。
加载有效地址指令 lean实际上是movl指令的变形。
指令形式实际上并没有引用存储器,它的第一个操作数其实是将有效地址写入到目的操作数
这条指令:1.可以为后面的存储器引用产生指针。2.简洁的表述普通算数操作。
目的操作数必须是一个寄存器。
*这里的操作数顺序与ATT格式的汇编代码中相反。
- 一元操作:只有一个操作数,既是源又是目的。操作数可以是一个寄存器,也可以是一个储存器位置。
- 二元操作:第二个操作数既是源又是目的。要注意源操作数是第一个,目的操作数是第二个。第一个操作数可以是立即数,寄存器或是存储器位置,第二个操作数可以是寄存器或者存储器位置。(不能两个操作数同时为储存器位置)
- 移位操作:先给出移位量,然后第二项给出的是要移位的数值。注意移位操作移位量可以是立即数或%cl中的数 。移位操作的目的操作数可以是一个寄存器或者是一个存储器位置。
t=test-expr;if(!t)goto false;then-statementgoto done;false:else-statementdone:
- 每一个指针都对应一个类型。
- 每一个指针都有一个值。
- 指针用&运算符创建
- 数组与指针紧密联系
- 将指针从一种类型强制转换为另一种类型只改变它的类型而不改变它的值。
- 指针也可以指向函数
命令 |
参数含义 |
说明 |
backtrace |
n 最内层的n个函数栈帧 -n 最外层n个函数栈帧 |
栈帧回溯 |
frame |
栈帧号或者内存地址 |
选定栈帧,不带参数时显示栈帧简要信息 |
up |
栈帧号 |
选定栈帧上移 |
down |
栈帧号 |
选定栈帧下移 |
显示栈帧
backtrace 命令可以在遇到断点而暂停执行时显示栈帧。此外,backtrace 的别名还有 where 和 info stack。
(gdb) backtrace
显示所有栈帧。
(gdb) backtrace N
只显示开头 N 个栈帧。
(gdb) backtrace -N
只显示最后 N 个栈帧。
(gdb) backtrace full
(gdb) backtrace full N
(gdb) backtrace full -N
不仅显示backtrace,还有显示局部变量。
显示栈帧之后,就可以看出程序在何处停止(即断点的位置),以及程序的调用路径。