前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《程序员的自我修养》第三章学习笔记

《程序员的自我修养》第三章学习笔记

作者头像
xcywt
发布2018-01-11 17:10:35
1K0
发布2018-01-11 17:10:35
举报
文章被收录于专栏:xcywtxcywt

1, 编译器编译源代码生成的文件叫做目标文件。

从结构上说,是编译后的可执行文件,只不过还没有经过链接

3.1 目标文件的格式

1,可执行文件的格式: Windows下的PE  和   Linux下的ELF

2,从广义上说,目标文件与可执行文件的格式几乎是一样的,所以广义上可以将目标文件与可执行文件看成是一种类型的文件。

3,可执行文件,动态链接库,静态链接库都按照可执行文件格式存储(Windows下是 PE-COFF格式,Linux下是ELF格式)。

4,Linux下命令: $: file   ***   显示出对应文件的类型

目标文件是什么样的

1,目标文件包含的内容:编译后的机器指令代码、数据,还有链接时要的一些信息(比如符号表、调试信息、字符串等)。

1,  一般目标文件把上述信息按不同的属性,以节(section)的形式存储。有时也叫段(segment)

2,  代码段常见的名字有 “.code”  “.text”,编译后的机器指令就放在代码段,

数据段:一般名字都是 “.data”,已初始化全局变量和局部静态变量数据放这

3, ELF文件的开头是一个”文件头”,它描述了整个文件的文件属性(是否可读可写可执行,是静态链接还是动态链接及入口地址)、目标硬件、目标操作系统等信息。

4,  文件头还有一个段表(section table)。描述文件各个段的数组(各个段在文件中的偏移和属性)。

5,  关于bss段:未初始化的全局变量和静态局部变量一般放在一个”.bss”段的地方。

它只是为未初始化的全局变量和静态局部变量预留位置而已,并没有内容,在文件中不占空间。

6,  总体来说:程序源代码被编译后主要分成两种段:程序指令(代码段),程序数据(数据段,bss段)。

3.3挖掘simplesection.o

1,objdump 查看各种目标文件的结构和内容, objdump –h main.o  -h 表示把ELF文件的各个段的基本信息打出来

   readelf 专门针对ELF文件格式的解析器

   size 可以用来查看ELF文件的代码段、数据段和bss段的长度   size main.o

3.3.1代码段

1,objdump 的参数 –s 将所有的段的内容以十六进制的方式打印出来

                    -d 将所有包含指令的段反汇编 比如 objdump  –s  –d  main.o

                    -h 把关键的段显示了出来,忽略了辅助性段

3.3.2数据段和只读数据段

1, “.data” :保存的是已初始化全局变量和局部静态变量

2,,”rodata”:存放的是只读数据,一般是程序中的只读变量(如用const修饰的)和字符串常量。

3.3.3 bss段

1,存放的是未初始化的全局变量和静态局部变量。不占磁盘空间

3.3.4 其他段

还可以自定义段GCC提供的扩展机制

3.4 ELF文件结构描述

3.4.1 文件头

1,查看 readelf –h main.o

2,ELF文件中定义了:ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、

ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表的位置和长度及段的数量等。

3,ELF魔数:文件头最开始的4个字节是所有ELF都必须相同的标识码。又称为魔数。

魔数用来确认文件类型,操作系统在加载可执行文件的时候会确认魔数是否正确,若不正确则拒绝加载。

4,ELF文件类型:分为 可重定位、可执行、共享目标文件、核心转储文件

3.4.2 段表(除了文件头以外最重要的结构)

1,描述了ELF各个段的信息(比如每个段的段名、段的长度、在文件中的偏移,读写权限及其他属性)。

2,编译器、链接器和装载器都是靠段表来定位和访问各个段的属性的。

3,readelf –S main.o 显示真正的段表结构

4,上述结果是一个以 “ELF32_Shdr” 结构体为元素的数组。

3.4.3 重定位表

1,一个叫”rel.text”的段,类型是”SHT_REL”,就是重定位表

2,链接器在处理目标文件时,需要对目标文件中的某些部位进行重定位(即代码段和数据段中那些对绝对地址的引用的位置)。

3,这些重定位信息都记录在ELF文件的重定位表里。对于每个需要重定位的代码段或数据段,都会有一个相应的重定位表。

3.4.3 字符串表

