- 进程虚拟地址空间划分和布局
- 函数调用堆栈的详细过程
进程虚拟地址空间划分和布局
任何的编程语言=》 都会产生两种东西 1.指令 2.数据 当一个程序运行时,Linux操作系统会给当前进程分配一个2的32次方的一块虚拟地址空间 也就是4个G。(×86 32位Linux系统下)
拓展:
它存在,你可以看得见,它是物理的 它存在,你看不见,它是透明的 它不存在,你却可以看见,它是虚拟的 它不存在,你也看不见 , 它是被删除的
用户空间(3G): 0×00000000到0×08048000此段不可访问/读写 指令在运行时存放在代码段/.text段 .rodata 只读数据段 .data 数据段 存放初始化或者初始化不为0的 .bss 数据段 存放未初始化或者初始化为0的(内核会自动给该段数据清0) .heap 堆空间 .加载动态链接库*.dll *so stack 函数运行时栈空间 命令行参数和环境变量
此上为用户空间默认划分大小
内核空间(1G): ZONE_DMA(16mb) ZONE_NORMAL(800mb) ZONE_HIGHMEN
全局变量 :不管是不是静态的 都叫做数据,编译后都会产生符号,初始化并不为0的都放在.data段 未初始化或初始化为0的都放在.bss段。
局部变量:编译不会产生符号,会生成指令。 比如 int a = 12; 会产生指令 mov dword ptr[a] , 0Ch,不管是否初始化后者初始化为0都会存放在.text段
但是对于静态局部变量,初始化了并且不为0,会存放在.data段。未初始化或者初始化为0 会存放在…bss段
注意:每一进程的用户空间是私有的,内核空间是共享的
函数调用堆栈的详细过程
代码语言:javascript复制#include <iostream>
// 求和函数
int sum(int a, int b)
{
int temp = 0;
temp = a b;
return temp;
}
int main()
{
// 测试求和函数
int num1 = 10;
int num2 = 20;
int result = sum(num1, num2);
std::cout << result << std::endl;
return 0;
}
底层分析:
- int num1 = 10; 对应指令:mov dword ptr[ebp - 4],0Ah
- int num1 = 20; 对应指令:mov dword ptr[ebp - 8],14h
- int result = sum(num1, num2); 会先调用sum函数 mov eax,dword ptr[ebp -8] push eax mov eax, dword ptr[ebp - 4] push eax call sum //自动将下一行地址压栈 得到地址后 add esp,8 mov dword ptr[ebp-0Ch],eax
- 进入左括号 int sum(int a, int b) {在原来的栈帧中开辟新空间 底层指令 : push ebp mov ebp,esp sub esp,4Ch rep stos for
- int temp = 0; 底层指令:mov eax,dword ptr[ebp - 4],0
- temp = a b; 底层指令:mov eax,dword ptr[ebp 8] a b mov dword ptr[ebp-4],eax
- return temp; 底层指令:mov eax,dword ptr[ebp-4] 把temp的值保存在寄存器eax中
- } //出右括号 底层指令:mov esp,ebp pop ebp ret //出栈操作,把出栈的内容放入CPU的PC寄存器里
举例: 在函数外边可以正常打印里面的返回值,因为 栈内存空间的数据还在,但是当中间有调用别的函数就会覆盖此处的空间从而报错。所以这样的代码不安全。
编译过程: 预编译 #开头的命令,除了#pragma lib/link等 编译 g /gcc -O 汇编 符号表的输出 二进制可重定位的目标文件(*.obj) ** . o文件格式 链接过程: 编译完成的所有.o文件 静态库文件 步骤一:所有.o文件段的合并,符号表合并后,进行符号解析 步骤二:符号的重定位(重定向) 符号解析成功后给所有的符号分配虚拟空间地址。 readelf -S main.o 查看各个段