编译地址和运行地址的区别分析

Ubuntu 16.04.2环境交叉编译   arm-linux3.4.5   arm架构的s3c2440芯片作为平台

       32位处理器中,每条指令都是4字节长度,以4个字节顺序存储。仅考虑顺序执行的情况下,处理器将按照指令顺序依次读出执行,但是如果考虑跳转执行的话,仅仅只有指令顺序存储这一条件的情况下,实现跳转执行唯一的方法即为跳转到“往后或者往前多少字节”的位置这样的约定。无疑这样的效率是很低的,而且计算也很麻烦

 

因此需要对指令进行编址,即对每个指令赋予地址。

这个操作在编译过程中完成,编译器以某个地址位初始地址,按照存储数据给每个字节赋予顺序的地址。

而这个地址即为编译地址。

运行地址位程序运行中实际的执行地址。


编译地址可以认为是程序编写者 “期望” 程序在运行时的地址,而实际上编译地址和运行地址有些差别。


首先需要知道的是程序源文件编译时可以使用交叉编译工具arm-linux-ld的-T参数对数据段的编译地址指定
如:
arm-linux-ld  -Ttext 0x30000000 start.O hello.O
即为将text(代码)段的编译地址设置以0x30000000为初始值,一般来说程序段的顺序为:
.text -> .rodata -> .data ->  .bss

.text为代码段,.rodata为只读数据段,.data为数据段(存放初始化过的全局变量或者静态变量),.bss段存放未初始化的全局变量

如果未给出-T参数的话,编译器将按照默认规则,以0地址作为初始地址依次编址

编译完成后生成的是.elf格式的文件,需要经过如下步骤:
arm-linux-objcopy -O binary -S xxx.elf xxx.bin
arm-linux-objdump -D xxx.elf > xxx.dis
才可以得到.bin格式文件用于烧写到芯片,第二个命令是用来生成反汇编代码的,通过反汇编代码可以直观的看到编译器对程序源码做出的转译、优化以及编址(编译地址)。


编译完成后得到的程序还要经过烧写到芯片的过程,而这里正是产生编译地址和运行地址的差别所在
假设程序一定是烧写在芯片的flash,也就是芯片的统一编址的0地址处。则:

1.如果arm-linux-ld的-Ttext 参数为0 或者不给出时,程序的编译地址为以0初始的编址,程序开始运行时都是从芯片                    编址的0地址开始,从而运行地址一定是从0地址开始(除非有特别指定),而编译地址也是从0开始,从这个角度来                          看,编译地址和运行地址并无区别

2.如果arm-linux-ld的-Ttext 参数给定为0x30000000,则编译地址为以0x30000000初始的编址,由1可知,程序上电开始运行还是从0地址 开始,所以运行地址为0,从这里编译地址和运行地址就不同了,但是程序依然会从0开始运行,而且还可以正常运行

 这里就需要提到一个位置无关码以及位置相关码的概念了,从名字可以看出来两者的区别在于代码执行时与代码所在位置是否有关系。
位置无关码通常使用相对跳转或者其他相对位置的指令进行跳转执行,与实际具体地址并无关系,所以称为位置无关码,类似于文件的相对路径。

位置相关码需要使用指令的绝对地址,也就是其编译地址进行跳转,也即跳转到指定的编译地址上,与指令实际所在地址有关,类似于文件的绝对路径。

arm架构指令集中,位置无关码有相对跳转指令b,而位置相关码有ldr指令

通过b跳转的 如b main的实际跳转方式为PC + 偏移值 (PC值等于当前地址+8)
而通过ldr实现的跳转ldr pc,lab (此处lab为标号)则是将lab标号的编译地址直接赋值给PC,从而跳转到绝对地址

程序烧写从芯片统一编制的0地址开始,所以程序在内存中的存储是在0地址开始的,而运行也是从0地址开始,如果中间没有位置相关码,则程序在0地址处的程序执行是可行的,因为是程序存储位置,所以需要的指令和数据都能取到,但是此时运行地址和编译地址就不一样了。
如果程序在进行一定初始化后,使用ldr指令将pc指向到 “预期” 地址,也即编译地址,则编译地址和运行地址就可以达到相等(运行时的pc)。

再裸机程序中或者其他程序中经常编译地址和运行地址不一致,以裸机程序为例,使用s3c2440芯片,使用norflash作为存储的话,norflash为0地址开始处,而正如我们所知norflash类似于rom,能够正常读取数据,但是需要特殊的方式才能写入数据,这就导致我们烧入的程序中,一些全局变量或者保存在栈区的全局变量如果存储在norflash上的话将无法按照程序中所希望的那样可以进行赋值修改,所以需要将代码重定位到其他存储位置上,如我的板子的SDRAM上,所以造成了编译地址和运行地址的不一致。

需要注意的是将代码重定位到编译地址区域之前,需要初始化目标存储区,比如我使用的SDRAM,则我需要初始化SDRAM设置,还有需要将代码、数据等信息复制到目标区域,因为我们的代码存储在0地址区域而不是存储在重定位目标区域,所以目标区域在复制代码前是空的,直接绝对跳转的话会发生不可预料的事···
 

还有其他原因造成编译地址和运行地址不同的情况,以及需要进行代码重定位的原因,这里做了一些简单情况的讲述分析,在其他情况下,道理相似,可以举一反三,触类旁通。重定位的方式也有区分,日后抽空分析。


结论:
编译地址:编译过程中人为指定的代码编址地址,是程序员 "期望" 程序运行的位置

 

运行地址:实际运行中程序指针pc的值,和实际运行载体和代码存储位置有关。

 

欢迎交流、讨论或者指正!共同进步!

---------------------------------------------------------

2020重新回顾有了一些新的见解,可以移步至https://blog.csdn.net/G_METHOD/article/details/104508545。

你可能感兴趣的:(编译,嵌入式,重定位,linux)