Chinaunix首页 | 论坛 | 博客
  • 博客访问: 254376
  • 博文数量: 37
  • 博客积分: 480
  • 博客等级: 下士
  • 技术积分: 443
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-13 12:36
文章分类
文章存档

2013年(8)

2012年(29)

分类: LINUX

2012-05-13 12:47:28

过年将近,潜心学习u-boot的实现。毕业设计曾打算做这个题目,不料老师来一句“要做大家都知道的”。其实报上的几个题目中,唯有这个是需要我花些时间的,另两个,如果可以报成功的话,基本上可以动笔写论文了。
想起了以前分析Grub2的体验。那时刚开始用Source Insight,现在倒发现它那种奇特的显示方式竟看着有些奇怪,倒是接受VIM显示了。回忆起来,似乎很遥远,算起来,也不过是半年前的事。想来,AT&T语法就是分析Grub2时强行学会了。那时还用Bochs一条一条地记下反汇编代码,最后钻进命令行的分析里,看着一些以yy开头的函数不知所措,于是放弃了。前几个月才接触到flex&bison,彼时才知道当年倒在什么地方。这让我对编译原理起了兴趣,机工重新影印了龙书,现在买来估计也看不完,去出工作又带不走。索性确定工作地点后再买来看。常听到有人表示上班很无聊,也听过大家上班没事做就聊QQ,斗地主,或者看电影。我倒觉得,未来几年内,要学了东西还有一大堆。如果工作之外尚有余裕,肯定会去研究自己感兴趣的东西,至少不会觉得无聊。只是担心这种激情会被工作磨灭。
买的TQ2440的板子,一直没怎么用,十分惭愧。本打算最后一个寒假好好研究,看看现在的形势,估计也研究不了多少了。让我感觉奇怪的是,U-boot的官方到现在没有支持2440的代码,只有网上零星的一些补丁。下了其中一个,结果在Nand那里卡的天昏地暗,用Skyeye模拟最后居然出现Unknow Nand Command。为了解决这个问题,还研究了好久MTD。后来发现,Skyeye在下断点上有个BUG,断点数超过256个便会出错,而我需定位的代码位于一个巨大的循环之中。一番尝试,始终找不到哪句话引起的Unkonw Nand Command。无奈之下,只得顺着官方支持的2410走。结果发现2410默认的配置里根本没编译进MTD层。。。。。。
像U-boot或者Linux这种支持不同平台的代码,很讨厌的一点是,条件编译太多了。有时候乱了主线,根本不知道哪些该看哪些不该看。以前用Source Insight看代码时也是,查看一个函数,结果出来不平平台的N多结果。现在用VIM当然也免不了这苦,尤其是一些开发板相关的函数,U-boot支持多少开发板就有多少不同选择,有时翻到序号翻到一百多才找到。好在可以用GDB跟踪,最初用GDB只是看看用汇编写成的start.S走向好何。后来发现C语言里的条件编译太多了,用GDB正好可以看看运行的路线。于是GDB一直用到现在。这个过程中还发现了原来GNOME下默认的终端也是可以开TAB的,以前还老惦记着那个KDE的默认终端。
经过两三天的分析,总算顺着执行线路看到Shell这里。考虑到上次看Grub就是倒在这里,此时还颇有些担心,不过心里也在想,就算再出现yy神马的我也不会迷糊了。好在这次并没有看到那些,所有的解析都是手动完成。只在关于查找命令的代码里面,__u_boot_cmd_start及__u_boot_cmd_end神马地看到莫名其妙。尤其是对它们取地址。从代码表面上来看,作者的意图是在一个结构体数组里寻找合适的项,而该数据便以&__u_boot_cmd_start与&__u_boot_cmd_end为首尾边界。查Tag无结果,其实已经猜到了这是在链接脚本里定义的符号。但对一个符号取地址为何意?到底这个数组是怎么来的?
其实以前也有类似疑惑。比如,引用链接器提供的end等符号,声明时却将其声明为extern char end[]。显然做这种奇怪的声明是有意为之。为什么不声明为extern char* end呢?如果声明成指针或者其它的结构体又会有什么效果呢?后来隐隐约约想到一点,因为数据名在链接以后实际上是个表地址的常量,如果声明成其它类型,那么引用end时得到的是那个地址里的内容。
今天又碰到这个类似的问题。以前隐约想明白的事,现在又有些不明白了。显然这里的__u_boot_cmd_start与end一样,为链接器提供。这些符号不像那些变量占内存,完全是空无一物的,对这些符号取地址怎么会有意义呢?做了几个实验,总算是明白了。原来对于C中&这个取址的操作符,我对其印象过于强烈,其操作对象一定要是一块内存。其实,当这个东西编译成指令以后,不再有什么C语言里的性质。即,对全局变量和静态变量,其最终的地址是确定的,所以对&symbol的引用就变成了对一个常数的引用。反映到指令的寻址方式上,就是立即数寻址。如果仅编译不链接,这些地址都是不确定的,指令里含这些信息的话,表地址的那些位全要以0代之,然后在重定位表里声明此处需修正,而等修正的契机便是symbol。
比如,可能会生成这样几句:


   9:    8b 15 00 00 00 00        mov  0x0,%edx
   f:    b8 00 00 00 00           mov  $0x0,%eax
  14:    c7 44 24 08 00 00 00     movl $0x0,0x8(%esp)

