现在的位置: 首页 > 综合 > 正文

arm-linux-gcc 裸机程序开发(二)

2013年09月09日 ⁄ 综合 ⁄ 共 5970字 ⁄ 字号 评论关闭

        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也应该是同样的错误。
        对于以上两种问题都是库的问题。既然链接器找不到相关的库。我们就加上一定的链接选项让他找到。在做这个之前我们先了解一下,gcc开发环境下程序链接的两种方式。第一种使用gcc自带的链接功能。以arm-linux-gcc来说明,这种链接方式命令基本形式如下

$(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实现了串口打印功能。
       简单的说一下实现printf的方法,首先要解决函数可变参数的问题,printf(char *fmt,...)显然是一个可变参数函数,第一个参数为字符串,后面是格式化输出参数列表。c语言中函数的参数都是压进栈里的,可变参数函数必须有一个参数表示参数的个数,才能让编译器知道要压进栈多少参数,以及函数返回时弹出多少参数,printf(char *fmt,...)实现这个功能的就是fmt字符串,里面有多少'%',就代表后面有多少个参数,所以我们必须提取出fmt字符串中'%'的个数,以及针对'%'后面不同的字符来处理参数。printf实现类似如下:

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
        标准C库的问题就算暂时解决了,不过还是给我提了一个醒,在arm-linux-gcc上开发裸机程序,如果用到标准C库一定要注意,不是所有的函数都可以使用,比如vsnprintf就不能使用。其他还有什么函数不能使用还是个未知数,所以尽量不用标准C库的函数才是保证程序安全的办法。典型的例子就是u-boot,它就没有使用标准c库,所用的相关函数都是自己实现的,这样就保证u-boot很强的可移植性。也许arm-elf-gcc会没有这个问题,毕竟链接的是ulibc库,专门针对嵌入式的,以后有机会一定要验证一下。

抱歉!评论已关闭.