少女祈祷中...

nasm使用

nasm是一种挥基于x86的汇编与反汇编软件。可以用于实现一些简单的功能或是简单操作系统

一、安装与使用

在Ubuntu环境下,可用使用sudo apt-get install nasm指令来安装nasm。在Windows或其他地方应该也有自己的安装方法

在安装完毕后,我们需要自己编写一个.asm文件,里面存放的就是汇编代码。

想要编译这个文件(例如文件名为main.asm),可用使用指令:

1
nasm –felf32 main.asm

生成一个elf文件。

然后使用以下指令:

1
ld main.o

进行链接。

注意:如果这里提示ld命令未找到的话,可以使用以下命令安装:

1
apt-get install libvcflib-tools

注意:如果使用后提示incompatible with i386:x86-64 output的话,就是说你的虚拟机是64位的。解决方法是先把刚刚生成的.o文件删除,然后把上一步指令的-felf32改为-felf64,重新执行,就可以了

链接完毕后可用开始运行。输入以下命令:

1
./main.out

就可以成功运行。

二、基本结构

1、section划分

以以下段代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
;Section to store initialized variables
section .data
string: db 'Hello World', 0Ah
length: equ $-string

;Section to store uninitialized variables
section .bss
var: resb 1

section .text
global _start:
_start:
mov eax, 4
mov ebx, 1
mov ecx, string
mov edx, length
int 80h

;System Call to exit
mov eax, 1
mov ebx, 0
int 80h

其中,;后面的内容表示的是注释。

这部分内容中包含不同的section。每一个section表示的是一个板块,有不同的功能,例如:

  • section .data表示用于声明并且初始化变量。
  • section .bss表示用于声明变量但不初始化
  • section .text表示存放可执行代码的部分,程序的入口。

2、.data部分操作

数据空间大小

不同的标识定义了一块不同的数据空间大小:

x 含义 字节数量
b BYTE 1
w WORD 2
d DOUBLE WORD 4
q QUAD WORD 8
t TEN WORD 20

以下是全部的数据类型,包括写法:

数据类型写法 含义
BYTE 8位无符号整数,B代表字节
SBYTE 8位有符号整数,S代表有符号
WORD 16位无符号整数
SWORD 16位有符号整数
DWORD 32位无符号整数,D代表双字
SDWORD 32位有符号整数,SD代表有符号双字
FWORD 48位整数(保护模式中的远指针)
QWORD 80位(10字节整数),T代表10字节
REAL4 32位(4字节)IEEE短实数
REAL8 64位(8字节)IEEE长实数
REAL10 80位(10字节)IEEE拓展实数s

声明变量

在.data中可以声明不同的变量。例如:

1
string: db 'Hello World', 0Ah

表示声明一个字符串变量。其中,string是一个标签,只用于指示内存中的地址,可以替换为其他名词。而db才是用于定义变量类型,为变量分配内存并初始化。0Ah表示换行符。

注意这里的db的b代表的就是byte,1字节。通常字符串都使用1字节的空间组成

1
length: equ $-string

表示声明一个符号表达式,使用equ。其中$表示当前位置的地址,而string表示之前声明过的标记所在的内存地址。

可以同时声明多个变量,只需要用逗号隔开

3、.bss部分操作

在.bss中可以声明未初始化的全局或静态变量。

声明变量

1
var: resb 2

resx表示每个单位大小,2表示数量。这里的resb表示的单位大小为1字节,声明两个大小1字节的空间,类似数组

4、.text部分操作

这一部分用于存放可执行的代码。

添加程序入口点

1
2
global _start:
_start:

global声明一个标签标识这个标签可以被其他源文件访问或引用。

5、寄存器

通用寄存器

注意寄存器有不同的长度,使用时需要注意

  • EAX,EBX,ECX,EDX等等为4字节
  • AX,BX,CX,DX为2字节
  • AH,AL,BH,BL等为1字节

**注意:**如果在64位机器中还存在着rax,rbx等寄存器。它们的大小为8字节。

三、基础语法

使用指令

指令名+一些寄存器名/立即数

例如:

1
MOV eax 10

表示把10存入eax中

访问变量:解引用

语法是变量大小[变量地址]

这个方法可以用于读取变量的值。因为通常标签存储的是地址。如果你想把标签当成变量名使用,访问变量的内容,就需要解引用。

例如:

1
BYTE[label]

表示访问的是label位置的大小为1字节的一个变量

1
dword[ebx]

表示将ebx中的内容视为一个地址,访问这个地址指向的一块4字节大小的内容。相当于地址的间接引用

系统调用

1
int 80h

通常int指令用于中断。它会转移到中断向量所指向的中断服务程序。这里的80h表示的是系统调用。

