NANDFLASH启动与标准库问题
把u-boot的start.S移植到我的程序上,这样程序可以用supervivi的D功能下载到内存中运行了,但是还不够。因为程序在内存里,如果掉电程序就没有了,所以我们得将程序固化在flash里面。这里我们要固化在NandFlash里,这就要求程序可以能够NandFlash启动。这里我参考了mini2440的nandlfash读写程序nand.c,里面有一个函数CopyProgramFromNand就是将Nandflash里的程序复制到内存里。在这之前我一直用u-boot默认的下载地址0x33f80000,这个是为了u-boot引导内核方便而定的,因为内核要下载到前面的内存中,既然我的程序没有这个功能,那下载到0x30000000里就可以了。把start.S中的TEXT_BASE改成0x30000000就可以了。还有在start.S里,复制完程序判断r0是否为零,如果不是那么程序就会进入死循环。根据AAPCS,C程序返回的值就保存在r0里,这里是判断程序是否返回了0,在CopyProgramFromNand里默认没有返回值,所以要在最后加上返回值。接下来就是消除编译错误。将nand.c链接到我们的程序里,注意一定要保证在程序的前4k的位置范围内,因为Nandflash启动的时候,S3C2440将Nandflsh的前4K的内容复制到芯片内部的SRAM里,如果NandFlash的读写程序不在这4k的代码里就无法完成程序的拷贝。
编译好的程序在我的下载资源里:http://download.csdn.net/detail/yaozhenguo2006/3752515
Nandflash启动进行的还算顺利,修改之后下到nandflash里,然后开发板切到nandflash启动,程序正常运行。这里我注意到流水灯不像下载到内存中运行的那样立即就运行,而是等了一段时间,说明拷贝代码用了一些时间。接下来就是要移植mini2440自带库函数了,对应的文件是2440lib.c,我本来以为简单的加到Makefile里就会成功。可是事情不是想象的那样简单,当编译的时候出现了一大堆错误。这些错误刚看起来都莫名奇妙,而且不是编译的错误,都是链接的错误。编译的时候没错说明不是语法错误,链接错误说明是库出了问题。主要提示的错误信息如下:2440lib.o: In function `Uart_Init':
2440lib.c:(.text+0x370): undefined reference to `__aeabi_i2d'
2440lib.c:(.text+0x390): undefined reference to `__aeabi_ddiv'
2440lib.c:(.text+0x3a8): undefined reference to `__aeabi_i2d'
2440lib.c:(.text+0x3c4): undefined reference to `__aeabi_ddiv'
在Uart_Init这个函数中,主要用了一些浮点数的运算和除法的运算,我们知道armv4指令集是没有除法和浮点运算指令的,所以必须软件模拟。编译的时候模拟除法与浮点运算需要的库,在链接的时候链接器没有找到,所以出现了这个问题。为了验证我的猜测,我把浮点数运算以及除法运算都注释掉,错误提示就没有了。说明就是没有找到浮点运算以及除法运算的库。
2440lib.o: In function `Uart_GetIntNum':
2440lib.c:(.text+0x994): undefined reference to `strlen'
2440lib.c:(.text+0xa20): undefined reference to `atoi'
2440lib.c:(.text+0xa5c): undefined reference to `__ctype_b_loc'
2440lib.c:(.text+0xa90): undefined reference to `__ctype_b_loc'
2440lib.o: In function `Uart_Printf':
2440lib.c:(.text+0xd90): undefined reference to `vsprintf'
在Uart_GetIntNum这个函数中主要调用了标准C库中的strlen和atio函数,找不到符号,说明链接的时候没有找到标准C库。Uart_Printf也应该是同样的错误。
$(CC) -static -Wl,-Tboot.lds,-Map,system.map -nostartfiles -o boot.elf $(objs)
CC 这里就是arm-linux-gcc
-Wl,-Tboot.lds,-Map,system.map -Wl表示后面的参数是传递给链接器的,参数以逗号分割。-Tboot.lds 是指定的链接脚本,规划了程序在内存中的位置 -Map,system.map 这是链接后生成的符号表
-nostartfile 不让链接器加入默认启动代码,如果不加这个选项,链接器就会默认加入一个启动代码,因为我们要链接自己的启动代码,当然不需要它默认的了。
-o boot.elf 最后生成的elf格式的文件。
$(objs) 我们编译的.o文件
用以上形式链接程序,arm-linux-gcc会调用collect2链接器,这个链接器有一定的默认选项,比如默认寻找库的路径,默认链接的库。通过给arm-linux-gcc 加上-v 选项,我们可以查看他的默认行为。现在以我的开发环境为例,链接的时候会有如下的输出:
/home/sun/study/crosstools/4.4.3/bin/../libexec/gcc/arm-none-linux-gnueabi/4.4.3/collect2
从这个输出中,我们可以发现,arm-linux-gcc调用了collect2。
--sysroot=/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root
-Bstatic -dynamic-linker /lib/ld-linux.so.3 -X -m armelf_linux_eabi -o boot.elf
sysroot暂时没有发现是做什么的,-Bstatic 很显然是静态链接
-L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3
-L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc
-L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3/../../../../arm-none-linux-gnueabi/lib
-L/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root/lib
-L/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root/usr/lib
以上就是collect2默认寻找的库路径
-Tboot.lds -Map system.map
传递过来的链接器参数
start.o lowlevel_init.o nand.o interrupt.o main.o 2440lib.o print.o
要链接的.o文件
--start-group -lgcc -lgcc_eh -lc --end-group
这个选项很重要,链接器链接的静态库。-lc 代表链接标准c库, -lgcc 代表要链接libgcc.a,这个库应该是gcc扩展的库。
下面说一下另外一种链接方式,就是用ld命令来链接,这里就是arm-linux-ld,这种方式也就是我链接出错的链接方式。这种链接方式,没有默认的参数,寻找库的路径以及链接库都要自己添加。之所以链接出错就是因为我没有指定链接的库以及寻找库的路径。既然如此,那么使用第一种链接方式链接我的程序就应该是没错的。我将Makefile修改了一下再make,还是有错,令人欣喜的就是错误减少了,就只剩下如下的这一种错误:
/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3/libgcc_eh.a(unwind-arm.o): In function `get_eit_entry':
/opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/libgcc/../gcc/config/arm/unwind-arm.c:673: undefined reference to `__exidx_end'
/opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/libgcc/../gcc/config/arm/unwind-arm.c:673: undefined reference to `__exidx_start'
经过分析我认为是Uart_Printf函数里调用vsprintf的缘故,将Uart_Printf注释掉,然后再make。链接就通过了,说明就是这个vsprintf的问题。放下这个问题先不管,我试一下ld这种链接方式,在加上寻找库的路径以及要链接的库名的情况下是否可以成功make。修改Makefile如下:
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
objs := start.o lowlevel_init.o main.o nand.o 2440lib.o
boot.bin: $(objs)
$(LD) -Bstatic -Tboot.lds -Ttext 0x33F80000 $(objs) \
-L/home/sun/study/crosstools/4.4.3/lib/gcc/arm-none-linux-gnueabi/4.4.3 \
-L/home/sun/study/crosstools/4.4.3/arm-none-linux-gnueabi/sys-root/usr/lib \
-Map boot.map -o boot.elf --start-group -lgcc -lgcc_eh -lgcov -lc --end-group
$(OBJCOPY) -O binary boot.elf boot.bin
%.o:%.c
$(CC) -Wall -c -o $@ {1}lt;
%.o:%.S
$(CC) -Wall -c -o $@ {1}lt;
clean:
rm -f *.bin *elf *.o
用以上的Makefile,make通过了。程序下载到板子上现象也是正确的。这说明ld链接器正确链接了程序。现在两种链接方式都可以正确的链接程序了。那么就剩下一个问题,就是Uart_Printf函数里vsprintf。这个函数是一个格式化字符串转换函数,应该是标准C库里的函数,在链接器能够找到c库的情况下,两种链接方式都提示同样的错误。我猜测vsprintf这里面还调用了其他库的函数,我找了很久也没有找到到底调用了什么库。打印函数对于程序调试是必须的。既然vsprintf不能使用,那么就自己实现一个。参考u-boot的printf的实现,我自己编写一了一个vsprintf,然后结合Uart_SendString实现了串口打印功能。
void myprintf(char *fmt,...)
{
va_list ap;
char string[256];
va_start(ap,fmt);
myvsprintf(string,fmt,ap);
Uart_SendString(string);
va_end(ap);
}
va_list 其实就是*char类型,va_list ap 也就是开始定义了一个char类型的指针变量ap。 va_start是一个宏,作用就是取得fmt指针地址,并跳过这个地址赋值给ap,这样ap就指向了除了fmt指针的第一个可变参数在内存中的地址,然后通过myvsprintf(string,fmt,ap)对fmt字符串结合可变参数进行转化,并把转化的结果赋值给string,最后通过Uart_SendString()函数将字符串发送给串口。具体实现可以看我的源代码。在我的资源里 http://download.csdn.net/detail/yaozhenguo2006/3774535