李迟按:学计算机的人不应该对二进制反感,因为计算机只认识0和1,但面对许多枯燥的0和1,有些人望而却步。我大约是大三、大四时候开始强迫自己认真对待二进制的,最早应该是研究SD卡时候,其时主要研究FAT表,使用WinHex工具;后来研究网络协议,主要是ENC28J60芯片,代码是国外一个组织写的,基于AVR芯片,这个实践算是理论结合实践。再后来,慢慢对底层的东西感兴趣,不断看,研究,心里老想着数据在内存中如何分布的(gdb是个强大工具)。现在,对于研究某个文件的结构,直接分析二进制数据是最好的方法。
因此,建议看到此文的同志,不妨对自己狠点,过了这个坎,后面将是一马平川。正如那些说学英文很难的同志,如果肯下点苦功(辛苦的“苦”),相信勤能补拙,定会有收获。然而,作为当局者的我们,又谈何容易?
如果在./arch/arm/lib/board.c文件中定义了DEBUG宏的话,在u-boot启动时就会打印u-boot映像在内存中地址以及其它一些信息。如:
1 | -Boot 2010.09-svn40 (Mar 06 2011 - 13:15:44) |
可以看到u-boot在内存中的地址为33F80000。这个地址可以在相应的开发板(我的是smdk2440目录)中的config.mk文件中找到说明。在编译生成的System.map文件中第一行为:33f80000 T _start
使用md.b查看这个地址,可以看到:
1 | LATE2440 $ md.b 33f80000 |
如果使用UltraEdit打开编译生成的u-boot.bin文件,可以发现前面4行的内容是一样的。因为u-boot启动时将自己搬到了33f80000这个地址了。 在很久以前我写了个测试u-boot内存分布的例子:[u-boot移植随笔:u-boot的内存分布图]/porting-uboot/u-boot-porting-memory-allocation.html),里面将gd_t和bd_t结构体的地址打印出来了。——其实,前天遇到那个未定义指令的错误中就包含有大量有用的信息。
1 | undefined instruction |
在代码中经常看到一个宏:
1 | DECLARE_GLOBAL_DATA_PTR; |
它就是将gd_t结构体地址放到寄存器r8中,这里看到r8的内容是33f4ffe0(至于其它寄存器的内容,这里不说了)。好,查看一下这个地址:
1 | LATE2440 $ md.b 33f4ffe0 /*gd_t结构体的地址*/ |
我们知道,bd_t是一个结构体(这是废话)。它的定义如下:
1 | typedef struct global_data { |
它的sizeof为32,这在前面的文章中证明了——4(大小)*8(成员) = 32。
现在就对照一下前面看到的地址中的内容,看看这个结构体变成了怎么样了(ARM默认为小端模式(?),而看到的地址是增长的,因此看到的数据都是反过来的)。
1、bd:指向bd_t的指针。c4 ff f4 33=>0x33f4ffc4,即bd_t结构体的地址(这里,指针占4字节)。
2、flags:标志,说明见下。03 00 00 00=>3,即flags为3,gd->flags |= GD_FLG_RELOC(宏定义,值为1)(board.c),gd->flags |= GD_FLG_DEVINIT(宏定义,值为2)(console.c),它们分别表示代码已经加载(relocate)到RAM、设备已经初始化好了。
3、baudrate:波特率。00 c2 01 00=> 0x1c200,即115200(dec),表示波特率为115200。
4、have_console:是否有控制台(终端,或串口之类的),01 00 00 00=>1,表示调用了serial_init函数。
5、env_addr:环境变量的地址,0c 00 f5 33=>0x33f5000c,环境变量结构体地址(就是看到的bootxxx那个地址)。
6、env_valid:环境变量checksum有效乎?01 00 00 00=>1,环境变量checksum有效。
7、fb_base:frame buffer的基地址,00 00 00 00=>0,暂时没有研究。
8、jt:一个jump table,50 00 f6 33=>0x33f60050,jump table有待研究。(查看这个地址得到许多其它的“地址”,见下)
(注:关于env_addr和env_valid在代码中的用法,见./common/env_common.c中代码。)
我们再来看看bd_t结构体地址:
1 | LATE2440 $ md.b 33f4ffc4 |
它的定义如下:
1 | typedef struct bd_info { |
它的sizeof为28,同样在前面的文章中证实了。
我们也分析一下:
1、bi_baudrate:波特率,00 c2 01 00=> 0x1c200,即115200(dec),表示波特率为115200。
2、bi_ip_addr:c0 a8 01 c8=> 十进制为:192 168 1 200,即服务器IP地址(注意:网络为大端模式)。
gd->bd->bi_ip_addr = getenv_IPaddr (“ipaddr”);(board.c)
3、bi_env:环境变量?00 00 00 00=> 0,没研究过。
4、bi_arch_number:machine type id。f0 03 00 00=>0x000003f0,开发板机器ID,即1008(MACH_TYPE_SMDK2440),gd->bd->bi_arch_number = MACH_TYPE_SMDK2440; (smdk2440.c)
5、bi_boot_params:准确地说,这是启动参数的地址,00 01 00 30=>0x30000100,gd->bd->bi_boot_params =0x30000100;(smdk2440.c),与内核中需要一致。
6、start:内存起始地址,00 00 00 30=>0x30000000,RAM起始地址,即0x30000000。
7、size:内存大小,00 00 00 04=>0x04000000,RAM大小,64MB,来自config.mk文件的:
SMDK2410(SMDK2440亦一样) has 1 bank of 64 MB DRAM
8、04 c4 ff f4 33 就是gd_t结构体的地址了,前面分析过了。
其实不用这么麻烦的,直接在u-boot下输入db就可以查看开发板的一些信息了(看代码就知道,其实两种方法没本质的区别)。
1 | LATE2440 $ bd |
前面说到的jump table的地址内容是这样的:
1 | LATE2440 $ md.b 33f60050 |
里面就是一些33f8xxxx地址,具体的,查了也没什么发现。jump table留到以后有机会再看看。
这次的实验,加上以前画的那张图,结合起来,可以对u-boot在内存中分布有一些了解了。
补记(2011-3-20): 前面说的大端小端模式,有必要澄清一下:一个字节是最小的单位,因此它不可能存在大端小端的问题,这些模式只针对大于一个字节以上的数据,比如我们看到的地址是4个字节(32bit),因此md.b看到的数据就要反过来,但是像环境变量这些可读字符,不能用这个规则来读,即我们看到的、在内存显示的,就是它们实际的形式。
2011-3-22记:
经过初步测试,发现jump table里面的地址是一些常用函数,或者称为库函数,如33f8c824地址是get_version函数。其它的如serial_getc、serial_puts、printf、malloc、free、getenv、setenv、i2c_write、i2c_read,等等。参见exports.c、exports.h和_exports.h等等文件。
木草山人于3.07