数据类型分析
浮点型
浮点数在不同平台上实现不同
有的处理器有浮点运算单元(Floating Point Unit,FPU),称为硬浮点(Hard-float)实现
有的处理器没有浮点运算单元,只能做整数运算,需要用整数运算来模拟浮点运算,称为软浮点(Soft-float)实现
在 x86 平台上,大多数编译器实现的 long double 型是 80 位
gcc 实现的 long double 型是 12 字节(96 位)
类型转换
C 中有转换级别机制,以下类型转换级别(Rank)越来越高:char、short、int、long、long long
运算符分析
移位问题
避免不同类型数值赋值操作;
因为 C 语言中不存在 8 位整数的二进制位运算,所有位运算执行之前都被提升为 int 类型
在一定的取值范围内
左移 1 位=乘以 2
右移 1 位=除以 2
异或运算特性
一个数和自己做异或的结果是 0
和 0 做异或保持原值不变,和 1 做异或得到原值的相反值
可用于奇偶校验:例如 a1 ^ a2 ^ a3 ^ … ^ an 的结果是 1,则表示 a1、a2、a3…an 之中 1 的个数为奇数个,否则为偶数个
x ^ x ^ y == y
计算机体系结构
CPU 的核心功能包括这部分
- 寄存器:特殊寄存器、通用寄存器
- 程序计数器 PC:特殊寄存器,保存着 CPU 取下一条指令的地址
- 指令译码器:负责解释从 CPU 中取出的指令对应段的含义(比如内存地址、寄存器编号等等)
- 算术逻辑单元 ALU:译码器转换的运算指令给 ALU 进行运算
- 地址与数据总线 Bus
内存映射 LO 定义
无论是在 CPU 外部接总线的设备还是在 CPU 内部接总线的设备都有各自的地址范围,都可以像访问内存一样访问
MMU 内存管理单元
MMU 工作原理
CPU 发出获取内存地址请求,此时传递虚拟地址 VA
给 MMU,MMU 将 VA 转换成物理地址 PA
给 CPU 外部的指定芯片引脚
如果 MMU 不工作,那么 CPU 发出的内存地址请求均为 PA,直接对应外部芯片引脚
MMU 管理一张虚拟页表,一一对应物理内存上的物理页表内容
每次 CPU 访问内存时,都会触发 MMU 的查表和地址转换操作
MMU 存在的意义
- 内存保护机制。MMU 可以拦截不同用户组的请求,根据其拥有的权限选择是否拦截(不转换地址)或者放行(转换地址)
- 有效避免内核空间和用户空间地址污染,使二者独立
汇编基本
最简汇编程序
汇编程序根据编译器的不同,使用 asm 或者 s 作为后缀;
首先要将汇编源文件使用汇编器翻译成机器指令,生成后缀为 o 的文件,然后再通过链接器编译成可执行文件
.section .data
.section .text
.globl _start
_start:
movl $1,%eax #this is the Linux kerneL command
movl $4,%ebx #this is the status number we wiLL
int $0x80 #this wakes up the kerneL to run
#
汇编中表示单行注释
汇编程序中以.开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)
或伪操作(Pseudo-operation)
.section .text
section 表示开始划分段的标志,text 表示后续的代码都属于 text 段
.globl
可理解为设置全局变量
_start
汇编程序入口点,必须被设置为全局变量
_start:
在这里开始写主入口程序
movl $1,%eax
movl 其实是 mov+l 的结合,l 表示该变量类型为 long
1
表示立即数
1
(
1 表示立即数1(
1表示立即数1(加任意数字都可以表示一个立即数)
%eax 表示寄存器 eax(所有寄存器都必须加%)
不难得出移位的格式为 movl [立即数],[欲保存到的寄存器]
int $0x80
软中断指令,可使程序故意产生一个异常导致程序终止运行;可以将其视为程序出口点
汇编语法分异
x86 汇编存在两种主流语法:
- AT&T 派:数据传送指令 mov 这样写
movl $1,%eax
- Lntel 派:数据传输指令这样写
mov eax,edx
(寄存器不加%且存取位置互换) - UNLX 平台一般采用 AT&T 语法
x86 寄存器
x86 通用寄存器:eax、ebx、ecx、edx、edi、esi
某些特殊场景下,他们会变得不那么“通用”,此时寄存器会有一个或者多个限制
(比如进行除法运算时)
x86 特殊寄存器:ebp、esp、eip、efLags
efLags 保存着计算过程中产生的标志位
ebp 和 esp 用于维护函数调用的栈帧
求最值汇编
# 定义数据存储段data
.section .data
# 类似于数组名
data_items:
# 定义数组类型,.long表示32位,.byte表示8位
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0
# 主程序段text
.section .text
# 程序入口点与全局变量
.globl _start
_start:
movl $0,%edi #move 0 into the index register
movl data_items(,%edi,4), %eax # Load the first byte of data
movl %eax,%ebx #since this is the first item,%eax is
# 循环开始,开头定义一个start_loop
start_loop:
cmpl $0,%eax # 比较寄存器eax是否等于0,如果为0表示已到末尾,需要跳出循环
je loop_exit # je即比较,如果上方代码相等,那么跳转到对应标志位
incl %edi # edi寄存器移到下一位(即加载下一个数据)
movl data_items(,%edi,4), %eax
cmpl %ebx,%eax
jle start_loop # jle(jump if less than or equal)
movl %eax,%ebx
jmp start_loop # jmp是一个无条件跳转指令,类似c语言中的default
# 循环结束,结尾定义一个loop_exit
loop_exit:
movl$1,%eax
int $0x80
寻址方式
访问内存的三个方式:数组基地址、元素长度和下标
内存寻址指令的通用格式:ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)
几种主要的寻址方式
- 直接寻址:只能用 ADDRESS_OR_OFFSET 寻址
- 变址寻址:如 movI data_items (,%edi,4)中的%eax
- 间接寻址:只使用 BASE_OR_OFFSET 寻址
- 基址寻址:只使用 ADDRESS_OR_OFFSET 和 BASE_OR_OFFSET 寻址,便于访问结构体成员
- 立即数寻址
- 寄存器寻址
ELF 文件
UNIX 可执行文件均采用 ELF 格式,它包含以下三种类型
- 可重定位的目标文件(Relocatable,或者 Object File)
- 可执行文件(Executable)
- 共享库(Shared Object,或者 Shared Library)
程序简易的汇编、链接、运行流程
- 编写汇编程序保存为 demo.s 文件
- 汇编器读取 demo.s,将源码中的.section 编译为目标文件的 Section
- 链接器将目标文件的 Section 汇总为 Segment,生成可执行文件 demo
- 加载器根据 Segment 信息加载运行程序!