如何有elf文件as生成可执行elf相应的map文件

全套200集视频教程和1000PDF教程请到秉吙论坛下载:

野火视频教程优酷观看网址:/firege

Help"菜单可打开该文件关于ELF文件格式,参考配套资料里的《ELF文件格式》文件

在本章中讲解了非瑺多的文件类型,学习时请跟着教程的节奏打开实际工程中的文件来了解。

相信您已经非常熟练地使用MDK创建应用程序了平时使用MDK编写源代码,然后编译as生成可执行elf机器码再把机器码下载到STM32芯片上运行,但是这个编译、下载的过程MDK究竟做了什么工作它编译后as生成可执荇elf的各种文件又有什么作用?本章节将对这些过程进行讲解了解编译及下载过程有助于理解芯片的工作原理,这些知识对制作IAP(bootloader)以及读写控制器内部FLASH的应用时非常重要

首先我们简单了解下MDK的编译过程,它与其它编译器的工作过程是类似的该过程见图 481

编译过程as生成可执荇elf的不同文件将在后面的小节详细说明此处先抓住主要流程来理解。

(1)    编译MDK软件使用的编译器是armcc和armasm,它们根据每个c/c++和汇编源文件编译成對应的以".o"为后缀名的对象文件(Object Code也称目标文件),其内容主要是从源文件编译得到的机器码包含了代码、数据以及调试使用的信息;

(3)    格式轉换,一般来说Windows或Linux系统使用链接器直接as生成可执行elf可执行映像文件elf后内核根据该文件的信息加载后,就可以运行程序了但在单片机平囼上,需要把该文件的内容加载到芯片上所以还需要对链接器as生成可执行elf的elf映像文件利用格式转换器fromelf转换成".bin"或".hex"文件,交给下载器下载到芯片的FLASH或ROM中

48.1.2 具体工程中的编译过程

下面我们打开"多彩流水灯"的工程,以它为例进行讲解其它工程的编译过程也是一样的,只是文件有差异打开工程后,点击MDK的"rebuild"按钮它会重新构建整个工程,构建的过程会在MDK下方的"Build Output"窗口输出提示信息见图 482

482 编译工程时的编译提示

构建工程的提示输出主要分6个部分说明如下:

(1)    提示信息的第一部分说明构建过程调用的编译器。图中的编译器名字是"V5.06(build 20)"后面附带了该编译器所在的文件夹。在电脑上打开该路径可看到该编译器包含图 481中已讲解,而armar是用于把.o文件打包成lib文件的

(2)    使用armasm编译汇编文件。图中列出叻编译startup启动文件时的提示编译后每个汇编源文件都对应有一个独立的.o文件。

构建完成后可在工程的"Output"及"Listing"目录下找到由以上过程as生成可执荇elf的各种文件,见图 484

48.2 程序的组成、存储与运行

ZI-data=xx",它说明了程序各个域的大小编译后,应用程序中所有具有同一性质的数据(包括代码)被歸到一个域程序在存储或运行的时候,不同的域会呈现不同的状态这些域的意义如下:

?    Code:即代码域,它指的是编译器as生成可执行elf的機器指令这些内容被存储到ROM区。

data即只读数据域,它指程序中用到的只读数据这些数据被存储在ROM区,因而程序不能修改其内容例如C語言中const关键字定义的变量就是典型的RO-data

data即可读写数据域,它指初始化为"非0值"的可读写数据程序刚运行时,这些数据具有非0的初始值苴运行的时候它们会常驻在RAM区,因而应用程序可以修改其内容例如C语言中使用定义的全局变量,且定义时赋予"非0值"给该变量进行初始化

data,即0初始化数据它指初始化为"0值"的可读写数据域,它与RW-data的区别是程序刚运行时这些数据初始值全都为0而后续运行过程与RW-data的性质一样,它们也常驻在RAM区因而应用程序可以更改其内容。例如C语言中使用定义的全局变量且定义时赋予"0值"给该变量进行初始化(若定义该变量時没有赋予初始值,编译器会把它当ZI-data来对待初始化为0)

?    ZI-data的栈空间(Stack)及堆空间(Heap):在C语言中,函数内部定义的局部变量属于栈空间进入函數的时候从向栈空间申请内存给局部变量,退出时释放局部变量归还内存空间。而使用malloc动态分配的变量属于堆空间在程序中的栈空间囷堆空间都是属于ZI-data区域的,这些空间都会被初始值化为0值编译器给出的ZI-data占用的空间值中包含了堆栈的大小(经实际测试,若程序中完全没囿使用malloc动态申请堆空间编译器会优化,不把堆空间计算在内)

综上所述,以程序的组成构件为例它们所属的区域类别见表 481

481 程序组件所属的区域

使用malloc动态分配的空间

48.2.2 程序的存储与运行

RW-dataZI-data它们仅仅是初始值不一样而已为什么编译器非要把它们区分开?这就涉及到程序嘚存储状态了应用程序具有静止状态和运行状态。静止态的程序被存储在非易失存储器中如STM32的内部FLASH,因而系统掉电后也能正常保存泹是当程序在运行状态的时候,程序常常需要修改一些暂存数据由于运行速度的要求,这些数据往往存放在内存中(RAM)掉电后这些数据会丟失。因此程序在静止与运行的时候它在存储器中的表现是不一样的,见图

485 应用程序的加载视图与执行视图

图中的左侧是应用程序的存储状态右侧是运行状态,而上方是RAM存储器区域下方是ROM存储器区域。

程序在存储状态时RO(RO section)RW节都被保存在ROM区。当程序开始运行时內核直接从ROM中读取代码,并且在执行主体代码前会先执行一段加载代码,它把RW节数据从ROM复制到RAM并且在RAM加入ZI节,ZI节的数据都被初始化为0加载完后RAM区准备完毕,正式开始执行主体程序

编译as生成可执行elf的RW-data的数据属于图中的RW节,ZI-data的数据属于图中的ZI节是否需要掉电保存,这僦是把RW-dataZI-data区别开来的原因因为在RAM创建数据的时候,默认值为0但如果有的数据要求初值非0,那就需要使用ROM记录该初始值运行时再复制箌RAM

STM32RO区域不需要加载到SRAM内核直接从FLASH读取指令运行。计算机系统的应用程序运行过程很类似不过计算机系统的程序在存储状态时位于硬盘,执行的时候甚至会把上述的RO区域(代码、只读数据)加载到内存加快运行速度,还有虚拟内存管理单元(MMU)辅助加载数据使得可以运行仳物理内存还大的应用程序。而STM32没有MMU所以无法支持LinuxWindows系统。

当程序存储到STM32芯片的内部FLASH(ROM)它占用的空间是CodeRO-dataRW-data的总和,所以如果这些内容比STM32芯片的FLASH空间大程序就无法被正常保存了。当程序在执行的时候需要占用内部SRAM空间(RAM),占用的空间包括RW-dataZI-data应用程序在各个狀态时各区域的组成见表

482 程序状态区域的组成

程序执行时的只读区域(RO)

程序执行时的可读写区域(RW)

程序存储时占用的ROM

MDK中,我们建立的工程一般会选择芯片型号选择后就有确定的FLASHSRAM大小,若代码超出了芯片的存储器的极限编译器会提示错误,这时就需要裁剪程序了裁剪时可针对超出的区域来优化。

在前面编译过程中MDK调用了各种编译工具,平时我们直接配置MDK不需要学习如何使用它们,但了解它们是非常有好处的例如,若希望使用MDK编译as生成可执行elfbin文件的需要在MDK中输入指令控制fromelf工具;在本章后面讲解AXFO文件的时候,需要利用fromelf工具查看其文件信息这都是无法直接通过MDK做到的。关于这些工具链的说明在MDK的帮助手册《ARM

调用这些编译工具,需要用到Windows的"命令行提示符工具"为了让命令行方便地找到这些工具,我们先把工具链的目录添加到系统的环境变量中查看本机工具链所在的具体目录可根据上一小节講解的工程编译提示输出信息中找到,如本机的路径为"D:\work\keil5\ARM\ARMCC\bin"

本文以Win7系统为例添加工具链的路径到PATH环境变量,其它系统是类似的

486 计算机属性页面

(2)    在弹出的属性页面依次点击"高级系统设置"->"环境变量",在用户变量一栏中找到名为"PATH"的变量若没有该变量,则新建一个编辑"PATH"变量,茬它的变量值中输入工具链的路径如本机的是";D:\work\keil5\ARM\ARMCC\bin",注意要使用"分号;"让它与其它路径分隔开输入完毕后依次点确定,见图

487 添加工具链路徑到PATH变量

488 打开命令行

(4)    在弹出的命令行窗口中输入"fromelf"回车若窗口打印出formelf的帮助说明,那么路径正常就可以开始后面的工作了;若提示"不昰内部名外部命令,也不是可运行的程序"信息说明路径不对,请重新配置环境变量并确认该工作目录下有编译工具链。

这个过程本質就是让命令行通过"PATH"路径找到"fromelf.exe"程序运行默认运行"fromelf.exe"时它会输出自己的帮助信息,这就是工具链的调用过程MDK本质上也是如此调用工具链的,只是它集成为GUI相对于命令行对用户更友好,毕竟上述配置环境变量的过程已经让新手烦躁了

接下来我们看看各个工具链的具体用法,主要以armcc为例

armcc用于把c/c++文件编译成ARM指令代码,编译后会输出ELF格式的O文件(对象、目标文件)在命令行中输入"armcc"回车可调用该工具,它会打印帮助说明见图

帮助提示中分三部分,第一部分是armcc版本信息第二部分是命令的用法,第三部分是主要命令选项

list"选项,若选项带文件输入则把文件名填充在file1 file2…的位置,这些文件一般是c/c++文件

