PC Assembly langua学习笔记

## 1 基础
### 1.1 基本指令
##### mov dest, src

“`
mov eax, 3
mov bx, ax
“`

##### add dest, src

“`
add eax, 4
add al, ah
“`

##### sub dest, src

“`
sub bx, 10
sub ebx, edi
“`

##### inc 和 dec 命令

“`
inc ecx ; ecx++
dec dl ; dl–
“`

### 1.2 指示符
* 指示/提示 程序做啥
* 定义 常量/变量
* 将内存组合成段
* 有条件的包含源代码(ifdef之类的
* 包含其他文件

##### equ
* symbol equ value
* 不可再次定义symbol的value

##### %define指示符
* 可再次定义
* 可定义比简单的常量数值更大的值
“`
%define SIZE 100
“`

##### 数据指示符
* 定义内存空间
* resx 定义内存空间
* dx 定义有初始值的内存空间
* 连续定义的数据储存在连续的内存中
* x可取值

| 单位 | 字母 | 解释 |
| — | — | — |
| 字节 | B | 一个字节, 8位 |
| 字 | W | 两个字节, 16位,-32768~32767 |
| 双字 | D | 四个字节。 32位,-20亿~20亿,dd定义整形和 c语言中的单精度 |
| 四字 | Q | 八个字节。 64位,dq只可以定义c语言中的双精度 |
| 十个字节 | T |

“`
L1 db 0
L2 dw 1000
L3 db 110101b ; 二进制值
L4 db 12h ; 16进制值
L5 db 17o ; 8进制值
L6 dd 1A92h ; 16进制值
L7 resb 1 ; 1个未初始化的字节
L8 db “A” ; ASCII值A(65), 单双引号同等
L9 db 0, 1, 2, 3 ; 定义四个字节
L10 db “w”, “o”, “r”, ‘d’, 0; 定义一个”word”的c字符串
L11 db ‘word’, 0 ; 等同于L10
“`

##### times指示符

“`
L12 times 100 db 0 ; 等价于100个值为0的字节
L13 resw 100 ; 存储空间为100个字
“`

##### []
* [var_name] 表示变量的值
* var_name 表示变量的内存空间地址

“`
mov al, [L1] ;
mov eax, L1 ;
mov [L1], ah
mov eax, [L6]
add eax, [L6]
add [L6], eax
mov al, [L6]

; byte word dword qword tword
mov dword [L6], 1 ; 储存1到L6中
“`

##### %include 预处理指示符
“`
%include “asm_io.inc”
“`
##### call
* 跳转到代码的另一段去执行,然后等待执行完成后又回到原始的地方
* asm_io.inc提供的函数

| 函数名 | 作用 |
| — | — |
| print_int | eax里的整型值 |
| print_char | al里的字符的ascii形式 |
| print_string | eax里的地址指向的字符串的内容。此字符串为c类型<=>以null结束的字符串 |
| print_nl | 换行 |
| read_int | 读入整形到eax |
| read_char | 读入单个字符以ascii形式存储到eax里 |

* asm_io.inc 提供的用于调试的宏

* dump_regs
* 打印寄存器值

“`
dump_regs 1 ; 这个整数是用来标志这次输出的
“`
* dump_mem
* ascii字符型形式显示内存区域的值。

“`
; 这个2类似dump_regs里那个参数。outmsg1表示内存地址。1表示从这个地址开始后的1×16个字节
dump_mem 2, outmsg1, 1
“`

* dump_stack

* 显示CPU堆栈的值。
* 这个堆栈由双字组成
* dump_stack 用于标识的整数, ebp寄存器里的地址下面需要显示的双字的数目, ebp寄存器里的地址上面需要显示的数目

* dump_math
* 显示数字协处理器寄存器的值
* dump_math 用于标识的整数

### 1.3 模板文件
“`
; linux模板文件
%include “asm_io.inc”

; 这个段定义有初始值的,如字符串常量
segment .data

; 这个段定义未初始化的数据,如变量
segment .bss

segment .text
global asm_main
asm_main:
enter 0, 0 ; 开始运行
pusha

; 这里放代码;勿改前后

popa
mov eax, 0
leave
ret
“`

### 1.4 32位下的寄存器们

* 览

| 类 | 名 | 释 |
| — | — | — |
| 数据寄存器 | eax, ebx, ecx, edx | 低16位寄存器ax, bx, cx, dx不可存地址, 而32位寄存器可以存地址 |
| 变址寄存器 | esi, edi | 低16位寄存器si, di。变址寄存器(index register)。 |
| 指针寄存器 | ebp, esp | 低16位寄存器bp, sp。 Pointer register |
| 段寄存器 | cs,ds,es,ss,fs,gs | 实模式下:cs(code segment register)-> 代码段段值, ds(data)->数据段, es(extra)->附加数据段, ss(stack)->堆栈段段值, fs、gs(同es)|
| 指令指针寄存器 | eip, ip | 存放下次将要执行的指令在代码段的偏移量。 实模式下每个段最大范围为64->eip高16位为0 && 相当于只用ip |
| 标志寄存器 |
| |
| 数据寄存器 | eax | 累加器accumulator。可用于 乘除、io等操作,使用频率高 |
| 数据寄存器 | ebx | 基地址寄存器base register ,可作为存储器指针使用 |
| 数据寄存器 | ecx | 计数寄存器count register。 1 循环&&字符串操作时->控制循环次数。 2 位操作时->cl指明移位位数 |
| 数据寄存器 | edx | 数据寄存器data register。 1 乘除时,可默认操作数参与运算。 2 可存放io端口地址。|
| |
| 标志寄存器 | 进位标志cf(carry flag) | +-, 无符号大小比较, 移位 时,最高位进位/借位则cf为1,否则为0 |
| 标志寄存器 | 奇偶标志pf(parity) | 运算结果1个数为偶数->1,否则0。 不常用 |
| 标志寄存器 | 辅助进位标志af(auxiliary carry flag) | 1 字操作时,低字节进位/结尾 2 字节操作时,低四位向高四位进位或借位 。 这两种情况置1,否则为0。 不常用 |
| 标志寄存器 | zf(zero) | 运算结果为0置一,否则为0 |
| 标志寄存器 | sf(sign) | 运算结果符号位,同于最高位 |
| 标志寄存器 | of(overflow) | 有符号数+-时是否溢出。 溢出时置1,否则为0 |

###### 二、状态控制标志位
* 状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。

* 1、追踪标志TF(Trap Flag)
当追踪标志TF被置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。
指令系统中没有专门的指令来改变标志位TF的值,但程序员可用其它办法来改变其值。
* 2、中断允许标志IF(Interrupt-enable Flag)
中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下:
(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;
(2)、当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。
CPU的指令系统中也有专门的指令来改变标志位IF的值。
* 3、方向标志DF(Direction Flag)
方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节——字符串操作指令——中给出。在微机的指令系统中,还提供了专门的指令来改变标志位DF的值。

###### 三、32位标志寄存器增加的标志位
* 1、I/O特权标志IOPL(I/O Privilege Level)
I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。
* 2、嵌套任务标志NT(Nested Task)
嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:
* (1)、当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;
* (2)、当NT=1,通过任务转换实现中断返回。
* 3、重启动标志RF(Restart Flag)
重启动标志RF用来控制是否接受调试故障。规定:RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。
* 4、虚拟8086方式标志VM(Virtual 8086 Mode)
如果该标志的值为1,则表示处理机处于虚拟的8086方式下的工作状态,否则,处理机处于一般保护方式下的工作状态。

###### lala
* 变址寄存器
* 可实现多种存储器操作数寻址方式,方便不同地址形式访问存储单元
* 不可分割为8位
* 可存储算数逻辑运算的操作数/结果
* 作为存储器指针
* 字符串操作指令执行时,有特定要求&&特殊功能

* 指针寄存器
* 主要用于存放堆栈内存存储单元的偏移量
* 可实现多种存储器操作数的寻址方式,方便不同地址形式访问存储单元
* 不可分割为8位寄存器。
* 可存储算数逻辑运算的操作数/结果
* 主要用于访问堆栈内的存储单元
* bp->base pointer,可直接存取堆栈中数据
* sp->stack pointer, 只可访问栈顶

* 段寄存器
* 保护模式下段寄存器里不再是段值,而是选择子(selector)的某个值

## 2 基本汇编语言
### 2.1 整形工作方式
#### 2.1.1 补码范围

“`
字节 8位 -128~127
字 16位 -32768~32767
双字 32位 -20亿~20亿
“`

#### 2.1.2 扩展和缩小
##### 缩小
* 无符号数

“`
要移除的都要是0
“`

* 有符号数

“`
1. 要移除的全0或全1
2. 移除后符号位不变(移除的这些是1还是0 要与 移除后得到的数的符号位一致
“`

##### 扩展
* 扩展无符号数
* 扩展到ax相关的

···
mov ah, 0 ; al中的无符号字节扩展到ax中
···

* 扩展到eax相关的

“`
80386:
movzx dest, src
dest要大于src
dest->16/32位寄存器
src->[8/16位寄存器]/[内存中的字]

eg.
movzx eax, ax;
movzx eax, al
movzx ax, al
movzx ebx, ax
“`

* 扩展有符号数

* 无操作数

| 指令 | 作用 |
| — | — |
| cbw | 8086; al扩展成ax |
| cwd | 8086; ax->dx:ax |
| cwde | 80386; ax->eax |
| cdq | eax->edx:eax |
| movsx | 类似movzx |

#### 2.1.3 补码计算
* add/sub 可同时用与有符号/无符号整型。 进位丢掉

##### 乘法
* mul -> 无符号

“`
mul source
source->寄存器/内存
source 1字节-> ax = source * al
source 2字节-> dx:ax = source * ax
“`

* imul -> 有符号

* immed8,immed16, immed32是立即数,(不是每一个常数都是立即数
* dest只能说寄存器
* s2只能是立即数

| dest | source1 | source2 | 操作 |
| — | — | — | — |
| | reg/mem8 | | ax = al * s1 |
| | reg/mem16| | dx:ax = ax * s1 |
| | reg/mem32| | edx:eax = eax * s1 |
| reg16 | reg/mem16/immed8/immed16 | | dest * = s1 |
| reg32 | reg/mem32/immed8/immed32 | | dest * = s1 |
| reg16 | reg/mem16 | immed8/immed16 | dest = s1 * s2 |
| reg32 | reg/mem32 | immed8/immed32 | dest = s1 * s2 |

##### 除法
* div source -> 无符号整型

“`
source -> 8位, ax / source, al = 商, ah = 余数
source -> 16位, dx:ax / source ax = 商, dx = 余数
source -> 32位, edx:eax / source eax = 商, edx = 余数
“`

* idiv -> 有符号整型

“`
似div而不似imul
若 商大于其寄存器 / 除数为0 -> 程序中断/中止
“`

##### 求反
* neg
* 通过计算操作数补码得到相反数
* 操作数 8/16/32位寄存器 / 内存区域

#### 2.1.4 扩充精度运算
* adc
* operand1 = operand1 + carry flag + operand2

“`
; 64位整型加法
; edx:eax = edx:eax + ebx:ecx
add eax, ecx ; 低32位相加
adc edx, ebx ; 高32位带上一步的进位相加

“`

* ssb
* operand1 = operand1 – carry flag – operand2

“`

; 64位整型减法
; edx:eax – ebx:ecx
sub eax, ecx ; 低32位相减
sbb edx, ebx ; 高32位带借位相减
“`

* clc
* clear carry 清楚进位->将进位标志位置0

### 2.2 控制结构
#### 2.2.1 比较
* cmp vleft vright
* 比较结果存于flags寄存器
* vleft vright最多一个是mem
* 通过减法比较
* 无符号整型
* vleft = vright -> zf=1, cf=0
* vleft > vright -> zf=0, cf=0
* vleft < vright -> zf=0, cf=1
* 有符号整型
* vleft = vright -> zf = 1
* vleft > vright -> zf = 0, sf = of
* vleft < vright -> zf = 0, sf != of

“`
vleft > vright:
of = 0 -> sf = 0
of = 1 -> 差值为负数 -> sf = 1
“`

#### 2.2.2 分支
* 无/有 条件分支

##### 1 jmp(jump) -> 无条件分支
* jmp 代码标号
* jmp指令后的指令不会被执行,除非被另一个分支所指
##### 2 有条件分支
* 简单比较

| 指令 | 跳转条件 | | 指令 | 跳转条件 |
| — | — |
| jz | zf = 1 | | jnz | zf = 0 |
| jc | cf = 1 | | jnc | cd = 0 |
| |
| jo | of = 1 | | jno | of = 0 |
| js | sf = 1 | | jns | sf = 0 |
| |
| jp | pf = 1 | | jnp | pf = 0 |

* 有符号和无符号的比较指令

| 有符号 | 跳转条件 | | 无符号 | 跳转条件 |
| — | — |
| je | vleft = vright | 同右 |
| jne | vleft != vright | 同右 |
| jl, jnge | vleft < vright | | jb, jnae | vleft < vright | | jle, jng | vleft <= vright | | jbe, jna | vleft <= vright | | jg, jnle | vleft > vright | | ja, jnbe | vleft > vright |
| jge, jnl | vleft >= vright | | jae, jnb | vleft >= vright |

* pf <- 结果中低8位1的数目为偶数->pf=1, 否则pf=0

##### 3 示例
* 示例1

“`
; if eax == 0 :
; ebx = 1;
; else:
; ebx = 2;
cmp eax, 0
jz trueblock
elseblock:
mov ebx, 2
jnz endifblock
trueblock:
mov ebx, 1
endifblock:
mov eax, ebx
call print_int
call print_nl

“`

* 示例2

“`
if eax >= 5:
ebx = 1
else:
ebx = 2
“`

“`
cmp eax, 5
jge trueblock
elseblock:
mov ebx, 2
jmp endifblock
trueblock:
mov ebx, 1
endifblock:
mov eax, ebx
call print_int
call print_nl
“`

#### 2.2.3 循环
##### loop
* –ecx; 若ecx != 0, 则jmp 代码标号;

##### loope, loopz
* for(; ecx !=0; ecx–) if (zf=1) jmp 代码标号
##### loopne, loopnz
* for(; ecx !=0 ; ecx–) if (zf=0) jmp 代码标号

“`
sum = 0;
for (int i=10; i > 0; –i)
sum += i;
“`

“`
mov ecx, 10
mov eax, 0
loopblock:
add eax, ecx
loop loopblock
nextblock:
call print_int
call print_nl
“`

### 2.3 控制结构的标准翻译
#### 2.3.1 if
##### if

“`
; 设置flags
jxx endif
; trueblock代码
endif:

“`

##### if…else

“`
; 设置flags
jxx elseblock
; thenblock
jmp endif
elseblock:
; elseblock
endif:
“`
#### 2.3.2 while

“`
while:
; 基于条件设置flags
jxx endwhile
; 循环体
jmp while
endwhile:
“`

#### 2.3.3 do while

“`
do:
; 循环体

; 设置flags
jxx do
“`

#### 2.3.4 代码规范

“`
; do something
while:
; do something
jxx endwhile1
; do something
while2:
; do something
jxx endwhile2
; do something
jmp while2
endwhile2:
; do something
if:
; do something
jxx endif
; do something
endif:
; do somehting
jmp while
endwhile1:

“`

## 3 位操作
### 3.1 移位操作
#### 3.1.1 逻辑移位
* shl指令 && shr指令
* shl/shr 寄存器 位数
* 位数是 常量/cl寄存器里的值
* 新进之位为0
* 最后从数据中移出的bit存于cf
* 应用:无符号数乘除

“`
shl ax, 1
mov cl, 3
shr ax, cl

“`

#### 3.1.2 算术移位
* 应用:有符号数乘除
* sal指令, sar指令。shift arithmethic left/right
* 最后移出的bit存于cf

“`
sal ax, 1
sar ax, 2
“`

#### 3.1.3 循环移位
* rol指令 -> 循环左移,
* ror指令 -> 循环右移
* rcl rcr <- 包含进位进行移动 * 移出的最后一个bit复制到进位标志位 ``` clc rcl ax, 1 mov bl ``` ### 3.2 布尔型按位运算 * and or xor not * test 指令: 模拟and操作,但只影响flags(如结果为0置位zf * 清0 * xor eax, eax ``` mov ax, 3 and ax, 1 cwde ``` * 应用 ``` ; 1 开启位i or 数, 2^i ; 2 关闭位i and 数, 掩码 ; 掩码: 只有位i为off ; 3 求反位i xor 数, 2^i ; 4 得到除以2^i之后的余数 and 数, 2^i - 1 ; 1.2 通用开启一位 mov cl, bh ; bh存需要置位的bit位 mov ebx, 1 shl ebx, c1 or eax, ebx ;开启位 ;2.2 通用关闭一位 mov c1, bh mov ebx, 1 shl ebx, c1 not ebx and eax, ebx ; 3.2 通用求反一位 mov c1, bh mov ebx, 1 sh1 ebx, c1 xor eax, ebx ``` ### 3.3 避免条件分支 * setxx * xx同jxx里的xx一样 * 条件为真,置1,否则置0 * setz al ``` ; 求最大值 ; eax存了input2 xor ebx, ebx ;ebx = 0 cmp edx, [n1] ; ebx = (n2 > n1) ? 1 : 0
setg bl

; ebx = (n2 > n1) ? 0xffffffff : 0
neg ebx
; 保留ebx值
mov ecx, ebx
and ecx, eax

; ebx = (n2 > n1) ? 0 : 0xffffffff
not ebx
and ebx, [n1]

; ecx = (input2 > input1) ? input2 : input1
or ecx, ebx

; 使用dec代替neg,得到值相反
“`

### 3.4 c中位操作
* & -> and
* | -> or
* not -> ~
* 移位
* 移位的数无符号->逻辑移位
* 移位的数有符号->算数移位
* << 左移 * >> 右移

## 4 子程序
* eg. 函数和进程
* 调用约定
* 标准c调用约定
* 通过指针来让子程序访问内存中的数据
* 可基于寄存器

### 4.1 间接寻址

“`
mov ax, [data] ; 标准的直接内存地址大小为一个字
mov ebx, data ; ebx = & data
mov ax, [ebx] ; ax = * ebx

; —

可存代码标号
“`

### 4.2 堆栈
* push -> esp -= 4 插入双字,双字存于[esp]
* pop 寄存器 -> [esp]读取双字到寄存器,esp += 4
* push pop在32位保护模式下最好单个双字。虽多字入栈也可
* 作用
* 临时存储数据
* 子程序调用
* 传递 参数/局部变量
* pusha -> 把eax ebx ecx edx esi edi ebp 值推入栈(不是这个顺序 <- 8086 * popa -> 将这些值移出栈 <- 8086 ``` push dword 1 push dword 2 push dword 3 pop eax ; eax = 3 pop ebx ; ebx = 2 pop ecx ; ecx = 1 ``` ### 4.3 call 和 ret * <- 8086 * call * 跳转到子程序 * 下一条指令地址入栈 * ret * 弹出一个地址并跳转去执行 ### 4.4 调用约定 * 高级语言的标准传递数据方法 * 不同编译器 -> 代码如何编译(如是否优化) -> 不同调用约定
* 广泛约定 : call调用代码, ret指令返回

#### 4.4.1 堆栈上传参
* 栈上传惨给子程序
* 参数为地址 -> 子程序进行修改

“`
; 将 最后压入的指针指向的值 设为 edx的值
mov eax, [ebp + 8 ]
mov [eax], edx

; mov eax, [eb[ + 8]等价于以下两行
lea eax, [ebp + 8]
mov eax, [eax]

mov [ebp – 4], 0 ; 将 第一个局部变量 设为0
“`

* 参数小于双字 -> 转换成双字再传
* 保护模式下,数据段、堆栈段为同一段
* 移除参数
* c -> c移除参数
* pascal -> 子程序移除参数。 关键字pascal用于函数原型/定义使编译器使用此约定。 不允许变参
* add ebp, N / pop ecx 用于移除参数

“`
subprogram:
push ebp ; 为了在子程序中不改变ebp的值,先将其备份,后再恢复
mov ebp, esp
sub esp, LOCAL_BYTES ; = #局部变量需要的字节数

; [ebp + 4] -> 返回地址
; [ebp + 8] -> 最后压入的参数

mov esp, ebp ; 释放局部变量
pop ebp
ret
“`

“`
一个源文件中,可有多个.data .text, 连接时终会组合成单一
“`

#### 4.4.2 堆栈上局部变量
* 子程序在堆栈存局部变量 => 可重入(可嵌套调用
* 不存于堆栈中的是 全局/静态变量, 存于内存于程序始终
* 堆栈帧:堆栈中含参数,返回信息,局部变量
* enter leave简化子程序开始/结束部分。 但慢于等价简单命令
“`
; enter ; leave相当于
push ebp
mov ebp, esp
sub esp, LOCAL_BYTES
;
mov esp, ebp
pop ebp
“`

“`
enter 0, 0 ; 第一个参数 -> 局部变量需要字节数, 第二个参数在c调用约定中为0
leave ; 无操作数

subprogram:
enter LOCAL_BYTES, 0

leave
ret
“`

### 4.5 多模块程序
* extern -> 变量定义于外部(指代码/数据变量
* global -> 定义外部可访问

### 4.6 c与汇编的接口技术
* 食用汇编
* 方法
* 内嵌汇编
* 调用汇编子程序
* 之因
* c很难/不肯能 做到的直接访问硬件特性
* 手动优化以更快
* 缺点
* 可移植性–
* 可读性–

#### 4.6.1 保存寄存器
* c之子程序保存
* ebx
* esi edi
* ebp
* cs, ds, ss, es

#### 4.6.2 函数名
* c编译器
* linux gcc 不附加_, f即调用function f
* djgpp 附加 _
* _全局/静态变量
* _f调用 function f

#### 4.6.3 传递参数
* c调用约定:先压入后出现的参数,后压入的更接近栈底
* => printf 里的格式化字符串恒为[ebp + 8]

#### 4.6.4 计算局部变量的地址
* 连接程序找到 .data .bss里变量的地址

“`
; 非法
mov eax, ebp – 8 ; 最后一个必须是常量

“`

* lea (load effective address, 载入有效地址)

“`
; lea不会从内从中读数据,仅仅计算地址于第一个操作数中;
; 无需指定内存大小(如用dword
lea eax, [ebp – 8]
lea eax, [eax + 4*ecx] ; 可有*号
“`

#### 4.6.5 返回值
* 整型(char, int, enum) (扩展成32位) 通过 eax返回
* 64位通过edx:eax返回
* 浮点数存于数学协处理器中的st0即存在

#### 4.6.7 其他调用约定
* gcc
* 扩展语法__attibute__指定函数调用约定

“`
; 返回值为空的函数f,带有一个int参数
; 使用标准调用约定
void f(int) __attribute__((cdecl));
“`

* 标准call调用约定,把cdecl替换成stdcall
* stdcall要求子程序将参数移除栈(同pascall调用约定), 只可使用在参数固定的函数上

* regparam约定
* 前三个整型参数通过寄存器传递给函数,而不是堆栈。
* 被许多编译器支持的一个共同的优化模式

* borland, microsoft使用同样语法声明调用约定
* 关键字”__cdecl” 关键字”__stdcall”, 用于原型声明的函数名前

“`
void __cdecl f(int);
“`

* cdecl约定
* 简单灵活
* 可用于任何 c函数 c编译器
* 其他约定限制子程序的可移植性
* 缺点
* 慢于其它约定
* 使用更多内存

#### 4.6.8 在汇编中调用c
* 记得 extern 声明c中函数
* c中函数遵循c调用标准。
* eax中返回整型
* 最后压入的参数,为c中函数第一个参数

### 4.8 可重入和递归子程序
* 可重入
* 要求
* 不修改代码指令。保护模式cs仅只读,不可改
* 不修改全局变量(如.data, .bss里的数据 )。 使用局部变量
* 好处
* 可递归调用
* 可被多进程共享。 有多个实例在运行中的程序,常常仅一份代码存于内存(共享库, dll也是这样)
* 可运行于多线程程序

#### 4.8.1 递归子程序
#### 4.8.2 回顾c变量的储存类型
* global
* static时仅同一模块可访问
* static
* automatic. 局部变量的类型
* register. 要求编译器使用寄存器存储(不一定遵循)
* 仅简单整形数据可以是。结构体不可
* 不能使用变量地址,否则不遵循(因为寄存器无地址
* 普通的自动变量常常自动转成寄存器变量
* volatile. 不稳定
* 如被多个线程修改
* 不能做这种优化:暂存变量于寄存器,出现变量代码部分使用这个寄存器

“`
x = 10;
y = 20;
z = x;
; 若x不是不稳定型,则编译器将z=10,但x可能被多线程修改
“`

## 5 数组
### 5.1 介绍
#### 5.1.1 定义数组
* .data .bss中定义

“`
segment .data
a1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
a2 dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
a3 times 10 dw 0
; a4中含200个0和100个1
a4 times 200 db 0
times 100 db 1
segment .bss
a5 resd 10
a6 resw 100
“`

* 堆栈上定义

“`
; 定义 1x字符, 2x双字, 1x50x字 的 数组
; 1 + 2 x 4 + 1 x 50 x 2 = 109
; 定义112,因为esp仅能减4的倍数 => 另存1x字符的后面跟3xUnused, 来保持在双字边界上 => 加速内存的访问
enter 112, 0
leave
“`

#### 5.1.2 访问数组中的元素

“`
segment .data
arr1 db 5, 4, 3, 2, 1
arr2 dw 5, 4, 3, 2, 1
segment .text
mov al, [arr1] ; al = arr1[0]
mov a1, [arr1 + 1] ; al = arr1[1]
mov [arr1 + 3], al ; arr1[3] = al
mov ax, [arr2] ; ax = arr2[0]
“`

#### 5.1.3 更高级的间接寻址方式

“`
; []中可以有*号
mov eax, [eax + 4*eax + 4]
“`

#### 5.1.4 多维数组
##### 二维数组
* 依行表示
* 依列表示

##### 大于二维
* a[L][M][N]
* 按行: &a[i][j][k] = a + i * M * N + j * N + k
* ->
* 按列: &a[i][j][k] = a + k * L * M + j * L * i
* <- ### 5.2 数组/传处理指令 * 串处理指令 * 使用变址寄存器(esi, edi) 并使其++/-- * df决定++/-- * cld -> 清方向标志位, ++
* std -> 置方向标志位, —

#### 5.2.1 读写内存
* 同时读写 b/w/d
* 读写
* esi(source index)用于读
* edi(destination index)用于写
* 包含数据寄存器固定: al, ax, eax
* es -> 需要写的段,而非ds .
* 保护模式下只有一个数据段, es自动初始化为引用ds(即和ds一样)
* 实模式下

“`
mov ax, ds
mov es, ax
“`

* 串之存取

| 命令 | 作用 | | 命令 | 作用 |
| — | — | — | — | —|
| lodsb | al = [ds:esi]; esi +-= 1 | | stosb | [es:edi] = al; edi +-= 1 |
| lodsw | ax = [ds:esi]; esi +-= 2 | | stosw | [es:edi] = ax; edi +-= 2 |
| lodsd | eax = [ds:esi]; esi +-= 4 | | stowd |[es:edi] = eax; edi +-= 4 |
| movsb | byte [es:edi] = byte [ds:esi]; esi +-= 1; edi +-= 1 |
| movsw | word [es:edi] = word [ds:esi}; esi +-= 2; edi +-= 2|
| movsd | dword [es:edi] = dword [ds:esi]; esi +-= 4; edi +-= 4 |

#### 5.2.2 rep
* 8086提供
* 是前缀指令
* 不是指令,是串处理指令前面的一个特殊的字节,用于修改指令的行为
* 重复执行下条串处理指令一定次数
* ecx指定次数,类似loop时

#### 5.2.3 串比较指令
* 比较
* 内存&&寄存器
* 内存&&内存
* 会设置flags寄存器
* 指令间区别
* cmpsx 比较内存空间
* scasx 根据指定值扫描内存空间
* 指令们

| 指令 | 作用 | | 指令 | 作用 | | 指令 | 作用 |
| — | — |
| cmpsb | 比较[ds:esi], [es:edi],并合适的增加esi, edi的值 | | cmpsw | 类比左 | | cmpsd | 类比左 |
| scasb | 比较al, [es:edi],并合适增加edi | | scasw | ax进行比较 | | scasd | eax进行比较 |

#### 5.2.4 repx前缀指令
* 指令们

| 指令 | 作用 |
| — | — |
| repe, repz | zf=1 / 重复次数<=ecx时,重复执行 | | repne, repnz | zf=0 / 重复次数<=ecx时,重复执行 | ## 6 浮点 ### 6.1 表示 * 数学协处理器使用 扩展精度 #### 1 ieee单精度 * 32bit,精确到小数点后七位 * 1.17x10^-35 ~ 3.4x10^35 * sign bit(符号位) [31], biased exponent(偏置指数)[23, 30], fraction(分数)[0,22] * biased exponentk = 真实指数 + 7f(=127) * 偏置指数总非负 * 分数部分不含零头的1 * 非规范数 * 小于 1x2^-126 * 偏置指数=0 * 分数部分为 x2^-127, 含小数点左边的1 * 特殊值表示 | e | f | 表示 | | -- | -- | -- | | 0 | 0 | 0(+-0) | | 0 | != 0 | 非规范数 | | ff | 0 | +-无穷大 | | ff | !=0 | NaN(Not a number), 不可定义的结果 | #### 2 ieee双精度 * 64bit, 小数点后15位 * s [63], e[52, 62], f[0, 51] * 偏置指数 = 真实指数 + 3ff(1023) * 10^-308 ~ 10^308 * 特殊值 * 无穷数 和 NaN, 偏置指数为7FF * 非规范数 * 用x2^-1023 ### 6.2 浮点运算 * 加减 * 移动 指数小的 数 的 有效位 -> 使指数相等
* 乘除
* 有效数相乘, 指数相加
* 结果四舍五入成所需有效位
* 比较
* 结果并不精确
* 如想要比较0时, 应改为与非常小的正数比较

### 6.3 数字协处理器
* 8个浮点数寄存器
* 80bit, 为扩展精度
* st0, st1, st2, …, st7
* 1个状态寄存器
* 用于比较的4个标志位: C0, C1, C2, C3
* 浮点寄存器被当作堆栈来管理
* st0总是指向栈顶的值(即新压入的值

#### 指令
* 协处理器助词符以f开头
* 指令们

##### 1 增删改
###### 1.1 内存 -> 栈

| 指令 | 作用 |
| — | — |
| fld source | 内存中读取浮点数到栈顶, source->单/双/扩展精度数 / 协处理器寄存器 |
| fild | 内存中读取整型数(接着转化为浮点数)到栈顶。 source-> w/d/q |
| fld1 | 1->栈顶 |
| fldz | 0->栈顶 |

###### 1.2 栈 -> 内存

| 指令 | 作用 |
| — | — |
| fst dest | st0 -> 内存。 dest->单/双精度数 / 协处理寄存器 |
| fstp dest | fst dest后,pop出st0 |
| fist dest | st0 -> 整型 -> 内存。 dest -> w, dword | fistp dest | fist dest后, pop出st0, dest可以是qword |

* 浮点数 -> 整型
* 默认四舍五入
* fstcw(store control word, 储存控制字)
* fldcw(load conrol word, 导入控制字)

###### 1.3 堆栈自身操作
* FXCH STn
* st0 <-> stn, n为[1,7]
* FFREE STn
* 释放堆栈中的寄存器(<-标记寄存器 unused/空 ##### 2 加乘 减除运算 * 计算st0 +- 另一个操作数 * 结果存于协处理寄存器 * 加和乘 | 指令 | 作用 | | 指令 | 作用 | | --- | --- | | fadd src | st0 += src, src -> register, mem float/double | | faddp src | fadd src后pop, 参数只能是register |
| fadd dest(<- reg), st0 | dest += st0 | | faddp dest, st0 | fadd dest, st0后pop | | fiadd src | st0 += (float) src, src->mem w/d |

将add改为mul即为乘法的指令,规则相同

* 减和除
* fsub
* fsub (st0, )src
* st0 = st0 – src
* 一个参数时src可以是(mem float/double),(两个参数时src只能是st0)
* fsubr (st0,) src中 st0 = src – st0
* fsubp
* fsubp dest(, st0)
* dest = dest – st0
* pop
* 参数只能是register
* fsubrp dest(, st0) 中 dest = st0 – dest
* fisub src
* st0 = st0 – (float)src
* src -> mem w/d
* fisubr src中 st0 = (float)src – st0

sub改为div即为除法的,规则相同。除法新增fidivr

##### 3 比较
* 加减乘除,比较时,一个参数时则参数->register, mem float/double, 两个参数时都只能是register。但fi开头的参数只能是mem w/d
* fcom src
* st0, src
* src -> register, mem float/double
* fcomp src在fcom src后pop
* fcompp
* 比较st0, st1, 接着pop() x 2
* ficom src
* st0, (float) src
* src 可是 mem w/d
* ficomp src 在 ficom src后pop
* ftst
* 比较st0, 0
* 状态字 -> flags
* fstsw dest
* 状态字 -> (mem word)/ax
* sahf
* ah -> flags
* 接着jxx类似无符号的
* lahf
* flags -> ah
* Pentium处理器所支持
* fcomi(p) src
* 比较st0, src
* src -> register

##### 4 杂项指令
* fchs
* st0 * = -1
* fabs
* st0 = |st0|
* fsqrt
* st0 = st0^1/2
* fscale :
* st0 = st0x2^Lst1J

### 6.4 代码

“`
; 不要想着用寄存器去替换局部变量的使用,
; 调用任意一个函数都可能改变eax, ecx, edx的值(因为调用约定不保证这三个的值不变), 不要想着用这三个寄存器来当局部变量之类的
%define 参数1 ebp+8 ;定义子程序的第一个参数
%define 局部变量1 ebp-4; 定义子程序的第一个局部变量,这里-的值等于 局部变量1的大小
%define a qword [ebp+8]
; %define只是替换,而不是求值
segment .data
; 这里可定义子程序的局部常量,如4

; |第二个局部变量,地址是 ebp-第一个局部变量的长度-第二个局部变量的长度 | 第一个局部变量,最左边的地址(即引用的地址)是ebp-局部变量的长度|最左边这里是ebp指向的地址的开始,这里长dword,这里存的 值是调用者的ebp |dword的返回地址(=ebp+4),这里长dowrd|这里保存了参数一(地址=ebp+8)

; 没有用空行分隔开的代码块们, 应该不依赖于上下文中的eax, ecx, edx

; 参数是指针时, 要
; mov eax, [ebp + 8] ; 运行之后eax为指针,
; mov dword [eax], 4

“`

## 7 结构体与c++
### 7.1 结构体
#### 7.1.1 简介
##### 汇编时
* 要结构体起始地址
* 要元素相对偏移地址
* 偏移地址不可计算,要编译器赋值
* 第一个元素偏移地址为0
* 内存中的存储顺序与struct定义中相同

#### 7.1.2 内存地址对齐
* 许多编译器
* 变量对齐在dword上
* 32位保护模式下,dword开始的数据可以快速读取内存
* c之对齐
* gcc

“`
// gcc:
typedef short int unaligned_int __attribute__ ((aligned(1)))
/*
则 unaligned_int为字节对齐
aligned()的括号里为2^n
double默认dword对齐
*/

// gcc; 压缩结构体
struct s {
int x
} __attribute__ ((packed))

“`

* microsoft, borland

“`
// #pragme
#pragme pack(push) // 保存对齐方式状态字
#pragma pack(1) // 1/2/4/8/16
struct S {

}
#pragme pack(pop) //恢复原始的对齐方式
“`

#### 7.1.3 位域s
* 位域 => 指定 结构体成员大小(单位为bit(不一定是8的倍数))
* 硬件设备接口常常使用奇数的bit
* 要尽量避免使用位域

“`
struct s {
unsigned f1 : 3; // 3个位域名
unsigned f2 : 10;
unsigned f3 : 11;
unsigned f4 : 8;
}
// 第一个位域在dword的最低有效位
// f4, f3, f2, f1
“`

#### 7.1.4 汇编之结构体
* 结构体做参数时传递指针
* 要结构体做返回值时,就将结构体指针放在参数里
* 大多编译器支持汇编中定义结构体

### 7.2 汇编之c艹
* 许多 c&&asm接口规则 适用于 c++

#### 7.2.1 重载函数和名字改编
* c不支持重载函数,但c艹可
* 函数签名 <- 参数顺序/类型 * int参数 -> 函数签名末尾有i
* double参数 -> 函数签名末尾有d
* 全局变量签名<- 变量类型 * void f(int x, int y, double z) * djgpp-> _f__Fiid
* borland-> @f$qiid
* <- c艹之类型安全连接(c无此) * 不同编译器编译的c艹代码或许不能连接在一起 * 写c艹之汇编时,须知c艹编译器的名字改编规则(或使用一些其它技术) * djgpp: * _f__Fi <- f(int) * _f__Fd <- f(double) * borland c艹 * @f$qi * @f$qd * c艹调用 c&&c之asm * extern "C" * 函数/全局变量 使用c链接 <=> 指定 函数/全局变量 为c标准约定
* 也支持 全局代码块

“`
extern “C” {
/* c链接的全局变量和函数原型
}
“`

* c艹编译器定义宏__cplusplus
* 可用于创建 使as能被c/c艹使用 的头文件

#### 7.2.2 引用
* c艹之特性
* -> 传递参数给函数时可以 看起来没使用指针

#### 7.2.3 内联函数
* c艹之特性
* 宏之使用 -> 减少 调用简单函数的 时间开支
* 并不call, 直接插入这段asm代码
* 内联函数代码改变,则需要重新编译使用此之源文件
* 非内联函数,原型不变则通常无需重新编译
* => 内联函数常置于头文件
* 违反c头之准则:不放执行的代码语句

#### 7.2.4 类
* 成员函数含 隐藏参数->指向对象的指针。为第一个参数(<- [ebp+8]) * 成员函数签名 <- 类名 * 第一个数据成员地址 = 类的地址 * djgpp, gcc, borland编译时-s产生汇编源文件 * gjgpp,gcc时,asm扩展名 .s -> AT&T语法
* borland, ms时,asm扩展名 .asm -> masm语法

#### 7.2.5 继承和多态
* 以下多态方法涉及的是microsoft, borland, windows使用的多态的实现方法(而非gcc
* 含virtual 成员方法的类有 vtable指针->指向array(成员方法)
* vtable是类之属性,存于类而非存于对象(类似static成员)
* 继承来的方法的地址 在子类和父类中 在子类未重载方法时 是一样的
* 可 从vtable读地址 来调用虚函数

“`
; 晚绑定(late binding)。运行时决定调用哪个
; 对应早绑定(early binding)
call dword [edx] ; edx里存的函数地址
“`

* microsoft 对c艹类成员方法 默认使用非标准c调用约定
* 此约定 将 对象指针存于ecx, 而非用堆栈
* borland不是microsoft这样

#### 7.2.6 c艹的其他特性

## 8 之后
* cli && sti
* cli屏蔽中断,sti恢复中断
* 用于改变ss:sp时
* 因为在去执行中断前将[寄存器,下一条指令地址等]保存到ss:sp处
* sp指示偏移

* out && in
* 还有inb outb
* in->从端口读, out->写到端口

“`
; 从21H端口读取一字节到al
in al, 21H

; 将al的值写入到21H端口
out 21H, al
“`

* x86/x86_64 内存管理寄存器 4个
* GDTR:Global Descriptor Table Register
* 保存 GDT 的 基址(<-第一个字节地址) 和 表限(<-表的大小) * LDTR:Load Descriptor Table Register * IDTR:Interrupt Descriptor Table Register * lidt指令 -> 设置 IDTR的值
* sidt -> 保存 IDTR的值
* TR:Task Register

###### 控制寄存器
* 功用说明
* 控制cpu重要特性
* 进程管理
* 虚拟内存管理
* cr0
* 0位 -> pe(protedted enable)(保护允许位)
* 用于启动保护模式(<- 为1时) * 2 -> em(emulate coprocessor)(模拟协处理器位)
* em=1 -> 不使用协处理器
* 4 -> et(processor extension type)(微处理器的扩展类型位)
* et=0 -> 287协处理器
* et=1 -> 387浮点协处理器
* 16 -> 写保护位(wp)
* wp=0 -> 禁用写保护
* 31 -> 分页允许位(paging enable)

* cr1 未定义
* cr2 页故障线性地址寄存器
* 最后一次出现页故障的全32位线性地址
* cr3 页目录基址寄存器
* 存储一个指针,这个指针指向一个page directory。
* cr4 Pentium系列(486及之后)
* 何时启用虚拟8086等

##### 立即数
* 立即数 = 8位常数 循环右移偶数位

##### other

* .spcace
* .space size(, fill=0)
* 保留size个字节空间,每个字节为fill

“`
.space SIZE
.global spacetop

// 使用:
mov $(spacetop), %eax
“`