ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

汇编语言:数据表示与存储

汇编语言:数据表示与存储

一、数据类型与内存对齐

(一)基本数据类型
• 字节(Byte):8位(bit)组成一个字节,是计算机存储数据的最小单位。
db(define byte)指令用于定义一个字节的数据。
• 字(Word):16位组成一个字。
dw(define word)指令用于定义一个字的数据。
• 双字(Double Word):32位组成一个双字。
dd(define double word)指令用于定义一个双字的数据。
• 四字(Quad Word):64位组成一个四字。
dq(define quad word)指令用于定义一个四字的数据。

(二)内存对齐
• 内存对齐:为了提高内存访问效率,数据通常需要按照其大小进行对齐。
例如,一个字(16位)的数据通常需要对齐到2字节的地址上,一个双字(32位)的数据通常需要对齐到4字节的地址上。
• 结构体内存布局:结构体是由多个不同类型的字段组成的复合数据类型。在汇编语言中,结构体的内存布局需要考虑字段的对齐和填充。
例如,一个包含字节、字和双字字段的结构体,其内存布局如下:

struct MyStruct {byte_field db 0word_field dw 0dword_field dd 0
}

• 演示:假设结构体的起始地址为0x1000,那么:
• byte_field存储在0x1000,占用1字节。
• word_field存储在0x1002(为了对齐到2字节地址),占用2字节。
• dword_field存储在0x1004(为了对齐到4字节地址),占用4字节。

+------+------+------------+---------------------------+
| byte | 填充  |    word    |           dword           |
+------+------+------------+---------------------------+
0x0000 0x0001 0x0002       0x0004                      0x0008

二、寄存器与内存

(一)寄存器的分类和用途
1.通用寄存器(General-Purpose Registers)
通用寄存器用于存储临时数据和地址,通常可以用于多种目的。
x86-64架构
• RAX/AX/AH/AL:累加器寄存器,常用于算术运算和I/O操作。
• RBX/BX/BH/BL:基址寄存器,常用于存储基地址。
• RCX/CX/CH/CL:计数寄存器,常用于循环计数。
• RDX/DX/DH/DL:数据寄存器,常用于存储数据。
• RSI/SI:源索引寄存器,常用于字符串操作的源地址。
• RDI/DI:目的索引寄存器,常用于字符串操作的目的地址。
• RBP/BP:基指针寄存器,常用于堆栈操作,指向当前函数的栈帧基地址。
• RSP/SP:堆栈指针寄存器,指向堆栈的栈顶。
• R8-R15:扩展通用寄存器,用于存储临时数据或地址。
RAX, EAX, AX, AH, AL关系图解(适用于64位的RAX)

+-----------------------------------+-------------------------------------+
|              RAX[63:32]           |              RAX[31:0]              | <- RAX
+-----------------------------------+----------------+--------------------+
|                                   |   EAX[31:16]   |      EAX[15:0]     | <- EAX
+-----------------------------------+----------------+--------------------+
|                                                    | AX[15:8] | AX[7:0] | <- AX
+----------------------------------------------------+--------------------+
|                                                    | AH[7:0]  | AL[7:0] | <- AH, AL
+----------------------------------------------------+--------------------+

RAX是64位寄存器,RAX[63:32]表示RAX的32~63位部分,EAX是RAX的低32位部分,AX是EAX的低16位部分,AH是AX的高8位部分,AL是AX的低8位部分。

ARM64架构
• X0-X7:用于函数参数传递,是易失性寄存器。
• X8-X18:用途灵活,通常用于存储局部变量或临时数据,是非易失性寄存器。
• X19-X28:用于存储局部变量,是非易失性寄存器。
• X29(FP):用作帧指针,是非易失性寄存器。
• X30(LR):链接寄存器,用于存储返回地址。
• X31(SP):堆栈指针寄存器,指向堆栈的栈顶。
在ARM64架构中,W0到W31是X0到X31的低32位别名,即32位中的R0~R31,用于32位数据操作。

