汇编语言 - 内存分段
内存分段(Memory Segmentation)是 x86 架构中的一个核心概念,它决定了程序在内存中如何组织代码、数据和栈空间。
为什么需要分段
在 x86 实模式下,CPU 使用 16 位寄存器,但需要访问 1MB 的内存空间(20 位地址)。
16 位寄存器只能表示 64KB 范围(2^16 = 65536),远远不够。
Intel 的解决方案是将内存划分为 段(Segment),每个段有基地址和偏移量,通过 段地址 + 偏移量 的方式组合出完整的物理地址。
物理地址计算公式:
物理地址 = 段地址 × 16 + 偏移地址
虽然在保护模式(32 位)下,分段机制更多地用于内存保护和权限控制,但理解分段对于理解汇编程序结构仍然至关重要。
三种基本段
一个典型的汇编程序使用三个主要段:
| 段名称 | 英文名 | 用途 | 对应的 Section |
|---|---|---|---|
| 代码段 | Code Segment | 存放可执行的机器指令 | section .text |
| 数据段 | Data Segment | 存放已初始化的全局变量 | section .data |
| 栈段 | Stack Segment | 存放函数调用信息、局部变量 | 操作系统自动管理 |
代码段(.text)
代码段存放程序的全部机器指令,是 只读、可执行 的内存区域。
操作系统在加载程序时,将代码段映射到只读内存页,防止程序意外修改指令。
实例
; 演示代码段使用
section .text ; 开始代码段
global _start
_start:
mov eax, 1 ; 这些指令都存放在代码段中
mov ebx, 0
int 0x80
; 以下是一个辅助函数,也存放在代码段
my_function:
mov eax, 42
ret
数据段(.data)
数据段存放程序中 已初始化的全局变量。
数据段是可读可写的,程序运行时可以修改其中的内容。
实例
; 演示数据段使用
section .data
; 定义各种类型的已初始化变量
msg db 'Hello, runoob!', 0 ; 字符串变量(字节序列)
count db 100 ; 字节变量,初始值 100
pi dd 314159 ; 双字变量,存放 π 的近似值 × 100000
array db 1, 2, 3, 4, 5 ; 字节数组
section .text
global _start
_start:
; 读取数据段中的变量
mov al, [count] ; 将 count 的值(100)加载到 al 寄存器
; ...
mov eax, 1
mov ebx, 0
int 0x80
BSS 段(.bss)
BSS(Block Started by Symbol)段存放 未初始化的全局变量。
与数据段不同,BSS 段在可执行文件中不占用实际空间,只在程序加载时由操作系统分配内存并清零。
实例
; 演示 BSS 段使用
section .bss
buffer resb 256 ; 预留 256 字节的缓冲区(未初始化)
num_array resd 100 ; 预留 100 个双字(400 字节)
section .data
; 已初始化数据
section .text
global _start
_start:
; 使用 BSS 段的缓冲区
mov byte [buffer], 'A' ; 向缓冲区写入字符 'A'
; ...
mov eax, 1
mov ebx, 0
int 0x80
将未初始化的数据放在 BSS 段而不是 .data 段中,可以减小可执行文件的体积。例如,一个 10KB 的未初始化缓冲区在 BSS 段中不占用文件大小,但如果放在 .data 段中则会让文件增大 10KB。
段寄存器
x86 架构有 6 个段寄存器,用于跟踪当前正在使用的段:
| 段寄存器 | 英文全称 | 用途 |
|---|---|---|
| CS | Code Segment | 指向代码段,存放当前执行指令所在的段基址 |
| DS | Data Segment | 指向数据段,存放大部分数据访问的段基址 |
| SS | Stack Segment | 指向栈段,存放栈所在的段基址 |
| ES | Extra Segment | 附加段寄存器,用于字符串操作等额外数据段 |
| FS | General Purpose | 通用段寄存器,常用于线程局部存储 |
| GS | General Purpose | 通用段寄存器,常用于线程局部存储 |
在 32 位保护模式下,程序员通常不需要手动设置段寄存器,操作系统和链接器会处理好。
内存分段示意图
一个运行中的程序在内存中的分布如下:
一个运行中的程序在内存中的分布大致如下:
高地址 +-------------------+ | 栈 (Stack) | <-- SS:ESP 指向栈顶 | 向下增长 | +-------------------+ | | | 空闲内存 | | | +-------------------+ | BSS 段 (.bss) | 未初始化的全局变量 +-------------------+ | 数据段 (.data) | 已初始化的全局变量 +-------------------+ | 代码段 (.text) | 程序指令(只读) +-------------------+ | 保留区域 | 操作系统使用 +-------------------+ 低地址
实模式 vs 保护模式
| 特性 | 实模式(16 位) | 保护模式(32 位) |
|---|---|---|
| 内存寻址 | 段地址 × 16 + 偏移 | 段选择子查描述符表 + 偏移 |
| 最大内存 | 1MB | 4GB(32 位) |
| 内存保护 | 无保护 | 有页级和段级保护 |
| 多任务 | 不支持 | 支持 |
| 寻址方式 | segment:offset | selector:offset(通过描述符表) |
本教程主要介绍 32 位保护模式下的汇编编程。在这种模式下,段的概念更多地是对内存区域的逻辑划分,物理地址由分页机制管理。你可以把 section .data/.bss/.text 简单地理解为程序不同部分在内存中的标记。
