uboot环境中运行HELLOWORLD

源起

手上这块7621板子,万一UBOOT代码有BUG,启不来了,因为没有烧写器在手上,或者有但拆FLASH困难,都只能罢工了。
所以,为了调试UBOOT,最好有一种方法能够在不改FLASH中UBOOT的代码的前提下,运行加载到内存中的UBOOT程序。
既可以直接运行UBOOT中的函数,又可以基于UBOOT的HELLOWORLD,由它来进一步进行复杂一点的函数调用。
UBOOT和HELLOWORLD程序还是物理上以两个BIN存在。这样,只需改动很小的HELLOWORLD,就可以实验。

为什么不仅仅是HELLOWORLD程序呢?
因为那没意义,光HELLOWORLD程序运行起来,只能说明二进制确实是做对了。还不够。
但HELLOWORLD和UBOOT都能跑起来,HELLOWORLD能够随意调用UBOOT的接口,那就强大了,这才相当于有了一个完整的操作系统,HELLOWORLD相当于在这个完整的操作系统上运行了,有GLIBC可以用了,有MEMCPY操作内存了,还有现成的BOOTM函数可以调用了。
这样一来,那不就可以验证自己版本的BOOTM函数了吗?觉得调用起LINUX的过程有疑问或者想跳过某些检查,那就用HELLOWORLD来调用新的DO_BOOTM_LINUX函数试试,这岂不是大大提高了调试的效率吗?

HELLOWORLD程序来源与编译

UBOOT会自带有一个EXAMPLE STANDALONE目录,里面就有hello_world.c源码。
它使用了UBOOT的PRINTF来实现打印。
还调用了UBOOT代码中的get_version()接口。
可见,这个UBOOT的HELLOWROLD满足前面的需求。能调用了PRINTF或者get_version,那,do_bootm_linux不是一样可以调吗?
这是UBOOT系统自带的程序,因此,编译UBOOT时,会在example/standalone/hello_world.bin同时生成这个文件。
通过研究分析Makefile可以看出,这个BIN文件是这样生成的:
首先,.c变.o , 经过gcc -c 是必然的。也很好理解。
其次,.o要用ld来链接,变成ELF。平时,我们在LINUX下编译一个应用程序,是不会用LD的,直接gcc -o 就OK了。但UBOOT下写应用程序不一样,这里为了变成ELF,不能用GCC ,要用LD,要指定基地址-Ttext=0x80200000 ,而且还要指定lds文件,-T example/standalone/mips.lds, 这个mips.lds文件干什么用的呀?a linker script,描述了链接时的参数。由于它的重要性,这里进一步展开讲述:

 1 /* SPDX-License-Identifier: GPL-2.0+ */
  2 /*
  3  * (C) Copyright 2003
  4  * Wolfgang Denk Engineering, <wd@denx.de>
  5  */
  6
  7 /*
  8 OUTPUT_FORMAT("elf32-bigmips", "elf32-bigmips", "elf32-bigmips")
  9 */
 10 OUTPUT_FORMAT("elf32-tradbigmips", "elf32-tradbigmips", "elf32-tradlittlemips")
 11 OUTPUT_ARCH(mips)
 12 SECTIONS
 13 {
 14     .text       :
 15     {
 16       *(.text*)
 17     }
 18
 19     . = ALIGN(4);
 20     .rodata  : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 21
 22     . = ALIGN(4);
 23     .data  : { *(.data*) }
 24
 25     . = .;
 26     _gp = ALIGN(16) + 0x7ff0;
 27
 28     .got : {
 29       __got_start = .;
 30       *(.got)
 31       __got_end = .;
 32     }
 33
 34     .sdata  : { *(.sdata*) }
 35
 36     . = ALIGN(4);
 37     __bss_start = .;
 38     .sbss (NOLOAD) : { *(.sbss*) }
 39     .bss (NOLOAD)  : { *(.bss*) . = ALIGN(4); }
 40
 41     _end = .;
 42 }

