您的位置:首页 > 编程语言 > C语言/C++

从U-Boot源码看C语言对汇编代码中的符号引用

2010-10-29 14:55 246 查看
以下内容来自笔者在中国Linux论坛Linux嵌
入技术讨论区的张贴:

aaronwong:

u-boot中代码的疑问(_armboot_start与_start)?

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



使用的是u-boot-1.3.0-rc2。在cpu/pxa/start.S中,有如下的标号定义:

_TEXT_BASE:

.word
TEXT_BASE /*uboot映像在SDRAM中的重定位地址,我
设置为0xa170 0000 */

.globl _armboot_start

_armboot_start:

.word _start /*_start是程序入口,链接完
毕它的值应该是0xa170 0000=TEXT_BASE*/

/*
这句话的意思应该是在_armboot_start标号处,保存了_start的值,也就是说,_armboot_start是存放_start的地址,
该地址对应的存储单元内容是0xa170 0000*/

/*

* These are defined in the
board-specific linker script. 下面的定义与上面应该是一个意思。

*/

.globl
_bss_start

_bss_start:

.word __bss_start

======================

按照上面的理解,__bss_start是uboot 的bss段起始地址,那么uboot映像的大小就是__bss_start -
_start;在relocate代码段中计算uboot的大小时,也体现了这一点。

实际上,_armboot_start并没有实际意义,它
只是在"ldr r2,
_armboot_start"中用來寻址_start的值而已,_bss_start也是一样的道理,真正有意义的应该是_start和
__bss_start本身。

但是,令我不解的是,在C入口函数start_armboot()中(对应文件为
lib_arm/board.c),有如下代码:

void start_armboot (void)

{

.........

gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); //第一句话

..........

monitor_flash_len
= _bss_start - _armboot_start; //第
二句话

...............

mem_malloc_init (_armboot_start -
CFG_MALLOC_LEN); //第三句话

..........

}

==============================================

按照上面的理
解,_armboot_start与_bss_start都是没有实际意义的,它们只是一个地址,有实际意义的是地址中的内容_start和
__bss_start(虽然也还是地址)。象第一句话,其“意图”很明显,是把gd作为全局数据结构体的指针,并初始化为“SDRAM中的uboot起
始地址(即TEXT_BASE)-CFG_MALLOC_LEN-全局数据结构体大小”。

要实现这个“意图”,应该是写成:gd =
(gd_t*)(_start - CFG_MALLOC_LEN - sizeof(gd_t));或者gd =
(gd_t*)(TEXT_BASE- CFG_MALLOC_LEN -
sizeof(gd_t));才对阿?用_armboot_start来作运算应该是没有任何意义才对!?

第二句话也是一样的道理,它的意图是
要计算u-boot映像的大小,应该写成__bss_start - _start才对阿?

我使用readelf工具查看编译所得到的
uboot映像文件得到信息如下:

[aaronwong@localhost build]$ readelf -s u-boot|grep
_start

1018: a1700048 0 NOTYPE GLOBAL DEFAULT 1 _bss_start

1083:
a1700044 0 NOTYPE GLOBAL DEFAULT 1 _armboot_start

1142: a1700000 0
NOTYPE GLOBAL DEFAULT 1 _start

1197: a171b070 0 NOTYPE GLOBAL
DEFAULT ABS __bss_start

上面我删除了与该讨论无关的包含“_start""t的标号信息。

显然,我前面的理
解应该是正确的(_start=TEXT_BASE=0xa170
0000)。那么u-boot源代码中的monitor_flash_len=_bss_start -
_armboot_start=0xa1700048 - 0xa1700044 = 4,有什么意义??

迷茫中,期盼大虾指点迷津,谢
谢~!!!

eltshan:
[Re: aaronwong]

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

1018: a1700048 0 NOTYPE
GLOBAL DEFAULT 1 _bss_start

1083: a1700044 0 NOTYPE GLOBAL DEFAULT 1
_armboot_start

1142: a1700000 0 NOTYPE GLOBAL DEFAULT 1 _start