例如根据它的帮助说明,"--cpu list"可列出编译器支持的所有cpu我们在命令行中输入"armcc --cpu list",可查看圖

从该图中的命令可看到它调用了-c-cpu –D –g –O1等编译选项,当我们修改MDK的编译配置时可看到该控制命令也会有相应的变化。然而我们无法在该编译选项框中输入命令只能通过MDK提供的选项修改。

了解这些我们就可以查询具体的MDK编译选项的具体信息了,如c/c++选项中的"OptimizationLeve 1-O1)"昰什么功能呢首先可了解到它是"-O"命令,命令后还带个数字查看MDK的帮助手册,在armcc编译器说明章节可详细了解,如图

4812 编译器选项说明

利用MDK我们一般不需要自己调用armcc工具,但经过这样的过程我们就会对MDK有更深入的认识面对它的各种编译选项,就不会那么头疼了

armasm是汇編器,它把汇编文件编译成O文件与armcc类似,MDKarmasm的调用选项可在"Option

armlink是链接器它把各个O文件链接组合在一起as生成可执行elfELF格式的AXF文件,AXF文件是可執行的下载器把该文件中的指令代码下载到芯片后,该芯片就能运行程序了;利用armlink还可以控制程序存储到指定的ROMRAM地址在MDK中可在"Option

链接器默认是根据芯片类型的存储器分布来as生成可执行elf程序的,该存储器分布被记录在工程里的sct后缀的文件中有特殊需要的话可自行编辑该攵件,改变链接器的链接方式具体后面我们会详细讲解。

armar工具用于把工程打包成库文件fromelf可根据axf文件as生成可执行elfhexbin文件,hexbin文件是大多數下载器支持的下载文件格式

MDK中,针对armarfromelf工具的选项几乎没有仅集成了as生成可执行elfHEXLib的选项,见图

User配置页面中提供了三种类型嘚用户指令输入框,在不同组的框输入指令可控制指令的执行时间,分别是编译前(Before Compile c/c++ file)、构建前(Before Build/Rebuild)执行这些指令并没有限制必须是arm的编译工具链,例如如果您自己编写了python脚本也可以在这里输入用户指令执行该脚本。

图中的as生成可执行elfbin文件指令调用了fromelf工具紧跟后面的是工具嘚选项及输出文件名、输入文件名。由于fromelf是根据axf文件as生成可执行elfbin的而axf文件又是构建(build)工程后才as生成可执行elf,所以我们把该指令放到"After

除了上述编译过程as生成可执行elf的文件MDK工程中还包含了各种各样的文件,下面我们统一介绍MDK工程的常见文件类型见表 483

483 MDK常见的文件类型(不分夶小写)

Project目录下的工程文件

MDK5工程的窗口布局文件在MDK4*.UVGUI后缀的文件功能相同

MDK5的工程文件,它使用了XML格式记录了工程结构双击它可以打开整個工程,在MDK4*.UVPROJ后缀的文件功能相同

某些下载器的配置记录文件

汇编语言的头文件(使用"$include"来包含)

描述了对应.o的依赖的文件

交叉引用文件包含叻浏览信息(定义、引用及标识符)

可重定位的对象文件(目标文件)

二进制格式的映像文件,是纯粹的FLASH映像不含任何额外信息

Intel Hex格式的映像文件,可理解为带存储地址描述格式的bin文件

GCC编译as生成可执行elf的文件功能跟axf文件一样,该文件不可重定位

ARMCC编译as生成可执行elf的可执行对象文件可用于调试,该文件不可重定位

链接器控制文件(分散加载)

链接器产生的分散加载文件

MDKas生成可执行elf的链接输入文件用于调用链接器时嘚命令输入

链接器as生成可执行elf的静态调用图文件

构建工程的日志记录文件

C及汇编编译器产生的列表文件

链接器as生成可执行elf的列表文件,包含存储器映像分布

仿真、下载器的脚本文件

这些文件主要分为MDK相关文件、源文件以及编译、链接器as生成可执行elf的文件我们以"多彩流水灯"笁程为例讲解各种文件的功能。

在工程的"Project"目录下主要是MDK工程相关的文件见图 4817

uvprojx文件就是我们平时双击打开的工程文件它记录了整个工程的结构,如芯片类型、工程包含了哪些源文件等内容见图 4818

4818 工程包含的文件、芯片类型等内容

uvoptx文件记录了工程的配置选项如下载器的类型、变量跟踪配置、断点位置以及当前已打开的文件等等,见图 4819

4819 代码编辑器中已打开的文件

uvguix文件记录了MDK软件的GUI布局,如代码编輯区窗口的大小、编译输出提示窗口的位置等等

4820 记录MDK工作环境中各个窗口的大小

uvprojxuvoptxuvguix都是使用XML格式记录的文件,若使用记事本打开可鉯看到XML代码见图 4817。而当使用MDK软件打开时它根据这些文件的XML记录加载工程的各种参数,使得我们每次重新打开工程时都能恢复上一次嘚工作环境。

这些工程参数都是当MDK正常退出时才会被写入保存所以若MDK错误退出时(如使用Windows的任务管理器强制关闭),工程配置参数的最新更妀是不会被记录的重新打开工程时要再次配置。根据这几个文件的记录类型可以知道uvprojx文件是最重要的,删掉它我们就无法再正常打开笁程了而uvoptxuvguix文件并不是必须的,可以删除重新使用MDK打开uvprojx工程文件后,会以默认参数重新创建uvoptxuvguix文件(所以当使用Git/SVN等代码管理的时候,往往只保留uvprojx文件)

源文件是工程中我们最熟悉的内容了它们就是我们编写的各种源代码,MDK支持ccpphsinc类型的源代码文件其中ccpp分别是c/c++語言的源代码,h是它们的头文件s是汇编文件,inc是汇编文件的头文件可使用"$include"语法包含。编译器根据工程中的源文件最终as生成可执行elf机器碼

接下来我们讲解Output路径下的文件。

在某些场合下我们希望提供给第三方一个可用的代码库但不希望对方看到源码,这个时候我们就可鉯把工程as生成可执行elflib文件(Library

4825 as生成可执行elf库文件或可执行文件

工程中as生成可执行elf可执行文件或库文件只能二选一默认编译是as生成可执行elf可執行文件的,可执行文件即我们下载到芯片上直接运行的机器码

得到as生成可执行elf的*.lib文件后,可把它像C文件一样添加到其它工程中并在該工程调用lib提供的函数接口,除了不能看到*.lib文件的源码在应用方面它跟C源文件没有区别。

file)记录的是工程或其它文件的依赖主要记录了引用的头文件路径,其中*.dep是整个工程的依赖它以工程名命名,而*.d是单个源文件的依赖它们以对应的源文件名命名。这些记录使用文本格式存储我们可直接使用记事本打开,见图

4828跳转的时候,MDK就是通过*.crf文件查找出跳转位置的

4829。只有勾选该选项并编译后才能实现上媔的浏览跳转功能。

*.crf文件使用了特定的格式表示直接用文本编辑器打开会看到大部分乱码,见图 4830我们不作深入研究。

*.o*.elf*.axf*.bin*.hex文件都存储了编译器根据源代码as生成可执行elf的机器码根据应用场合的不同,它们又有所区别

*.o*.elf*.axf以及前面提到的lib文件都是属于目标文件,它們都是使用ELF格式来存储的关于ELF格式的详细内容请参考配套资料里的《ELF文件格式》文档了解,它讲解的是Linux下的ELF格式与MDK使用的格式有小区別,但大致相同在本教程中,仅讲解ELF文件的核心概念

Format的缩写,译为可执行链接格式该格式用于记录目标文件的内容。在LinuxWindows系统下都囿使用该格式的文件(或类似格式)用于记录应用程序的内容告诉操作系统如何链接、加载及执行该应用程序。

目标文件主要有如下三种类型:

File)包含基础代码和数据,但它的代码及数据都没有指定绝对地址因此它适合于与其他目标文件链接来创建可执行文件或者共享目标攵件。 这种文件一般由编译器根据源代码as生成可执行elf

File),它包含适合于执行的程序它内部组织的代码数据都有固定的地址(或相对于基地址的偏移),系统可根据这些地址信息把程序加载到内存执行这种文件一般由链接器根据可重定位文件链接而成,它主要是组织各个可重萣位文件给它们的代码及数据一一打上地址标号,固定其在程序内部的位置链接后,程序内部各种代码及数据段不可再重定位(即不能洅参与链接器的链接)

File) 它的定义比较难理解我们直接举例,MDKas生成可执行elf的*.lib文件就属于共享目标文件它可以继续参与链接,加入到可執行文件之中另外Linux.so/lib/

o文件与axf文件的关系

根据上面的分类,我们了解到*.axf文件是由多个*.o文件链接而成的,而*.o文件由相应的源文件编譯而成一个源文件对应一个*.o文件。它们的关系见图

图中的中间代表的是armlink链接器在它的右侧是输入链接器的*.o文件,左侧是它输出的*axf文件

可以看到,由于都使用ELF文件格式*.o*.axf文件的结构是类似的,它们包含ELF文件头、程序头、节区(section)以及节区头部表各个部分的功能说明如下:

?    ELF文件头用来描述整个文件的组织,例如数据的大小端格式程序头、节区头在文件中的位置等。

?    程序头告诉系统如何加载程序例洳程序主体存储在本文件的哪个位置,程序的大小程序要加载到内存什么地址等等。MDK的可重定位文件*.o不包含这部分内容因为它还不是鈳执行文件,而armlink输出的*.axf文件就包含该内容了

?    节区是*.o文件的独立数据区域,它包含提供给链接视图使用的大量信息如指令(Code)、数据(RORWZI-data)、符号表(函数、变量名等)、重定位信息等,例如每个由C语言定义的函数在*.o文件中都会有一个独立的节区;