解析如下:
10行:OUTPUT_FORMAT是什么意思呢?
GNU的文档中,是这么定义的:
OUTPUT_FORMAT(default, big, little),在链接的时候,如果使用了-EB的命令行参数,则使用这里的big参数指定的字节序,如果使用了-EL的命令行参数,则使用这里的little参数指定的字节序,如果没有使用任何命令行参数,则使用这里的default参数指定的字节序。
由mips.lds中的定义OUTPUT_FORMAT(“elf32-tradbigmips”, “elf32-tradbigmips”, “elf32-tradlittlemips”)可见,LD链接时,缺省采用的是elf32-tradbigmips大端字节序。
11行:这是目标体系,7621是mips体系,与常见的另一种以小端字节序的mipsel相对,要注意区分。
12行:SECTIONS 是什么。这就是LDS中的各段描述。
它的完整定义是:

SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}

secname和contents是必须的,其他的都是可选的。
start参数是指定在哪个地址运行
AT指定在哪个地址加载
如果省略AT,那加载地址等于运行地址。
比如:
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
head.o和init.o按顺序放到0x00000000的加载地址,运行也是这个地址。
main.o放在以0X30000000偏移4096字节处

现在开始SECTIONS,编译之后的BIN文件里,顺序存放的内容分别是(可以打开二进制hello_world.bin对照十六进制内容)
1、14-18行解析
这是一个标准代码段
(.text) ;表示所有的代码部分
2、19行解析
19 . = ALIGN(4);
这是里指定一个.地址,表示当前地址,为当前地址赋值一个4字节对齐的值,再开始下一SEC部分内容。
3、20行 这也是一个标准.rodata 只读数据段
4、22行,. = ALIGN(4); 修改当前地址为4字节对齐后的地址
5、23行,这也是一个标准.data 数据读写段。
6、25行,当前地址更新,不对齐 . = .;
7、26行,_gp = ALIGN(16) + 0x7ff0; 这是一个赋值语句,把当前地址按16字节对齐之后,加上偏移值7FF0后,赋给名为_gp的符号,这个符号在代码中可引用,是等价于printf这样的一个有效符号。
8、紧接着放got段,这是UBOOT特有的非标准段。
9、标准.sdata段
10、36行,4字节对齐
11、37行,在经过4字节对齐之后,这里定义了一个标准的__bss_start符号,等于对齐后的地址。
在hello_world.c中一开始就会找到这个地址,进行清零。
注意__bss_start的地址是这里定义的,不是uboot/System.map 总System.map中定义的__bss_start。
12、.sbss标准段,NOLOAD含义是该段在程序运行时,不被载入内存。但是空间的地址都是保留了的。hello_world运行时,有相应的内存清零工作,完成它的初始化
13、指定.bss标准段。
14、把_end赋值为当前位置,即bss段的结束位置

LD执行实质:

cat .hello_world.cmd
cmd_examples/standalone/hello_world := mipsel-openwrt-linux-musl-ld.bfd   -EL -m elf32ltsmip -G 0 -static -n -nostdlib -g -Ttext 0x80200000 -T ./examples/standalone/mips.lds -o examples/standalone/hello_world -e hello_world examples/standalone/hello_world.o examples/standalone/libstubs.o arch/mips/lib/lib.a
看到这里把lib.a链了进来吧,这使得printf的调用成为可能。

这时输出hello_world
用objdump观察

$ mipsel-openwrt-linux-objdump -d hello_world

hello_world:     file format elf32-tradlittlemips


Disassembly of section .text:

