| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 系统运维 -> 读书计划--程序员的自我修养(二) -> 正文阅读 |
|
[系统运维]读书计划--程序员的自我修养(二) |
目录一、引言二、第二章------> 2.1、编译过程------> 2.2、编译器三、第三章------> 3.1、目标文件的类型------> 3.2、目标文件的格式------> 3.3、反汇编来查看elf文件------> 3.4、ELF结构详细描述------> 3.5、链接的接口–符号一、引言本篇文章会和大家介绍《程序员的自我修养》中的第二、三章内容。 二、第二章1、编译过程一个程序,从代码到可在某个平台上执行的二进制文件,其中中间还有很多流程,需要用到很多工具,而现在的IDE,将编译器、链接器等都集成进去了,所以给我们的假象就是程序写好编一下就能运行了 预处理主要处理代码中’#‘开始的预编译指令,如’#include’、’#define’等
编译将预处理完的文件进行词法分析、语法分析等操作,最终产生汇编文件,是最复杂的部分之一,由编译器完成
现在的GCC框预编译和编译合成一个步骤,叫做cc1的程序
所以实际上gcc命令只是这些后台程序的包装,它会根据不同的参数去调用不同的程序,如预编译编译程序cc1、汇编器as、链接器ld等 有兴趣的朋友可以去看下交叉编译器目录下的bin,就有如下命令 汇编汇编器是将汇编代码转变成机器可以执行的指令,这步骤比较简单,通常只需要根据汇编指令和机器指令的对照表翻译即可
也可以用如下命令
也有命令可以直接从源文件开始生成目标文件
链接链接是将目标文件与库链接为可执行程序或库文件,通常我们的程序都会依赖很多库文件,包括系统库与外部库等 2、编译器编译器的工作就是将高级语言翻译成机器语言,为什么要有这个流程呢。 具体的编译过程我这里就不详细展开了,有兴趣的小伙伴可以自己去看一下 静态链接静态链接其实就是告诉目标文件调用外部接口的地址,让我们可以直接引用其他模块的函数、全局变量而无需关系地址 静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。 动态链接动态链接的进行则是在程序执行时 动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。 而且如果在程序运行的时候找不到这个动态库,程序会无法继续往下运行 gcc的相关参数-L选项告诉编译器去哪里找需要的库文件,-lstack告诉编译器要链 接libstack库,-I(大写的i)选项告诉编译器去哪里找头文件 三、第三章源代码经过编译器编译后生成的文件叫做目标文件,此时的目标文件已经是可执行文件格式,但是还没有经过链接过程,其中有些符号和地址还没有被调整,剩余部分和真正的可执行文件已经一样了 1、目标文件的类型主流PC平台的可执行文件 除了可执行文件,动态链接库、静态链接库、core文件也是按照这个格式来储存的,其中静态链接库是将很多目标文件捆绑在一起再加上一些缩影形成的文件 linux下可以使用file来查看文件的文件格式,我们随便来看一个交叉编译器下的文件 可执行文件 2、目标文件的格式目标文件中的内容需要包含代码、数据、链接时所需要的的一些信息(符号表、调试信息)等 代码段(.code 或 .text)源代码编译后的机器指令通常放在代码段 数据段(.data)全局变量和局部静态变量存放在数据段 bss段(.code 或 .text)未初始化的全局变量和局部静态变量存放在bss段 整体的框图如下 文件头如上所示,ELF文件的开头还会有一个文件头,描述了整个文件的属性、是否可执行、静/动态链接、入口地址、目标硬件、目标操作系统等 运行时的文件内存分布这里找到一张可以很形象描述运行时程序的内存分布 其他部分在elf文件中还有其他段,这里就简单介绍下,主要都是和链接有关 .interp段 dynamic 段 dynsym 段 .got(全局偏移表)和.plt(过程链接表)是由动态链接器操纵的两个主要结构。.got是变量的间接表,.plt是函数的间接表。每个可执行文件或库(称为“共享库”)都有其自己的.got和.plt,这些是该共享库所引用的符号的表,这些表实际上包含在其他共享库中。 .dynsyn段: 书上也有相关的介绍,如下图 查看elf格式的命令可以通过"readlf"或"objdump"来查看elf文件
LMA与VMA的区别 其中除了我们之前提过的几个部分,还有很多其他段 最终得出实际的分布 size命令size 命令可以专门来查看ELF文件的代码段、数据段、BSS段 3、反汇编来查看elf文件代码段
数据段数据段可以使用如下命令查看,这里就不详细说了
4、ELF结构详细描述先来看一张图,去掉了一些繁琐的结构,将最重要的结构提取出来 文件头先来看一下之前用objdump看到的elf段分布 我们可以使用 readelf 来查看elf头部的信息 更多详细的头文件解析这里就暂时不分析了,后面如果用到的话再展开 段表ELF文件中有各种各样的段,段表就是用来描述各个段信息的,同样可以使用readelf -s来看,之前我们使用"objdump -h"将elf中关键的段显示出来,而忽略了其他辅助性的段,如符号表、段名字符串表等。 我们再来看一下段表在elf文件中的位置 重定位将重定位表前,先讲一下什么是重定位。 重定位的发生是由于一个作业装入到与其地址空间不一致的存储空间所引起的,对有关地址部分的调整过程称为地址的重定位。这个调整过程就是把作业地址空间中使用的逻辑地址变换成主存中物理地址的过程。这种地址变换也称为地址映射. 举个例子,我们程序在运行前,会将其中链接的静态库都放入内存中,当程序开始运行时,运行到了这个库中的外部接口,就需要跳转到外部库中的这个地址,去运行这个接口,地址就有程序内部的逻辑地址转换到实际的物理地址,这个过程就叫做重定位 重定位分为两种:静态重定位、动态重定位 重定位表我们前面用"readelf -s"时看到在代码段后有一个".rela.text",这就是重定位表,链接器在处理目标文件时,必须对引用绝对地址的地方进行重定位,.rela.text就是针对.text段的重定位表,.rela.data就是针对.data段的重定位表,关于重定位表,会在下一章详细将 字符串表ELF文件中用到了很多字符串,如段名、变量名等。所以我们将字符串集中起来存放到一个表中,之后就可以根据在表中的偏移找到这个字符串 字符串表在ELF中也以段的形式保存,我们刚刚用readelf -S就可以看到 5、链接的接口–符号链接的本质就是将多个不同的目标文件拼接到一起,而这个拼接实质上就是目标文件之间对地址的引用,即对函数和变量地址的引用。 在链接中,我们将函数和变量统称为符号,函数名和变量名就是符号名。 每个目标文件中都有一个符号表,记录了这个目标文件中所用到的所有符号,每个符号都有一个对应的值,叫做符号值。 符号的种类1、定义在目标文件中的全局符号,可被其他文件引用,比如"main"等 2、在文件中引用到的全局符号,但在本文件中没有定义,如"printf"等 3、段名,由编译器产生,就是该段的起始地址,如".data",".text"等 4、局部符号,就是局部变量,只在编译单元内部使用,常用来分析转储文件,不用于链接 5、行号信息,即目标文件指令与源代码中代码行号的对应关系,为可选项 其中前面两项最为重要,链接过程只会用到前面两项,后面三项对于其他文件来说都是不可见的 符号表的查看我们可以使用很多命令来查看符号表:readelf、objdump、nm等 这里举个例子,我写了一个有两个目标文件链接而成的可执行文件 符号表结构符号表在elf中为一个段,叫做".symtab",符号表为一个"Elf32_Sym"的数组,每个符号对应一个符号结构体"Elf32_Sym",定义如下
Bind:绑定信息 Vis:没用到,暂时不讲 Name:符号名称 大家可以自己对照看一下 特殊符号当我们使用ld作为链接器时,它会为我们定义很多特殊的符号,这些符号虽然没有在我们的程序中定义,但我们却可以直接声明并定义它们,下面讲几个比较有代表性的特殊符号 extern “C”C++为了与C兼容,定义了一个声明用来处理C语言代码,
C++编译器会将extern "C"大括号内的代码当作C来处理 弱符号与强符号我们在编程中经常会遇到符号重复定义的问题,多个目标文件内含有相同名字全局符号的定义,链接时就会报符号重定义的错。 针对强弱符号的概念,链接器会按照如下规则处理与选择被多次定义的符号 强引用与弱引用强引用:我们对外部文件的符号引用在目标文件被链接成可执行文件时,如果没有找到该符号的定义,链接器就会报符号未定义的错 弱引用的用途: 同时我们也可以只用编译器会将某些弱引用的值默认为0来做一些判断。 调试信息我们在调试代码时,经常会使用gdb、addr2line等工具,有兴趣的小伙伴可以看我这篇文章 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/16 0:52:38- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |