汇编语言学习(3)
更好的阅读体验,请点击 YinKai 's Blog 。
内存段
? 上面讨论的汇编程序的三个部分,也代码各种内存段。
? 有趣的是,如果将 section 关键字替换为 segment,将会得到相同的结果,这是因为对于汇编器而言,这两个关键字在某些上下文中是可以互相使用的,这两个关键字都是为了告诉汇编器下面的代码是代码段。
内存段
? 在分段内存模型中,系统内存被划分为不同的独立段组,每个段组由位于段寄存器中的指针引用。
? 每个段用于包含特定类其型的数据。其中一个段用于包含指令代码,另一个段用于存储数据元素,第三个段用于保存程序堆栈。
? 这种划分使得程序可以更灵活地管理内存,有选择地引用不同类型的数据和指令,从而更有效地执行各种计算任务。
? 因此,我们可以将各种内存段指定为:
- **数据段:由 .data 部分和 .bss 部分表示。 .data 部分用于声明内存区域,其中为程序存储数据元素,该部分在数据元素声明后无法扩展,并且在整个程序中保持静态。.bss 部分也是一个静态内存部分,其中包含稍后在程序中声明的数据的缓冲区。该缓冲区被零填充。
- **代码段:**它由 .text 部分表示。这定义了内存中存储指令代码的区域。这也是一个固定区域。
- **堆栈:**该段包含传递给程序内的函数和过程的数据值。
寄存器
? 处理器操作主要涉及对数据的处理,而数据通常存储在内存中。然而,内存访问可能会降低处理器速度,因为它需要通过控制总线发送请求并进行复杂的内存访问。
? 为了提高速度,处理器包含一些内部存储位置,称为寄存器。
处理器寄存器
? IA-32架构中包含 10 个 32 位和 6 个 16 位的处理器寄存器,主要分为三类:
- **通用寄存器:**通用寄存器用于存储临时数据,进行算术、逻辑运算等操作。
- **控制寄存器:**控制寄存器用于控制和反映处理器的状态。
- **段寄存器:**段寄存器用于存储各个段的起始地址,实现内存访问和管理。
? 通用寄存器进一步可以分为:
- 数据寄存器
- 指针寄存器
- 索引寄存器
数据寄存器
? 在IA-32架构中,有四个32位的数据寄存器,分别是EAX、EBX、ECX、EDX。这些寄存器可以按照不同的位数划分为更小的寄存器,具体如下:
- 作为完整的32位数据寄存器:EAX、EBX、ECX、EDX。
- 32 位寄存器的下半部分可用作四个 16 位数据寄存器:AX、BX、CX 和 DX。
- 上述4个16位寄存器的下半部分和上半部分可以用作8个8位数据寄存器:AH、AL、BH、BL、CH、CL、DH和DL。
? 一些数据寄存器在算术运算中具有特定用途:
- AX: 主累加器,用于输入/输出和大多数算术指令。例如,在乘法运算中,根据操作数的大小将一个操作数存储在EAX或AX或AL寄存器中。
- BX: 被称为基址寄存器,用于索引寻址。
- CX: 被称为计数寄存器,与ECX一样,存储迭代操作中的循环计数。
- DX: 数据寄存器,用于输入/输出操作,与AX寄存器和DX一起使用,用于涉及大值的乘法和除法运算。
指针寄存器
? 指针寄存器是指 32 位的 EIP、ESP 和 EBP 寄存器以及相应的 16 位 右部分 IP、SP 和 BP。
? 指针寄存器可以分为三类:
- **指令指针(IP):**16 位 IP 寄存器存储下一条要执行的指令的偏移地址。 IP 与 CS 寄存器(代码段)(如CS : IP)关联,给出了代码段中当前指令的完整地址。
- 堆栈指针(SP): 16 位 SP 寄存器提供程序堆栈内的偏移值。 SP 与 SS 寄存器(堆栈段)(SS:SP)相关,指的是程序堆栈中数据或地址的当前位置。
- 基址指针(BP): 16 位 BP 寄存器主要帮助引用传递给子程序的参数变量。 SS 寄存器中的地址与 BP 中的偏移量相结合,得到参数的位置。 BP 还可以与 DI、SI(索引寄存器) 组合作为基址寄存器进行特殊寻址。
索引寄存器
? 索引寄存器包括32位的 ESI 和 EDI 以及它们的 16 位最右边的部分。SI 和 DI 通常用于索引寻址,并有时用于执行加法和减法操作。这两个索引指针分别是:
- 来源索引 (SI): 用作字符串操作的源索引。在字符串处理中,SI通常用于指向源字符串的当前位置。
- 目的地索引 (DI): 用作字符串操作的目标索引。DI通常用于指向目标字符串的当前位置,特别是在字符串复制等操作中。
控制寄存器
? 控制寄存器包括 32 位指令指针寄存器和 32 位标志寄存器,用于管理程序的执行流程和状态。其中的常见标志位有:
-
溢出标志 (OF): 表示有符号算术运算后数据的高位(最左位)是否溢出。
-
方向标志 (DF): 确定移动或比较字符串数据的左或右方向。DF为0时,字符串操作从左到右;DF为1时,字符串操作从右到左。
-
中断标志 (IF): 决定是否忽略或处理外部中断,如键盘输入。IF为0时禁用外部中断,为1时启用中断。
-
陷阱标志 (TF): 允许将处理器设置为单步模式,以便一次执行一条指令,常用于调试。
-
符号标志 (SF): 显示算术运算结果的符号,由最左边位的高位表示。SF为0表示正结果,为1表示负结果。
-
零标志 (ZF): 表示算术或比较运算的结果是否为零。ZF为1表示零结果,为0表示非零结果。
-
辅助进位标志 (AF): 包含算术运算后从位 3 到位 4 的进位,用于特殊的算术操作。
-
奇偶校验标志 (PF): 表示算术运算结果中1位的总数,用于奇偶校验。PF为1表示奇数个1位,为0表示偶数个1位。
-
进位标志 (CF): 包含算术运算后从高位(最左边)的进位,也存储shift或rotate操作的最后一位内容。
段寄存器
? 段在计算机内存中是为了组织和管理存储空间而引入的概念。在汇编编程中,处理器通过段寄存器来访问内存位置。以下是关于段的主要信息:
- 代码段(CS):
- 包含要执行的指令的区域。
- 由 16 位代码段寄存器(CS 寄存器)存储代码段的起始地址。
- 数据段(DS):
- 包含数据、常量和工作区的区域。
- 由 16 位数据段寄存器(DS 寄存器)存储数据段的起始地址。
- 堆栈段(SS):
- 包含过程或子例程的数据和返回地址,实现为堆栈数据结构。
- 由16位堆栈段寄存器(SS 寄存器)存储堆栈的起始地址。
- 其他段寄存器:
- 额外段(ES): 提供额外的段来存储数据。
- FS 和 GS: 提供额外的段用于特定目的。
? 在汇编编程中,程序需要访问内存位置。段内的所有内存位置都相对于段的起始地址。段从可被 16 整除的地址开始,因此所有这类内存地址中最右边的十六进制数字通常是 0。为了引用段中的任何内存位置,处理器将段寄存器中的段地址与该位置的偏移值组合起来。
示例
? 下面的程序会在代码中输出 9 个连续的星号。
section .text
global _start ;必须为链接器声明(gcc)
_start: ;告诉链接器入口点
mov edx,len ;消息长度
mov ecx,msg ;要写入的消息
mov ebx,1 ;文件描述符(stdout)
mov eax,4 ;系统调用号(sys_write)
int 0x80 ;调用内核
mov edx,9 ;消息长度
mov ecx,s2 ;要写入的消息
mov ebx,1 ;文件描述符(stdout)
mov eax,4 ;系统调用号(sys_write)
int 0x80 ;调用内核
mov eax,1 ;系统调用号(sys_exit)
int 0x80 ;调用内核
section .data
msg db 'Displaying 9 stars',0xa ;一条消息
len equ $ - msg ;消息的长度
s2 times 9 db '*' ;9个星号
? 我们使用以下命令进行编译和执行:
nasm -f elf nine_stars.asm
ld -m elf_i386 -s -o nine_stars nine_stars.o
? 输出结果如下:
Displaying 9 stars
*********
系统调用
? 系统调用是用户空间和内核空间之间接口的 API。我们之前已经使用过了 sys_write 和 sys_exit 这两个系统调用,分别用于写入屏幕和退出程序。
Linux 系统调用
? 我们在汇编程序中使用系统调用,需要按照如下步骤:
- 将系统调用号放入 EAX 寄存器中;
- 将系统调用的参数存储在 EBX、ECX 等寄存器中
- 调用相关中断(0x80),然后执行 EAX 中的系统调用号对应的程序
- 结果通常返回 EAX 寄存器中
? 可以存储系统调用参数的存储器有 基址寄存器 EBX、计数寄存器 ECX、数据寄存器 EDX、源索引寄存器 ESI、目标索引寄存器 EDI、基址指针寄存器 EBP。
? 下面给大家演示一下几个示例:
? (1)使用 sys_exit:
mov eax, 1 ; 系统调用号 sys_exit
int 0x80 ; 调用内核
? (2)使用 sys_write:
mov edx, 4 ; 消息长度
mov ecx, msg ; 要写入的消息
mov ebx, 1 ; 文件描述符
mov eax, 4 ; 系统调用号
int 0x80 ; 调用内核
常见系统调用
%eax | Name | %ebx | %ecx | %edx | %esx | %edi |
---|---|---|---|---|---|---|
1 | sys_exit | int | - | - | - | - |
2 | sys_fork | struct pt_regs | - | - | - | - |
3 | sys_read | unsigned int | char * | size_t | - | - |
4 | sys_write | unsigned int | const char * | size_t | - | - |
5 | sys_open | const char * | int | int | - | - |
6 | sys_close | unsigned int | - | - | - | - |
示例
? 下面举一个复杂一点的例子,包含了之前我们讲过的 data、bss、text 三个部分,也希望通过这个例子,加深一下大家对 data 部分和 bss 部分的区别
section .data ; 数据段
userMsg db '请输入一个数字: ' ; 提示用户输入数字的消息
lenUserMsg equ $-userMsg ; 消息的长度
dispMsg db '您输入的是: '
lenDispMsg equ $-dispMsg
section .bss ; 未初始化的数据
num resb 5 ; 用于存储用户输入的变量,5字节
section .text ; 代码段
global _start ; 声明程序入口点
_start: ; 程序入口
; 输出提示消息 '请输入一个数字: '
mov eax, 4
mov ebx, 1
mov ecx, userMsg
mov edx, lenUserMsg
int 80h
; 读取并存储用户输入
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ; 读取5字节的信息(数字和符号,1字节)
int 80h
; 输出消息 '您输入的是: '
mov eax, 4
mov ebx, 1
mov ecx, dispMsg
mov edx, lenDispMsg
int 80h
; 输出用户输入的数字
mov eax, 4
mov ebx, 1
mov ecx, num
mov edx, 5
int 80h
; 退出程序
mov eax, 1
mov ebx, 0
int 80h
? 同样,我们需要通过下述命令来编译运行:
nasm -f elf get_num.asm # 将汇编程序编译成机器码
ld -m elf_i386 -s -o get_num get_num.o # 将目标文件和其他必要的文件组合成可执行文件
./get_num # 运行可执行文件
? 输出结果如下:
请输入一个数字:
123
您输入的是: 123
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!