80200000 <hello_world>:
80200000:       27bdffd8        addiu   sp,sp,-40
80200004:       afb3001c        sw      s3,28(sp)
80200008:       00809821        move    s3,a0
8020000c:       00a02021        move    a0,a1
80200010:       afbf0024        sw      ra,36(sp)
80200014:       afb40020        sw      s4,32(sp)
80200018:       3c148020        lui     s4,0x8020
8020001c:       afb20018        sw      s2,24(sp)
80200020:       3c128020        lui     s2,0x8020
80200024:       afb10014        sw      s1,20(sp)
80200028:       00008821        move    s1,zero
8020002c:       afb00010        sw      s0,16(sp)
80200030:       0c0800c6        jal     80200318 <app_startup>
80200034:       00a08021        move    s0,a1
80200038:       0c080040        jal     80200100 <dummy>
8020003c:       26520398        addiu   s2,s2,920
80200040:       3c048020        lui     a0,0x8020
80200044:       00402821        move    a1,v0
80200048:       0c080054        jal     80200150 <printf>

为了在UBOOT上能够运行,做成ELF还不够,这个ELF文件是不能直接运行的,要用OBJCOPY剥掉这个ELF头,才是最终UBOOT上运行的代码hello_world.bin
其执行实质是:

cat .hello_world.bin.cmd
cmd_examples/standalone/hello_world.bin := mipsel-openwrt-linux-musl-objcopy -O binary  examples/standalone/hello_world examples/standalone/hello_world.bin

hello_world.bin的加载运行

两步:
1、先加载同时编译出来的uboot.bin,为什么啊?这相当于要给HELLOWROLD准备好PRINTF等函数所在的库啊。
2、再加载hello_world.bin,而且是必须加载到相同的首地址。为什么啊?这是准备要通过go指令去执行的程序啊。
go指令跳到hello_world.bin所加载的首地址之后,里面的代码会进一步引用了uboot.bin的地址。
在hello_world.bin生成的时候,是指定过基地址的,才能将printf的符号转变成以基地址为基准的绝对地址。
在运行时,如果这个要找的PRINTF绝对地址处没有PRINTF的代码,那当然就不能执行了呀。
所以才有把UBOOT.BIN先加载到同一个基地址的要求。
这种往基地址写两份代码的做法,存在一个缺陷,就是重合部分的UBOOT代码功能丢失了。
好在HELLOWORLD很小,这部分代码不多,也就影响不大。

执行过程:

Hit any key to stop autoboot:  0

  *** U-Boot Boot Menu ***

     1. Startup system (Default)
     2. Upgrade firmware
     3. Upgrade Factory
     4. Upgrade bootloader
     5. Upgrade bootloader (advanced mode)
     6. Load image
     0. U-Boot console


  Press UP/DOWN to move, ENTER to select
=> tftp 80200000 boot.bin
Using eth@1e100000 device
TFTP from server 192.168.100.191; our IP address is 192.168.100.3
Filename 'boot.bin'.
Load address: 0x80200000
Loading: ###################
         6.6 MiB/s
done
Bytes transferred = 269960 (41e88 hex)
=> tftp 80200000 hello_world.bin
Using eth@1e100000 device
TFTP from server 192.168.100.191; our IP address is 192.168.100.3
Filename 'hello_world.bin'.
Load address: 0x80200000
Loading: #
         507.8 KiB/s
