这节介绍PC最常见的架构 x86和扩展 x64框架
CPU操作模式
对x86处理器而言 有三个最主要的保护模式
保护模式
实地址模式
系统管理模式
还有一个保护模式的子模式
虚拟8086模式
保护模式
保护模式是处理原生状态 这个时候所有指令和特性都是可以使用的
分配给程序的独立内存区域 叫做内存段
处理器阻止程序使用自身段以外的内存区域
同时 寄存器扩展到32位 解决内存寻址空间的问题
并且各个指令区分特权等级 危险的只有较高权限才能执行
增加CPU对内存地址的访问的权限控制
实地址模式
是早起intel的编程环境 该模式程序可以直接访问硬件和其实际地址
没有经过虚拟地址的映射
方便了程序开发
没有权限的说法
程序发往内存是直接读写 没有审核
CPU可以执行任何程序 内存之间没有隔离保护
系统管理模式
为操作系统提供电源管理 安全保护等特性机制
对于x86_64的处理器
还有一个操作模式
IA_32E
这个模式包含着两个子模式
兼容模式:
现有的32位和16位不需要重新编译
64位模式:
处理器将在64位的地址空间下运行程序
语法风格
x86 有两个语法风格
AT&T风格和Intel风格
gcc gdb objdump都默认使用AT&T风格
这里我们关注一下
间接寻址的格式
AT&A
%sreg:disp(%base,index,scale)
%sreg:是段寄存器 用于制定段地址
cs:
disp是偏移量 是相对于基地址的偏移 可以是在程序计算出来的值
cs:4
%base是基地址寄存器 存储内存块的开始
cs:4(%eax,)
index为变址寄存器 表示偏移地址的计算
cs:4(%eax,%ebx)
scale为比例因子 可以为 1,2,4,8等 表示 index*scale作为偏移地址
cs:4(%eax,%ebx,2)
mov %ecx,4(%eax,%ebx,2)
表示 从ecx的地方读取 存入 eax+2*ebx+4的地址去
intel就很简单
mov [esi+eax*4+0x10], ebx
这里就可以发现ebx存入 esi+eax*4-0x10的地址去
操作位数
AT&T
指令+l/w/b是什么意思呢
我们先知道l/w/b代表什么
l为32位整数双字
w为16位整数单词
b为8位整数字节
举例
addl $0x12345678,%ebx
把双字数据0x12345678存入ebx中 并且大小为32位
但是实际操作数为 33位
addw $0x1234, %ah
把字数据0x1234存入 ah中 大小为16位
实际操作数为17位
addb 0x11,%al
把字节数据 0x11存入al中 大小为8位
实际操作数为9位
不加的话默认64位
Intel
qword ptr 为64位操作数
dword ptr 为32位操作数
寄存器和数据类型
寄存器
从8位处理器到16位 再到32和64位 寄存器名字也不一样
在 64位模式下 操作数大小还是32位 如果我们需要把他变为64位 只需要赋值给64位的寄存器
mov rax, eax
并且还增加了 8个带符号的通用寄存器(R8~R15)
数据常量
对于整数常量 1234 可以是10进制 16进制 或者8进制 所以需要后缀进行区分
如果16进制中包含了字母 为了防止 汇编器把他当汇编指令或标识符
需要再字母开头的十六进制中+0
例如 0ABCh
浮点数常量
也叫做实数常量
x86有单独的浮点数寄存器和浮点数指令 对浮点数进行操作
我们通常以10进制表示浮点数
16进制表示编码浮点数
一个浮点数应该包含一个十进制整数和一个十进制小数点
1.
+2.3
-3.1415
26.E5
都是合法的
字符串常量
字符串常量是用 ''/ ""括起来的字符序列(包含空格符)
"hello world",'he says "hello"',"he's a funny man" 这是合法的 字符串嵌套
字符串处理在内存中是以整数字节序列保存的
字符串"ABCDEFGH'’在gdb中的显示样子为
这个是以16进制打印
这里能发现是小端序 这个我们后面再学
数据传送与访问
MOV
MOV是指令是最基本的数据传输指令 几乎所有程序都在使用
可以在一个程序中只使用mov就可以完成 证明了 图灵完备
MOV指令的基本格式 第一个参数为目的操作数 第二个参数为源操作数
MOV EAC,ECX 就是把ECX的值拷贝到EAX中
MOV 支持从寄存器到寄存器
从内存到寄存器
从寄存器到内存
从立即数到内存
从立即数到寄存器的数据传输
但是!!!不支持内存到内存的传输
想要实现这个 就需要一个寄存器作为中转
MOV EAX,0 EAX= 00000000h
MOV AL,78h AL = 00000078h
MOV AX,1234h AX = 00001234h
MOV EAX,12345678h EAX=12345678h
如果我们要把操作数扩展到大的操作数的话 我们需要通过全零扩展和符号扩展
全零扩展和符号扩展
我们使用例子来说明全零扩展和符号扩展
unsigned char x = 0xC1; // 11000001
全零扩展 直接把c1存到底8位 剩下24位通过 0 填充
x = 0x000000c1
符号扩展 因为0xc1 为 11000001 最高位为1(符号位)所以通过1复制到高24位
x=0xffffffc1
XGHG
数据访问内存指令还有 XCHG 允许我们交换两个操作数的值
可以是寄存器到寄存器
内存到寄存器
或者寄存器到内存
数组
在x86中 使用变量名和偏移量来代表一个 直接偏移量操作数
.data
testarray BYTE 99h, 98h, 97h, 96h
.code
MOV AL,testarray al=99h
MOV BL,[testarray+1] bl=98h
MOV CL,[testarray+2] cl=97h
双字数组的汇编代码段 需要使用符合数组元素的偏移量才可以正确表示数组元素的位置
就是需要准确的偏移量
.data
testarrayW WORD 99h, 98h, 97h
testarrayD DWORD 1000h,2000h,3000h
.code
MOV AX,testarrayW AX=99h
MOV BX,[testarrayW+2] BX=98h
MOV ECX,testarrayD ECX=1000h
MOV EDX,[testarrayD+4] EDX=2000h
算数运算和逻辑运算
最简单的算数运算指令 INC和DEC 分别用于操作数+1 操作数-1
这两个指令的操作数既可以是寄存器 也可以是内存
.data
testWord WORD 1000h
.code
INC EAX
DEC testWord
补码
计算机底层数据是以补码表示的
两个机器数相加的补码相加可以先对两个机器数补码 然后相加
在采用补码形式表示时
进行加法运算 就可以把符号位和数值位一起进行运算 符号位有进位直接舍弃
结果之和为两个数的补码之和
ADD
长度相同的操作数进行相加
.data
testData Dword 10000h
testData2 Dword 20000h
.code
MOV EAX, testData EAX=10000h
ADD EAX, testData2 EAX=30000h
SUB
从目的操作数中减去源操作数
.data
testData DWORD 20000h
testData2 DWORD 10000h
.code
MOV EAX, testData eax=20000h
SUB EAX, testData2 eax=10000h
NEG
NGS是把操作数转化为二进制补码 并且把操作数的符号位取反
跳转指令和循环指令
CPU是顺序加载并且执行程序的
但是 指令集中会存在 条件型指令
将根据CPU的标志位寄存器决定程序控制流的走向
在x86中 每一个条件指令都暗含着跳转指令
跳转指令分成
有条件跳转 和 无条件跳转
JMP
为无条件跳转
JMP labe11
MOV EBX, 0
labe11:
MOV EAX, 0
LOOP
loop创建 循环代码块
ECX寄存器为循环的计数器
CX是 loop指令和loopw指令的默认循环计数器
ECX寄存器是loopd指令的循环计数器
64位的x86 loop指令使用RCX为默认循环计数器
经过一次循环
ECX将-1
MOV AX, 0
MOV ECX, 3
L1:
INC AX
LOOP L1
XOR EAX,EBX
循环一次 ECX-1 并且和0进行判断
如果ECX=0 就不进行跳转 执行 XOR EAX,EBX
如果ECX!=0 就进行跳转 重新执行 L1
注意
如果在loop开始前 EAX设置为0
-1 就会跳转到 FFFFFFFFh
非常大的循环
栈和函数的调用
CALL
相当于PUSH 返回地址 JMP 函数地址的指令序列
RET
相当于POP 返回地址 JMP 返回地址的指令序列
如果要存储变量的话
这些部分我以前写过
直接给出
1.栈的介绍-C语言调用函数(一)_双层小牛堡的博客-CSDN博客
1.栈的介绍-C语言调用函数(二)_双层小牛堡的博客-CSDN博客