在嵌入式编程领域,开发者们常遇到各种各样的问题。这些问题包括设置代码地址和库函数使用中的潜在风险等,这些都给开发者带来了困扰。
默认下载地址的考量
在开发阶段,过去我们通常采用u-boot的预设下载位置,这背后有着特定的考虑。这样的设定是为了让u-boot引导内核更加便捷,因为内核必须被下载至内存的特定区域。然而,对于那些不依赖u-boot引导内核功能的软件,这种地址的限制就显得多余,可以选择其他更为灵活的地址。比如,在某个公司开发物联网嵌入式设备的过程中,最初按照默认地址配置,结果导致内存使用不均衡,经过调整后问题才得到解决。
选择地址不能只照搬常规,还需结合程序的具体要求。各种程序架构对内存的需求各异,需根据具体情况来决定地址。
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'
start.S中的判断逻辑
start.S文件中有一段关于程序返回值检查的代码。程序复制完成后,会检查r0寄存器的值是否为零。若不为零,程序将陷入无限循环。按照AAPCS标准,C语言程序返回值应存储在r0寄存器中。在此环境下,若没有返回值,则需要手动添加。在众多小型嵌入式控制器开发项目中,若忽视这一判断逻辑,程序可能会出现停滞不前的状况。
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'
开发人员需熟知编码准则,对AAPCS的规定要透彻领会,切不可因疏忽大意而忽略诸如返回值等关键要素,否则程序运行时将可能出现错误。
nand.c的链接位置
$(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文件
nand.c文件在程序中的接入有特定位置要求,必须确保其位于程序的前4k区域。启动过程中,系统只会将前4K的数据复制至芯片的SRAM。若nand.c的读写代码不在这一4k区域内,程序拷贝将无法完成。例如,一位学生在进行嵌入式存储系统实验时,因未留意这一4k限制,导致nand存储持续出现错误。
开发者需对程序启动的初始化环节有深入把握,操作链接时需特别留意代码的布局,确保程序能够顺利运行。
标准C库函数的移植
/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扩展的库。
在将内置库函数引入项目中时,常常会遇到麻烦,比如直接将相应的.c文件加入后却无法正常运行。在构建支持除法和浮点运算的库时,链接过程中,链接器无法找到必要的库,从而出现错误提示。此外,当某个函数调用标准C库的atoi函数时,因为找不到该符号,系统显示在链接过程中未能找到标准C库。
这说明在使用arm-linux-gcc进行开发的过程中,其使用的链接器自带一些预设的选项,比如默认查找库的路径和默认要链接的库。这无疑对开发者对环境配置的知识提出了更高的要求,开发者需要了解库的存放位置等相关信息。
两种链接方式的探索
/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'
在开发阶段,存在两种连接方法需要研究。以开发环境为参照,第一种方法在默认设置下会产生输出,且无需指定参数,用户需自行设定库的路径和进行库的链接。第二种方法采用ld链接,需要在指定库路径和库名后执行make操作。当遇到格式化字符串转换函数的函数时,即便链接器能够找到c库,两种连接方法都会出现相同的错误提示。
开发者在使用各种连接方法时,应当细致检查错误源头,是库的问题还是其他原因,不可简单以为有了库就不会出错。
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
标准C库使用的考量
标准C库虽便利,但在arm-linux-gcc环境下进行裸机编程时,不可随意依赖。并非所有函数都适用,且可能遇到符号无法找到的问题。例如,u-boot就不依赖标准C库,而是自行实现相关函数以确保其可移植性。在开发过程中,我们往往不清楚哪些函数不可用,因此减少使用标准C库函数是确保程序安全的一种方法。在嵌入式开发中,你有没有因误用库函数而遭遇过困境?欢迎在评论区分享你的经历,并请为这篇文章点赞和转发。