done
Bytes transferred = 1040 (410 hex)
=> md 80200000
80200000: 27bdffd8 afb3001c 00809821 00a02021    ...'....!...! ..
80200010: afbf0024 afb40020 3c148020 afb20018    $... ... ..<....
80200020: 3c128020 afb10014 00008821 afb00010     ..<....!.......
80200030: 0c0800c6 00a08021 3c048020 24050009    ....!... ..<...$
80200040: 2484034c 0c080054 265203a8 0c080040    L..$T.....R&@...
80200050: 00000000 3c048020 00402821 0c080054    .... ..<!(@.T...
80200060: 2484036c 3c048020 0c080054 2484038c    l..$ ..<T......$
80200070: 3c048020 02602821 0c080054 2484039c     ..<!(`.T......$
80200080: 0271102a 1440000a 3c048020 8e060000    *.q...@. ..<....
80200090: 50c00001 26860344 02202821 0c080054    ...PD..&!( .T...
802000a0: 02402021 26310001 1000fff5 26100004    ! @...1&.......&
802000b0: 0c080054 248403bc 0c080048 00000000    T......$H.......
802000c0: 1040fffd 00000000 0c080044 00000000    ..@.....D.......
802000d0: 3c048020 0c080054 248403d8 00001021     ..<T......$!...
802000e0: 8fbf0024 8fb40020 8fb3001c 8fb20018    $... ...........
802000f0: 8fb10014 8fb00010 03e00008 27bd0028    ............(..'
=> go 80200000
## Starting application at 0x80200000 ...
Example expects ABI version 9
Actual U-Boot ABI version 9
Hello World
argc = 1
argv[0] = "80200000"
argv[1] = "<NULL>"
Hit any key to exit ...

## Application terminated, rc = 0x0
=>

常见问题

1、运行hello_world时异常Ooops

=> tftp 80100000 boot.bin
Using eth@1e100000 device
TFTP from server 192.168.100.191; our IP address is 192.168.100.3
Filename 'boot.bin'.
Load address: 0x80100000
Loading: ###################
         6.6 MiB/s
done
Bytes transferred = 269960 (41e88 hex)

=> tftp 80100000 hello_world.bin
Using eth@1e100000 device
TFTP from server 192.168.100.191; our IP address is 192.168.100.3
Filename 'hello_world.bin'.
Load address: 0x80100000
Loading: #
         507.8 KiB/s
done
Bytes transferred = 1040 (410 hex)
=> md 80100000
80100000: 27bdffd8 afb3001c 00809821 00a02021    ...'....!...! ..
80100010: afbf0024 afb40020 3c148020 afb20018    $... ... ..<....
80100020: 3c128020 afb10014 00008821 afb00010     ..<....!.......
80100030: 0c0800c6 00a08021 3c048020 24050009    ....!... ..<...$
80100040: 2484034c 0c080054 265203a8 0c080040    L..$T.....R&@...
80100050: 00000000 3c048020 00402821 0c080054    .... ..<!(@.T...
80100060: 2484036c 3c048020 0c080054 2484038c    l..$ ..<T......$
80100070: 3c048020 02602821 0c080054 2484039c     ..<!(`.T......$
80100080: 0271102a 1440000a 3c048020 8e060000    *.q...@. ..<....
80100090: 50c00001 26860344 02202821 0c080054    ...PD..&!( .T...
801000a0: 02402021 26310001 1000fff5 26100004    ! @...1&.......&
801000b0: 0c080054 248403bc 0c080048 00000000    T......$H.......
801000c0: 1040fffd 00000000 0c080044 00000000    ..@.....D.......
801000d0: 3c048020 0c080054 248403d8 00001021     ..<T......$!...
801000e0: 8fbf0024 8fb40020 8fb3001c 8fb20018    $... ...........
801000f0: 8fb10014 8fb00010 03e00008 27bd0028    ............(..'
=> go 80100000
## Starting application at 0x80100000 ...

Ooops:
$ 0   : 00000000 00000000 0000002a be000c00
$ 4   : 00000001 8fe7ef0c 8fe7ef0c 00000001
$ 8   : 00000000 00000004 8ffe1bd8 0000000f
$12   : 8fe7ebf8 00000004 00000002 00000002
$16   : 00000002 8fe7ef08 80100000 8fe7ef08
$20   : 8fe7ef4c 00000002 00000020 00001000
$24   : 00000200 80100000
$28   : be10a818 8fe7eca0 80007b40 8ffa4a60
Hi    : 00000000
Lo    : 00000004
epc   : 80100000 (text 70360000)
ra    : 8ffa4a60 (text 80204a60)
Status: 00000006
Cause : e000802c (ExcCode 0b)
PrId  : 0001992f
### ERROR ### Please RESET the board ###

这个原因是go跳转地址是80100000,不等于helloworld的链接地址80200000,所以无法执行。

2、运行hello_world时异常也是Ooops
虽然 地址对,但uboot.bin没有TFTP到指定地址,也会出现OOP,因为要调用的函数不存在。此时OOP的EPC会指向要调用的函数的地址。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值