【软件开发底层知识修炼】二十 深入理解可执行程序的结构
上一篇文章记录了GDB调试从入门到熟练掌握的学习全过程。点击链接查看:【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录
- 还记得在以前的学习Binutils工具的时候,学习了很多工具来查看可执行程序的结构,那个时候并没有详细说明程序的结构,今天就来学。下面是之前学习的Binutils工具集的几篇文章,可以参考学习:
- 【软件开发底层知识修炼】六 Binutils辅助工具之- addr2line与strip工具
- 【软件开发底层知识修炼】七 Binutils辅助工具之- ar工具与nm工具
- 【软件开发底层知识修炼】八 Binutils辅助工具之- objdump工具 与 size,strings工具
本篇文章开始学习可执行程序的结构。也就是我们平时说的可执行文件的结构。本文不会像《程序员的自我修养》那样详细解释可执行文件的每一个细节,我希望通过这次学习能够对程序的结构有一个永久性的认知。当然,还是建议要把《程序员的自我修养》仔细阅读完。
文章目录
- 1 程序是由不同的段构成的
- 1.1 代码示例
- 2 程序中的栈结构
- 3 堆(Heap)的简要概述
- 4 内存映射段(mmap)
- 4 总结
1 程序是由不同的段构成的
- 至于什么是段,如果看了《X86汇编语言-从实模式到保护模式》应该会非常清楚。段不过就是一段内存结构。把类似的指令放到连续的内存区域就是代码段(.text段),把初始化了的数据放到连续的内存区域就是数据段(.data段),把未初始化的数据放在一起就是(.bss段)
- 程序的静态特征是指令和数据:实际上就是一堆二进制放在那里(磁盘)不动。
- 程序的动态特征就是执行指令来处理数据:实际上就是把磁盘上的二进制文件加载到内存,让指令来处理数据。
那么你用C语言写一个代码,它与可执行文件的内部结构是如何对应的?
- 代码段(.text):
- 源代码中的可执行语句编译后进入代码段
- 代码段在内存管理单元的系统属性中具有只读属性,它是不可写的。
- 代码段的大小在编译结束后,大小就直接确定了,运行的过程中不会被改变
- 代码段中可以包含常量数据,(如字符串常量)
数据段(.data , .bss, .rodata)
数据段中用于存放源代码中具有全局生命周期的变量。不具有全局生命期的局部非静态变量不在数据段中,而是在栈中。栈后面会讲。
.bss
- .bss是存储未初始化的变量。或者说初始化为0的变量
.data
- .data存储的是具有非0初始值的变量
.rodata
- .rodata存储的是const修饰的变量
为什么同是全局变量和静态局部变量,为什么初始化的和未初始化的变量放在不同的段中?
.bss段在可执行文件中不赋初值。在加载到内存中时直接全部初始化为0。这样减少了可执行文件的体积也提高额加载的效率
.data段中,在可执行文件中直接将变量对应的初始值保存,加载到内存中时直接将文件中对应的值加载到内存中即可。这也提高了程序的加载效率。
- 文件头(File header)
文件头并不是今天的重点。简单来说文件头中保存了程序的各个段的信息,操作系统加载程序的时候,首先要读取这个文件头,先计算出各个段的大小,才能从磁盘中准确的读取相应的执行与数据。
并且在文件头中也记录了类似于符号,符号变之类的信息。这些不再多讲。
1.1 代码示例
下面我们写一个代码来使用一些具体的工具,查看各个段。
test.c
char g_no_val; // .bss 1byte
int g_value=1; //.data 4byte
char g_str[]="D.T.SoftWare_lyy"; // .data 17byte
const int g_const=3; //.rodata 4byteint dt_main(){static char c_no_value; // .bss 1bytestatic int c_value=2; // .data 4bytereturn 0;
}
- 可以看到上述程序没有main函数,但是我们可以指定dt_main()函数为入口函数。使用以下方式进行编译:
- gcc -e dt_main -nostartfiles test.c -o test.out
- 得到可执行代码文件test.out,使用下面的命令查看它的各个段的信息:
- objdump -h test.out
- 由上图可以看到各个段的大小,起始段的地址等
- .data段大小0x1c=28字节:我们由上述代码可以看到,.data段的变量一共是25字节,由于对其,最终是28字节,也就是16进制的1c。.data段的起始地址是:08049ff4
- .bss段大小0x4=4字节:这个很明显。.bss段的起始地址是:0804a010
- .rodata段大小:4字节:这个也很明显。起始地址为:0804819c
- 使用nm test.out 查看各个符号的属性:
- 因为符号g_const所在的.rodata段只有它一个,所以它的地址自然就是.rodata段的起始地址。如上图
其他的信息也很容易看懂。这里不再赘述。
- 我们还可以使用命令:objdump -s -j .rodata test.out 查看某一个段的信息:
2 程序中的栈结构
在最开始的那张图:
我们始终没有说a,b这两个变量在哪里。它们不在上述的那些段中。当然,它们肯定也是在某一块内存中的。这块内存,我们叫做:栈。
对于栈,我们这也不说特别详细值说明栈的基本用处。
- 栈的本质是一块连续的内存结构。它与数据结构中的栈不是一个概念,但是操作很像。
- 其中SP寄存器(当然这是最基本的16位的,32位的叫ESP)存的是栈顶的指针。它用于栈的入栈操作与出栈操作。
- 栈的增长方向一般是向下。这与下面即将要说的堆内存正好相反。
栈一般有什么用处呢?
除了保存类似于上述的a,b这种局部变量以外。还有以下几种用途。
- 中断发生时,栈用于保存一些寄存器的值。
- 函数调用时,栈用于保存函数的上下文信息(活动记录,依然是一些寄存器的值等)
- 并发编程时,每一个线程都有一个自己独立的栈。(说是独立有点不恰当,其实它们都是在一个大的独立的进程空间中。)
在本文,就不打算再详细说栈这种结构与作用。可以参考《程序员的自我修养》
知道了栈结构,理应还要知道堆结构。它们总是放到一起做对比。
3 堆(Heap)的简要概述
在这里我们知道堆是用于以下用途即可,具体的后面还会学习。
- 堆,是一片闲置的内存空间,用于程序在运行的时候动态分配的
- 堆空间的分配需要函数的支持,比如malloc。C++的new关键字底层也是调用相应的函数
- 堆空间的使用后需要显示的释放(栈就不需要),一般是用free。C++中为delete。不过更加高级的语言具有垃圾回收机制比如java
4 内存映射段(mmap)
上述学习了各个段以及堆结构与栈结构。
还有一种内存段,叫做内存映射段。它是用来做什么的呢?
如果了解动态链接的过程,应该知道如果程序加载时需要动态链接相关动态库的话,操作系统内核会将相应的动态库的文件直接映射到内存中。映射的位置可以称为内存映射段。
还有一种情况是如果想要读取一个文件的内容,一点一点的读,开销总是相当大。如果操作系统内核直接将文件映射到某一块内存,再来读取文件的内容,将会块的多。
还有一种是程序执行时可以创建匿名映射区来存放程序数据。什么是匿名映射区?比如一个程序生产了很多数据,要将它最终存到一个文件中。那么不可能说生产一个数据就存放到文件中,更加高效的做法是,在内存中创建一个赋值全为0的区域,将生产的数据暂时先存放到这里,生产完或者生产了足够多的数据后再将数据写到磁盘上的文件。这就是匿名映射区。毕竟磁盘的读写都是以扇区为单位,一个扇区大小为512字节,一次写多一点数据总是比你一次写1字节的数据更加高效。
有一点要明白,将文件的内容映射到内存,实际上是映射到进程的虚拟地址空间,而且,映射的过程,是没有数据的迁移的,也就是没有数据的拷贝。
其实上面我们并没有说明白为什么将文件映射到内存后再读写会快一些。
如果使用正常的read函数进行读文件,是需要两次的数据拷贝:一次是内核从磁盘将文件的内容拷贝到内核地址空间,然后再从内核地址空间拷贝到用户地址空。这里进行了两次数据的拷贝。开销比较大
但是如果是使用内存映段的话,就不一样了
如下图:
- 首先将硬盘上的文件数据从逻辑上映射到内存中,这没有数据拷贝,零耗时。
- 当用户程序读数据的时候,从虚拟地址空间读,通过缺页中断进行文件数据的实际载入(这里就是真正的数据的拷贝,从文件中拷贝到真实的物理内存)
- 这里要注意一点:映射后的内存(虚拟内存)的读写,就是对文件数据的读写。
4 总结
其实这些内容以前都见过学过。下面给一个大的进程的虚拟地址空间的内存分配图:
- 当然在Linux系统中内核与用户空间的比例是1:3,但是在windows系统中就是2:2了.
本文章参考狄泰软件学院相关课程 想学习的可以加狄泰软件学院群, 群聊号码:199546072
学习探讨加个人(可以免费帮忙下载CSDN资源):
qq:1126137994
微信:liu1126137994
学习交流资源分享qq群:962535112
【软件开发底层知识修炼】二十 深入理解可执行程序的结构相关推荐
- 【软件开发底层知识修炼】十五 快速学习GDB调试二 使用GDB进行断点调试
上一篇文章我们学习了使用GDB的最基本方法:[软件开发底层知识修炼]十四 快速学习GDB调试一 入门使用 本篇文章将学习GDB的断点调试.断点调试是一种非常重要的调试方法. 文章目录 1 断点类型 2 ...
- 【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录
本文记录之前写过的5篇关于GDB快速学习的文章,从第一篇开始学习到最后一篇,保证可以从入门GDB调试到熟练掌握GDB调试的技巧. 学习交流加 个人qq: 1126137994 个人微信: liu112 ...
- 【软件开发底层知识修炼】十六 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变
上一篇文章我们学习了如何使用GDB进行软件断点调试和硬件断点调试:[软件开发底层知识修炼]十五 快速学习GDB调试二 使用GDB进行断点调试 本篇文章继续上一篇文章的学习,如何使用GDB的数据断点监测 ...
- 【软件开发底层知识修炼】十八 快速学习GDB调试五 使用GDB进行调试的一些小技巧
上一篇文章学习了如何使用GDB进行函数调用栈的查看:[软件开发底层知识修炼]十六 快速学习GDB调试四 使用GDB进行函数调用栈的查看 本篇文章是GDB调试快速学习系列的最后一篇.将综合前几篇文章做一 ...
- 【软件开发底层知识修炼】十四 快速学习GDB调试一 入门使用
前面几篇文章学习了链接器相关的内容.现在开始来学习GDB调试.我们的目的是通过这几篇文章将GDB调试完全学会. 文章目录 1 为什么需要GDB 2 GDB 的常规应用 3 GDB调试程序实例 4 总结 ...
- 【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)
上一篇文章学习了链接脚本的语法与相关概念:链接脚本的概念 在继续学习链接器的内容的同时,先学习一个新内容:内嵌汇编. GCC编译器一般支持C/C++内嵌汇编语言,这样可以实现语言本身无法实现的内容.我 ...
- 【软件开发底层知识修炼】十 链接器-main函数不是第一个被执行的函数
上一篇文章,大概了解了链接器的工作内容就是:符号解析和重定位.点击上一篇文章查看:点击查看. 本片文章其实还是围绕链接器来学习.只不过不是很明显,当你学到下一篇文章时,就明白了. 本篇文章来弄明白一个 ...
- 【软件开发底层知识修炼】十三 链接器-如何写出不依赖C库函数的代码
本文将综合以下4篇文章,学习如何写出不依赖libc库的程序: [软件开发底层知识修炼]九 链接器-可重定位文件与可执行文件 [软件开发底层知识修炼]十 链接器-main函数不是第一个被执行的函数 [软 ...
- 【软件开发底层知识修炼】十七 快速学习GDB调试四 使用GDB进行函数调用栈的查看
上一篇文章学习了如何使用GDB数据断点进行内存监测:[软件开发底层知识修炼]十五 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变 本篇文章继续上一篇文章的学习:如何使用GDB进行函数调用栈 ...
最新文章
- [导入]二叉树(C#数据结构四)
- ThinkPHP6项目基操(16.实战部分 redis+token登录)
- 如何用C#将tif格式的图片转化成jpg?
- SpringCloud学习笔记025---SpringBoot_注释理解_@Qualifier 注释
- css字体倾斜角度_css如何实现渐变效果?css背景色渐变与文字渐变效果的实现(代码实例)...
- Atitti v5住宿服务部后勤部建设指引指南.docx
- 毕设题目:Matlab无人机飞行作业
- U盘文件系统FAT32转为NTFS
- 解读《美国国家BIM标准》 – BIM能力成熟度模型(四)
- MFC工程中划定ROI区域
- pytorch drop_last参数
- lodop php 快递单_lodop打印控件打印申通快递单
- Java的三大特性之封装、继承、多态-----最简理解
- 基于传统方法的单目深度估计
- uniapp 上传图片 + 预览图片 + 删除图片
- Redis 之BIO与RIO
- 酒美网CEO:线上线下向300万人卖红酒
- 软件工程导论张海蕃书籍pdf_《软件工程导论》张海蕃 课后习题答案.docx
- 如何正确选择合适自己的英文学习方法和材料?
- 一台windows机器和一台linux机器之间互相拷贝文件的方法,windows系统和Linux系统之间拷贝文件***--pscp...
热门文章
- spring mvc学习(42):restful的编辑功能实现
- 让LwIP拥有PING其他设备的能力
- element-ui中表单验证rules
- CSS之REM和EM的区别
- oracle 安装display,Linux安装Oracle 11时报错DISPLAY解决方案
- iOS 11: CORE ML—浅析
- bzoj3524 [Poi2014]Couriers
- 解析IP地址与MAC地址
- 该文件可能是只读的 或者您要访问的位置_喔噢小贴士:如何保护PPT不被更改,将其设为只读...
- 第5章-css选择器初级和背景