2.控制寄存器(Control Registers)
x86-64架构
• CR0(Control Register 0)
• PE(Protection Enable):第0位,启用保护模式。PE=1表示CPU运行在保护模式,PE=0表示CPU运行在实模式。
• MP(Monitor Coprocessor):第1位,用于浮点协处理器的监视功能。
• EM(Emulation):第2位,浮点协处理器仿真模式。
• TS(Task Switched):第3位,任务切换标志,用于浮点协处理器的状态保存。
• ET(Extension Type):第4位,浮点协处理器类型。
• NE(Numeric Error):第5位,浮点异常使能。NE=1表示浮点异常会中断程序运行。
• WP(Write Protect):第16位,写保护。WP=1表示禁止对只读页面的写操作。
• AM(Alignment Mask):第18位,对齐检查使能。AM=1表示CPU会检查内存访问是否对齐。
• NW(Not Write-through):第30位,非写通。NW=1表示CPU缓存采用非写通模式。
• CD(Cache Disable):第31位,禁用缓存。CD=1表示CPU缓存被禁用。

• CR2:用于存储最后一次页面错误的线性地址,是分页机制中的关键寄存器。

• CR4(Control Register 4)
• VME(Virtual-8086 Mode Extensions):第0位,虚拟8086模式扩展。VME=1表示支持虚拟8086模式。
• PVI(Protected-Mode Virtual Interrupts):第1位,保护模式下的虚拟中断。
• TSD(Time Stamp Disable):第2位,禁用时间戳计数器。TSD=1表示禁止用户模式访问时间戳计数器。
• DE(Debugging Extensions):第3位,调试扩展。
• PSE(Page Size Extension):第4位,大页面支持。PSE=1表示支持4MB大页面。
• PAE(Physical Address Extension):第5位,物理地址扩展。PAE=1表示支持超过4GB的物理内存。
• MCE(Machine Check Enable):第7位,机器检查使能。
• PGE(Page Global Enable):第8位,全局页面使能。PGE=1表示支持全局页面表项。
• PCE(Performance-Monitoring Counter Enable):第9位,性能监控计数器使能。
• OSFXSR(Operating System Support for FXSAVE and FXRSTOR):第10位,支持FXSAVE和FXRSTOR指令。OSFXSR=1表示操作系统支持浮点状态保存和恢复。
• OSXMMEXCPT(Operating System Support for Unmasked SIMD Floating-Point Exceptions):第11位,支持SIMD浮点异常。

ARM64架构
• CPSR(Current Program Status Register)
• N(Negative Flag):第31位,负标志。如果上一次运算结果为负数,N=1。
• Z(Zero Flag):第30位,零标志。如果上一次运算结果为零,Z=1。
• C(Carry Flag):第29位,进位标志。如果上一次运算有进位或借位,C=1。
• V(Overflow Flag):第28位,溢出标志。如果上一次运算结果超出范围,V=1。
• Q(Saturation Flag):第27位,饱和标志。如果上一次运算发生饱和,Q=1。
• E(Endianess Flag):第25位,字节序标志。E=1表示大端模式,E=0表示小端模式。
• I(IRQ Disable Flag):第7位,中断禁用标志。I=1表示禁止IRQ中断。
• F(FIQ Disable Flag):第6位,快速中断禁用标志。F=1表示禁止FIQ中断。
• M(Mode Bits):第0-4位,模式位,表示当前CPU的运行模式(如用户模式、中断模式等)。
• SPSR(Saved Program Status Register)
• SPSR的结构与CPSR完全相同。它用于保存中断或异常发生时的状态,以便在异常处理完成后恢复原来的状态。