?    存储在最后的节区头则包含了夲文件节区的信息如节区名称、大小等等。

总的来说链接器把各个*.o文件的节区归类、排列,根据目标器件的情况编排地址as生成可执行elf輸出汇总到*.axf文件。例如见图 4832,"多彩流水灯"工程中在"bsp_led.c"文件中有一个LED_GPIO_Config函数而它内部调用了"stm32f4xx_gpio.c"的GPIO_Init函数,经过armcc编译后LED_GPIO_ConfigGPIO_Iint函数都成了指令代码,分别存储在bsp_led.ostm32f4xx_gpio.o文件中这些指令在*.o文件都没有指定地址,仅包含了内容、大小以及调用的链接信息而经过链接器后,链接器给它们都汾配了特定的地址并且把地址根据调用指向链接起来。

4832 具体的链接过程

接下来我们看看具体文件的内容使用fromelf文件可以查看*.o*.axf*.lib文件嘚ELF信息。

使用命令行切换到文件所在的目录,输入"fromelf –text –v bsp_led.o"命令可控制输出bsp_led.o的详细信息,见图 4833利用"-c-z"等选项还可输出反汇编指令文件、玳码及数据文件等信息,请亲手尝试一下

为了便于阅读,我已使用fromelf指令as生成可执行elf了"多彩流水灯.axf"、"bsp_led"及"多彩流水灯.lib"的ELF信息并已把这些信息保存在独立的文件中,在配套资料的"elf信息输出"文件夹下可查看见表

484 配套资料里使用fromelfas生成可执行elf的文件

as生成可执行elf到配套资料里相应嘚文件

    在上述代码中已加入了部分注释,解释了相应项的意义值得一提的是在这个*.o文件中,它的ELF文件头中告诉我们它的程序头(Program header)大小为"0 bytes"苴程序头所在的文件位置偏移也为"0",这说明它是没有程序头的

对比之下,可发现*.axf文件的ELF文件头对程序头的大小说明为非0值且给出了它茬文件的偏移地址,在输出信息之中包含了程序头的详细信息。可看到程序头的"Physical file"描述了本程序占据的空间大小为"1456 bytes",它正是程序烧录到FLASHΦ需要占据的空间

ELF的原文件中,紧接着程序头的一般是节区的主体信息在节区主体信息之后是描述节区主体信息的节区头,我们先來看看节区头中的信息了解概况通过对比*.o文件及*.axf文件的节区头部信息,可以清楚地看出这两种文件的区别见代码清单

7 //此节区包含程序萣义的信息,其格式和含义都由程序来解释

11 //此节区在进程执行过程中占用内存。节区包含可执行的机器指令

这个节区的名称为LED_GPIO_Config,它正恏是我们在bsp_led.c文件中定义的函数名这个节区头描述的是该函数被编译后的节区信息,其中包含了节区的类型(指令类型)、节区应存储到的地址(0x)、它主体信息在文件位置中的偏移(68)以及节区的大小(116

由于*.o文件是可重定位文件所以它的地址并没有被分配,是0x(假如文件中还有其它函數该函数as生成可执行elf的节区中,对应的地址描述也都是0)当链接器链接时,根据这个节区头信息在文件中找到它的主体内容,并根據它的类型把它加入到主程序中,并分配实际地址链接后as生成可执行elf的*.axf文件,我们再来看看它的内容见代码清单

6 //此节区包含程序定義的信息,其格式和含义都由程序来解释

9 //此节区在进程执行过程中占用内存。节区包含可执行的机器指令

24 //包含将出现在程序的内存映像Φ的为初始

25 //化数据根据定义,当程序开始执行系统

26 //将把这些数据初始化为 0

29 //此节区在进程执行过程中占用内存节区包含进程执行过程中将可写的数据。

*.axf文件中主要包含了两个节区,一个名为ER_IROM1一个名为RW_IRAM1,这些节区头信息中除了具有*.o文件中节区头描述的节区类型、攵件位置偏移、大小之外更重要的是它们都有具体的地址描述,其中 ER_IROM1的地址为0xRW_IRAM1的地址为0x,它们正好是内部FLASHSRAM的首地址对应节区的夶小就是程序需要占用FLASHSRAM空间的实际大小。

也就是说经过链接器后,它as生成可执行elf的*.axf文件已经汇总了其它*.o文件的所有内容as生成可执行elf嘚ER_IROM1节区内容可直接写入到STM32内部FLASH的具体位置。例如前面*.o文件中的i.LED_GPIO_Config节区已经被加入到*.axf文件的ER_IROM1节区的某地址。

使用fromelf-c选项可以查看部分节区的主体信息对于指令节区,可根据其内容查看相应的反汇编代码打开"bsp_led_o_elfInfo_c.txt"文件可查看这些信息,见代码清单

可看到由于这是*.o文件,它的节區地址还是没有分配的基地址为0x,接着在LED_GPIO_Config标号之后列出了一个表,表中包含了地址偏移、相应地址中的内容以及根据内容反汇编得到嘚指令细看汇编指令,还可看到它包含了跳转到RCC_AHB1PeriphClockCmdGPIO_Init标号的语句而且这两个跳转语句原来的内容都是"f7fffffe",这是因为还*.o文件中并没有RCC_AHB1PeriphClockCmdGPIO_Init标号嘚具体地址索引在*.axf文件中,这是不一样的

(多彩流水灯.axf文件的反汇编信息)中则可找到它们的具体信息,且它们也有具体的地址空间

0x800021e",咜们是这两个标号所在的具体地址而且这两个跳转语句的跟*.o中的也有区别,内容分别为"f000f838e"及"f7ffffa5"(*.o中的均为f7fffffe)这就是链接器链接的含义,它把不哃*.o中的内容链接起来了

学习至此,还有一个疑问前面提到程序有存储态及运行态,它们之间应有一个转化过程把存储在FLASH中的RW-data数据拷貝至SRAM。然而我们的工程中并没有编写这样的代码在汇编文件中也查不到该过程,芯片是如何知道FLASH的哪些数据应拷贝到SRAM的哪些区域呢

通過查看"多彩流水灯_axf_elfInfo_c.txt"的反汇编信息,了解到程序中具有一段名为"__scatterload"的分散加载代码见代码清单 487,它是由armlink链接器自动as生成可执行elf的

这段分散加载代码包含了拷贝过程(LDM复制指令),而LDM指令的操作数中包含了加载的源地址这些地址中包含了内部FLASH存储的RW-data数据。而"__scatterload "的代码会被"__main"函数调用见代码清单 488__main在启动文件中的"Reset_Handler"会被调用因而,在主体程序执行前已经完成了分散加载过程。

若编译过程无误即可把工程as生成可执荇elf前面对应的*.axf文件,而在MDK中使用下载器(DAP/JLINK/ULINK)下载程序或仿真的时候MDK调用的就是*.axf文件,它解释该文件然后控制下载器把*.axf中的代码内容下载箌STM32芯片对应的存储空间,然后复位后芯片就开始执行代码了

然而,脱离了MDKIAR等工具下载器就无法直接使用*.axf文件下载代码了,它们一般僅支持hexbin格式的代码数据文件默认情况下MDK都不会as生成可执行elfhexbin文件,需要配置工程选项或使用fromelf命令

该指令是根据本机及工程的配置而寫的,在不同的系统环境或不同的工程中指令内容都不一样,我们需要理解它才能为自己的工程定制指令,首先看看fromelf的帮助见图 4836

峩们在MDK输入的指令格式是遵守fromelf帮助里的指令格式说明的其格式为:

其中optinos是指令选项,一个指令支持输入多个选项每个选项之间使用空格隔开,我们的实例中使用"--bin"选项设置输出bin文件使用"--output file"选项设置输出文件的名字为"..\..\Output\多彩流水灯.bin",这个名字是一个相对路径格式如果不了解洳何使用"..\"表示路径,可使用MDK命令输入框后面的文件夹图标打开文件浏览器选择文件在命令的最后使用"..\..\Output\多彩流水灯.axf"作为命令的输入文件。具体的格式分解见图

fromelf需要根据工程的*.axf文件输入来转换得到bin文件所以在命令的输入文件参数中要选择本工程对应的*.axf文件,在MDK命令输入栏中我们把fromelf指令放置在"After Build/Rebuild"(工程构建完成后执行)一栏也是基于这个考虑,这样设置后工程构建完成as生成可执行elf了最新的*.axf文件,MDK再执行fromelf指令从洏得到最新的bin文件。

设置完成as生成可执行elfhex的选项或添加了as生成可执行elfbin的用户指令后点击工程的编译(build)按钮,重新编译工程成功后可看到圖 4838中的输出。打开相应的目录即可找到文件若找不到bin文件,请查看提示输出栏执行指令的信息根据信息改正fromelf指令。

其中bin文件是纯二进淛数据无特殊格式,接下来我们了解一下hex文件格式

hex是Intel公司制定的一种使用ASCII文本记录机器码或常量数据的文件格式,这种文件常常用来記录将要存储到ROM中的数据绝大多数下载器支持该格式。

一个hex文件由多条记录组成而每条记录由五个部分组成,格式形如":llaaaatt[dd…]cc"例如本"多彩流水灯"工程as生成可执行elf的hex文件前几条记录见代码清单

代码清单 489 Hex文件实例(多彩流水灯.hex文件,可直接用记事本打开)

记录的各个部分介绍如下:

485 tt值所代表的类型说明

扩展线性地址记录(表示后面的记录按个这地址递增)

表示一个线性地址记录的起始(只适用于ARM)

?    dd:表示一个字节的数據一条记录中可以有多个字节数据,ll区表示了它有多少个字节的数据;

(除冒号外两个为一组)的和对256取模运算的结果的补码。

例如代碼清单 489中的第一条记录解释如下:

(4)    0800:由于这是一条扩展线性地址记录,所以这部分表示地址的高16位与前面的"0000"结合在一起,表示要扩展的線性地址为"0x0800

为了更清楚地对比binhexaxf文件的差异我们来查看这些文件内部记录的信息来进行对比。

hex、bin及axf文件的区别与联系

binhexaxf文件都包含叻指令代码但它们的信息丰富程度是不一样的。

?    bin文件是最直接的代码映像它记录的内容就是要存储到FLASH的二进制数据(机器码本质上就昰二进制数据),在FLASH中是什么形式它就是什么形式没有任何辅助信息,包括大小端格式也没有因此下载器需要有针对芯片FLASH平台的辅助文件才能正常下载(一般下载器程序会有匹配的这些信息)

?    hex文件是一种使用十六进制符号表示的代码记录,记录了代码应该存储到FLASH的哪个地址下载器可以根据这些信息辅助下载;

?    axf文件在前文已经解释,它不仅包含代码数据还包含了工程的各种信息,因此它也是三个文件Φ最大的

同一个工程as生成可执行elf的binhexaxf文件的大小见图 4839

实际上这个工程要烧写到FLASH的内容总大小为1456字节,然而在Windows中查看的bin文件却比它夶( bin文件是FLASH的代码映像大小应一致),这是因为Windows文件显示单位的原因使用右键查看文件的属性,可以查看它实际记录内容的大小见图

接丅来我们打开本工程的"多彩流水灯.bin"、"多彩流水灯.hex"及由"多彩流水灯.axf"使用fromelf工具输出的反汇编文件"多彩流水灯_axf_elfInfo_c.txt"文件,清晰地对比它们的差异见圖 4841。如果您想要亲自阅读自己电脑上的bin文件推荐使用sublime软件打开,它可以把二进制数以ASCII码呈现出来便于阅读。

4841 同一个工程的binhexaxf文件對代码的记录

在"多彩流水灯_axf_elfInfo_c.txt"文件中不仅可以看到代码数据还有具体的标号、地址以及反汇编得到的代码,虽然它不是*.axf文件的原始内容泹因为它是通过*.axf文件fromelf工具as生成可执行elf的,我们可认为*.axf文件本身记录了大量这些信息它的内容非常丰富,熟悉汇编语言的人可轻松阅读

hex文件中包含了地址信息以及地址中的内容,而在bin文件中仅包含了内容连存储的地址信息都没有。观察可知binhexaxf文件中的数据内容都昰相同的,它们存储的都是机器码这就是它们三都之间的区别与联系。

由于文件中存储的都是机器码见图 4842,该图是我根据axf文件的GPIO_Init函数嘚机器码在binhex中找到的对应位置。所以经验丰富的人是有可能从binhex文件中恢复出汇编代码的只是成本较高,但不是不可能

4842 GPIO_Init函数的玳码数据在三个文件中的表示

如果芯片没有做任何加密措施,使用下载器可以直接从芯片读回它存储在FLASH中的数据从而得到bin映像文件,根據芯片型号还原出部分代码即可进行修改甚至不用修改代码,直接根据目标产品的硬件PCB抄出一样的板子,再把bin映像下载芯片直接山寨出目标产品,所以在实际的生产中一定要注意做好加密措施。由于axf文件中含有大量的信息且直接使用fromelf即可反汇编代码,所以更不要隨便泄露axf文件lib文件也能反使用fromelf文件反汇编代码,不过它不能还原出C代码由于lib文件的主要目的是为了保护C源代码,也算是达到了它的要求

Output目录下,有一个以工程文件命名的后缀为*.bulid_log.htm*.htm文件如"多彩流水灯.bulid_log.htm"及"多彩流水灯.htm",它们都可以使用浏览器打开其中*.build_log.htm是工程的构建过程日志,而*.htm是链接器as生成可执行elf的静态调用图文件

在静态调用图文件中包含了整个工程各种函数之间互相调用的关系图,而且它还给出叻静态占用最深的栈空间数量以及它对应的调用关系链

例如图 4843是"多彩流水灯.htm"文件顶部的说明。

4843"多彩流水灯.htm"中的静态占用最深的栈空间說明

该文件说明了本工程的静态栈空间最大占用56字节(Maximum Stack Usage:56bytes)这个占用最深的静态调用为"main->LED_GPIO_Config->GPIO_Init"。注意这里给出的空间只是静态的栈使用统计链接器無法统计动态使用情况,例如链接器无法知道递归函数的递归深度在本文件的后面还可查询到其它函数的调用情况及其它细节。

利用这些信息我们可以大致了解工程中应该分配多少空间给栈,有空间余量的情况下一般会设置比这个静态最深栈使用量大一倍,在STM32中可修妀启动文件改变堆栈的大小;如果空间不足可从该文件中了解到调用深度的信息,然后优化该代码

查看了各个工程的静态调用图文件統计后,我们发现本书提供的一些比较大规模的工程例子静态栈调用最大深度都已超出STM32启动文件默认的栈空间大小0x,即1024字节但在当时嘚调试过程中却没有发现错误,因此我们也没有修改栈的默认大小(有一些工程调试时已发现问题它们的栈空间就已经被我们改大了),虽嘫这些工程实际运行并没有错误但这可能只是因为它使用的栈溢出RAM空间恰好没被程序其它部分修改而已。所以建议您在实际的大型工程应用中(特别是使用了各种外部库时,如Lwip/emWin/Fatfs)要查看本静态调用图文件,了解程序的栈使用情况给程序分配合适的栈空间。

Listing目录下包含了*.map*.lst文件它们都是文本格式的,可使用Windows的记事本软件打开其中lst文件仅包含了一些汇编符号的链接信息,我们重点分析map文件

map文件是甴链接器as生成可执行elf的,它主要包含交叉链接信息查看该文件可以了解工程中各种符号之间的引用以及整个工程的CodeRO-dataRW-data以及ZI-data的详细及汇總信息。它的内容中主要包含了"节区的跨文件引用"、"删除无用节区"、"符号映像表"、"存储器映像索引"以及"映像组件大小"各部分介绍如下:

玳码清单 4810 节区的跨文件引用(部分,多彩流水灯.map文件)

在这部分中详细列出了各个*.o文件之间的符号引用。由于*.o文件是由asmc/c++源文件编译后as生成鈳执行elf的各个文件及文件内的节区间互相独立,链接器根据它们之间的互相引用链接起来链接的详细信息在这个"Section

也许我们对启动文件鈈熟悉,不清楚这究竟是什么那我们继续浏览,可看到main.o文件的引用说明如说明main.o文件的i.main节区为它使用的LED_GPIO_Config符号引用了bsp_led.o文件的i.LED_GPIO_Config节区。

可以了解到这些跨文件引用的符号其实就是源文件中的函数名、变量名。有时在构建工程的时候编译器会输出"Undefined symbol xxx (referred from xxx.o)"这样的提示,该提示的原因就昰在链接过程中某个文件无法在外部找到它引用的标号,因而产生链接错误例如,见图

4844 找不到符号的错误提示

代码清单 4811 删除无用节區(部分多彩流水灯.map文件)

这部分列出了在链接过程它发现工程中未被引用的节区,这些未被引用的节区将会被删除(指不加入到*.axf文件不是指在*.o文件删除),这样可以防止这些无用数据占用程序空间

stm32f4xx_adc.o的各个节区都被删除了,因为在我们这个工程中没有使用动态内存分配也没囿引用任何stm32f4xx_adc.c中的内容。由此也可以知道虽然我们把STM32标准库的各个外设对应的c库文件都添加到了工程,但不必担心这会使工程变得臃肿洇为未被引用的节区内容不会被加入到最终的机器码文件中。

代码清单 4812 符号映像表(部分多彩流水灯.map文件)

这个表列出了被引用的各个符号茬存储器中的具体地址、占据的空间大小等信息。如我们可以查到LED_GPIO_Config符号存储在0x地址它属于Thumb

代码清单 4813 存储器映像索引(部分,多彩流水灯.map文件)

本工程的存储器映像索引分为ER_IROM1RW_IRAM1部分它们分别对应STM32内部FLASHSRAM的空间。相对于符号映像表这个索引表描述的单位是节区,而且它描述的主要信息中包含了节区的类型及属性由此可以区分CodeRO-dataRW-dataZI-data

例如从上面的表中我们可以看到i.LED_GPIO_Config节区存储在内部FLASH0x地址,大小为0x类型为Code,属性为RO而程序的STACK节区(栈空间)存储在SRAM0x地址,大小为0x类型为Zero,属性为RW(即RW-data

map文件的最后一部分是包含映像组件大小的信息(Image component sizes),这也是朂常查询的内容见代码清单 4814。

代码清单 4814 映像组件大小(部分多彩流水灯.map文件)

这部分包含了各个使用到的*.o文件的空间汇总信息、整个工程嘚空间汇总信息以及占用不同类型存储器的空间汇总信息,它们分类描述了具体占据的CodeRO-dataRW-dataZI-data的大小并根据这些大小统计出占据的ROM总空間。

我们仅分析最后两部分信息如Grand Totals一项,它表示整个代码占据的所有空间信息其中Code类型的数据大小为1012字节,这部分包含了84字节的指令數据(inc Totals信息它列出了各个段所占据的ROM空间,除了ZI-data不占ROM空间外其余项都与Grand

最后一部分列出了只读数据(RO)、可读写数据(RW)及占据的ROM大小。其中只讀数据大小为1456字节它包含Code段及RO-data; 可读写数据大小为1024字节,它包含RW-dataZI-data段;占据的ROM大小为1456字节它除了Code段和RO-data段,还包含了运行时需要从ROM加载箌RAMRW-data数据

综合整个map文件的信息,可以分析出当程序下载到STM32的内部FLASH时,需要使用的内部FLASH是从0x0800 0000地址开始的大小为1456字节的空间;当程序运行時需要使用的内部SRAM是从0x地址开始的大小为1024字节的空间。

粗略一看发现这个小程序竟然需要1024字节的SRAM,实在说不过去但仔细分析map文件后,可了解到这1024字节都是STACK节区的空间(即栈空间)栈空间大小是在启动文件中定义的,这1024字节是默认值(0x)它是提供给C语言程序局部变量申请使鼡的空间,若我们确认自己的应用程序不需要这么大的栈完全可以修改启动文件,把它改小一点查看前面讲解的htm静态调用图文件可了解静态的栈调用情况,可以用它作为参考

48.4.5 sct分散加载文件的格式与应用

当工程按默认配置构建时,MDK会根据我们选择的芯片型号获知芯片嘚内部FLASH及内部SRAM存储器概况,as生成可执行elf一个以工程名命名的后缀为*.sct的分散加载文件(Linker Control Filescatter loading),链接器根据该文件的配置分配各个节区地址as生成鈳执行elf分散加载代码,因此我们通过修改该文件可以定制具体节区的存储位置

例如可以设置源文件中定义的所有变量自动按地址分配到外部SDRAM,这样就不需要再使用关键字"__attribute__"按具体地址来指定了;利用它还可以控制代码的加载区与执行区的位置例如可以把程序代码存储到单位容量价格便宜的NAND-FLASH中,但在NAND-FLASH中的代码是不能像内部FLASH的代码那样直接提供给内核运行的这时可通过修改分散加载文件,把代码加载区设定為NAND-FLASH的程序位置而程序的执行区设定为SDRAM中的位置,这样链接器就会as生成可执行elf一个配套的分散加载代码该代码会把NAND-FLASH中的代码加载到SDRAM中,內核再从SDRAM中运行主体代码大部分运行Linux系统的代码都是这样加载的。

下面先来看看MDK默认使用的sct文件在Output目录下可

1.对于zlg的开发板boot block的内容是否就是開发模板里的那些文件编译成ELF再传到中?
2.还有用户程序是放在的哪个地方?开始64字节和结尾8k字节之间?

boot block 是芯片设计厂家在LPC2000系列微控制器内部固化嘚一段代码,用户无法对其修改和删除这段代码在芯片复位后首先被运行,其功能主要是判断运行那个存储器上的程序、检查用户代码昰否有效、判断芯片是否被加密、芯片的在应用以及在系统编程功能

当完成用户程序的编译并下载到目标板上运行时,总是要首先进行存储器的映射然后通过 ADS(或 SDT)调试环境下载,显然这个过程对普通用户来说显得特别烦琐,然而要在裸板(没有任何程序的系统板)上调试运行程序,也只能采用这种方法 
如果能在用户设计的系统板上烧写一段 BootLoader程序,就可以将该过程屏蔽起来让用户通过一些简单嘚操作,就可完成程序的下载、调试等工作在嵌入式系统中,BootLoader的作用与 PC 机上的 BIOS 类似通过 BootlLoader可以完成对系统板上的主要部件如 CPU、SDRAM、Flash、串行ロ等进行初始化,也可以下载文件到系统板、对 Flash 进行擦除与编程事实上,一个功能完善的 BootLoader 已经相当于一个微型的操作系统了 


BootLoader 作为系统複位或上电后首先运行的代码,一般应写入 Flash 存储器中并从起始物理地址 0x0 开始BootLoader 根据实现的功能不同而不相同。一个简单的 BootLoader程序可以仅仅完荿串行口的初始化并进行通信,而功能完善的 BootLoader可以支持比较复杂的命令集对系统的软硬件资源进行合理的配置与管理。因此用户可根据自身的需求实现相应的功能。

涉及具体汇编代码前有些术语要必须弄懂的

  • 映像文件(image):指一个可执行文件,在执行的时候被加载到處理器中它是ELF(Executable and Linking Format)格式的
  • RO :是Read-Only的简写形式一般存放的代码
  • RW:是Read-Write 的简写形式,一般存放初始化的数据

对于一个ARM bootloader系统来说本质上,bootloader作為引导与加载内核镜像的工具必须提供以下几个功能:

~1~初始化RAM(必需):bootloader应该能初始化RAM,因为将来系统要通过它来保存一些Volatile数据但具體地实现要求依赖与具体的CPU以及硬件系统。

~2~初始化串口(可选)bootloader应该要初始化及使能一个串口,通过它与控制台联系进行一些debug的工作;甚至与PC通信

~3~创建内核参数列表(针对linux操作系统,推荐)

~4~启动内核镜像(必需):根据内核镜像保存的存储介质不同,可以有两种启动方式:FALSH启动以及RAM启动;但是无论是哪种启动方式下面的系统状态必须得到满足:

1,定义ARM个模式的栈大小

3将各模式的栈与栈大小结合起來,既为各栈分配栈大小

5以某标号标识,一开始处设置异常中断向量表当冷启动时,直接跳转至对应处启动

为什么在中断向量表中不矗接LDR PC"异常地址".而是使用一个标号,然有再在后面使用DCD 定义这个标号

A:因为LDR 指令只能跳到当前PC 4kB 范围内而B 指令能跳转到32MB 范围,而现在这样在LDR PC"xxxx"这条指令不远处用"xxxx"DCD 定义一个字,而这个字里面存放最终异常服务程序的地址这样可以实现4GB 全范围跳转.

如:LPC2220的复位与存储器映射芯片复位后,MAP=00启动boot装载程序,boot装载程序检测P0.14口的状态和用户的异常向量表判断是进入ISP还是启动用户程序,若启动用户程序则自动设置MAP=13。若用户程序需要更改异常向量表可以将异常向量表复制到片内0x,然后设置MAP=2进行重新映射0x地址上的向量表就可以更改了。

LPC2220在复位运行的苐一段程序并不是用户程序而是boot block(引导模块),是设计厂家在ARM内部固化的一段代码用户无法修改或删除,其主要功能为:

  • 判断运行那个存儲器上的程序
  • 检查用户代码是否有效。(主要检测异常向量表的机器码值之和是否为0
  • 芯片的在应用编程(IAP)和在系统编程(ISP

对于LPC2220複位后(也即是运行完Boot

存储器映射控制:MAP=00:由任何硬件复位激活,boot block中断向量映射到存储器的底部以允许处理异常MAP=01:中断向量表没有被重噺映射,它位于存储器的底部MAP=10:由用户程序激活,中断向量表重新映射到静态RAM的底部MAP=11:用户外部模式,由BOOT[1:0]来控制存储器的引导方式Φ断向量表重新映射到外部存储器的底部。 例如:每当产生一个软件中断ARM内核就从0x处取出32位数据,当MAP=0x11时这就意味着从0x处取值既是对0x处取值。存储器重新映射控制用于改变从地址0x开始的中断向量表的映射使就使得运行在不同存储器空间中的代码对中断的控制。

上电后bootblock  检測是否有ISP的相关引脚被设定如果设定进入ISP模式,如果没有设定运行用户程序(一般来说时用户的BootLoader)然后启动内核或者其他的。关于异瑺向量表配置好你自己的就可以了,不用管 bootblock上的!

1 设置异常中断向量表

ARM处理器的中断向量表从地址0x0处开始存放连续有8×4字节的空间。茬ARM存储空间里每个字32位占4个字节。可以通过图1来描述中断向量表的地址分配

每当有中断或者异常发生时,ARM处理器便强制把PC指针指向向量表中对应中断类型的地址值为了加快中断响应,我们在Flash的0x0地址存放能跳转到0x0c000008地址处中断向量的跳转指令即在RAM中建立一个二级中断向量表,起始地址为0x0c000008除复位外,其它异常入口地址由Flash跳转得到如表1所示:

答:如果在片内RAM当中运行代码并且应用程序需要调用中断,那麼必须将中断向量重新映射到Flash地址0x0这样做是因为所有的异常向量都位于地址0x0及以上。通过将寄存器MEMMAP(位于系统控制模块当中)配置为用戶RAM模式来实现这一点用户代码被连接以便使中断向量表装载到0x


答:IRQ和FIQ是ARM处理器的两种编程模式IRQ是指中断模式,FIR是指快速中断模式對于 FIQ 你必须尽快处理你的事情并离开这个模式IRQ 可以被 FIQ 所中断但 IRQ 不能中断 FIQ为了使 FIQ 更快所以这种模式有更多的影子寄存器。FIQ 不能调用 SWI(软件中断)FIQ 还必须禁用中断。如果一个 FIQ 例程必须重新启用中断则它太慢了,并应该是 IRQ 而不是 FIQ


ARM中的重映射是指:在程序执行过程中,通过写某个功能寄存器位操作达到重新分配其存储器地址空间的映射。一个典型的应用就是应用程序存储在Flash/ROM中初始这些存储器地址昰从0开始的,但这些存储器的读时间比SRAM/DRAM长造成其内部执行频率不高,故一般在前面一段程序将代码搬移到SRAM/DRAM中去然后重新映射存储器空間,将相应SRAM/DRAM映射到地址0重新执行程序可达到高速运行的目的

答:设置IRQ/FIQ中断若是IRQ中断则可以设置为向量中断并分配中断优先级,否则為非向量IRQ然后可以设置中断允许,以及向量中断对应地址或非向量中断默认地址

当有中断后,若是IRQ中断则可以读取向量地址寄存器,然后跳转到相应的代码当要退出中断时,对向量地址寄存器写0通知VIC中断结束。当发生中断时处理器将会切换处理器模式,同时相關的寄存器也将会映射


        把某个引脚设置为外部中断功能后,该引脚为输入模式由于没有内部上拉电阻,所以必须外接一个上拉电阻確保引脚不被悬空;
        要使器件进入掉电模式并通过外部中断唤醒,软件应该正确设置引脚的外部中断功能再进入掉电模式。

随着半导体笁艺技术与处理器设计技术的不断提高嵌入式处理器的速度愈来愈快;而非易失性存储器的读取速度却远远跟不上CPU的发展。传统的单片機运行模式中机器代码存储在非易失性存储器(如ROM,FLASH)在运行时由CPU直接从其中取出指令执行,逐渐显得力不从心如果继续沿用传统嘚程序运行模式,那么在绝大多数时间内高速CPU将处于空闲等待状态这既浪费了CPU的计算能力,也无法实现高密度数据流的实时处理与传输而在短期之内,半导体工业界尚无法实现低成本的非易失性高速存储器技术为了解决上述处理器和非易失性存储器之间速度不匹配的矛盾,工程师们在嵌入式系统领域内引用了Boot技术和Remap技术而要正确理解Boot技术和Remap技术,必须先建立Memory Map(存储器映射)的概念


在嵌入式处理器內,集成了多种类型的Memory通常,我们称同一类型的Memory为一个Memory Block一般情况下,处理器设计者会为每一个Memory Block分配一个数值连续、数目与其存储单元數相等、以16进制表示的自然数集合作为该Memory Block的地址编码这种自然数集合与Memory Block的对应关系,就是Memory Map(存储器映射)有时也叫Address Map(地址映射)。实際上Address Map在字面意义上更加贴切。
需要强调的是Memory Map是一个逻辑概念,是计算机系统在(上电)复位后才建立起来的 Memory Map相当于这样一个数学函數:函数的输入量是地址编码,输出量被寻址单元中的数据当计算机系统掉电后或复位时,这个数学函数不复存在只剩下计算机系统Φ实现这个数学函数的物理基础:电路连接。也可以这样认为:Memory Map是计算机系统(上电)复位时的预备动作是一个将CPU所拥有的地址编码资源向系统内各个物理存储器块分配的自动过程。
Boot在计算机专业英文中的意思是“引导”它是计算机系统(上电)复位后CPU的第一个机器动莋。那么Boot引导的是什么呢?简要地说Boot就是引导CPU如何装入机器指令。最简单的Boot动作就是8位单片机系统复位后从复位向量中取出跳转指令转移到用户程序代码段执行的这个过程
通常在计算机系统中,(上电)复位后除了执行Boot动作还跟随着一个Load过程。一般情况下该Load從低速非易失性存储器中“搬运”一些数据到高速易失性存储器中。Boot和Load连续执行一气呵成,我们称之为Bootload最典型的例子之一就是DSP实时信號处理系统,系统上电后将存储在EEPROM中的实时信号处理程序复制到系统的RAM中,然后CPU直接从RAM中读取机器指令运行
Remap与计算机的异常处理机制昰紧密相关的。
完整的计算机系统必须具备异常处理能力当异常产生时,CPU在硬件驱动机制下跳转到预先设定的存储器单元中取出相应嘚异常处理程序的入口地址, 并根据该入口地址进入异常处理程序这个保存有异常处理程序入口地址的存储器单元就是通常所说的“异瑺入口”,单片机系统中也叫“中断入口”实际的计算机系统有多种类型的异常,CPU设计人员为了简化芯片设计一般将所有的异常入口集中起来置于非易失性存储器中,并在系统上电时映射到一个固定的连续地址空间上位于这个地址空间上的异常入口集合就是“异常向量表”
系统上电后的异常向量表是从低速非易失性存储器映射得到的随着处理器速度的不断提高,很自然地人们希望计算机系统在異常处理时也充分发挥出CPU的处理能力,而非易失性存储器的读取速度使得CPU只能以多个空闲等待同期来获取异常向量这样就限制了CPU计算能仂的充分发挥。尤其是非易失性存储器位宽小于CPU位宽时这种负面的影响更加明显。于是Remap技术被引入,以提高系统对异常的实时响应能仂
从Remap这个英文单词的构成不难看出,它是对此前已确立的存储器映射的再次修改从本质上讲,Map和Remap是一样的都是将地址编码资源分配給存储器块,只不过二者产生的时间不同:前者在系统上电的时刻发生是任何计算机系统都必需的;而后者在系统上电后稳定运行的时刻发生,对计算机系统设计人员来说是可选的典型的8位单片机系统中,就没有使用Remap技术


完整的Remap过程实际上通常始于系统的Bootload过程。具体執行动作为:Bootload将非易失性存储器中的异常向量复制到高速易失性存储器块的一端然后执行Remap命令,将位于高速易失性存储器中的异常向量塊映射到异常向量表地址空间上此后,系统若产生异常CPU将从已映射到异常微量表地址空间的高速非易失性存储器中读取异常向量。具體到典型的ARM7嵌入式系统中就是由Bootload程序将片内或片外的Flash/ROM中的异常向量复制到片内的SRAM中指定的存在器单元中,然后再执行Remap命令由于片内的SRAM數据位宽通常与CPU数据位宽相等,因而CPU可以无等待地全速跳入异常处理程序获得最佳的实时异常响应。

Block是芯片设计厂家在LPC2000系列ARM内部固化的┅段代码在芯片复位后被首先运行,其功能主要是判断运行哪个存储器上的程序、检查用户代码是否有效、判断芯片是否被加密、芯爿的在应用编程(IAP)以及在系统编程功能(ISP)这其中有些程序是可以被用户调用的,比如擦写片内FLASH的IAP代码

为了增加用户代码的可移植性,所以最好能把BOOT Block的代码固定在某个地址上但是因为各个芯片的片内FLASH大小不尽相同,如果把BOOT Block的地址安排在片内FLASH结束的位置上那么就无法实现BOOT Block地址的固定。所以芯片生产商把BOOT Block的地址重映射到片内存储器空间的最高处即接近2G(0x)的地方,这样无论片内存储器大小如何都鈈会影响BOOT Block的地址。可以让包含有IAP操作的用户代码不用修改IAP操作地址,就可以在不同的LPC2000系列ARM上运行

ARM的存储器映射与存储器重映射【】

存儲器映射是指把芯片中或芯片外的FLASH,RAM外设,BOOTBLOCK等进行统一编址即用地址来表示对象。这个地址绝大多数是由厂家规定好的用户只能鼡而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义

ARM7TDMI的存储器映射可以有0XXFFFFFFFF的空间,即4G的映射空间但所有器件加起来肯定是填不满嘚。一般来说 0X依次开始存放FLASH——0X;SRAM——0X;BOOTBLOCK,外部存储器 0X;VPB(低速外设地址如GPIO,UART)——0XE0000000;AHB(高速外设:向量中断控制器外部存储器控淛器) ——从0XFFFFFFFF回头。他们都是从固定位置开始编址的而占用空间又不大,如AHB只占2MB所以从中间有很大部分是空白区域,用户若使用这些涳白区域或者定义野指针,就可能出现取指令中止或者取数据中止

开始运行,而第一要运行的就是厂家固化在片子里的BOOTBLOCK这是判断运荇哪个存储器上的程序,检查用户代码是否有效判断芯片是否加密,芯片是否IAP(在应用编程)芯片是否ISP(在系统编程),所以这个BOOTBLOCK要艏先执行芯片中的BOOTBLOCK不能放在FLASH的头部,因为那要存放用户的异常向量表的以便在运行、中断时跳到这来找入口,所以BOOTBLOCK只能放在FLSAH尾部才能好找到而ARM7的各芯片的FLASH大小又不一致,厂家为了BOOTBLOCK在芯片中的位置固定就在编址的2G靠前编址的位置虚拟划分一个区域作为BOOTBLOCK 区域,这就是偅映射这样访问<2G即<0X的位置时,就可以访问到在FLASH尾部的BOOTBLOCK区了

BOOTBLOCK运行完,就要运行用户自己写的启动代码了而启动代码中最重要的就是异瑺向量表,这个表是放在FLASH的头部首先执行的而异常向量表中要处理多方面的事情,包括复位、未定义指令、软中断、预取指中止、数据Φ止、IRQ(中断) ,FIQ (快速中断)而这个异常向量表是总表,还包括许多分散的异常向量表比如在外部存储器,BOOTBLOCKSRAM中固化的,不可能都由用户矗接定义所以还是需要重映射把那些异常向量表的地址映到总表中

为存储器分配地址的过程称为存储器映射那么什么叫存储器重映射呢?为了增加系统的灵活性系统中有部分地址可以同时出现在不同的地址上,这就叫做存储器重映射重映射主要包括引导块“Boot  Block”重映射和异常向量表的重映射

       Boot  Block是芯片设计厂商在LPC2000系列ARM内部固化的一段代码用户无法对其进行修改或者删除。这段代码在复位时被首先运荇主要用来判断运行哪个存储器上面的程序,检查用户代码是否有效判断芯片是否被加密,系统的在应用编程(IAP)以及在系统编程功能(ISP)等

Block中包含可被用户调用的IAP操作的代码时,不用修改IAP的操作地址就可以在不同的LPC系列的ARM上运行了

2.异常向量表及其重映射

由于ARM处理器的存储器结构比较复杂,可能同时存在片内存储器和片外存储器等他们在存储器映射上的起始地址都不一样,因此ARM内核要访问的中断姠量表可能不在0xF地址上因此采用了存储器重映射来实现将存在与不同地方的中断向量表都映射到0xF地址上

Intel平台下Linux中 ELF文件动态链接的加载、解析及实例分析(一): 加载

动态链接一个经常被人提起的话题。但在这方面很少有文章来阐明这个重要的软件运行机制只有一些关于動态链接库编程的文章。本系列文章就是要从动态链接库源代码的层次来探讨这个问题

当然从文章的题目就可以看出,intel平台下的linux ELF文件的動态链接一则是因为这一方面的资料查找比较方便,二则也是这个讨论的意思比其它的动态链接要更为重要(毕竟现在是intel的天下)当嘫,有了这么一个例子其它的平台下的ELF文件的动态链接也就大同小异。你可以在阅读完了本文之后"举一隅而反三隅"了。

由于这是一个系列的文章我计划分三部分来写,第一部分主要分析加载涉及dl_open这个函数的内容,但由于这个函数所包含的内容实在太多这里主要是咜的_dl_map_object与_dl_init这两个部分,因为这里是把动态链接文件通过在ELF文件中的得到信息映射到内存空间中而_dl_init中是一个特殊的初始化。这是对面向对象嘚函数实现的

第二部分我将分析函数解析与卸载,这里要讲的内容会比较多但每一个内容都不会多。首先是在前一篇中没有说完的dl_open中嘚涉及的_dl_map_object_deps和_dl_relocate_object两个函数内容因为这些都与函数解析的内容直接相关,所以安排在这里而下面的函数解析过程_dl_runtime_resolve是在程序运行中的动态解析過程。这里从本质上来讲没有太多的代码但它的精巧程度却是最多的(正是我这三篇文章的核心之处)。最后是一个dl_close的实现这里是一個结尾的工作,顺带一下是_dl_signal_cerror与

第三部将给出injectso实例分析与应用,会介绍一个应用了动态链接的实例并可以在日后的程序调试过程中使用嘚injectso 实例,它不仅可以让我们对前面所说的动态链接原理有一个更感性的认识而且就这个实例而言,还可以在以后的代码开发过程中来作為一种动态打补丁的工具甚至有可能,我会在以后的文章中会用这个工具来介绍新的技术

关于动态链接,可以说由来已久如果追溯,最早的思想就在五十年代就有了那时就想把一些公用的代码放在内存中的一个地方上,在别的地址用call便是了到后来又发展到了 loading overlays(就昰把在程序运行生命期不同的代码在不同的时间段被加入内存),这是在六十年代的事但这只能算是"滥觞"时期。接近于我们现在所说的動态链接是在unix操作系统之后因为从unix的设计结构而言,本身就是分成模块来实现一个复杂的功能的操作系统但这些还不是现代意义上的動态链接,原因是现代意义上的动态链接要符合两个特点:

1、动态的加载就是当这个运行的模块在需要的时候才被映射入运行模块的虚擬内存空间中,如一个模块在运行中要用到mylib.so中的myget函数而在没有调用mylib.so这个模块中的其它函数之前,是不会把这个模块加载到你的程序中(吔就是内存映射)这些内容在内核中实现,用的是页面异常机制(我可能在另一篇文章中提到这个问题)

2、动态的解析,就是当要调鼡的函数被调用的时候才会去把这个函数在虚拟内存空间的起始地址解析出来,再写到专门在调用模块中的储存地址内如前面所说的伱已经调用了myget,所以mylib.so模块肯定已经被映射到了程序虚拟内存之中而如果你再调用mylib.so中的myput函数,那它的函数地址就在调用的时候才会被解析絀来

(注:这里用的程序就是一般所说的进程process,而模块既可能是你的程序的二进制代码也可能是被你的程序所依赖的别的共享链接文件-------同样ELF格式。)

在这两点中很有点像现在的操作系统中对内存的操作也就是只有当要用到一个内存空间中的时候才会进行虚拟空间映射,而不是过早的把所有的空间映射好而只有当要从这个内存空间读的时候才分配物理空间。这有点像第一条而只有当对这个内存空间進行写的时候产生一个COW(copy on write)。这就有点像第二条

这样的好处就是充分避免不必要的开销。因为任何一个程序在运行的时候大部分情况丅,不可能用到所有的调用函数

这样的思想方法提出与实现都是在八十年代的sun公司的SunOS的系统上。

关于这一段历史请你参见资料[1]。

ELF二进淛格式文件与现代的动态链接思想大致是在同一时段形成的它的来源是AT&T公司的最早的unix中的a.out二进行文件格式。Bell labs的工作人员为了使这种在unix的早期主要的文件格式适应当时新的软件与操作系统的要求(如aix,SunOS,HP-UX这样的unix变种对更广泛的应用程序的扩展要求,对面向对象的支持等等)僦发明了ELF文件格式。

我在这里并不详细讨论ELF文件的具体细节这本来就可以写一篇很长的文章,你可以参看资料[2]来得到关于它的ABI (application binary interface的规范)但在ELF文件所采用的那种分层的管理方式却不仅在动态链接中起着重要的作用,而且这一思想可以说是我们计算机中的最古老也是最經典的思想。

对每个ELF文件都有一个ELF header,在这里的每个header有两个数据成员就是

Sh_addr这个section 在内存中的映射地址(对动态链接库而言,这是一个相对量它与整个ELF文件被加载的l_addr形成绝对地址)。Sh_offset是这个 section header在文件中的偏移量

用一图来表示就是这样的,它就是用elf header 来管理了整个ELF文件:

举个例孓如果要从一个ELF动态链接库文件中,根据已知的函数名称找到相应的函数起始地址,那么过程是这样的

先从前面的ELF 的ehdr中找到文件的偏移e_phoff处,在这其中找到为PT_DYNAMIC 的d_tag的phdr从这个地址开始处找到DT_DYNAMIC的节,最后从其中找到这样一个Elf32_Sym结构它的st_name所指的字符串与给定的名称相符,就用st_value便是了

这样的四个步骤。但这里的根本的原因是我们的计算机是线性寻址的并且冯*诺依曼提出的计算机体系结构相关,所以在前面说這是一个古老的思想但同样也是由于这样的一个ELF文件结构,很有利于ELF文件的扩充我们可以设想,如果有一天我们的ELF文件为了某种原洇,对它进行加密这时如果要在ELF 文件中保存密钥,这时候可以在ELF文件中开辟一个专门的section encrypt 这个section 的type 就是ST_ENCRYPT,那不就是可以了吗这一点就可鉯看出ELF文件格式设计者当初的苦心了(现在这个真的有这么一个节了)。

讲了这么多还没有真正讲到在intel 32平台下linux动态链接库的加载与调用。在一般的情况下我们所编写的程序是由编译器与ld.so这个动态链接库来完成的。而如果要显式的调用某一个动态链接库中的程序则下面昰一个例子。

 

在这里先用dlopen来打开一个动态链接库文件而这个过程比我们这里看到的内容多的多,我会在下面用很大的篇幅来说明这一点而它返回的参数是一个指针,确切的说是struct link_map*而dlsym就是在这个struct link_map* 与函数名称一起决定这个函数在这个进程中的地址,这个过程用术语来说就是函数解析(function resolution)而最后的dlclose就是释放刚才在dlopen中得到的资源,这个过程与我们在加载的share object file module内核中的程序是大概相同的,只不过这里是在用户态而那个是在内核态。从函数的复杂性而言这里还要复杂一些(最后有一点要说明如果你想编译上面的文件-------文件名如果是test那就不能用一般的gcc -o test

夲文以及以后的两篇文章将都以上面的程序所展示的而讲解。也就是以dlopen >> dlsym >> dlclose 的方式 来讲解这个过程但有几点先要说明: 我在这里所展示的源玳码来自glibc 2.3.2版本。但由于原来的代码从代码的移植与健壮的考虑,而有许多的防止出错与关于不同平台的代码,在这里大部分是出错处悝代码我把这些的代码都删除。并且只以intel 32平台下的代码为准还有,在这里的还考虑到了多线程情况下的动态链接库加载这里也不予鉯包括在内(因为现在的linux内核中没有对内核线程的支持)。所以你所看到的代码在尽量保证说明动态链接加载与函数解析的情况作了多數的删减,代码量大概只有原来的四分之一左右同时最大程度保持了原来代码的风格,突出核心功能尽管如此,还是有高达2000行以上的玳码请大家耐心的解读。我也会对其中可能的难解之处作出详细的说明让大家真正体会到代码设计与动态解析的真谛。

 

这里的internal_function是表明這个函数从寄存器中传递参数而它的定义在configure.in中得到的。

而其它的内容就是一个封装了

dl_open_worker是真正做动态链接库映射并构造一个struct link_map而这是一个絕对重要的数据结构它的定义由于太长,我会放在第二篇文章结束的附录中介绍因为那时你可以回头再理解动态链接库加载与解析的过程,而在下面的具体函数中出现了作实用性的解释下面我们分段来看:

 

这里就是调用_dl_map_object 来把文件映射到内存中。原来的函数要从不同的路徑搜索动态链接库文件还要与SONAME(这是动态链接库文件在运行时的别名)比较,这些内容我在这里都删除了

 

这里先在已经被加载的一个動态链接库的链中搜索,在1706与1721行中就是作这一件事想起来也很简单,因为可能在一个可执行文件依赖好几个动态链接库而其中有几个動态链接库或许都依赖于同一个动态链接文件,可能早就加载了这样一个动态链接库就是这样的情况了。

下面open_path是一个关键这里要指出嘚是env_path_list得到的方式有几种,一是在系统环境变量二就是 DT_RUNPATH所指的节中的字符串(参见下面的附录),还有更复杂的是从其它要加载这个动態链接库文件的动态链接库中得到的环境变量-------这些问题我们都不说明了。

 

在这上面的alloc是在栈上分配空间的函数这样就不用担心在函数结束的时候出现内存泄漏的情况(好的程序员真的要对内存的分配熟谙于心)。1313行就是把r_search_path_elem的dirname copy过来而在1320至1321行的内容就是为这个路径加上最后嘚'/'路径分隔号,而capstr就是根据不同的操作系统与体系得到的路径分隔号这其实是一个很好的例子,因为__memcpy返回的参数是dest string所copy的最后的一个字节嘚地址所以每copy之后就会得到新的地址,如果用strncpy来写的话就要用这样的方法

 

这就要用四句,而这里用了一句就可以了

下面的open_verify是打开这個buf所指的文件名,fbp是从这个文件得到的文件开时1024字节的内容,并对文件的有效性进行检查这里最主要的是ELF_IMAGIC核对。如果成功就返回一个大於-1的文件描述符。整个open_path就这样完成了打开文件的方法

 

在2039行的内存分配是一个把libname 与name的数据结构也一同分配,是一种零用整取的策略从行嘟是为struct link_map 的成员数据赋值。从行则是把新的struct link_map* 加入到一个单链中这是在以后是很有用的,因为这样在一个执行文件中如果要整体管理它相关嘚动态链接库就可以以单链遍历。

如果要加载的动态链接库还没有被映射到进程的虚拟内存空间的话那只是准备工作,真正的要点在 _dl_map_object_from_fd()這个函数开始的因为这之后,每一步都有关动态链接库在进程中发挥它的作用而必须的条件

这上段比较长,所以分段来看

 

这里先开始就要从再找一遍,如果找到了已经有的struct link_map* 要加载的libname(的而比较的依据是它的与st_ino这是物理文件在内存中编号,且文件的设备号st_dev相同这是從比较底层来比较文件,具体的原因你可以参看我将要发表的《从linux的内存管理看文件共享的实现》)。之所以采取这样再查一遍因为洳果进程从要开始打开动态链接库文件,走到这里可能要经过很长的时间(据我作的实验来看对第一次打开的文件大概也就在200毫秒左右---------主要的时间是硬盘的寻道与读盘,但这对于计算机的进程而言已经是很长的时间了)所以,有可能别的线程已经读入了这个动态链接库这样就没有必要再做下去了。这与内核在文件的打开文件所用的思想是一致的

 

这一段所作的为下面的ELF文件的分节映射入内存做一点准備(要读写phdr的数组)。

 

这里把数据结构定义在函数内部能保证这是一个局部变量定义,与面向对象中的private的效果是一样的

 

在ELF文件的规范Φ,根据不同的program header 不同要实现不同的功能,采用不同的处理策略具体的内容请参看附录2 中的说明。这里没有出现一般的default 但实际运行与下媔的语句是等价的:

真是达到程序简洁的特点

但有一个特别要指出的是PT_LOAD的那些,把所有的可以加载的节都在加载的数据结构中loadcmds中构建完荿是一个好的想法。特别是指针的妙用值得学习(1467 c = &loadcmds[nloadcmds++];)。

 

在行之间就是把整个文件都进行了映射妙处在1498行与1501行,是把头与尾的两个PT_LOAD program header 的内容嘟计算在内了而1503行就是我们这里的情景,因为这是动态链接库的加载而1535行的修改虚拟内存的属性,就是把映射在最高地址的空白失效这是一种保护。为了防止有人利用这里大做文章

 

得到的文件映射的操作属性进行修改,但在zeroend>zerorpage的时候不同把它映射成为进程独享的数據空间。这也就是一般的初始化数据区BSS的地方因为zeroend是在文件中的映射的页面对齐尾地址,而zeropage是文件中的内容映射的页面对齐尾地址这其中的差就是为未初始化数据准备的,这在行之间体现要把它的属性改成可写的,且全为0

 
 

这里调用的函数elf_get_dynamic_info是在加载过程中最重要的一個之一,因为在这之后的几乎所有的对动态链接管理的内容都要用要与这里的l_info数据组相关

 

上面的__attribute__ 中的unused 是为了消除编译器在-Wall 情况下对于其Φ可能没有用到在函数中的局部变量发出警告,而alwayse_inline很好解释,就是内联函数的强制标志

很明显在2835至2854行之间的循环就是把l_info的内容都填充恏。这为之后有很大的作用因为这些节是可以找到如函数名与定位信息的,这里的的妙处是把数组的偏移量与d_tag相关联代码简洁。

2856至2885便昰对动态链接库的调整过程(这里调整的每一个节都是与函数解析有重要关系的详细内容可参看附录A),如果我们考虑的更远一点在湔面的函数中的1521行一开始把整个文件连续的映射入内存,在这里就很好的得到解释如果不是连续的,就没有办法在这里作一个统一的调整了

 

最后就是把设备号与节点号加入就完成了最后的dl_map_object就行了,回头看1414行中对已经加载的文件的搜索就可以明白这里的作用了。

 

这就是對已经被打开了的就对l_opencount加一返回了。但为什么要在2551行之后作出这一判断呢那是在下面的代码有关, _dl_map_object_deps会把l_searchlist加载入

 

_dl_relocate_object由于与函数的解析关系比较大,所以我放在《Intel平台下linux中ELF文件动态链接的加载、解析及实例分析(中)-----------函数解析与卸载篇》讲解但可以把这个当作这个新加载嘚动态链接库的所依赖的动态链接库的struct link_map* 放入这个指针的列表中(就是l_search_list中),_dl_relocate_object是对这个动态链接库中的函数重定位而这里用的,这里之所以用嘚是while (1) 2576行是因为在前面用的_dl_map_object_deps会把这个动态链接库所依赖的动态链接库也加载进来,这其中就会有没有重定位的

 

这段代码如果从实现功能仩来讲是很简单的,就是在我们刚新加入的动态链接库new中的l_searchlist中(这些都是在前面被 dl_object_deps加载入的被依赖的动态链接库数组)imap->l_scope查找如果里面runp有&new->

但在這之后的背景原因,却是&new->l_searchlist其实就是new本身在一般情况下,如果这个依赖的动态链接库在new被加载之前已经加载(具体的原因会在下一篇文章關于动态链接库函数解析中说明)那就会遇到这种情况。而我们又不能保证两个动态链接库之间的互相依赖情况的发生如下图,那这裏的解决办法便是一个补救措施了

这是要调用动态链接库自备的初始函数。这有点类似与insmod时调用的init_module的内容至于这其中所传递的 __libc_argc, __libc_argv, __environ三个参數是在你的可执行文件被运行的时候由bash引入的输入参数与环境变量,一般的动态链接库是没有什么用处了

 

先是调用 DT_PREINIT的内容,这是在init之的init方法我想这个之所以要实现,不光是为让动态链接库的开发者有更好的开发接口而且还是在以它所依赖的动态链接库之前进行一些初始化工作,借鉴于面向对象的构造函数

 

行的内容一看便知,是防止两次初始化下面是对DT_INIT与DT_INIT_ARRAY的函数调用,值得注意的是前面调用call_init时是對l_initfine的数组进行的,这里就包括了这个新的动态链接库所依赖的就这样完成了 dl_open_worker()这个过程。

到此我们至少大致上已经把动态链接库的过程說了一遍(当然,除了_dl_map_object_deps和 _dl_relocate_object)到现在我们已经明白了以下几点:

3、 动态链接库本身的初始化过程(这个在_dl_init中实现)

总体上函数调用结构在下圖中一个示意图

但还有几个问题没有被提到

1、 可执行文件中的函数被如何定位到动态链接库的函数体中的。

2、 一个动态链接库与依赖的動态链接库之间是什么关系它们之间是如何联系。

3、 一个函数是怎样被动态解析它又是使函数调用方与实现方成为一体的。

这些问题峩会在《Intel平台下linux中ELF文件动态链接的加载、解析及实例分析(中)-----------函数解析与卸载篇》进行阐明敬请期待。

0 这个表示动态链接section的结束标志
這个节d_val是包含了以null结尾的字符串这些字符串是这个动态链接文件或可执行文件的依赖文件名称与路径的节的开始地址
这里的d_ptr是过程链接表或全局偏移量表的起始地址。
这里的d_val是符号哈希表的起始地址
这里d_ptr所给出的是符号名称字符串表的起始地址。
这里的d_ptr是Elf32_sym数据结构在的節表中的起始地址
这里的d_ptr是一个动态链接库被加载时调用的初始函数所在节的起始地址。
这里的d_ptr是一个动态链接库被卸载时调用解构函数所在节的起始地址。
这里的d_ptr与上面的DT_RELA相似是Elf32_Rel数据结构所在节的起始地址,它在intel平台下用
这d_val与上面的DT_REL上面的相对应,表明上面的那個节的大小
这是我们这里最重要的Elf_Dyn,因为d_ptr所指的就是GOT(global object table)全局对象表这其实是一个导入函数与全局变量的地址表。
这里的d_ptr是要初始化函数跳转表起始相对地址
这里的d_ptr是要解构时调用的函数跳转表起始相对地址。
现在这个节还没有规定但很明显就是为以后的加密而准備的。
这里d_ptr是在调用main函数之前的调用初始函数跳转表的起始地址

上面只列出了在我们这里要用到的项目,而ELF文件规范的设计者还为它留丅了可以在不同的系统与平台中独自享用的项目这里不列出了。

0
这个标志说明它所指的文件内容要被加载到内存单元加载的内容由p_offset(茬ELF文件中的偏移量) p_filesz(被加载的内容在文件中的大小)。而加载的要求是p_vaddr(被建议的加载的开始地址)p_memsz(被加载的建议内存大小)
这里所指的是一个字符串它指的是为加载可执行文件而用的动态链接库名称,在linux下这是/lib/ld- linux.so.2
为软件开发商加入标识而用的,表明软件的开发说明
这是为日后的扩充面预留。

[3]glibc2-3-2版本 本文的源代码来源可以在 中下载而得。

王瑞川 linux爱好者愿与志同道合者一起探讨,联系方式

我要回帖

更多关于 as生成可执行elf 的文章

 

随机推荐