1,把ELF文件中用到的字符串(段名、变量名等)集中起来存放到一个表中。然后使用字符串在表中的偏移来引用字符串。这个表就是字符串表。

2,一般字符串表在ELF文件中也以段的形式保存。常见的段名有”.strtab”, ”.shstrtab”

3,”.strtab”:字符串表:保存普通的字符串

4,”.shstrtab”:段表字符串表:保存段表中用到的字符串,最常见的就是段名。

3.5 链接的接口——符号

1,链接过程的本质就是要把多个不同的目标文件之间相互粘滞到一起。

2,为了使不同目标文件之间能够相互粘合,这写目标文件必须要有固定的规则才行。

3,在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。

4,在链接中,我们将函数和变量统称为符号(symbol),函数名或变量名就是符号名(symbol name)。

5,每一个目标文件都会有一个相应的符号表(symbol table),记录着目标文件中所用到的所有的符号。

6,每个定义的符号有一个对应的值叫做符号值(symbol value)。对于变量或函数来说,符号值就是它们的地址。

8,  符号的分类: (1)和(2)比较需要关注

(1)       定义在本目标文件的全局符号,可被其他目标文件引用。

(2)       在本目标文件中引用的全局符号,在别的目标文件中定义。一般叫做外部符号(external symbol)。

(3)       段名,这种符号往往由编译器产生,它的值就是该段的起始地址。

(4)       局部符号,这类符号只在编译单元内部可见,

(5)       行号信息,即目标文件指令与源代码中代码行的对应关系,是可选的。

3.5.1 ELF符号表结构

1,ELF文件中的符号表往往是文件中的一个段,y一般叫做 “.symtab”。是一个Elf32_Sym的数组,数组中每个元素对应一个符号。

2,结构体定义如下

代码语言:javascript
复制
typedef struct{
    Elf32_Word st_name;
    Elf32_Addr st_value;
    Elf32_Word st_size;
    unsigned char st_info; 
    unsigned char st_other;
    Elf32_Half st_shndx;
}Elf32_Sym;

3.5.2 特殊符号

1,ld链接器产生可执行文件时,会给我们定义很多符号(没有在自己的程序中定义),但是可以直接声明并且引用它,我们称之为特殊符号。

3.5.3 符号修饰与函数签名

1,c++增加了名称空间(namespace)的方法来解决多模块之间的符号冲突问题。

2,c++符号修饰

(1)       函数签名:包含了一个函数的信息(函数名、参数类型、所在的类和名称空间和其他信息)。函数签名用于识别不同的函数。

3.5.4 extern”C”

1,c++为了与c兼容,在符号的管理上,c++有一个用来声明或定义一个C的符号extern”C”的关键字用法。

2,可以让c++的名称修饰机制不作用。

3.5.5 弱符号与强符号

1,多个目标文件含有相同名字的全局符号的定义,在链接时将会出现符号重复定义的错误。

2,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。

3,强弱符号都是针对定义来说的,不是针对符号的引用。 假如 extern int ext; ext不是强符号也不是弱符号,因为它是一个外部变量的引用。

4,链接器按下面规则处理与选择被多次定义的全局符号。

(1)       不允许强符号被多次定义,否则报错。

(2)       若一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。

(3)       若一个符号早所有的目标文件中都是若符号,那么选择其中占用空间最大的那一个。

5,弱引用与强引用:

强引用:假如链接时没找到该符号的定义,链接器就会报符号未定义错误。

弱引用:假如链接时没找到该符号的定义,链接器不会报错,默认其为0或是一个特殊值。

6,GCC中可以通过 “__attribute__((weakref))”这个扩展关键字来声明一个外部函数为弱引用  p93

用法: __attribute__((weakref)) void foo();  这样就说明foo为弱符号。

7,弱符号和弱引用对库来说非常有用,比如库中定义的弱符号可以被用户定义的强符号所覆盖。

8,补充: Linux中支持多线程时需要在编译时加上 –lpthread 选项, 比如 gcc test.c -lpthread

3.6 调试信息

1,在gcc编译时加上 –g 参数就会在产生的目标文件里面加上调试信息。 目标文件会多些 debug段。

2,假设有个目标文件 test, Linux下可以用 ”strip” 命令来去除调试信息, 用法: $ strip test

3.7 本章小结

参考《程序员的自我修养》

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015-11-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档