汇编

  1. 编译

    nasm -f elf 文件名

    -f:指定输出文件格式,elf:linux格式下的可执行文件

    ld -m elf_i386 -s -o 目标文件名 原可执行文件名

    ld:调用ld链接器 -m:指定格式(elf_i386) -s:去除所有符号表和重定位信息,生成更小的可执行文件 -o:指定输出名

  2. 代码部分(section后面都有空格)

    1. section .data:常量
    2. section .bss:变量
    3. 分号后是注释,如//
    4. section .text:代码,格式必须是
    1
    2
    3
    4
    5
    section	.text
    global _start ;must be declared for linker (gcc)

    _start: ;tell linker entry point
    code
  3. 每一句代码的格式:[label] 操作码 [目标对象地址] [源对象地址][label]\ 操作码\ [目标对象地址]\ [源对象地址]

    INC: 1 加1

    MOV:2 后赋值前

    ADD: 2 后加到前

    AND: 2 后与前

  4. 内存一般有三个部分:

    1. 数据段
    2. 代码段
    3. 堆栈
  5. 寄存器

    1. 普通寄存器:EAX,EBX,ECX,EDX

      低16位:AX(accumualator,用于输入输出和算数),BX(用于索引地址),CX(记录循环次数),DX(输入输出)

      低8位:AL,BL,CL,DL

      中低8位:AH,BH,CH,DH

    2. 指针寄存器(ESP,EBP)

      SP:栈指针(函数调用就会下移,返回就上移)

      BP:栈数据指针,指向函数的局部变量和参数

      IP:记录下一条要执行语句的位置

    3. 索引寄存器(ESI,EDI)

      SI:源对象地址

      DI:目标对象地址

    4. 标志状态寄存器

      OF:计算溢出

      DF:表示字符串处理方向

      IF:是否启用外部中断(如键盘)

      TF:是否启用单步执行(调试)

      SF:算数结果的符号

      ZF:运算或比较的结果是否为0

      AF:存储第3位到第四位的进位情况(有些运算需要)

      PF:奇偶校验

      CF:存储最高位进位

    5. 段寄存器

      CS:代码段寄存器,记录代码段的起始处和最大大小

      DS:数据段寄存器,记录全局变量和静态变量的首地址和最大大小

      SS:堆栈段寄存器,记录栈的底部位置和最大可扩展深度

      ES:额外段寄存器,可以临时被更改指向用于访问特定空间,用于字符串

      FS/GS:同ES

      由于指向的地址一般为16的整数倍,因此在16进制下,最右侧的一位0被省略

  6. 系统调用

    系统的内核中有一些已经写好的汇编函数,可以用来使用当前系统进行一些操作,而不需要知道细节。

    方法:将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 ?

  7. 寻址

    1. 寄存器操作数:操作数为寄存器(MOV EAX, EBX
    2. 立即操作数:操作数为立即数,一般为第二个操作数(ADD BYTE_VALUE, 65
    3. 直接存储地址:使用变量名存储地址(偏移值)(ADD BYTE_VALUE, DL)(byte_value就是变量)
    4. 直接偏移寻址:就像数组一样,用数组名[n]或者数组名+n来获取
    5. 间接存储器寻址:对于一个变量,给它加上[]就是取地址,而给寄存器加上[]就是获取其存储的地址所指向的值。

    对于mov来说不能将memory移动到memory,一般来说都会通过寄存器来中转。

    如果一开始定义变量没有定义类型,那么给它mov立即数时要在立即数前加上类型来避免歧义mov [name], dword 'Nuha'

  8. 定义变量(变量实际上就是一个地址,指向内存中的一个位置)

    1. 要初始化D(B/E/D/Q/T)

      变量名 类型 初始值(可以是各种类型的初始值,只是按照这种类型的方式存储了)

      可以一下子写很多初始值,相当于申请了一个数组,而对于字符串来说本身没有逗号也算是一个字符数组了

    2. 不初始化RES(B/E/D/Q/T)

      为未初始化的数据保留空间。

    3. 定义数组

      变量名 times 数组大小 类型 初始化值(给每一个元素都赋这样的初始值)

    当连续进行定义的时候会连续分配空间?

  9. 定义常量

    1. equ

      常量名 equ 表达式(如变量和变量✖️)

    2. %assign

      常量名 assign 值(不知道可不可以表达式)

      可以被重复赋值,但是有限制,只能用这个定义表达式对已定义的进行修改。

    3. %define(宏)

      %define 被替换字符串 替换字符串

  10. 算数运算

    1. inc/dec(可以操作内存),加减1

    2. add/sub,会影响溢出和进位状态位

    3. mul(无符号)/imul(有符号) 乘数

      被乘数必须是已经放在特定寄存器中了

      1字节相乘:放AL,结果在AX

      2字节相乘:放AX,结果在DX(高)和AX(低)

      4字节相乘:放EAX,结果在EDX和EAX

    4. 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

  11. 逻辑运算

    1. and,or,xor,not,会更改第一个操作数,并改变标志状态位(CF,OF,PF,SF,ZF)
    2. test,与and效果一致,但不会更改第一个操作数,但会改变标志状态位。
  12. 汇编条件

    1. 设定label,格式,一行label:

    2. 无条件跳转,jmp label。

    3. 有条件跳转,通过判断标志状态位来选择是否跳转。

      可以通过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 正值时跳转
  13. 汇编循环

    虽然可以使用条件和跳转来实现循环,但也可以更加简便。

    loop label

    次数为ecx中存储的数字,ecx会递减,直到0。

  14. 汇编编号

    汇编中输入,都会用ascii存储。如果要进行算数运算,就得先-’0’将其转为数字,运算后再转回去,效率低下。

    在ascii(或者BCD)下进行三者运算后和除法前进行调整来在ascii下的运算也能是正确的,AA(A/S/M/D),并使用DA(A/S)来调整小数加减。对于压缩的BCD(两位放在一个字节)不能乘除。

  15. 字符串

    要获取一个字符串的长度,一种方式是显式定义长度,另一种是在字符串定义之后,立即计算字符串变量名,-字符串变量名,代表现在能用的存储开头,而字符串变量名指的是字符串的首地址,相减得出的自然就是长度了。

    1. MOVS EDI ESI,将EDI指向的内存存储的字符串MOV到ESI指向的内存
    2. LODS (E)A(X/L) ESI,将ESI指向的内存存储的字符串MOV到寄存器中
    3. STOS EDI (E)A(X/L),将寄存器存储的字符串MOV到ESI指向的内存存储中
    4. CMPS ESI EDI,将两处字符串进行比较
    5. SCAS EDI (E)A(X/L),将内存中的字符串与寄存器中的字符串进行比较

    更大的部分进行操作的时候在指令后加上对应的字母(B/W/D)

    方向标志会决定进行的顺序,无方向则从小到大,否则从大到小

    在操作前加上REP会使操作进行CX(随之变小)次,REPE/Z和REPNE/Z会根据0标志位来决定是否继续循环。

  16. 函数

    对于一段代码,在前面加上label:,在结尾加上ret这一段程序就变成函数了。

    在调用时,直接call label即可。

  17. 栈(用于形参等方面)

    push memory/reigiter/imediate

    pop memory/reigiter

  18. 宏函数

    1
    2
    3
    4
    5
    %macro name 参数个数
    body;要用参数的时候直接按顺序形参名分别位%i
    %endmacro

    调用:name 参数1, 参数2....

汇编
https://lhish.github.io/project/汇编/
作者
lhy
发布于
2024年6月30日
许可协议