《操作系统真象还原》——0.21 Section和Segment的区别

本节书摘来自异步社区《操作系统真象还原》一书中的第0章,第0.21节,作者:郑钢著,更多章节内容可以访问云栖社区“异步社区”公众号查看

0.21 Section和Segment的区别

C程序大体上分为预处理、编译、汇编和链接4个阶段。预处理阶段是预处理器将高级语言中的宏展开,去掉代码注释,为调试器添加行号等。编译阶段是将预处理后的高级语言进行词法分析、语法分析、语义分析、优化,最后生成汇编代码。汇编阶段是将汇编代码编译成目标文件,也就是转换成了目标机器平台上的机器指令。链接阶段是将目标文件连接成可执行文件。这里我们只关注汇编和链接这两个阶段。

在汇编源码中,通常用语法关键字section或segment来表示一段区域,它们是编译器提供的伪指令,作用是相同的,都是在程序中“逻辑地”规划一段区域,此区域便是节。注意,此时所说的section或segment都是汇编语法中的关键字,它们在语法中都表示“节”,不是段,只是不同编译器的关键字不同而已,关键字segment在语法中也被认为与section意义相同。首先汇编器根据语法规则,会将汇编源码中表示“节”的语法关键字section或segment在目标文件中编译成“节”,此“节”便是我们要讨论的section。经过汇编生成目标文件之后,由这些section或segment修饰的程序区域便成为了“节”(section)。但操作系统加载程序时并不关心节的数量和大小,操作系统只关心节的属性,因为程序必然是要加载到内存中才能运行的,而内存的访问会涉及到全局描述符表中段描述符的访问权限等属性,保护模式下对任何内存的访问都要经过段描述符才行。比如程序代码所在的段描述符权限属性必须是只读,数据所在的段描述符的权限属性必然是可读写,程序中那些只读的节(比如代码区域)必然不能指向可读写的段描述符,同样,程序中的数据也不能用只读权限的段描述符去访问。如果此时您对段描述符不了解,以后咱们在介绍保护模式下全局描述表时就明白了。操作系统在加载程序时,不需要对逐个节进行加载,只要给出相同权限的节的集合就行了,例如把所有只读可执行的节(如代码节.text和初始化代码节.init)归并到一块,所有可读写的节(如数据节.data和未初始化节.bss)归并到一块,这样操作系统就能为它们分配不同的段选择子,从而指向不同段描述符,实现不同的访问权限了。为了程序能在操作系统上运行,操作系统和编译器需要相互配合,此时汇编器只生成了目标文件,尚未链接,因此这个将“节”合并的工作是由链接器来完成的,链接器将目标文件中属性相同的节合并成一个大的section集合,此集合便称为segment,也就是段,此段便是我们平时所说的可执行程序内存空间中的代码段和数据段。

现在总结一下。

section称为节,是指在汇编源码中经由关键字section或segment修饰、逻辑划分的指令或数据区域,汇编器会将这两个关键字修饰的区域在目标文件中编译成节,也就是说“节”最初诞生于目标文件中。

segment称为段,是链接器根据目标文件中属性相同的多个section合并后的section集合,这个集合称为segment,也就是段,链接器把目标文件链接成可执行文件,因此段最终诞生于可执行文件中。我们平时所说的可执行程序内存空间中的代码段和数据段就是指的segment。

在大多数情况下,这两者都被混为一谈,现在咱们做个实际测试,通过实验结果来展示出这两者的不同。其实用一个测试样例就能得出结果,不过为了消除大家的疑虑,测试得更彻底一点,在这里给大家准备了两个小汇编文件,将它们编译链接后,我们通过readelf命令查看其信息来得出结论。上菜了。

文件1.asm

screenshot

这个汇编文件是在本地中声明了字符串,并调用外部的打印函数print,大家可以参考注释,弄个大概明白就行。

文件2.asm

screenshot

在文件2.asm中声明了函数print。下面将这两个文件分别编译成elf格式,这样方便我们通过readelf来查看其编译结果。开始编译,链接成可执行文件12。

[work@localhost test]$nasm -f elf 1.asm -o 1.o
[work@localhost test]$nasm -f elf 2.asm -o 2.o
[work@localhost test]$ld 1.o 2.o -o 12

没问题,再执行一下。

[work@localhost test]$ ./12
Hello,world!

打印出了Hello,world!,结果正确。让我们用readelf查看下文件12的头信息,如图0-12所示。

screenshot

screenshot

结果好长,为了方便查看,我对关键部分加以注释,如图0-13和图0-14所示。

在上面重点部分我都用文字标出了,要注意section headers的部分,此部分显示可执行文件中所有的section,也包括我们在两个汇编文件中用关键字section定义的部分。从第2个section到第5个section,是1.asm中的自定义数据section: file1data,自定义代码section: file1text和2.asm中的自定义数据section: file2data和自定义代码section: file2text。

再往下看Program Headers部分,此处一共有两个段,第一个段是我们的代码段,通过其Flg值为RE便可推断,只读(Readonly)可执行(Execute),其MemSiz为0x000c3。此段对应Section to Segment mapping部分中的第00个Segment,此segment中包括section: .text file1data file1text file2data file2text。

screenshot

第二个段便是我们的数据段,但此数据段中只包含.bss节(section),它用于存储全局未初始化数据,故其Flg必然可读写,其属性为RW。此段MemSiz大小为0x40,即十进制的64,可见,这和1.asm中定义的bss大小一致,而在2.asm中未定义.bbs section,所以此bss指的就是1.asm中的定义。此段对应Section to Segment mapping部分中的第01 个Segment,而此segment只包括.bss节,独立成一个段了。

到此文件分析完毕,总结一下。

自定义的section名,会在elf的section header 中显示出来。下面是几个标准的section(节)名,不是segment(段)名,segment没有名称。

节名     说明
.data   用于存入数据,可读可写
.text   用于存入代码,只读可执行
.bss    全局未初始化区域

在汇编代码中,若以标准节名定义section,如我们定义的.bss便是标准节名。编译器会按照以上说明中的要求使用section内的数据。

不管定义了多少节名,最终要把属性相同的section,或者编译认为可以放到一块的,合并到一个大的segment中,也就是elf中说的 program header 中的项。由此可见,某个节(section)属于某个段(segment),段是由节组成的。另外多说一句,最终给加载器用的也是program header中显示的段,这才是进程的资源,这部分内容将在加载内核时展开。在第3章中介绍了section在地址分配上的内容,大家有兴趣可以提前了解下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值