理解程序内存

简介:

内存对很多人来说感觉是个很熟悉的东西,因为我们在用VC调试程序时,很多时候都会察看内存中变量的值。但是,很多时候我们的思维也就因此局限在有源码的模块,当遇到一些跨模块或是没有源代码的Bug,我们还是无从下手。因此,很有必要我们要对整个程序内存有个比较全局性的认识,这样遇到任何问题,你都能从容面对。

我这里以32位的程序为例,我们知道32位程序总共有4G内存空间,其中低2G是用户地址空间,高2G是内核地址空间,下面我们借助WinDbg工具来分析低2G用户地址空间的内存分布。

因为所有程序的内存分布都大同小异,我这里用WinDbg分析任务管理器进程的内存分布。打开任务管理器,然后用WinDbg Attach到taskmgr.exe进程。
输入 !address -summary 察看内存的使用情况, 结果如下:


从上图可以看到,程序内存根据使用情况大致分为:
Free - 没有被使用的
Image - 加载到内存的模块(dll,exe等)
MappedFile - 内存映射文件
unclassified - 实际上应该是堆(heap)
Stack - 堆栈
TEB - 线程环境块(thread environment block)
PEB - 进程环境块(process environment block)

内存根据使用类型又可以分为:
MEM_IMAGE  - 加载到内存的模块(dll, exe等)
MEM_MAPPED - 内存映射
MEM_PRIVATE - 私有(stack, heap, teb, peb等)

内存根据使用状态又可分为:
MEM_FREE - 空闲
MEM_COMMIT - 已经提交
MEM_RESERVE - 保留

根据页面属性又可分为只读,可读写,可执行,写时拷贝等。

实际上我们可以通过!address命令来查看更详细的内存使用情况:


可以看到上面列出了所有2G用户空间的页面使用情况(截图只是开始的一部分),我们可以根据某个地址来分析该地址属于那块内存区域。当然也可以通过命令来分析某个地址所属的内存区域, 比如输入!address 7c554来分析地址7c554的情况,会显示:

上面告诉我们7c554是某个堆栈(Stack)空间的地址.

对我们程序来说最常接触的内存应该是: Module, Heap, Stack,接下来依次分析.

(1)Module
Module在上面被叫住Image,实际上就是被加载到内存的Exe和DLL文件, 我们可以通过lm命令来查看所有的模块分布情况:

上面可以看到每个模块的内存起始地址,那么各个模块具体内部又是如何分布,它和磁盘上的DLL(exe)文件又是什么关系呢?
实际上内存的中DLL和磁盘上的DLL文件非常相似,系统在加载时只是根据页面大小(一般4K)作了一些对齐,另外有些数据节如果运行时用不到(比如dll的重定位节)就不会被加载.

我们在!address查看内存空间时,可以看到taskmgr.exe模块的内存分布如下:

上面可以看到taskmgr.exe模块在内存中分为4块,第一块是只读的, 实际上是PE文件头;第二块是可执行的,实际上就是代码节(.text);第三块是可读写的,实际上数据节(.Data); 最后一块也是只读的,实际上资源节(.rsrc)。
要详细的了解taskmgr.exe模块的文件头属性,可以通过!dh [module address]来查看, 输入!dh 1000000,查看结果:


上面的运行结果可以验证我们关于taskmgr.exe模块内部分布的猜想.

(2)Heap
Heap实际上就是堆,我们所有new(malloc)出来的内存就是分布在堆里,每个程序会有若干个堆,有些是系统创建的,也有的是C/C++运行库创建的,当然我们自己也可以创建私有堆.我们可以通过!heap命令来查看堆的使用情况.

可以看到taskmgr.exe一共有9个堆。
!heap命令非常强大,通过开启页堆功能,可以很方便的让我们跟踪所有堆内存的分配和使用情况,以后有机会再细说heap相关的.

(3)Stack
Stack即我们通常所说的栈,我们的局部变量就是分配在栈上面。说到栈就要说到线程,我们的代码都是通过线程跑起来的,每个线程包含2块东西,一块是线程内核对象,还有一块就是堆栈,线程运行过程也是堆栈不断压栈和出栈的过程。
我们可以通!address -f:stack 来查看堆栈的分布情况:

从上图我们可以看到taskmgr.exe一共有4个线程, 对应着4个堆栈, 同时也可以看到每个堆栈内存的起始地址。

如果有兴趣,我们也可以看下每个线程的堆栈情况, 输入~* kp

可以看到相应的4个线程堆栈,最后一个线程(debugBreakPoint)看起来有些奇怪,实际上它是调试器为调试而插入的,不是真正的属于taskmgr.exe, 所以任务管理器实际上一共应该有3个线程.

通过上面的介绍,相信大家对程序内存有了比较全局的理解,以后大家分析问题,遇到一个地址,首先要判断这个地址分布在哪里:
如果是Image上,那么是在哪个模块中,这个地址是属于该模块的代码段(.text)还是数据段(.data),如果是代码段,又是属于哪个函数?
如果是Heap上,那么究竟是在哪个堆里面,是我们new出来的吗,是在什么时候new的(new时堆栈状况)?
如果是在Stack上,那么究竟是属于哪个线程的堆栈,当时线程的堆栈是怎么样?

总之,程序在内存中运行,只有你真正理解了内存,你才能真正懂计算机。
目录
相关文章
|
3月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
202 6
|
2月前
|
NoSQL 测试技术
内存程序崩溃
【10月更文挑战第13天】
143 62
|
13天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
37 1
|
1月前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
2月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
138 21
|
3月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
3月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
4月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
4月前
|
监控 Java API
如何从 Java 程序中查找内存使用情况
【8月更文挑战第22天】
253 0
|
5月前
|
算法 Java Serverless
Java演进问题之Java程序占用的内存经常比实际应用运行产生的对象占用要多如何解决
Java演进问题之Java程序占用的内存经常比实际应用运行产生的对象占用要多如何解决

热门文章

最新文章