3.状态寄存器(Status Registers)
x86-64架构
• RFLAGS/FLAGS(标志寄存器)
• CF(Carry Flag):第0位,进位标志。如果上一次运算结果有进位或借位,CF=1。
• PF(Parity Flag):第2位,奇偶校验标志。如果上一次运算结果的最低字节是偶数个1,则PF=1。
• AF(Auxiliary Carry Flag):第4位,辅助进位标志。如果上一次运算的低4位有进位或借位,AF=1。
• ZF(Zero Flag):第6位,零标志。如果上一次运算结果为零,ZF=1。
• SF(Sign Flag):第7位,符号标志。如果上一次运算结果为负数,SF=1。
• TF(Trap Flag):第8位,单步调试标志。TF=1表示CPU在每条指令后触发中断。
• IF(Interrupt Flag):第9位,中断标志。IF=1表示允许中断,IF=0表示禁止中断。
• DF(Direction Flag):第10位,方向标志。DF=1表示字符串操作从高地址向低地址进行;DF=0表示从低地址向高地址进行。
• OF(Overflow Flag):第11位,溢出标志。如果上一次运算结果超出范围,OF=1。

ARM64架构
• CPSR(Current Program Status Register)

4.段寄存器(Segment Registers)
段寄存器用于存储内存段的基地址,主要用于分段内存管理。
x86-64架构
• CS(Code Segment Register):代码段寄存器,用于存储代码段的基地址。当处理器执行指令时,它会从代码段中读取指令。
• DS(Data Segment Register):数据段寄存器,用于存储数据段的基地址。程序可以使用数据段寄存器来访问全局变量、静态变量等。
• SS(Stack Segment Register):堆栈段寄存器,用于存储堆栈段的基地址。堆栈段用于存储函数调用时的局部变量、返回地址等。
• ES、FS、GS:附加段寄存器,用于存储其他数据段的基地址。这些寄存器通常用于访问特定的内存区域,例如操作系统内核数据、线程局部存储等。

ARM64架构
• ARM64架构采用平坦的地址空间模型,这意味着它直接使用线性地址来访问内存。程序中的指令和数据地址直接映射到物理内存空间,不需要通过段寄存器来计算偏移量。

5.指令指针寄存器(Instruction Pointer Register)
x86-64架构
• RIP/IP:指令指针寄存器,指向当前执行的指令地址。
ARM64架构
• PC(Program Counter):程序计数器,同样指向当前执行的指令地址。

6.浮点寄存器(Floating-Point Registers)
x86-64架构
• ST0-ST7:浮点寄存器,用于存储浮点数和进行浮点运算。
• XMM0-XMM31:128位向量寄存器,用于SIMD操作。
• YMM0-YMM31:256位向量寄存器,用于AVX指令集。
• ZMM0-ZMM63:512位向量寄存器,用于AVX-512指令集。

ARM64架构
• S0-S31:单精度浮点寄存器。
• D0-D31:双精度浮点寄存器。
• Q0-Q15:四倍精度浮点寄存器。

7.向量寄存器(Vector Registers)
向量寄存器用于存储向量数据和进行向量运算,通常用于SIMD(单指令多数据)操作。
x86-64架构
• XMM0-XMM31:128位向量寄存器。
• YMM0-YMM31:256位向量寄存器。
• ZMM0-ZMM63:512位向量寄存器。

ARM64架构
• V0-V31:向量寄存器。

8.调试寄存器(Debug Registers)
x86-64架构
• DR0-DR7:调试寄存器,用于设置断点和监视内存访问。

(二)数据传输

寄存器到寄存器:
mov eax, ebx ; 将 EBX 寄存器的值复制到 EAX 寄存器

寄存器到内存:
mov [eax], ebx ; 将 EBX 寄存器的值存储到 EAX 寄存器指向的内存地址

内存到寄存器:
mov eax, [ebx] ; 将 EBX 寄存器指向的内存地址中的值加载到 EAX 寄存器

(三)地址模式分类
立即寻址:操作数直接给出。例如:
mov eax, 123 ; 将立即数 123 加载到 EAX 寄存器

寄存器寻址:操作数在寄存器中。例如:
mov eax, ebx ; 将 EBX 寄存器的值复制到 EAX 寄存器

