汇编
-
编译
nasm -f elf 文件名
-f:指定输出文件格式,elf:linux格式下的可执行文件
ld -m elf_i386 -s -o 目标文件名 原可执行文件名
ld:调用ld链接器 -m:指定格式(elf_i386) -s:去除所有符号表和重定位信息,生成更小的可执行文件 -o:指定输出名
-
代码部分(section后面都有空格)
- section .data:常量
- section .bss:变量
- 分号后是注释,如//
- section .text:代码,格式必须是
1
2
3
4
5section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
code -
每一句代码的格式:
INC: 1 加1
MOV:2 后赋值前
ADD: 2 后加到前
AND: 2 后与前
-
内存一般有三个部分:
- 数据段
- 代码段
- 堆栈
-
寄存器
-
普通寄存器:EAX,EBX,ECX,EDX
低16位:AX(accumualator,用于输入输出和算数),BX(用于索引地址),CX(记录循环次数),DX(输入输出)
低8位:AL,BL,CL,DL
中低8位:AH,BH,CH,DH
-
指针寄存器(ESP,EBP)
SP:栈指针(函数调用就会下移,返回就上移)
BP:栈数据指针,指向函数的局部变量和参数
IP:记录下一条要执行语句的位置
-
索引寄存器(ESI,EDI)
SI:源对象地址
DI:目标对象地址
-
标志状态寄存器
OF:计算溢出
DF:表示字符串处理方向
IF:是否启用外部中断(如键盘)
TF:是否启用单步执行(调试)
SF:算数结果的符号
ZF:运算或比较的结果是否为0
AF:存储第3位到第四位的进位情况(有些运算需要)
PF:奇偶校验
CF:存储最高位进位
-
段寄存器
CS:代码段寄存器,记录代码段的起始处和最大大小
DS:数据段寄存器,记录全局变量和静态变量的首地址和最大大小
SS:堆栈段寄存器,记录栈的底部位置和最大可扩展深度
ES:额外段寄存器,可以临时被更改指向用于访问特定空间,用于字符串
FS/GS:同ES
由于指向的地址一般为16的整数倍,因此在16进制下,最右侧的一位0被省略
-
-
系统调用
系统的内核中有一些已经写好的汇编函数,可以用来使用当前系统进行一些操作,而不需要知道细节。
方法:将syscall的代码mov入EAX,将参数mov入EBX,ECX等等,最后调用相关中断(int 0x80/80h)。
syscall的代码能在/usr/include/asm/unistd.h中找到。
sys_exit 返回状态码
sys_read 输入状态码 address length
sys_write 输出状态码 address length(只能输出字符字符串)
sys_write null address length
sys_close(对文件) null 文件描述符(文件描述符在创建新文件或打开现有文件时会生成一个)
sys_open ?
-
寻址
- 寄存器操作数:操作数为寄存器(
MOV EAX, EBX
) - 立即操作数:操作数为立即数,一般为第二个操作数(
ADD BYTE_VALUE, 65
) - 直接存储地址:使用变量名存储地址(偏移值)(
ADD BYTE_VALUE, DL
)(byte_value就是变量) - 直接偏移寻址:就像数组一样,用数组名[n]或者数组名+n来获取
- 间接存储器寻址:对于一个变量,给它加上[]就是取地址,而给寄存器加上[]就是获取其存储的地址所指向的值。
对于mov来说不能将memory移动到memory,一般来说都会通过寄存器来中转。
如果一开始定义变量没有定义类型,那么给它mov立即数时要在立即数前加上类型来避免歧义
mov [name], dword 'Nuha'
。 - 寄存器操作数:操作数为寄存器(
-
定义变量(变量实际上就是一个地址,指向内存中的一个位置)
-
要初始化D(B/E/D/Q/T)
变量名 类型 初始值(可以是各种类型的初始值,只是按照这种类型的方式存储了)
可以一下子写很多初始值,相当于申请了一个数组,而对于字符串来说本身没有逗号也算是一个字符数组了
-
不初始化RES(B/E/D/Q/T)
为未初始化的数据保留空间。
-
定义数组
变量名 times 数组大小 类型 初始化值(给每一个元素都赋这样的初始值)
当连续进行定义的时候会连续分配空间?
-
-
定义常量
-
equ
常量名 equ 表达式(如变量和变量✖️)
-
%assign
常量名 assign 值(不知道可不可以表达式)
可以被重复赋值,但是有限制,只能用这个定义表达式对已定义的进行修改。
-
%define(宏)
%define 被替换字符串 替换字符串
-
-
算数运算
-
inc/dec(可以操作内存),加减1
-
add/sub,会影响溢出和进位状态位
-
mul(无符号)/imul(有符号) 乘数
被乘数必须是已经放在特定寄存器中了
1字节相乘:放AL,结果在AX
2字节相乘:放AX,结果在DX(高)和AX(低)
4字节相乘:放EAX,结果在EDX和EAX
-
div/idiv 除数(会影响状态标志位,OF,SF,ZF,AF,PF,CF)
2字节/1字节:放AX,AL为商,AH为余数
4字节/2字节:高位放DX,低位放AX,商在AX,余数在DX
8字节/4字节:高位放EDX,低位放EAX,商在EAX,余数在EDX
-
-
逻辑运算
- and,or,xor,not,会更改第一个操作数,并改变标志状态位(CF,OF,PF,SF,ZF)
- test,与and效果一致,但不会更改第一个操作数,但会改变标志状态位。
-
汇编条件
-
设定label,格式,一行label:
-
无条件跳转,jmp label。
-
有条件跳转,通过判断标志状态位来选择是否跳转。
可以通过cmp来辅助跳转,判断两个是否相等。
条件跳转指令 检查的标志位 跳转条件 JE/JZ ZF 相等/为零时跳转 JNE/JNZ ZF 不相等/不为零时跳转 JG/JNLE SF, ZF 大于/不小于等于时跳转 JGE/JNL SF, OF 大于等于/不小于时跳转 JL/JNGE SF, OF 小于/不大于等于时跳转 JLE/JNG SF, ZF 小于等于/不大于时跳转 JA/JNBE CF, ZF 高于/不低于等于时跳转 JAE/JNB CF 高于等于/不低于时跳转 JB/JNAE CF 低于/不高于等于时跳转 JBE/JNA CF, AF 低于等于/不高于时跳转 JCXZ CX寄存器为零时跳转 JC CF 有进位时跳转 JNC CF 无进位时跳转 JO OF 有溢出时跳转 JNO OF 无溢出时跳转 JP/JPE PF 奇偶校验为偶数时跳转 JNP/JPO PF 奇偶校验为奇数时跳转 JS SF 负值时跳转 JNS SF 正值时跳转
-
-
汇编循环
虽然可以使用条件和跳转来实现循环,但也可以更加简便。
loop label
次数为ecx中存储的数字,ecx会递减,直到0。
-
汇编编号
汇编中输入,都会用ascii存储。如果要进行算数运算,就得先-’0’将其转为数字,运算后再转回去,效率低下。
在ascii(或者BCD)下进行三者运算后和除法前进行调整来在ascii下的运算也能是正确的,AA(A/S/M/D),并使用DA(A/S)来调整小数加减。对于压缩的BCD(两位放在一个字节)不能乘除。
-
字符串
要获取一个字符串的长度,一种方式是显式定义长度,另一种是在字符串定义之后,立即计算代表现在能用的存储开头,而字符串变量名指的是字符串的首地址,相减得出的自然就是长度了。
- MOVS EDI ESI,将EDI指向的内存存储的字符串MOV到ESI指向的内存
- LODS (E)A(X/L) ESI,将ESI指向的内存存储的字符串MOV到寄存器中
- STOS EDI (E)A(X/L),将寄存器存储的字符串MOV到ESI指向的内存存储中
- CMPS ESI EDI,将两处字符串进行比较
- SCAS EDI (E)A(X/L),将内存中的字符串与寄存器中的字符串进行比较
更大的部分进行操作的时候在指令后加上对应的字母(B/W/D)
方向标志会决定进行的顺序,无方向则从小到大,否则从大到小
在操作前加上REP会使操作进行CX(随之变小)次,REPE/Z和REPNE/Z会根据0标志位来决定是否继续循环。
-
函数
对于一段代码,在前面加上label:,在结尾加上ret这一段程序就变成函数了。
在调用时,直接call label即可。
-
栈(用于形参等方面)
push memory/reigiter/imediate
pop memory/reigiter
-
宏函数
1
2
3
4
5%macro name 参数个数
body;要用参数的时候直接按顺序形参名分别位%i
%endmacro
调用:name 参数1, 参数2....