第一行反映出对symbol的引用,第三行反映出对&symbol的引用。在链接之后就变成这样:


 80483cd:    8b 15 54 96 04 08        mov  0x8049654,%edx
 80483d3:    b8 c4 84 04 08           mov  $0x80484c4,%eax
 80483d8:    c7 44 24 08 54 96 04     movl $0x8049654,0x8(%esp)

可以看到,之前一三行都是待确定的,现在都已被修正,而且修正为同一个值。关键在于,这两条指令的寻址方式不一样。第一条是直接寻址,即入edx的是0x8049654这个地址下的内存里的内容;第三句是立即数寻址,即,入0x8(%esp)里的是0x8049654这个数值。这个例子是对普通符号操作的话,可能会觉得结果十分理所当然。如,int a后,第一句对应的是对a的引用,第二句对应的是对&a的引用。因为我们确定a代表一块内存,那么&a自然能取到其地址。然而对于链接器提供的符号,并没有一块内存与之对应,&取到的又是什么呢?费解的产生在于过分地从C语言的角度对去解释这个问题,其实上面的汇编里可以看出,对symbol与&symbol进行重修正时,依靠的都是symbol这个符号。也就是说,从“重修正”的角度上来看,两都没有任何区别,修正的类型以及关联的符号一模一样,可以认为是同一个修正的不同实例。然而,链接器修正的时候对指令类型毫无所知,所以即便对两处修正做的都是同一件事,不同指令对其解读便有不同效果。
所以,对于链接器提供的符号symbol,从C语言的角度上,很难理解&symbol的含义。本质上,对&symbol的引用都会被翻译为一条操作一个立即数的指令,而这个立即数的值便是链接器提供给symbol这个符号的值。之前所说将end声明为extern char end[]亦是同理。因为对于编译器而言,全局或者静态数组的首地址是个常数,所以对这个数组名的引用都会被翻译为一条对立即数操作的指令,链接器便依据符号表里的符号对这个立即数进行修正。当然,&作用于全局或者静态变量时得到的值虽是常量,其本身还是特定的指针类型。如果对其进行算数运算,生成的指令还是类型相关的。但在链接时,类型已经反映在了生成的指令上。
那u-boot的那些个关于命令的结构体到底从何而来呢?在include/command.h里定义了这样的宏:


#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))


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


#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \


cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

第二个宏展开便定义了一个代表u-boot命令的结构体。也就是说定义了一个变量,并赋与其初值。关键在于Struc_Section,它被展开后成了__attribute__ ((unused,section(".u_boot_cmd")))。也就是说,每个变量(占内存!)都放在.u_boot_cmd段里。它们还被加上了unused的属性,应该是为了平息编译器的警告。确实,没有任何代码引用过它们!!如果这些变量被放在一个显示声明的数组里,那么,每增加一个命令都得去更改数组的定义。然后,通过这种方法,各人想要增加新的命令时,只需用上面的这个宏即可。这些表示命令的结构体被统一放在.u_boot_cmd段里,链接脚本里又有:

__u_boot_cmd_start = .; 

.u_boot_cmd : { *(.u_boot_cmd) } 

__u_boot_cmd_end = .;

即,把所有待链接文件里u_boot_cmd段合并在一起生成一个大的.u_boot_cmd段,并且用__u_boot_cmd_start与__u_boot_cmd_end两个符号标识这块内存的首尾边界。这样,不用大家去修改某一文件的代码(为了改变数组的定义),而是像数据库一样,各个提供自己的信息。借由编译器与链接器生成这个“数据库”,操作数据时对“数据库”进行查询即可。正是一个月前看《计算机程序构造与解释》时看到的“Data Direct”(好像是这么说)。在Grub及内核里也用了这样机制。
阅读(1622) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:使用initramfs启动Linux

给主人留下些什么吧!~~