直接寻址:操作数的地址直接给出。例如:
mov eax, [1000h] ; 将地址 0x1000 处的值加载到 EAX 寄存器

间接寻址:操作数的地址在寄存器中。例如:
mov eax, [ebx] ; 将 EBX 寄存器指向的内存地址中的值加载到 EAX 寄存器

基址变址寻址:操作数的地址由基址寄存器和变址寄存器相加得到。例如:
mov eax, [ebx + ecx] ; 将 EBX 寄存器和 ECX 寄存器相加得到的地址中的值加载到 EAX 寄存器

相对寻址:操作数的地址相对于某个基地址。例如:
mov eax, [ebx + 100h] ; 将 EBX 寄存器指向的地址加上偏移量 0x100 处的值加载到 EAX 寄存器

四、数据段和堆栈段

(一)数据段
• 组织方式:数据段用于存储程序运行时需要的数据,包括全局变量和静态变量。数据段的组织方式是线性的,数据按照定义的顺序存储在内存中。
• 示例:

section .datanum1 db 10num2 dw 20num3 dd 30

(二)堆栈段
• 工作原理:堆栈段用于存储函数调用时的局部变量和返回地址。堆栈的操作是后进先出(LIFO)的,使用push和pop指令进行操作。
• 函数调用中的作用:在函数调用时,堆栈段用于保存调用者的上下文信息,包括返回地址和局部变量。函数调用时,堆栈帧的结构如下:
• 返回地址:函数调用完成后返回的位置。
• 局部变量:函数内部使用的变量。
• 旧的基址指针:保存调用者的基址指针。
• 示例:

section .textglobal _start_start:push ebp                ; 将基指针寄存器(EBP)的当前值压入堆栈; 保存上一个函数的栈帧基地址mov ebp, esp            ; 将栈指针寄存器(ESP)的值复制到基指针寄存器(EBP); 建立当前函数的栈帧基地址sub esp, 4              ; 将栈指针寄存器(ESP)减去4,为局部变量分配4字节空间; 在栈上分配4字节的空间用于存储局部变量mov dword [ebp - 4], 10 ; 将值10存储到局部变量的位置(EBP - 4); 在栈上分配的4字节空间中存储值10pop ebp                 ; 从堆栈中弹出一个值到基指针寄存器(EBP); 恢复上一个函数的栈帧基地址ret                     ; 从当前函数返回; 将控制权返回到调用者函数

五、字符串和数组

(一)字符串
• 存储:字符串以字节序列的形式存储在内存中,通常以空字符(\0)结尾。
• 操作:
• 比较:使用cmpsb指令逐字节比较两个字符串。
• 拷贝:使用movsb指令逐字节拷贝字符串。
• 示例:

section .datastr1 db 'Hello', 0          ; 定义字符串 "Hello",以空字符 '\0' 结尾str2 db 'World', 0          ; 定义字符串 "World",以空字符 '\0' 结尾section .textglobal _start               ; 定义程序的入口点_start:mov esi, str1               ; 将字符串 str1 的地址加载到 ESI 寄存器mov edi, str2               ; 将字符串 str2 的地址加载到 EDI 寄存器mov ecx, 5                  ; 将比较的字节数(5)加载到 ECX 寄存器repe cmpsb                  ; 重复比较字节,直到 ECX 为 0 或遇到不相等的字节; repe 表示重复操作,cmpsb 表示比较字节je equal                    ; 如果字符串相等(ZF=1),跳转到 equal 标签jmp not_equal               ; 如果字符串不相等,跳转到 not_equal 标签equal:; 字符串相等; 这里可以添加字符串相等时的处理代码jmp end                     ; 跳转到程序结束部分not_equal:; 字符串不相等; 这里可以添加字符串不相等时的处理代码jmp end                     ; 跳转到程序结束部分end:; 退出程序mov eax, 1                  ; 将系统调用号 1(exit)加载到 EAX 寄存器mov ebx, 0                  ; 将退出状态码 0 加载到 EBX 寄存器int 0x80                    ; 触发中断,调用系统调用,退出程序

