寄存器

通用寄存器:
- eax(累加器):常用与算数、逻辑运算和函数返回值等操作
- ebx(基址寄存器):可用于存储内存基地址,在访问内存数据时发挥作用
- ecx (循环计数器):在循环指令中,自动对循环次数进行计数
- edx(数据寄存器):用于存储数据,常配合 eax 进行乘除等运算
- esi、edi(索引寄存器):主要用于字符串操作和内存数据的索引寻址
- esp(堆栈指针):指向栈顶,用于管理程序运行时的堆栈
- ebp(基址指针):指向当前栈帧的底部,辅助访问函数栈帧中的局部变量和参数
- eip(指令指针):存放下一条要执行的指令的地址
段寄存器:
- cd(代码段寄存器):指向当前正在执行的代码所在的段
- ds(数据段寄存器):指向程序使用的数据所在的段
上图中右侧图表展示了通用寄存器的结构,以 eax 为例,它是 32 位寄存器,可拆分为 16 位的 ax,ax 又能进一步分为 8 位的 ah 和 al,其他通用寄存器也有类似的结构关系
指令集
mov

- 指令功能:“mov” 是数据传送指令,它的作用是将源操作数(source)的值传送到目标操作数(dest)中
- 语法结构:格式为 “mov dest, source”,其中目标操作数可以是寄存器或内存地址,源操作数可以是寄存器、内存地址或立即数(常数)
- 例子讲解:
- “mov eax, [ebx]”:将寄存器 ebx 所指向的内存地址中的值传送到寄存器 eax 中
- “mov eax, [ebx]”:将寄存器 ebx 所指向的内存地址中的值传送到寄存器 eax 中
- “mov [var], ebx”:把寄存器 ebx 中的值传送到名为 “var” 的内存位置中
push pop

- 指令功能:
- “push” 是压栈指令,用于将数据存入堆栈。在操作时,堆栈指针(esp)会先减小相应的字节数,然后将数据存储到新的栈顶位置
- “pop” 是出栈指令,用于从堆栈中取出数据。执行时,先从当前栈顶位置读取数据,然后堆栈指针(esp)会增加相应的字节数
- 例子讲解:
- “push eax”:将寄存器 eax 中的值压入堆栈
- “push 0”:把立即数 0 压入堆栈
- “pop eax”:从堆栈中弹出一个值,并将其存入寄存器 eax 中
- “pop [ebx]” :从堆栈中弹出一个值,并将其存入 ebx 寄存器所指向的内存地址中
Arithmetic

- 指令概述:“add, sub, mul, div” 分别是加法(Addition)、减法(Subtraction)、乘法(Multiplication)和除法(Division )的算术运算指令 ;“inc” 和 “dec” 分别是递增(Increment)和递减(Decrement)指令,用于将操作数的值加 1 或减 1
- 语法说明:“add” 指令需要两个操作数,格式为 “add 目标操作数(dest), 源操作数(source)”,表示将源操作数的值加到目标操作数上;“inc” 指令的操作数可以是寄存器(
)或内存位置( ),使对应的值增加 1 - 例子讲解:“add eax, 10” 表示将数值 10 加到寄存器 eax 中的值上;“inc eax” 表示将寄存器 eax 中的值增加 1
jump

- 无条件跳转指令:“jmp” 是无条件跳转指令,一旦执行该指令,程序会直接跳转到指定的目标地址(标签处)继续执行
- 条件跳转指令:
- “je
- “jne
” :当运算结果不相等时,程序跳转到指定标签处。 - “jz
” :若上一次运算结果为 0,程序跳转到指定标签处。 - “jg
” :当运算结果大于(无符号比较)时,执行跳转。 - “jge
” :当运算结果大于或等于时,进行跳转。 - “jl
” :当运算结果小于时,程序跳转到对应标签。 - “jle
” :当运算结果小于或等于时,执行跳转操作。
cmp

- 指令功能:“cmp” 是比较指令,通过对两个操作数执行减法操作,根据结果设置标志寄存器(如零标志位 ZF、进位标志位 CF 等),但不保存运算结果。主要用于后续的条件判断跳转
- 例子讲解:
- “cmp DWORD PTR [eax], 10”:将 eax 寄存器所指向的内存地址中的双字(DWORD,4 字节)数据与立即数 10 进行比较,根据比较结果设置标志位。之后 “je loop” 表示如果比较结果相等(即零标志位 ZF 被置位),则跳转到 “loop” 标签处执行。
- “cmp eax, ebx”:比较寄存器 eax 和 ebx 中的值,根据比较结果设置标志位。随后 “jle done” 表示如果 eax 中的值小于或等于 ebx 中的值,就跳转到 “done” 标签处。
- “jmp DWORD PTR [eax]”:这是一个跳转指令,将程序执行流跳转到 eax 寄存器所指向的内存地址处的双字数据指定的地址继续执行,与 “cmp” 指令配合,用于更复杂的程序控制流。
系统调用