在系统调用前需要先设置eax和其他寄存器的值。例如

  • sys_write:写入数据,置eax为4,并且ebx需要设置为输出目的地(0=标准输入,1=标准输出,2=标准错误输出)。如果想要打印到屏幕上可以置ebx为1。ecx表示写入的数据地址,edx表示写入数据大小
  • sys_read:读入数据,置eax为3,ebx与sys_write相同。如果想要从控制台读入,那么你需要置ebx位0。之后,ecx表示读入的数据存放的地址,edx表示最多读取大小。
  • sys_exit:退出程序,置eax为1,ebx为0

预处理指令

例如:

1
2
3
4
5
6
7
8
9
10
11
%ifdef DEBUG
;打印提示信息
mov rsi, out_string
mov rdx, out_string_length
call print

;打印输入的字符
mov rsi, in_char
mov rdx, input_buffer_length
call print
%endif

当代码中使用%define定义了DEBUG的时候,这段代码就会被执行

函数

1
call function

使用call指令,function是函数名,它是一个标签。

函数中可以在最后添加一个ret,当执行到ret时就会返回到调用函数的位置

宏类似一个函数,可以拥有参数、函数名。

例如:

1
2
3
4
5
6
7
%macro print_msg 1
mov eax, 4 ; 调用 sys_write 系统调用
mov ebx, 1 ; 文件描述符 stdout
mov ecx, %1 ; 输出的字符串地址
mov edx, %1_len ; 输出的字符串长度
int 0x80 ; 触发系统调用
%endmacro

使用%macro来表示一个宏,然后print_msg是宏的名称,后面的数字代表参数的个数。如果想要访问这些参数,可以使用%数字的形式来访问。例如%1代表第一个参数,%1_len表示第一个参数的长度

四、常用指令

MOV指令

将后面的参数的内容赋值到前面的位置中

1
MOV eax, ebx

表示将ebx中的内容赋值到eax中

1
mov word[var2], 200

表示将200赋值给var2位置的变量

MOVZX指令

当把一个较小的数据移动到较大的空间时,使用MOVZX指令,自动进行无符号扩展

1
movzx eax,ah

MOVSX指令

与上面相反,当大变量移到小空间时自动截断

ADD指令

1
add eax, ebx

表示把ebx加到eax上

SUB指令

1
sub eax, ebx

表示把eax减去ebx

MUL

两个数相乘之后位数会翻倍
例如:AX = AL * src

1
MUL src

AL中存放另一个乘数,并且最后的积存放在AX中

DIV

1
DIV src

被除数放在AX中,最后的商放在AL,余数放在AH

条件分支指令

JMP

1
JMP label

跳转到label的位置

CMP

1
CMP op1, op2

能够影响到CPU的flag

基于CMP之后的flag的跳转:

位运算

  • AND
  • OR
  • XOR
  • NOT
  • TEST
    = SHL
  • SHR
  • ROL
  • ROR
  • RCL
  • RCR

AND

1
AND op1, op2

op1和op2与运算的结果存放在op1中

or,xor类似

NOT

1
NOT op1

TEST

1
TEST op1, op2

相当于AND,但是结果存入CPU FLAG中

SHL/SHR

逻辑左移/右移(直接补0)

1
SHL op1, op2

op1左移op2个位

ROL/ROR

循环左移/右移(移出去的位会去到另一边)

1
ROL op1, op2

栈操作

  • PUSH:入栈
  • POP:出栈
  • PUSHA:所有通用寄存器入栈
  • POPA:所有通用寄存器出栈

push会减少ESP的值,pop会增加

注意:32位机器push和pop时需要操作的是eax、ebx等寄存器,而64位机器只能push和pop:rax、rbx等寄存器。

五、一些预设的函数/宏定义

  • 从控制台读入数据,存入一个空间:
  • 参数1:存入读取的数据的地址
  • 参数2:读入数据的长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%macro input 2
push rax
push rbx
push rcx
push rdx
mov eax, 3
mov ebx, 0
mov ecx, %1
mov edx, %2
int 80h
pop rdx
pop rcx
pop rbx
pop rax
%endmacro
  • 将字符串打印到控制台上
  • 参数1:要写入的数据的地址
  • 参数2:写入数据的长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%macro print 2
push rax
push rbx
push rcx
push rdx
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
pop rdx
pop rcx
pop rbx
pop rax
%endmacro
  • 用于退出程序
    %macro exit 0
    mov eax,1
    mov ebx,0
    int 80h
    %endmacro

  • 用于通用寄存器出入栈

1
2
3
4
5
6
7
8
9
10
11
12
13
%macro allPush 0        ;用于将全部寄存器入栈
push rax
push rbx
push rcx
push rdx
%endmacro

%macro allPop 0 ;用于将全部寄存器出栈
pop rdx
pop rcx
pop rbx
pop rax
%endmacro