首先上一张图:
转自 http://blog.csdn.net/ouyang_linux007/article/details/7448505
程序从片内地址0开始,但为什么链接地址又设0x30000000,那不就从0x30000000开始了,反汇编可以看到不是从0开始的?
韦老大回答:
- 裸板程序烧在FLASH上 一上电,肯定从0地址运行
- 但是,0地址要么对应NOR FLASH,要么对应只有4K的片内内存
- 程序要读写数据,或是程序大于4K,怎么办?
- 程序就要复制到SDRAM里去执行
- SDRAM那么大,复制到哪个地址去?能随便选择地址吗
- 不能,要复制到它的链接地址去
- 为什么一定要复制到它的链接地址去?
- 因为这个链接地址是程序运行时“应该位于的地方”,比如要访问某个全局变量时,就是访问这个全局变量的链接地址
- 既然链接地址是SDRAM的地址,那为什么一开始程序可以从0地址运行
- 因为一开始的程序是“位置无关码”
独孤君回答:
以下是我自己总结的,看对你有帮助不
加载时域与运行时域:可以这么理解,加载时域涉及到存储地址;运行时域涉及到连接地址(连接地址开始作用的时间是在使用伪指令ldr(adr、adrl)pc,=某符号或是某立即数时)。可执行程序在被下载到相应存储器件里时,它的存储地址可以通过oflash来选择;而当运行该程序时,一开始时PC寄存器的值是指向存储空间的起始地址(起始地址由oflash决定)的,但在遇到伪指令ldr(adr、adrl)pc,=某符号或是某立即数(位置无关相对跳转指令B与BL不影响运行地址)后,程序运行时的绝对地址(即PC寄存器的值)就发生了改变,它是以连接地址(由arm-Linux-ld之-T选项设定)为基址,指令所在位置为相对地址共同组成的。总之:运行地址=-T指定的连接地址+相对偏移地址;存储地址=由oflash指定某存储器件的起始地址+由链接文件中AT指定的加载地址。
转自http://blog.csdn.net/ce123_zhouwei/article/details/6990100
加载时地址就是程序放置的地址,运行地址就是程序定位的绝对地址,也即在编译连接时定位的地址。
如果程序是在flash里运行,则运行地址和加载地址是相同的。
如果程序是在ram里运行,但程序是存储在flash里,则运行地址指向ram,而加载地址是指向flash。
代码一般是烧写在NAND里面,比如S3C2440 如果开机从NAND启动 其开始的4K代码会被COPY到2440内部的4KRAM 用于对关键硬件的初始化 这时候内部RAM被映射为0x0地址。
如果从NOR启动,因为NOR支持片上运行,代码可以直接在NOR上运行 此时NOR便被映射成0x0,S3C2440 内部的4KRAM便被映射到了0x40000000处。
下面我们看看链接文件。
对于.lds文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。先看一下GNU官方网站上对.lds文件形式的完整描述:
SECTIONS {
…
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
…
}
secname和contents是必须的,其他的都是可选的。下面看看几个常用的:
1、secname:段名
2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
3、start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
4、AT(ldadr):定义本段存储(加载)的地址。
看一个简单的例子:
/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
以上,head.o放在0x00000000地址开始处,init.o放在head.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);
main.o放在4096(0x1000,是AT指定的,存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0x1000(加载处)复制到0x30000000(运行处),
此过程也就用到了读取Nand flash。这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds连接脚本文件中分别指定。
编写好的.lds文件,在用arm-linux-ld连接命令时带-Tfilename来调用执行,如arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext参数直接指定连接地址,
如arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。
总之:
连接地址<>运行地址
存储地址<>加载地址
既然程序有了两种地址,就涉及到一些跳转指令的区别,下面就来具体看看这些跳转指令。
ARM汇编中,常有两种跳转方法:b跳转指令、ldr指令向PC赋值。
(1)b step1 :b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该指令本身的bit[23:0]算出来的,这使得使用b指令的程序不依赖于要跳到的代码的位置,只看指令本身。
(2)ldr pc, =step1 :该指令是从内存中的某个位置(step1)读出数据并赋给PC,同样依赖当前PC的值,但是偏移量是那个位置(step1)的连接地址(运行时的地址),
所以可以用它实现从Flash到RAM的程序跳转。
转自https://blog.csdn.net/qingkongyeyue/article/details/52298640
1、概念理解
运行地址<—>链接地址:他们两个是等价的,只是两种不同的说法。
加载地址<—>存储地址:他们两个是等价的,也是两种不同的说法。
运行地址:程序在SRAM、SDRAM中执行时的地址。就是执行这条指令时,PC应该等于这个地址,换句话说,PC等于这个地址时,这条指令应该保存在这个地址内。
加载地址:程序保存在Nand flash中的地址。
位置无关码:B、BL、MOV都是位置位置无关码。
位置有关码:LDR PC,=LABEL等类似的代码都是位置有关码。
2、链接脚本格式
基本命令
SECTIONS描述输出文件的映射图:->输出文件各段、各文件怎么放置
一个SECTIONS命令内部包含一个或多个段,段是连接脚本的基本单元,它表示输入文件某部分怎么放置;
格式:
SECTIONS{
…
secname start ALIGN(align)(NOLOAD):AT(ldadr)
{contents}>region:phdr=fill
…
}
secname:命名这个段
contents:用来确定代码的什么部分放在这个段
start:是这个段的重定位地址,也叫运行地址。如果代码中有位置无关的指令,程序在运行时必须放在这个地址上。
ALIGN(align):虽然指定了运行地址,但仍可以使用ALIGN(align)来指定对齐的要求—这个对齐的地址才是真正的地址
(NOLOAD):来告诉加载器,在运行时不用加载这个段
AT(ldadr):指定这个段在编译出来的映像文件中的地址——加载地址
3、链接脚本分析
先看一个链接脚本:
SECTIONS{
first 0x00000000 : {head.o init.o}
second 0xB0004000 : AT(2048){leds.o}
}
链接脚本将程序分为两个段:first和second。前者由head.o和init.o组成,它的加载地址和运行地址都是0,所以在运行时不需要移动代码,后者由leds.o组成,它的加载地址为2048,重定位地址为0xB0004000,这表明段second存放在编译所得的映像文件的2048处,在运行前需要将它复制到地址0xB0004000(MMU映射),将编译所得的映像文件烧入到nand flash后,head.o和init.o依次从0x00000000处存放,而leds.o存放在2048处。从nand flash启动时,cpu首先将nand flash的前4KB复制到cpu自身的ram(steppingstone)中去,这样leds.o存放在地址为2048处,而运行的时候需要将steppingstone中2048 - 4096的内容复制到sdram中起始地址0xB0004000处,从而使用ldr跳转时才会正确执行下去。
4、假若程序不位于链接地址时程序会出现什么问题
去访问某些全局变量时就会出错,因为访问这些全局变量时用的是它的链接地址,我去链接地址访问你,你就必须位于链接地址上。如下图,程序应该位于SDRAM上的某个地方,但是一上电是从2440的片内4K RAM 的0地址开始执行的,为什么从0地址执行的一小段代码能够正确运行,这一小段代码用到位置无关码写
其实有时候知道的越多,困扰也就越多,抛弃那些东西,我们来看:
还是很好理解的,什么是加载地址:说白了,就是从哪里拿数据代码放到内存,也就是你下载后放在了flash的什么地方。这就是加载地址,对于pc来说应该是磁盘地址吧,(具体叫什么不知道,仅仅是推测)。那什么是运行地址那?其实我一开始不曾想过运行地址的决定最开始竟然是由连接器决定的,但是反过来想想也是对的,因为我们知道一个程序的各个段,以及内存的映射,从生成文件的时候就已经确定了,所以在内存中
的地址映射也就确定了,那么地址自然也就确定了,而且需要注意的是,生成的文件是有个头信息的,linux下是elfwindows下是PE,这些东西主要是为了操作系统在加载的时候,可以加载对各个段。但是现在我们没有操作系统我们现在是裸机。我们想要运行一个程序,在连接器链接完一个程序的一些核心的东西地址已经确定了,(确定的原因是
连接器自己有一套地址规则)最为典型的就是全局变量,地址是固定的,比如地址是0x 40008000,那么这个程序在下载到flash中进而从flash的地址加载到内存的时候能否把这个全局变量的值加载在这个内存地址上,这就关键了,如果不能,那你这段代码执行的时候,必然出问题找这个地址结果值不对,对于裸机来说,我们内存的地址似乎我们是可以掌控的,我们是明确的,我们的ram地址是多少我们也是确定的,那好了,我们能不能就如我们心愿,把这个全局变量的地址就搞到对应的内存地址上哪?
其实,我们是可以明确的,首先来说,硬件是改不了的,那么我们只能够修改我们的代码了,我们把生成的段的地址修改了,让他和我们的实际内存相匹配,那我们就ok了。而修改的这个过程叫做重定位,可以用命令 -Ttext=0x40009000也可以写个脚本实现。然后我们用 go 40009000就可以运行了。当我说我这句你是不是有蒙蔽了!对,我讲的是裸机程序倘若谈真正意义上的裸机程序,那就是bootloader是如何运行的,其实一上电,arm来说必然是0x00000000处运行,但是这个0x00000000处是什么那,时候从来都是rom,当然也有ram,各个开发板不一样,其实这俩种东西都会让你产生问题rom把,不是说代码不能在ROM中运行吗?其实不然,是不能写数据,可以写条跳转指令啊!对于ram来说,一上电,谁把数据写入ram其实是靠硬件的一些控制器来实现的,一上电将数据加载到内存,然后运行。
但是在uboot下这是靠lds链接脚本实现。当然如果你外接-Ttext=0x40009000,那么以外部为准。回到我们的问题:我们应该明确的是程序一开始是在rom即使加载到内存也仅仅是几k,放到这几k的代码往往是位置无关的代码然后用这些代码,来初始化真正的大内存,(外接内存)然后这时候就需要我们在链接的时候重定向的代码,放到和链接地址
一样的地方,然后运行