汇编学习笔记(一)
文章目录
注意: 下面的所有代码,都是在nasm环境下完成,遵循nasm的指令集,且汇编程序都为64位程序。
数据在处理器和内存中的存储方式
处理器中:它在获取内存中数据时是从高位到低位获取的,即需要对内存中数据进行一个翻转。如下图,数据是0725h
内存中: 将数据0725H存储在内存中,低位存在低地址,高位存在高地址。
这种存储方式就是小端序存储!
编写一个简单汇编程序
汇编程序结构
一个简单汇编程序分为三段:
- data段:用来声明初始化数据或常量,这些数据在运行是不允许被改变。
- bss段:用来声明变量。
- text段:真正的代码执行段,用来执行指令。
汇编程序语句
汇编语句有三种类型:
- 可执行指令或者简单指令:我们也可以它们为
opcode(operation code)
。当计算机需要运行一个汇编程序的时候,这个汇编文件先会变成二进制文件,随后链接为可执行文件。在变成二进制文件时,每一个指令(opcode)都会被翻译成机器语言,即二进制代码(我不想叫它二进制代码,但是找不到名词形容它了)。- 汇编指令或伪操作:翻译是这样翻译的,但是不知道为啥叫汇编指令。据我理解,这部分的代码实际上不会被翻译为机器语言。类似于C语言中的
#include<stdio.h>
,它告诉编译器需要包含stdio.h
这个头文件,让它在真正执行可执行代码前导入这个头文件,而不是作为具体的指令去执行一些操作。- 宏定义:和C语言实际上来说时一样的。是一种文本替换机制。
汇编基础语法
[label] mnemonic [operands] [;comment]
这是汇编语法的基本结构,这个看着挺奇怪的,实际上看到真正的汇编代码就能理解语法是怎么样的了。注意,汇编语言里面的注释符号是;
这个后面的语句将会被注释(行注释)。
第一个汇编程序:
bits 64
default rel
segment .data
msg db "Hello world!", 0xd, 0xa, 0 ;0xd和0xa加在一起换一行,0代表字符串以0结尾
segment .text
global main
extern ExitProcess
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 32 ;提升堆栈空间
lea rcx, [msg]
call printf ;printf 这个函数通过rcx传参
xor rax, rax ;rax传参,以0退出
call ExitProcess
这是一个用来打印hello world
的汇编程序,这个其实非常的清楚。第一段为.data
段,用来存一些变量和常量。.text
段中是需要执行的代码。
MOV指令
接下来我将用一个程序演示mov
指令和调用printf
需要注意的事项
bits 64
default rel
segment .data
msg db "Hello world!", 0xd, 0xa, 0
b db 1,2,3,4
c dw 100,200,300,400
format1 db "%d", 0
format2 db "%f", 0
segment .text
global main
extern ExitProcess
extern scanf
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx,[format1]
movzx rdx,byte b[0]
add rdx,10h
call printf
xor rax, rax
call ExitProcess
分析:
bits 64
和default rel
是汇编指令,它们设置了编译器编译代码时需要使用的一些属性。这里是它们的详细说明:
bits 64
:
这是一个用于设置目标操作系统架构(指令集)的编译指令。bits 64
表示我们正在为一个 64 位操作系统(例如 x86-64 体系结构)编写汇编代码。同样,如果我们需要为一个 32 位操作系统编写代码,应使用 bits 32
。这个指令确定了指令寄存器和地址的宽度。
default rel
:
这个编译指令用于设置 x86-64 下默认的代码和数据标签寻址方式。rel
是 “relative”(相对)的缩写。在64位汇编中,推荐使用相对寻址方式,不仅因为这样更简洁,还因为它与被称为在 Position Independent Code (PIC,位置无关代码) 的代码模型兼容。
default rel
指定了在64位模式下,没有特定寻址前缀的 jxx, call, jmp 等跳转指令默认使用相对寻址。同样地,一些数据访问指令(mov, lea 等)也会默认使用相对寻址。
简而言之,bits 64
指定我们在编写在 64 位环境下运行的代码,而 default rel
指导编译器在64位模式下,默认使用相对寻址方法。这两个指令常用于 64 位汇编代码中来配置编译器的行为。
printf
需要格式化输出,所以需要传递它的第一个参数format
。然后再传递第二个参数,即需要打印的值。movzx
表示高位用0补齐,rax
实际大小为64位,而这个数组为byte类型所以需要高位补零。
以下是mov的指令形式:
MOV register, register
MOV register, immediate
MOV memory, immediate
MOV register, memory
MOV memory, register ;register是寄存器,immediate是立即数,也可以看作为常量,memory是内存
变量
为初始化的变量分配空间
定义变量的格式
[variable-name] define-directive initial-value [,initial-value]...
这是由NASM提供的用于定义变量的指令格式,这段指令写在data段中,实际上,上面的两个代码已经写过类似的格式,比如:
b db 1,2,3,4
这就是一个定义数组的指令,定义了一个byte类型的b[4],b[4] = {1,2,3,4}。在定义这个数组后,这个数组名就是该数组首地址的offset,在这里,我们都采用相对地址寻址。
变量的可定义大小(五种):
为未初始化的变量分配空间
预留空间指令:
segment .data
; 预留变量空间
var1 RESB 1 ; 预留 1 个字节 (8位) 的空间
var2 RESW 1 ; 预留 1 个字 (16位,即2字节) 的空间
var3 RESD 1 ; 预留 1 个双字 (32位,即4字节) 的空间
var4 RESQ 1 ; 预留 1 个四字 (64位,即8字节) 的空间
var5 REST 1 ; 预留 1 个十字 (80位,即10字节) 的空间
多次定义与多次初始化:
choice DB 'Y' ;ASCII of y = 79H
number1 DW 12345 ;12345D = 3039H
number2 DD 12345679 ;123456789D = 75BCD15H
这是多次定义,这些数据在data段中是连续的。
marks TIMES 9 DW 0
times
代表重复初始化,这里的意思是marks这一个dword类型的数组重复9次初始化0,即marks[0]到marks[8]都为0。
常量
下面,我将介绍一些NASM里面提供的用来声明常量的语法。
- EQU
- %assign
- %define
EQU
CONSTANT_NAME EQU expression
还是很容易看懂这玩意的,常量名 + EQU + 表达式
,这种定义方法类似于C语言里面的const int a = xxx
。当然,严谨一点来说数据类型不一定是int。
下面是一个代码示例,需要注意的是,这个常量不是定义在数据段中的。
bits 64
default rel
TESTT equ 5000
segment .data
format db "%d"
segment .text
global main
extern ExitProcess
extern scanf
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx,[format]
mov rdx,TESTT
call printf
xor rax, rax
call ExitProcess
提一个坑点,这个常量的名字不能是TEST
因为和某个指令名称冲突了。
%assign
%assign CONSTANT_NAME EXPRESSION
注意,和EQU
不同的是,%assign
可以运行重复定义,而且这个指令是大小写敏感的。如下:
%assign CONSTANT_NAME 10
%assign CONSTANT_NAME 20
可以在一个写一个汇编程序时同时存在这行代码,如下程序:
bits 64
default rel
%assign CONSTANT_NAME 10
%assign CONSTANT_NAME 20
segment .data
format db "%d"
segment .text
global main
extern ExitProcess
extern scanf
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx,[format]
mov rdx,CONSTANT_NAME
call printf
xor rax, rax
call ExitProcess
最后打印出来的值是 20。
%define
和C语言中的#define
是类似的,可以定义常量也可以定义字符串。注意它可以重定义也是对大小写完全敏感的
bits 64
default rel
%define CONSTANT_NAME 10
%define CONSTANT_NAME 20
%define dati mov
segment .data
format db "%d"
segment .text
global main
extern ExitProcess
extern scanf
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx,[format]
dati rdx,CONSTANT_NAME ;类似于文本替代,学过C语言的实际上能够很容易看懂
call printf
xor rax, rax
call ExitProcess
总结
- 目前使用的编译器是NASM,系统是Windows,所有汇编程序都是64位程序。
- 从现在开始,终于开始正式学习汇编了,进度比想象的慢,希望之后能加快进度。
- 如果有时间会不定期得更新汇编学习的博客,如有问题欢迎提出。