【软件开发底层知识修炼】二十 深入理解可执行程序的结构

上一篇文章记录了GDB调试从入门到熟练掌握的学习全过程。点击链接查看:【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录

  • 还记得在以前的学习Binutils工具的时候,学习了很多工具来查看可执行程序的结构,那个时候并没有详细说明程序的结构,今天就来学。下面是之前学习的Binutils工具集的几篇文章,可以参考学习:
  • 【软件开发底层知识修炼】六 Binutils辅助工具之- addr2line与strip工具
  • 【软件开发底层知识修炼】七 Binutils辅助工具之- ar工具与nm工具
  • 【软件开发底层知识修炼】八 Binutils辅助工具之- objdump工具 与 size,strings工具

本篇文章开始学习可执行程序的结构。也就是我们平时说的可执行文件的结构。本文不会像《程序员的自我修养》那样详细解释可执行文件的每一个细节,我希望通过这次学习能够对程序的结构有一个永久性的认知。当然,还是建议要把《程序员的自我修养》仔细阅读完。

1 程序是由不同的段构成的

  • 至于什么是段,如果看了《X86汇编语言-从实模式到保护模式》应该会非常清楚。段不过就是一段内存结构。把类似的指令放到连续的内存区域就是代码段(.text段),把初始化了的数据放到连续的内存区域就是数据段(.data段),把未初始化的数据放在一起就是(.bss段)
  • 程序的静态特征是指令和数据:实际上就是一堆二进制放在那里(磁盘)不动。
  • 程序的动态特征就是执行指令来处理数据:实际上就是把磁盘上的二进制文件加载到内存,让指令来处理数据。

那么你用C语言写一个代码,它与可执行文件的内部结构是如何对应的?

