写着玩-窥视内存
0x00 口胡
最近状态下滑,没啥说的,基础重要,继续坚持
0x01 程序的内存布局
内存,大家再熟悉不过
给出linux进程地址空间布局
给出很常见的活动记录示意图
函数的标准序言和尾声
GCC取消帧指针(ebp)的编译选项
小姿势
调用惯例
函数的调用方和被调用方对函数如何调用有着统一的理解,这种约定称为调用惯例。
调用惯例一般包括以下几方面内容:
默认调用惯例cdecl:
其他调用惯例:
函数返回值传递
我们知道eax是返回值的通道,函数将返回值存储在eax中。但eax本身只有4个字节,对于5-8字节的情况,采用eax和edx联合返回的方式进行。对于超过8字节的,C语言在函数返回时会使用一个临时的栈上内存区域作为中转,结果返回值对象会被拷贝两次。
Windows的MSVC9和linux的gcc都是通过栈上的隐藏参数传递临时对象的地址,只不过在将临时对象写回到实际的目标对象时,MSVC9使用rep movs指令,而gcc使用memcpy函数。
对于C++,它处理返回对象时进行了一个拷贝构造函数的调用以及一次operate=的调用,也就是说,仍然产生了两次拷贝,因此C++的对象同样会产生临时对象。
上面说的返回对象的拷贝情况完全不具备可移植性,不同的编译器产生的结果可能不同。
linux进程堆管理
linux提供了两种堆空间的分配方式,即两个系统调用:brk()和mmap()
也就是说,linux下管理堆最底层的就是这两个系统调用,只用于申请空间,只不过申请空间的方式不同,其他的申请空间的函数都是对这两个系统调用的封装,最终都会经由这两个系统调用中的一个实现
可以看出来,程序起初是握有一块堆空间的,小空间直接从这个空间中分配,大的使用mmap先分配匿名空间,然后从匿名空间中分配。
malloc到底可以开多大:
Windows进程堆管理
给出一般情况下,一个Windows进程的地址空间分布:
对于该图,说明以下几点:
- 栈的位置及个数问题,从图中可以看出,栈的位置在0x00030000和exe文件后面都有分布,这是因为每个线程的栈都是独立的,所以一个进程中有多少个线程,就应该有多少个对应的栈,对于windows来说,每个线程的默认栈大小是1MB,在线程启动时,系统会为它在进程地址空间中分配相应的空间作为栈,所以整个地址空间中栈的位置及个数就不确定。
我们前面说过,对于VirtualAlloc这个系统调用级函数,用它来申请空间时,空间大小必须为页的整数倍,这难免会造成浪费。windows采取的做法是首先通过VirtualAlloc向OS一次性批发大量空间,比如10MB,然后再根据需要分配给程序。分配的算法已经有很经典的实现,所以我们没必要自己写,直接用就行。这个算法的实现位于堆管理器。堆管理器提供了一套与堆相关的API,用来创建,分配,释放和销毁堆空间
上面提到的默认堆,以及不够用时用VirtualAlloc扩展与前面提到的linux下类似
看到这,我们要清楚一个关系:malloc包装Heapxxxx,HeapCreate包装VirtualAlloc
堆分配算法
对于堆分配算法,其问题可以归结为:如何管理一大块连续的内存空间,能够按照需求分配、释放其中的空间。下面介绍几种简单的堆分配算法,只是概述。
空闲链表
空闲列表的方法实际上就是把堆中各个空闲的块按照链表的方式连接起来,当用户请求一块空间时,可以遍历整个列表,直到找到合适大小的块并且将它拆分;当用户释放空间时将它合并到空闲列表中。0day那本书上描述的还是很详细的。
位图
对象池
0x02 小结
就看了一些内存的总体布局以及和内存相关的结构的介绍,就酱