(二)数组
• 初始化:数组是一组相同类型的数据的集合,可以使用db、dw、dd等指令初始化。
• 访问:通过索引访问数组元素,索引从0开始。
• 遍历:使用循环结构遍历数组。
• 示例:

section .dataarray db 1, 2, 3, 4, 5  ; 定义一个字节类型的数组,包含5个元素:1, 2, 3, 4, 5section .textglobal _start           ; 定义程序的入口点_start:mov ecx, 5              ; 将循环计数器初始化为5,表示数组中有5个元素mov esi, array          ; 将数组的地址加载到ESI寄存器loop_start:mov al, [esi]           ; 将ESI寄存器指向的数组元素加载到AL寄存器; AL寄存器现在包含当前数组元素的值; 对数组元素进行操作; 这里可以添加对数组元素的操作代码; 例如,可以将AL寄存器的值打印出来,或者进行其他处理inc esi                 ; 将ESI寄存器加1,指向下一个数组元素; 由于数组元素是字节类型,每次加1即可loop loop_start         ; 使用LOOP指令进行循环; LOOP指令会将ECX寄存器减1,如果ECX不为0,则跳转到loop_start; 退出程序mov eax, 1              ; 将系统调用号1(exit)加载到EAX寄存器mov ebx, 0              ; 将退出状态码0加载到EBX寄存器int 0x80                ; 触发中断,调用系统调用,退出程序

六、寻址模式

(一)决策树流程图
• 何时用基址变址 vs 相对寻址:
• 基址变址寻址:适用于访问数组元素,特别是当数组元素的地址需要动态计算时。
• 相对寻址:适用于访问结构体成员,特别是当成员的地址相对于基地址固定时。

(二)案例:通过不同寻址方式实现数组遍历
• 基址变址寻址:

section .dataarray db 1, 2, 3, 4, 5section .textglobal _star_start:mov ecx, 5mov esi, arrayxor eax, eaxloop_start:mov al, [esi + eax]; 对数组元素进行操作inc eaxloop loop_start; 退出程序mov eax, 1mov ebx, 0int 0x80

• 相对寻址:

section .dataarray db 1, 2, 3, 4, 5section .textglobal _start_start:mov ecx, 5mov esi, arrayloop_start:mov al, [esi + ecx - 1]; 对数组元素进行操作loop loop_start; 退出程序mov eax, 1mov ebx, 0int 0x80

七、堆栈机制详解
(一)函数调用时堆栈变化
函数调用时的堆栈变化如下:
• 调用函数时,将返回地址压入堆栈。
• 进入函数后,将旧的基址指针压入堆栈,并设置新的基址指针。
• 分配局部变量空间。
• 函数调用完成后,恢复旧的基址指针,释放局部变量空间,从堆栈中弹出返回地址并返回。
• 示例代码:

section .textglobal _start_start:call my_function          ; 调用 my_function 函数; 此时返回地址被压入堆栈; 退出程序mov eax, 1                ; 系统调用号:退出程序mov ebx, 0                ; 返回值:0int 0x80                  ; 触发系统调用my_function:push ebp                  ; 将旧的基址指针 (ebp) 压入堆栈mov ebp, esp              ; 将当前堆栈指针 (esp) 的值赋给 ebp,作为新的基址指针sub esp, 4                ; 为局部变量分配 4 字节空间; esp 指针向下移动 4 字节mov dword [ebp - 4], 10   ; 在局部变量空间存储值 10mov eax, [ebp - 4]        ; 将局部变量的值加载到 eax 寄存器add eax, 20               ; 将 eax 中的值加 20mov [ebp - 4], eax        ; 将结果存储回局部变量空间mov esp, ebp              ; 恢复 esp 到函数调用前的值pop ebp                   ; 恢复旧的基址指针 (ebp)ret                       ; 从堆栈中弹出返回地址并返回到调用点