- 系统调用的作用:系统调用(Syscalls)是用户程序和 Linux 内核之间的接口。通过系统调用,用户程序可以请求内核提供的服务,如文件操作、进程管理、网络通信等
- 寄存器的使用:
- 在进行系统调用前,需要将相关值存入寄存器。其中 eax 寄存器用于表示系统调用号,不同的系统调用号对应不同的内核服务。
- ebx、ecx 等其他寄存器用于传递系统调用所需的参数。
- 执行过程:准备好寄存器中的值后,通过执行 “int 0x80” 指令触发系统调用,使程序从用户态切换到内核态,内核根据 eax 中的系统调用号执行相应的操作
- 返回值:内核完成系统调用后,会将返回值存放在 eax 寄存器中,供用户程序获取并处理
例子
例如以下程序:
#include<stdio.h>
int sum(int i,int j){
int sum;
sum=i+j;
return sum;
}
int main(void){
int i;
int j;
int k;
scanf("%d%d",&i,&j);
k=sum(i,j);
printf("Sum:%d\n",k);
return 0;
}
使用以下命令:
gcc -m32 -o hello hello.c


下面是我自己编译出来的,和图片上的一样
pwndbg> disassemble main
Dump of assembler code for function main:
0x000011bd <+0>: lea ecx,[esp+0x4]
0x000011c1 <+4>: and esp,0xfffffff0
0x000011c4 <+7>: push DWORD PTR [ecx-0x4]
0x000011c7 <+10>: push ebp
0x000011c8 <+11>: mov ebp,esp
0x000011ca <+13>: push ebx
0x000011cb <+14>: push ecx
0x000011cc <+15>: sub esp,0x10
0x000011cf <+18>: call 0x10a0 <__x86.get_pc_thunk.bx>
0x000011d4 <+23>: add ebx,0x2e20
0x000011da <+29>: sub esp,0x4
0x000011dd <+32>: lea eax,[ebp-0x14]
0x000011e0 <+35>: push eax
0x000011e1 <+36>: lea eax,[ebp-0x10]
0x000011e4 <+39>: push eax
0x000011e5 <+40>: lea eax,[ebx-0x1fec]
0x000011eb <+46>: push eax
0x000011ec <+47>: call 0x1050 <__isoc99_scanf@plt>
0x000011f1 <+52>: add esp,0x10
0x000011f4 <+55>: mov edx,DWORD PTR [ebp-0x14]
0x000011f7 <+58>: mov eax,DWORD PTR [ebp-0x10]
0x000011fa <+61>: sub esp,0x8
0x000011fd <+64>: push edx
0x000011fe <+65>: push eax
0x000011ff <+66>: call 0x119d <sum>
0x00001204 <+71>: add esp,0x10
0x00001207 <+74>: mov DWORD PTR [ebp-0xc],eax
0x0000120a <+77>: sub esp,0x8
0x0000120d <+80>: push DWORD PTR [ebp-0xc]
0x00001210 <+83>: lea eax,[ebx-0x1fe7]
0x00001216 <+89>: push eax
0x00001217 <+90>: call 0x1040 <printf@plt>
0x0000121c <+95>: add esp,0x10
0x0000121f <+98>: mov eax,0x0
0x00001224 <+103>: lea esp,[ebp-0x8]
0x00001227 <+106>: pop ecx
0x00001228 <+107>: pop ebx
0x00001229 <+108>: pop ebp
0x0000122a <+109>: lea esp,[ecx-0x4]
0x0000122d <+112>: ret
End of assembler dump.
- 栈对齐作用:代码确保栈按 16 字节对齐。在操作后,堆栈指针寄存器 esp 的值会小于或等于操作前的值,意味着栈可能会增长,这样可以保护栈上已有的数据
- 栈对齐原因:在主函数中进行栈对齐,是为了防止函数在栈未对齐的情况下被调用,因为未对齐可能导致程序运行缓慢。虽然 4 字节对齐很重要,但在 x86 架构中,16 字节是缓存行宽度。如果主函数的栈未对齐,那么程序的其余部分也会受影响
![]()
![]()
![]()
下面是我编译的,与图片上一致
pwndbg> disassemble sum
Dump of assembler code for function sum:
0x0000119d <+0>: push ebp
0x0000119e <+1>: mov ebp,esp
0x000011a0 <+3>: sub esp,0x10
0x000011a3 <+6>: call 0x122e <__x86.get_pc_thunk.ax>
0x000011a8 <+11>: add eax,0x2e4c
0x000011ad <+16>: mov edx,DWORD PTR [ebp+0x8]
0x000011b0 <+19>: mov eax,DWORD PTR [ebp+0xc]
0x000011b3 <+22>: add eax,edx
0x000011b5 <+24>: mov DWORD PTR [ebp-0x4],eax
0x000011b8 <+27>: mov eax,DWORD PTR [ebp-0x4]
0x000011bb <+30>: leave
0x000011bc <+31>: ret
End of assembler dump.