在这里插入图片描述

  • 代码段(.text):
  1. 源代码中的可执行语句编译后进入代码段
  2. 代码段在内存管理单元的系统属性中具有只读属性,它是不可写的。
  3. 代码段的大小在编译结束后,大小就直接确定了,运行的过程中不会被改变
  4. 代码段中可以包含常量数据,(如字符串常量)
  • 文件头(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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422912.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端学习(225):尺寸属性

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>CSS尺寸属性…

前端学习(226):定位使用

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>CSS定位<…

【数据结构与算法-java实现】三 Java数组类实现

上一篇文章学习了&#xff1a;最好、最坏、平均、均摊时间复杂度的计算与分析方法.本片文章学习数组这种结构。由于数组这种结构比较简单&#xff0c;本文直接简单介绍&#xff0c;然后给出两种实现数组类的Java代码:整形数组类与通用性的数组类 由于数组是相比于其他数据结构实…

解决VMware6.5 以上版本安装RHEL 5的自动安装的问题

解决VMware6.5 以上版本安装RHEL 5的自动安装的问题记得在学习 RHEL5的时候&#xff0c;教室里用的VMware5.5.3的版本&#xff0c;在教室里安装的时候是很正常的有步骤的那种&#xff0c;回到寝室后&#xff0c;用我的VMware6.5 安装的时候却发现&#xff0c;完全变成了自动安装…

【软件开发底层知识修炼】二十一 ABI-应用程序二进制接口一

前面学习了可执行程序的结构&#xff0c;点击链接查看上一篇文章&#xff1a;【软件开发底层知识修炼】二十 深入理解可执行程序的结构本篇文章开始新的篇章&#xff0c;学习应用程序的二进制接口-ABI。 文章目录1 什么是ABI&#xff08;Application Binary Interface&#xff…

【软件开发底层知识修炼】二十二 ABI-应用程序二进制接口 二

上一篇文章学习了ABI的相关内容&#xff0c;具体最后分析了不同ABI下结构体的对齐方式的不同。点击链接查看上一篇文章&#xff1a;【软件开发底层知识修炼】二十一 ABI-应用程序二进制接口一本篇文章继续学习ABI相关内容。是上一篇文章的补充&#xff0c;如果没有看过上一篇文…

asp.net中RegularExpressionValidator控件中正则表达式用法

验证数字&#xff1a; 只能输入1个数字 表达式 ^\d$ 描述 匹配一个数字 匹配的例子 0,1,2,3 不匹配的例子 只能输入n个数字 表达式 ^\d{n}$ 例如^\d{8}$ 描述 匹配8个数字 匹配的例子 12345678,22223334,12344321 不匹配的例子 只能输入至少n个数字 表达式 ^\d{n,}$ 例如^\d{8…

VS2017社区版30天到期无法使用的激活方法

VS2017社区版30天到期无法使用的激活方法VS2017社区版是免费的&#xff0c;但是第一次安装时&#xff0c;没有登录&#xff0c;导致只要30天的试用期&#xff0c;现在试用期结束&#xff0c;无法使用&#xff0c;本教程就是解决这个问题。我先在控制面板中将2017社区版软件卸载…

五分钟搞懂内网和外网之间的通信的原理

写的通熟易懂&#xff0c;特转过来备忘&#xff01;原创链接找不到了 对于初学者而已&#xff0c;我们学习的网络编程(如TCP,UDP编程)&#xff0c;我们通常都是在局域网内进行通信测试&#xff0c;有时候我们或者会想&#xff0c;我们现在写的内网网络数据和外网的网络数据有什…

【软件开发底层知识修炼】二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁

上两篇文章我们初步接触了ABI-应用程序二进制接口的概念&#xff0c;点击链接查看上一篇文章&#xff1a;【软件开发底层知识修炼】二十二 ABI-应用程序二进制接口 二。了解了为什么会有ABI的存在。本篇文章继续学习ABI 的内容。学习在ABI规范下&#xff0c;函数栈帧的结构与函…

【转】电脑GPS导航软件下载,教你把笔记本做成GPS

在开始之前&#xff0c;先说一下&#xff0c;相信很多朋友在谷哥搜索“电脑GPS导航软件”时&#xff0c;都很难找到真正的下载地址&#xff0c;多数是只能下载到灵图的破解文件&#xff0c;那么&#xff0c;本文不同&#xff0c;本文不但教你怎么打造电脑GPS&#xff0c;而且提…

【软件开发底层知识修炼】二十四 ABI之函数调用约定

上一篇文章学习了Linux环境下的函数栈帧的形成与摧毁。点击链接查看相关文章&#xff1a;软件开发底层知识修炼】二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁本篇文章继续学习ABI接口相关的内容。函数调用约定 文章目录1 函数参数如何入栈&#xff0c;返回…

【软件开发底层知识修炼】二十五 ABI之函数调用约定二之函数返回值为结构体时的约定

上一篇文章学习了几种函数调用约定的区别&#xff0c;点击链接查看上一篇文章&#xff1a;【软件开发底层知识修炼】二十四 ABI之函数调用约定本篇文章继续学习函数调用约定中&#xff0c;关于函数返回值的问题。当函数返回值为结构体时&#xff0c;函数返回值是如何来传给调用…

CSDN-Markdown-图片设置(大小,居中)

利用markdown在编写文档时插入图片是默认靠左&#xff0c;有些时候将图片设置为居中时可以更加的美观&#xff0c;这时就需要在图片的信息前边添加如下程序 <div aligncenter>![这里写图片描述](http:...如果想将图片位于右侧&#xff0c;只需要将center改为right<di…

CSDN中markdown字体颜色,大小,首行缩进,居中排布

一、下面是首行缩进的两种方法 1.这里实用空格去替代缩进的字符&#xff0c;下面讲的替代包括分号 2.把输入法由半角改为全角。 两次空格之后就能够有两个汉字的缩进。 半方大的空白用&ensp;或 全方大的空白用&emsp;或 不断行的空白格用 或 示例&#xff1a; 略略略…

使用VNC软件与花生壳进行内网穿透实现在嵌入式平台中进行广域网下的远程控制

在嵌入式平台中如何实现广域网下的远程登录控制&#xff1f; 文章目录1 项目需要2 解决方案3 首先实现局域网下的VNC远程控制4 总结1 项目需要 在IM.X6q硬件平台&#xff0c;Linux4.1.15内核版本中实现在广域网下进行远程登录控制。主控机为Windows机器&#xff0c;被控机是IM…