1197:
a171b070 0 NOTYPE GLOBAL DEFAULT ABS __bss_start

我想:

_start
所在的地址是a1700000,

_armboot_start 所在的地址是a1700044,

那么 根据这句:

_armboot_start:
.word _start

所以_armboot_start的值应该是a1700000

所以

monitor_flash_len
= _bss_start - _armboot_start = a171b070 - a1700000 = 1b070

而不是你说的 =
4

以上个人意见.

aaronwong:
[Re: eltshan]

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

谢谢,eltshan!你的理解是正确的,不过我看
了之后还是没能想得很明白,因为我在想,按你所说,那么_start的值应该是多少呢?难道是“b
reset”这条指令的机器码?所以我对ELF格式的u-boot映像文件作了反汇编,分析之后终于找到了症结所在。以下是部分分析过程,首先是反汇编:

arm-iwmmxt-linux-gnueabi-objectdump -D u-boot > u-boot.s

并提取
了monitor_flash_len = _bss_start - _armboot_start;这条语句相关的反汇编代码如下:

==============================

a1700044 <_armboot_start>:

a1700044: a1700000 .word
0xa1700000

a1700048 <_bss_start>:

a1700048: a171b070
.word 0xa171b070

a171b070 <monitor_flash_len>:

a171b070:
00000000 .word 0x00000000

.....