- 代码片段分析:代码是sum函数的汇编实现。push %ebp将基址指针寄存器ebp的值压入栈,mov %esp, %ebp建立新的栈帧,sub $0x10, %esp从堆栈指针寄存器esp中减去 16(十六进制的 0x10),后续指令进行数据移动和加法操作等。
- 疑问及解释:
- “Why?” 标注的sub $0x10, %esp指令,目的是为栈帧分配空间,这可能是编译器为保证栈按字边界对齐而进行的优化操作,通过添加填充字节来实现。
- “I only use 4bytes” 标注的mov -0x4(%ebp), %eax指令,虽然只使用 4 字节数据,但整体操作与栈对齐和编译器优化有关。
- 总结说明:编译器有时会通过添加填充字节优化代码,使其对齐到字边界,理解准确的栈位置需要仔细查看汇编代码 。
![]()
- 函数开始:push %ebp将当前基址指针压入栈,保存上一个函数的栈帧基址。mov %esp, %ebp把当前堆栈指针的值赋给基址指针,建立新的栈帧。sub $0x10, %esp为当前函数的局部变量和临时数据在栈上分配 16 字节空间。
- 参数传递:
- mov 0x8(%ebp), %edx:从相对于基址指针ebp偏移 8 字节的内存位置读取数据,存入edx寄存器,这是获取第一个参数。
- mov 0xc(%ebp), %eax:从相对于ebp偏移 12 字节的内存位置读取数据,存入eax寄存器,这是获取第二个参数。在 32 位程序中,函数参数通常按顺序从右向左压入栈,所以从栈中相对ebp的特定偏移处获取参数。
- 函数运算与返回:add %edx, %eax将两个参数相加。mov %eax, -0x4(%ebp)把相加结果暂存到相对于ebp偏移 -4 字节的栈位置。最后mov -0x4(%ebp), %eax把结果再存入eax寄存器,因为在 32 位程序中,函数返回值一般通过eax寄存器传递 ,随后leave和ret指令用于恢复栈帧并返回。
程序在内存中的完整布局

- 内核空间(Kernel space):位于内存的高地址部分,大小为 1GB,地址从0xc0000000(即TASK_SIZE)往上。内核空间由操作系统内核使用,用于管理硬件资源、提供系统服务等,用户程序一般不能直接访问。
- 用户空间(User space):大小为 3GB,从地址 0 到0xc0000000。其中包含以下几个部分:
- 栈(Stack):在用户空间的较高地址,向下增长。用于存储函数的局部变量、参数和返回地址等,随着函数调用和返回,栈空间动态变化。
- 内存映射区域(Memory Mapping Region):用于映射文件、共享内存等,在栈和堆之间。
- 堆(Heap):在用户空间较低地址,向上增长。由程序动态分配内存时使用,如通过malloc等函数申请的内存。
- BSS 段(BSS segment):存储未初始化的全局变量和静态变量,在程序加载时会被初始化为 0。
- 数据段(Data Segment):存放已初始化的全局变量和静态变量。
- 文本段(Text Segment,ELF):处于最低地址,存放程序的可执行代码(机器指令),通常是只读的 。
布局的对应
int a = 0; // .data
int b; // .bss
const double PI = 3.14159f; // .rodata
int main(){
int c = 321; // stack
char *d = malloc(16); // heap
return 0;
}



