基于x86_64汇编语言简单教程3: 一些概念的补充与整理
目录
详细分析一些补充的概念
几个常见的段
注释
汇编语言指令的种类和格式导览
寄存器
补充和详细解释一些汇编器指令的内容
关于db与equ的区别详细说明
equ(Equal)
总而言之:
times
初始化一个数组
填充数据
学以致用
我的答案
详细分析一些补充的概念
我们下面就是要开始深入学习咯!
几个常见的段
汇编程序为什么分段?上一篇文章我讲过了,这里不再赘述:大致就是方便程序的重定位和我们的书写。
汇编程序在最最通用的情况下可以分为三个段(其实分几段都行,但是大伙都这样写)
-
data段:数据(data)段被用于声明初始化的数据或常数。此数据在运行时不会更改。我们可以在段中声明各种常量值,文件名或缓冲区大小等
-
bss 段:在bss段用于声明未初始化的全局变量。
-
text段:代码段被用于保持实际的代码。代码段里放着代码。一般按照目前我们书写汇编的方式,该段必须以全局声明_start开头,该声明告诉内核程序从何处开始执行。
数据段怎么用,您看到了上一篇文章我们怎么用的,这里就是怎么用:
section .data message db "Hello, World!", 0 ; message字符串以null结尾 count db 10 ; 初始化一个字节的变量
.bss段是一个新东西,还记得您C语言里粗暴写的(在哪里都行!)
static int _uninitialized_data; // 注意我故意不初始化
在一般的情况下,_uninitialized_data会放到bss段,当然,bss段的存在也是为了超级无敌省内存而诞生的,我会告诉你直到我们用它的时候,它才会被分配内存存储使用,你不用,他就是一个空壳!
section .bss buffer resb 64 ; 分配64字节的空间用于缓冲区 array resd 100 ; 分配100个32位整型的数组
.text 段用于存放程序的执行代码,即汇编指令。一般Read Only不让改
section .text global _start ; 指定入口点 _start: ; 代码逻辑 mov eax, 4 ; 系统调用号:write mov ebx, 1 ; 文件描述符:stdout mov ecx, message ; 字符串地址 mov edx, 14 ; 字符串长度 int 0x80 ; 调用内核
注释
汇编语言注释以分号(;)开头。它可以包含任何可打印字符,包括空格。它可以单独出现在一行上,例如-
; 该程序在屏幕上显示一条消息
或者,与指令在同一行上,例如:
add eax, ebx ; 加上 ebx 到 eax
汇编语言指令的种类和格式导览
欸!写代码了,我们首先要问:汇编的语言指令到底多么丰富多彩呢?嗯,其实没多丰富,至少我们需要花费点心思才能写出漂亮的代码。一般而言,你在汇编程序里,就能看到三种常见的种类
-
可执行指令或说明(mov一类的!)
-
汇编程序指令或伪操作(db... resb...)
-
宏(%macro,我是宏仙人!)
现在我们就接触到了前两种,后面的宏我会谈到的。可执行指令或简单的指令告诉处理器做什么。每个指令由一个操作码(操作码)组成。每个可执行指令生成一个机器语言指令。该汇编指令或伪操作讲述了装配过程的各方面的汇编。这些是不可执行的,不会生成机器语言指令。宏基本上是一种代码替换机制(是的,C语言的#define)。
在格式上,你看到的汇编语言语句每行输入一个语句。每个语句遵循以下格式-
[label] opcode [operands] [;comment]
方括号中的字段是可选的。基本指令包括两段,第一段是要执行的指令(或助记符)的名称,第二段是命令的操作数或参数。
label是标签的意思,当然,他们可能是在定义变量的时候出现的。比如说
charliechen db "is handsome :) "
opcode是操作码指令的核心,指定要执行的操作。操作码可以是指令名,如 mov
、add
、sub
等。
inc count ; 增加内存变量count mov total, 48 ; 将值48转移到 ; 内存变量total add ah, bh ; 添加寄存器bh内容 ; 到ah寄存器 and mask1, 128 ; 对变量mask1和128 ; 执行and操作 add marks, 10 ; 将10加到变量marks mov al, 10 ; 将值10传送到al寄存器
这里一些指令你可能没见过,不担心,至少您看一眼知道这是汇编就行!
寄存器
寄存器是CPU内部用于暂时存储数据的一些小型存储区域,当然数电课告诉你,说了一大堆让人发懵的话。不过没关系,在这里你认为他就是一个箱子就好,一个可以在1或者是几个时钟周期存放取出东西的超级无敌迅速的箱子!需要寄存器是因为处理器操作主要涉及处理数据。该数据可以存储在存储器中并从其访问。然而,从存储器中读取数据并将数据存储到存储器中会减慢处理器的速度,因为这涉及到通过控制总线发送数据请求并进入存储器存储单元并通过同一通道获取数据的复杂过程。这就把那些现在正在使用的数据(根据时空局部性原则,他们大概率还会用到!)放到寄存器里,快!
IA-32体系结构(Intel Architecture 32 bits)中有10个32位和6个16位处理器寄存器。依照每一个寄存器的职能,寄存器分为三类。
-
通用寄存器(干啥都行!)
-
控制寄存器(说白了就是指示计算机状态如何,我们好进行相应的行为,比如说JZ指令就是看Zero Flag是不是1进行跳转的)
-
段寄存器(我们说的程序分段就是在这里分的)
通用寄存器进一步分为以下几类:
-
数据寄存器
-
指针寄存器
-
索引寄存器
-
数据寄存器
我们下面来一一看看:
寄存器 | 用途 |
---|---|
EAX(累加器寄存器) | 用于算术运算,常用作操作数和返回值的存储。 |
EBX(基址寄存器) | 通常用于存储数据的基地址,配合内存访问指令。 |
ECX(计数寄存器) | 在循环和字符串操作中用作计数器。 |
EDX(数据寄存器) | 参与乘法和除法操作,存储扩展结果。 |
值得注意的是EAX ~ EDX可以各分拆成2个16位寄存器来用,一般的,低16位叫做AX, BX, CX, DX,而每一个这样的16位寄存器有可以分为上八位和下八位:
以AX为例,上八位就是AH,下八位就是AL(High和Low,好记吧!),你可能会在一些程序中见到,不要忘记!
指针寄存器是32位EIP,ESP和EBP寄存器以及相应的16位右部分IP,还有我们的SP和BP。
寄存器 | 用途 |
---|---|
ESI(源索引寄存器) | 在字符串和数组操作中,指向源数据的起始地址。 |
EDI(目的索引寄存器) | 在字符串和数组操作中,指向目标数据的起始地址。 |
EBP(基址指针寄存器) | 用于指向当前栈帧的基地址,常用于访问函数参数和局部变量。 |
ESP(栈指针寄存器) | 指向栈的顶部,用于管理函数调用的栈帧。 |
里面可能有些陌生的概念,笔者建议您先记着!我们会在用到的时候一一解答!
程序计数器是IP(Instruction Pointer Register的意思),它存储当前执行指令的地址,自动更新到下一条指令。我的意思是:在IA32体系架构下,每一个指令的地址都是32位4字节,一般而言,我们的计算机体系是按照字节进行对齐的,意味着字节这个信息可以被省略,所以你会发现IP这个指令每一次就会自动的+4,这一点,您可以看看CS61C课程对CPU设计的课程相关来看看我们的数字电路是怎么设计的,这里就不展开了。
我们还有标志寄存器EFLAGS,他用来存储运算结果的状态信息(如零标志、进位标志等),用于条件跳转。
关于EFLAGS里的每一个位,笔者建议您先过目一下:
标志位 | 全称 | 名称 | 作用 | 用途 |
---|---|---|---|---|
ZF | Zero Flag | 零标志 | 运算结果为零时置为1 | 条件跳转指令(如JE) |
SF | Signed Flag | 符号标志 | 结果为负时置为1 | 判断运算结果的符号 |
CF | Counter Flag | 进位标志 | 无符号运算发生进位或借位时置为1 | 无符号加法和减法 |
OF | Overflow Flag | 溢出标志 | 有符号运算结果超出可表示范围时置为1 | 有符号数的加法和减法 |
PF | Parity Flag | 偶数奇偶标志 | 运算结果中1的个数为偶数时置为1 | 错误检测 |
AF | Auxiliary Flag | 辅助进位标志 | BCD运算中低四位到高四位发生进位时置为1 | 十进制调整操作 |
DF | Direction Flag | 方向标志 | 控制字符串操作方向(1为低地址,0为高地址) | 影响字符串操作指令 |
IF | Interrupt Flag | 中断标志 | 允许外部中断(1)或禁止外部中断(0) | 控制中断响应 |
TF | Trap Flag | 陷阱标志 | 调试时单步执行(1) | 用于调试程序 |
IOPL | IO Privilege Level | I/O权限级别 | 指示当前任务的I/O权限级别 | 与操作系统的特权级管理相关 |
NT | Nested Task | 嵌套任务标志 | 指示当前任务是否为嵌套任务 | 任务切换时的状态管理 |
他们在EFLAG的位置如下:
位 | 标志位 |
---|---|
0 | CF |
1 | Reserved |
2 | PF |
3 | Reserved |
4 | AF |
5 | Reserved |
6 | ZF |
7 | SF |
8 | TF |
9 | IF |
10 | DF |
11 | OF |
12 | IOPL |
13 | NT |
14 | Reserved |
15 | Reserved |
还有段寄存器,几个最常见的段寄存器如下:
寄存器 | 用途 |
---|---|
CS(代码段寄存器) | 指向当前执行代码的段。 |
DS(数据段寄存器) | 指向默认数据段。 |
SS(堆栈段寄存器) | 指向当前堆栈段。 |
ES、FS、GS | 用于额外的数据段,常用于特定用途(如线程局部存储)。 |
补充和详细解释一些汇编器指令的内容
关于db与equ的区别详细说明
db
指令用于在汇编代码中定义字节(Byte)数据。它通常用于数据段中,可以用来初始化静态数据,或者分配内存空间。db
后可以跟一个或多个数值,字符或其他字节数据。
label db value1, value2, ..., valueN
-
label
:可选,表示数据的名称。 -
value
:可以是数值、字符或表达式。
这个玩意如上所示,这样使用即可。
当然,他还有其他的变种:
db
(Define Byte):用于定义字节数据,适合存储字符和小数值。例如:message db 'Hello', 0 ; 定义字符串并以0结尾
dw
(Define Word):用于定义 16 位数据,适合存储较小的整数。例如:values dw 1000, 2000 ; 定义两个 16 位整数
dd
(Define Doubleword):用于定义 32 位数据,适合存储较大的整数。例如:largeNumbers dd 1000000, 2000000 ; 定义两个 32 位整数
dq
(Define Qword):用于定义 64 位数据,适合存储更大的整数。例如:bigNumbers dq 10000000000, 20000000000 ; 定义两个 64 位整数
你可能会在这些地方用到db类的指令
-
数据存储:用于存储固定的常量值,如字符串、数组等。
-
内存分配:在程序运行前分配内存空间,存放初始化数据。
看一个例子:
section .data msg db 'Hello, World!', 0 ; 定义一个字符串并以0结尾 array db 1, 2, 3, 4, 5 ; 定义一个字节数组
在这个示例中,msg
是一个以零结尾的字符串,array
是一个包含 5 个字节的数组。编译后,这些数据将被分配到数据段中。在程序运行时,可以通过相应的标签(如 msg
或 array
)来访问这些数据。例如,可以使用指针或索引来读取或修改 array
中的值。
equ
(Equal)
equ
指令用于定义常量或符号,赋予某个值一个名字。这种方式使得代码更具可读性,并且方便在多个地方引用同一个值,而无需重复写出具体的数值。
label equ value
其中:label
:表示常量的名称,value
:可以是数值、表达式或其他常量。
在这些时候用equ比较好:
-
常量定义:定义程序中使用的常量,如数学常量、配置参数等。
-
增强可读性:通过给数字或表达式命名,使得代码更容易理解和维护。
总而言之:
-
db
是用于定义和初始化字节数据的指令,主要用于数据段,适合存储固定的、已知的值。 -
equ
是用于定义常量的指令,不占用内存,仅仅是将一个名字与一个值关联起来,适合在代码中使用时提升可读性。
在 x86 汇编中,times
是一种伪指令,用于重复生成指定数量的字节或指令。它通常用于初始化数据段或填充空间。
times
times用于重复生成指定数量的字节或指令,通常用于初始化数据段或填充空间。
times count instruction
-
count
:要重复的次数。 -
instruction
:要重复的指令或数据。
一般的,我们这样用:
初始化一个数组
section .data array db 0 times 10 ; 定义一个包含 10 个零的字节数组
填充数据
section .data buffer resb 256 ; 预留 256 字节的空间 times 256 db 0 ; 用 0 填充这 256 字节的空间
学以致用
笔者建议你完成下面的练习,先自己试试看:
-
请使用自己喜欢的方式打印10个星号,您的输出看起来是这样的:
> ./result Display 10 stars for you :) ********** >
我的答案
有千万种答案,只要你的程序可以完成上面的工作,那就是正确的程序!笔者展示我的程序
; -------------------------------------------------- ; Program written in 10.2o 2024 ; Author: Charlie chen ; Functionality: Print 10 stars with greetings ; -------------------------------------------------- section .data ; using times instruction to put 10 *s stars_here times 10 db '*' ; define welcome strings here welcome_user db "Display 10 stars for you :)", 0xA welcome_user_len equ $ - welcome_user SLASH db 0xA section .text global _start _start: ; we put welcome strings first mov eax, 4 mov ebx, 1 mov ecx, welcome_user mov edx, welcome_user_len int 0x80 ; then is our 10 stars mov eax, 4 mov ebx, 1 mov ecx, stars_here mov edx, 10 int 0x80 mov eax, 4 mov ebx, 1 mov ecx, SLASH mov edx, 1 int 0x80 mov ebx, 0 mov eax, 1 int 0x80
笔者的输出:
charliechen@Charliechen:~/demo/demo5$ ./result Display 10 stars for you :) ********** charliechen@Charliechen:~/demo/demo5$