a1700f40: e59f41d0 ldr r4,
[pc, #464] ; a1701118 <start_armboot+0x1dc>

//r4=[a1701118]=a1700044

.....

a1700f7c: e59f3198 ldr r3, [pc, #408] ; a170111c
<start_armboot+0x1e0>

//r3=[a1700044]=a1700048

a1700f80: e5942000 ldr r2, [r4]

//r2=[a1700044]=a1700000

a1700f84: e59f4194
ldr r4, [pc, #404] ; a1701120 <start_armboot+0x1e4>

//r4=[a1701120]=a1719d24

a1700f88:
e5933000 ldr r3, [r3]

//r3=[a1700048]=a171b070

a1700f8c: e0623003 rsb r3, r2, r3

//r3= r3-r2 = a171b070-a1700000 = 1b070;

a1700f90:
e59f218c ldr r2, [pc, #396] ; a1701124 <start_armboot+0x1e8>

//r2=[a1701124]=a171b070

a1700f94:
e5823000 str r3, [r2]

//monitor_flash_len=[r2]=r3=1b070

......

a1701118: a1700044 .word 0xa1700044

a170111c:
a1700048 .word 0xa1700048

a1701120: a1719d24 .word 0xa1719d24

a1701124:
a171b070 .word 0xa171b070

========================================


面//是我自己的注释。这表明,你的理解的确是正确的。

经过这个过程之后,我终于认识到自己的误解在哪里了。原来,我是把"汇编语言中LDR伪
指令对符号的引用"与"C语言中对汇编程序中符号/常量/变量的引用"搞混淆了。我想说明以下几点:

(1)
readelf以及u-boot.map和System.map所给出的符号表中符号的值,实际上是表示符号所在的地址,而不是指符号本身的值。

(2) 汇编语言中没有指针的概念,因此对符号的引用是"赤裸裸"的。
例如:

==========

.globl _armboot_start

_armboot_start: .word _start

ldr r2,
_armboot_start

==========

实际上反汇编以后是:

============

a1700044
<_armboot_start>:

a1700044: a1700000 .word 0xa1700000

a1700074:
e51f2038 ldr r2, [pc, #-56] ; a1700044 <_armboot_start>

============

也就是说,_armboot_start是一个地址0xa1700044,其中的内容是0xa1700000,上面对
_armboot_start的引用是直接将其替换为其表示的地址0xa1700044,而非其中的内容0xa1700000。这就是"赤裸裸"的引用。

(3)
C语言则不同,对变量/符号/常量的引用必须要通过地址来寻址,不管是全局变量还是局部变量,不同的是局部变量在生命期结束后,所占的地址空间会被释放而
已。即使是函数调用时的参数传递,虽然是将实参的值"拷贝"给形参,但"拷贝"的过程也是通过实参和形参的地址来对两者进行访问的。


以,在C语言中的 "monitor_flash_len = _bss_start - _armboot_start"
这句话中对_armboot_star的引用,实际上是把它用作了指针,把它作为访问对象的地址来使用,通过这个地址即a1700044
来访问对应存储空间所存放的内容亦即0xa1700000,_bss_start也是同样的道理。所以这句话实际上是monitor_flash_len
=[a1700048]-[0xa1700044]=a171b070-a1700000 = 1b070,这样就得到了正确的结果。


在,我们再回答最前面的问题:_start的值是什么?
_start
表示地址0xa1700000
,在汇编语言中,对_start的"绝对引用"(这里是与用相对寻址进行跳转进行区别)就是将其替换为0xa1700000,但其中存放的内容的的确确就
是"b
reset"这条指令的机器码,所以如果在C语言中引用_start,得到的结果反而就是这个指令的机器码了。其实这个问题很简单,只是和C语言的引用搅
在一起,一些概念被偷换了而已。

以上是我的个人理解,有什么不对,还请指正。

再次感谢eltshan的提点。

楼主,你还是没有完全理解对,在(2)

==========

.globl _armboot_start

_armboot_start: .word _start

ldr r2, _armboot_start

==========

实际上反汇编以后是:

============

a1700044 <_armboot_start>:

a1700044: a1700000 .word 0xa1700000

a1700074: e51f2038 ldr r2, [pc, #-56] ; a1700044 <_armboot_start>

============

_armboot_start是一个地址0xa1700044,它的值为0xa1700000,上面这句不是你理解的对_armboot_start的引用是直接将其替换为其表示的地址0xa170044,应该是从地址为0xa1700044的地方把值(0xa1700000)读入。不要被GNU反汇编出来的注释麻痹了,呵呵,它是帮你指明pc,#-56指向了哪个地址。

这个道理跟你自己前面作的注释一样,你看看你自己注释的那句:

//r4=[a1701118]=a1700044

它前面那条语句最后的反汇编注释。呵呵,你前面都明白了,为什么最后还要糊涂?

ldr r0, _start   ;从内存地址_start的地方把值读入r0,与位置无关,对于_start这个符号表示的地址是通过相对pc的位移来实现,但ldr的结果是读值。

ldr r0,=_start  ;这个是伪指令,读取_start的绝对地址到r0。这个跟位置有关,编译后一般是通过把表示_start地址的值存入一个位置(地址),然后再通过对这个位置的与位置无关的ldr来实现对_start地址的读取。不管程序在哪里执行,读取的值一定是一样的。

还有一个adr r0, _start的伪指令,是通过add来实现的相对pc的位移读取_start的地址到r0,与位置就无关了。

总的来说,在c代码中如果应用第一阶段汇编代码中定义的符号,变量或常量时实际上是引用的该符号处存放的内容,而不是该符号的地址。所以对于在start_armboot函数中出现的:

gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

monitor_flash_len = _bss_start - _armboot_start;

mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

里面的_armboot_start和_bss_start符号就不难理解拉,在C函数中这些符号代表着他们里面存放着的内容也就是_start和__bss_start值,而不是这两者的绝对地址拉。

而在汇编代码中对于符号的运用也有两种情况:

对于

.globl _armboot_start

_armboot_start: .word _start

ldr r2, _armboot_start //对于这种形式的ldr指令,我们可以看到反汇编出来的代码,实际上就是将_armboot_start存放的内容取出来赋值给r2

而对于

ldr r2,=_armboot_start//对于这种形式的ldr 指令,将_armboot_start的实际绝对地址赋值给r2

其实上面两种说发,表示的是一个意思,只不过深入的程度不同,第一种说法中提到的在汇编代码中对符号赤裸裸的引用,意思是直接将符号的绝对地址拿出来去取值,也就是反汇编后括号[ ]里面存放的直接地址,而第二种说话,只不过就是将从这种地址取到的值赋值出来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: