内存布局与栈
内存布局与栈
本文是《Professional Assembly Language》 的读书笔记
什么是汇编语言?
汇编语言是一种用助记符号表示操作指令机器码的语言。汇编语言程序一般由下述三个部分来定义程序操作:
- 操作码助记符(opcode mnemonics):如,
push %ebp
,mov %esp, %ebp
等。 - 数据段(data section): 汇编语言提供两种存储和获取数据的方式:定义指向某个内存地址的标签, 定义该内存存储数据的类型并设置其初始化值; 使用栈来存储函数间传递的数据。
- 指令(directive): 汇编语言提供的表示特殊功能的助记符, 如 ,
.long
指令声明变量的类型,.section
指令表示汇编程序所定义的元素所在的内存段。所有的汇编程序至少有以下三个内存段: 数据段(data section), BSS 段(bss section) 和文本段。
The data section is used to declare the memory region where data elements are stored for the program. This section cannot be expanded after the data elements are declared, and it remains static throughout the program.
The bss section is also a static memory section. It contains buffers for data to be declared later in the program. What makes this section special is that the buffer memory area is zero-filled.
The text section is the area in memory where the instruction code is stored. Again, this area is fixed, in that it contains only the instruction codes that are declared in the assembly language program.
IA-32 处理器
如图,处理器通过控制总线(Control Bus) 同步处理器和各设备之间的功能。数据总线(Data Bus) 用于处理器和各设备之间的数据传输。地址总线(Address Bus) 用于定位各设备中正在处理的数据。
处理器主要由控制单元(Control Unit), 执行单元(Execution Unit) 和寄存器(Register) 组成。
控制单元
控制单元主要有以下功能:
- 获取内存中的指令
- 解码指令
- 获取内存中的数据
- 保存结果
执行单元:用于执行指令,包括一个或多个算逻单元(ALU)
寄存器: 主要包括通用寄存器(General registers), 段寄存器(Segment registers), 索引和指针寄存器(Indexes and pointers)和标识寄存器(EFLAGS register)
- 通用寄存器: 主要用于临时暂存数据。包括 EAX, EBX, ECX 和 EDX
- 段寄存器: 用于保存不同的段地址。包括CS(代码段寄存器), DS(数据段寄存器), SS(栈段寄存器), 以及 ES, FS 和 GS
- 索引和指针寄存器: 用于保存地址的偏移量。
Register | Name | Description |
---|---|---|
EDI | Destination index register | Used for string, memory array copying and setting |
ESI | Source index register | Used for string and memory array copying |
ESP | Stack pointer register | Holds the top address of the stack |
EBP | Stack Base pointer register | Holds the base address of the stack |
EIP | Instruction Pointer | Holds the offset of the next instruction, It can only be read |
- EFLAGS 寄存器:用于保存处理器的状态
Bit | Label | Desciption |
---|---|---|
0 | CF | Carry flag |
2 | PF | Parity flag |
4 | AF | Auxiliary carry flag |
6 | ZF | Zero flag |
7 | SF | Sign flag |
8 | TF | Trap flag |
9 | IF | Interrupt enable flag |
10 | DF | Direction flag |
11 | OF | Overflow flag |
12-13 | IOPL | I/O Priviledge level |
14 | NT | Nested task flag |
16 | RF | Resume flag |
17 | VM | Virtual 8086 mode flag |
18 | AC | Alignment check flag (486+) |
19 | VIF | Virutal interrupt flag |
20 | VIP | Virtual interrupt pending flag |
21 | ID | ID flag |
程序栈
如下图是 Linux 程序虚拟内存的布局。其最高位地址存放的是内核数据(固定大小),接下来是栈空间(向下生长),然后是用于文件映射(包括动态链接库)和匿名文件他映射的内存映射段(向下生长),接着是堆空间(向上生长),紧接着是存放未初始化静态变量的 .bss 段, 然后是存放初始化的静态变量的 .data 段,接着是存放进程的二进制镜像的 .text 段。
栈
当程序启动时,其栈的内存布局如下图。
通过栈传递函数参数
当程序进入某个函数时,其栈的内存布局如下图所示。
通过栈传递函数参数的过程如上左图,将函数中的参数倒序压入栈中(如上左图 Function Parameter1
, Function Parameter2
和 Function Parameter3
)。在调用 CALL
指令时,会将返回地址压入栈内,以便在执行完函数后返回之前执行的指令。
一般的 C 语言风格的函数在进入函数后和退出函数前会做以下汇编操作。前两行指令表示将原始栈的基地址(%ebp
) 压入栈内保存,然后将当前的栈顶(%esp
) 作为新栈的基地址,存放在 EBP 寄存器 中。
在函数执行完后,会执行下列汇编中的最后三行指令。将当前函数栈的基地址(%ebp
)恢复为之前栈的栈顶指针(%esp
),然后弹出存入栈中的原始栈的基地址到 EBP 寄存器 中,并根据栈顶的返回地址,跳转到返回地址的指令,继续执行之前的指令。
在函数内,可以继续压入数据到函数栈中,存为作用域在函数体内的局部变量。
function:
pushl %ebp
movl %esp, %ebp
... ...
movl %ebp, %esp
popl %ebp
ret