堆栈变化示意图
1._start 调用 my_function 之前

堆栈指针 (ESP) 指向栈顶
-----------------------------------
|                                 |
-----------------------------------

2._start 调用 my_function 时

调用 my_function,返回地址被压入堆栈
-----------------------------------
| 返回地址 (_start + 5)             | <- ESP
-----------------------------------

3.my_function 开始执行

push ebp:将旧的 ebp 压入堆栈
-----------------------------------
| 返回地址 (_start + 5)             |
-----------------------------------
| 旧的 ebp 值                       | <- ESP
-----------------------------------

4.设置新的基址指针

mov ebp, esp:将 esp 的值赋给 ebp
-----------------------------------
| 返回地址 (_start + 5)             |
-----------------------------------
| 旧的 ebp 值                       | <- EBP
-----------------------------------

5.分配局部变量空间

sub esp, 4:为局部变量分配 4 字节空间
-----------------------------------
| 返回地址 (_start + 5)             |
-----------------------------------
| 旧的 ebp 值                       |
-----------------------------------
|                                  | <- ESP
-----------------------------------
|                                  |
-----------------------------------

6.存储局部变量

mov dword [ebp - 4], 10:在局部变量空间存储值 10
-----------------------------------
| 返回地址 (_start + 5)             |
-----------------------------------
| 旧的 ebp 值                       |
-----------------------------------
| 10                               | <- [EBP - 4]
-----------------------------------
|                                  |
-----------------------------------

7.函数执行完成

mov esp, ebp:恢复 esp 到函数调用前的值
-----------------------------------
| 返回地址 (_start + 5)             |
-----------------------------------
| 旧的 ebp 值                       | <- ESP
-----------------------------------

8.恢复旧的基址指针

pop ebp:恢复旧的 ebp 值
-----------------------------------
| 返回地址 (_start + 5)             | <- ESP
-----------------------------------

9.返回到调用点

ret:从堆栈中弹出返回地址并跳转
-----------------------------------
|                                  | <- ESP
-----------------------------------

八、实战
问题描述:设计一个内存安全的字符串拷贝函数
• 示例代码:

section .textglobal _start               ; 定义程序的入口点_start:mov esi, str1               ; 将字符串 str1 的地址加载到 ESI 寄存器mov edi, str2               ; 将字符串 str2 的地址加载到 EDI 寄存器call safe_strcpy            ; 调用 safe_strcpy 函数,将 str1 的内容复制到 str2; 退出程序mov eax, 1                  ; 将系统调用号 1(exit)加载到 EAX 寄存器mov ebx, 0                  ; 将退出状态码 0 加载到 EBX 寄存器int 0x80                    ; 触发中断,调用系统调用,退出程序safe_strcpy:push ecx                    ; 保存 ECX 寄存器的值到堆栈xor ecx, ecx                ; 将 ECX 寄存器清零,用于后续的循环计数copy_loop:mov al, [esi + ecx]         ; 从 ESI 指向的地址加上偏移量 ECX 处加载一个字节到 AL 寄存器mov [edi + ecx], al         ; 将 AL 寄存器的值存储到 EDI 指向的地址加上偏移量 ECX 处cmp al, 0                   ; 比较 AL 寄存器的值是否为 0(字符串结束标志)je done                     ; 如果 AL 为 0,跳转到 done 标签,表示字符串复制完成inc ecx                     ; 将 ECX 寄存器加 1,指向下一个字节jmp copy_loop               ; 跳转回 copy_loop 标签,继续复制下一个字节done:pop ecx                     ; 恢复 ECX 寄存器的值ret                         ; 从函数返回section .datastr1 db 'Hello', 0          ; 定义字符串 "Hello",以空字符 '\0' 结尾str2 db 'World', 0          ; 定义字符串 "World",以空字符 '\0' 结尾
返回列表