目录
这篇文章记录u-boot加载调试的过程,包括ds的使用方法,fvp的使用方法,以及u-boot的修改点等。
关闭u-boot编译优化
代码编译的过程中如果开了优化选项,则编译出来的程序在加载之后,关联源码,就会出现在C源代码单步的时候出现乱跳,而不是一行一行的单步运行。为了方便我们后面单步调试,观察代码的运行情况,需要关闭u-boot的编译优化选项。
在u-boot源码根目录下的Makefile文件搜索“-O‘”,搜索到下面的代码:将下面的-Os和-O2都修改成-O0。
ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS += -Os
else
KBUILD_CFLAGS += -O2
endif
重新编译:Make V=1(V=1,有了这个参数,make的时候就会将编译的所有细节,包括所有文件的编译选项,能让我们了解到编译的详细细节)。
编译出现错误:
arm-linux-gnueabi-gcc -Wp,-MD,cmd/.bootefi.o.d -nostdinc -isystem /home/ubuntu/u-boot/code/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabi/bin/../lib/gcc/arm-linux-gnueabi/6.2.1/include -Iinclude -I./arch/arm/include -include ./include/linux/kconfig.h -D__KERNEL__ -D__UBOOT__ -Wall -Wstrict-prototypes -Wno-format-security -fno-builtin -ffreestanding -O0 -fno-stack-protector -fno-delete-null-pointer-checks -g -fstack-usage -Wno-format-nonliteral -Werror=date-time -D__ARM__ -marm -mno-thumb-interwork -mabi=aapcs-linux -mword-relocations -fno-pic -mno-unaligned-access -ffunction-sections -fdata-sections -fno-common -ffixed-r9 -msoft-float -pipe -march=armv7-a -D__LINUX_ARM_ARCH__=7 -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(bootefi)" -D"KBUILD_MODNAME=KBUILD_STR(bootefi)" -c -o cmd/bootefi.o cmd/bootefi.c
cmd/bootefi.c: In function 'do_bootefi_exec':
cmd/bootefi.c:230:1: error: fp cannot be used in asm here
为了解决这个问题,需要修改Makefile,如下所示:增加了编译选项-fomit-frame-pointer,修改之后u-boot编译成功。
ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS += -O0
KBUILD_CFLAGS += -fomit-frame-pointer
else
KBUILD_CFLAGS += -O0
KBUILD_CFLAGS += -fomit-frame-pointer
endif
在FVP上加载u-boot
按照前面文章的介绍,使能multipass下的mount功能(虚拟机关机之后重启,需要重新配置),然后重新mount一下共享目录,将u-boot根目录下的文件:u-boot(这是elf格式的编译产物,包含了debug调试信息)拷贝到windows下,并将u-boot的源码解压一份,用来单步调试的时候关联源码。
按照下图所示,指定加载文件,以及FVP连接之后pc寄存器的初值:
FVP加载u-boot失败的问题
连接FVP之后出现错误,如下图所示:
分析一下u-boot的section信息,看看加载段都有哪些:
ubuntu@vm01:~/u-boot/code/u-boot-2016.11$ arm-linux-gnueabi-objdump -h u-boot
u-boot: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0005bcbc 60800000 60800000 00010000 2**5
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 0000dab6 6085bcc0 6085bcc0 0006bcc0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .hash 00000018 60869778 60869778 00079778 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .data 00001c78 60869790 60869790 00079790 2**3
CONTENTS, ALLOC, LOAD, DATA
4 .got.plt 0000000c 6086b408 6086b408 0007b408 2**2
CONTENTS, ALLOC, LOAD, DATA
5 .u_boot_list 00000774 6086b414 6086b414 0007b414 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .efi_runtime 00000130 6086bb88 6086bb88 0007bb88 2**3
CONTENTS, ALLOC, LOAD, CODE
7 .dynsym 00000030 60874200 60874200 00084200 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .efi_runtime_rel 00000090 6086bcb8 6086bcb8 0007bcb8 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rel.dyn 000084b8 6086bd48 6086bd48 0007bd48 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .bss_start 00000000 6086bd48 6086bd48 000842d0 2**2
CONTENTS
11 .bss 0003775c 6086bd48 6086bd48 00000000 2**6
ALLOC
12 .bss_end 00000000 608a34a4 608a34a4 000842d0 2**2
CONTENTS
13 .dynstr 00000001 60874230 60874230 00084230 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
从上面可以看出,.text段的加载地址是0x60800000,那我们看看FVP手册里的memory map,看看0x60800000这个地址对应的是什么设备的内存:可以看出0x60800000对应的设备是Ext AXI,这个不是DDR或者其他存储设备,真正的DDR空间是从蓝色方框标注的0x80000000开始的。
接下来我们走查一下u-boot的代码,看看u-boot的.text的地址是怎么指定的:可以看出在vexpress_ca9x4.h文件中定义了宏CONFIG_VEXPRESS_ORIGINAL_MEMORY_MAP,导致在vexpress_common.h文件中V2M_PA_CS0的基地址是0x40000000,而实际上我们使用的FVP模型VE Cortext-A9x1的memory map显示DDR基地址是0x80000000;
所以应该将vexpress_ca9x4.h中的宏定义修改成:CONFIG_VEXPRESS_EXTENDED_MEMORY_MAP
u-boot-2016.11\include\configs\vexpress_ca9x4.h
/*
* (C) Copyright 2011 Linaro
* Ryan Harkin, <ryan.harkin@linaro.org>
*
* Configuration for Versatile Express. Parts were derived from other ARM
* configurations.
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __VEXPRESS_CA9X4_H
#define __VEXPRESS_CA9X4_H
#define CONFIG_VEXPRESS_ORIGINAL_MEMORY_MAP
#include "vexpress_common.h"
#endif /* VEXPRESS_CA9X4_H */
u-boot-2016.11\include\configs\vexpress_common.h
/*
* Definitions copied from linux kernel:
* arch/arm/mach-vexpress/include/mach/motherboard.h
*/
#ifdef CONFIG_VEXPRESS_ORIGINAL_MEMORY_MAP
/* CS register bases for the original memory map. */
#define V2M_PA_CS0 0x40000000
#define V2M_PA_CS1 0x44000000
#define V2M_PA_CS2 0x48000000
#define V2M_PA_CS3 0x4c000000
#define V2M_PA_CS7 0x10000000
#define V2M_PERIPH_OFFSET(x) (x << 12)
#define V2M_SYSREGS (V2M_PA_CS7 + V2M_PERIPH_OFFSET(0))
#define V2M_SYSCTL (V2M_PA_CS7 + V2M_PERIPH_OFFSET(1))
#define V2M_SERIAL_BUS_PCI (V2M_PA_CS7 + V2M_PERIPH_OFFSET(2))
#define V2M_BASE 0x60000000
#define CONFIG_SYS_TEXT_BASE 0x60800000
#elif defined(CONFIG_VEXPRESS_EXTENDED_MEMORY_MAP)
/* CS register bases for the extended memory map. */
#define V2M_PA_CS0 0x08000000
#define V2M_PA_CS1 0x0c000000
#define V2M_PA_CS2 0x14000000
#define V2M_PA_CS3 0x18000000
#define V2M_PA_CS7 0x1c000000
按照下面的方法进行修改之后,再重新编译:
#ifndef __VEXPRESS_CA9X4_H
#define __VEXPRESS_CA9X4_H
/*#define CONFIG_VEXPRESS_ORIGINAL_MEMORY_MAP*/
#define CONFIG_VEXPRESS_EXTENDED_MEMORY_MAP
#include "vexpress_common.h"
#endif /* VEXPRESS_CA9X4_H */
使用重新编译之后的产物进行加载:可以看到加载成功,pc指向了elf文件的entry point,且自动关联了u-boot的源码;
能自动关联,一是因为我将编译产物u-boot和解压后的源码放在了同一个目录下,另外一个是因为我在指定加载文件的时候,将符号加载的选项也打了勾,二者缺一不可。
如果加载文件与源码没有放在同一个目录下,汇编不能自动关联源码的话,需要你在汇编视图中右键单击,选择show in source,它会弹出窗口让你选择源码的实际路径(前提是要你加载了符号表)。
u-boot运行调试
找不到telnet的问题
点击运行之后出现弹出窗口,提示有错误:这是因为FVP下的串口是模拟出来的,模拟的串口实际上需要通过telnet去登录对应的port(模型上有多个串口,每个串口对应一个port),这里出错是因为win10下默认没有打开telnet服务。
使用win+R
键打开运行程序,在输入框里输入:OptionalFeatures
点击确定;
勾选上Telnet客户端
并点击确定,就会开始安装telnet客户端;
autoboot的问题
下面运行之后在telnet窗口(实际上是模拟的串口),可以看到没有倒计时就直接autoboot了;
U-Boot 2016.11 (May 09 2022 - 22:50:00 +0800)
DRAM: 1 GiB
WARNING: Caches not enabled
Flash: 128 MiB
MMC: MMC: 0
*** Warning - bad CRC, using default environment
In: serial
Out: serial
Err: serial
Net: smc911x: Invalid chip endian 0x00000000
No ethernet found.
Hit any key to stop autoboot: 0
MMC Device 1 not found
no mmc device at slot 1
Card did not respond to voltage select!
No ethernet found.
missing environment variable: pxeuuid
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/00000000
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/0000000
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/000000
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/00000
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/0000
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/000
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/00
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/0
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/default-arm
No ethernet found.
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/default
No ethernet found.
Config file not found
No ethernet found.
No ethernet found.
Wrong Image Format for bootm command
ERROR: can't get kernel image!
=>
接下来要走查u-boot的代码,看看怎么设置一下等待时间,设置成5-10秒:
u-boot-2016.11\common\main.c
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */
cli_init();
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
panic("No CLI available");
}
u-boot-2016.11\common\autoboot.c
const char *bootdelay_process(void)
{
char *s;
int bootdelay;
#ifdef CONFIG_BOOTCOUNT_LIMIT
unsigned long bootcount = 0;
unsigned long bootlimit = 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#ifdef CONFIG_BOOTCOUNT_LIMIT
bootcount = bootcount_load();
bootcount++;
bootcount_store(bootcount);
setenv_ulong("bootcount", bootcount);
bootlimit = getenv_ulong("bootlimit", 10, 0);
#endif /* CONFIG_BOOTCOUNT_LIMIT */
s = getenv("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
#ifdef CONFIG_OF_CONTROL
bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay",
bootdelay);
#endif
debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
#if defined(CONFIG_MENU_SHOW)
bootdelay = menu_show(bootdelay);
#endif
bootretry_init_cmd_timeout();
#ifdef CONFIG_POST
if (gd->flags & GD_FLG_POSTFAIL) {
s = getenv("failbootcmd");
} else
#endif /* CONFIG_POST */
#ifdef CONFIG_BOOTCOUNT_LIMIT
if (bootlimit && (bootcount > bootlimit)) {
printf("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
(unsigned)bootlimit);
s = getenv("altbootcmd");
} else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
s = getenv("bootcmd");
process_fdt_options(gd->fdt_blob);
/* stored_bootdelay = bootdelay; */
stored_bootdelay = 10;
return s;
}
可以看出,最终从环境变量里面提取出delayboot的启动参数,存储在变量stroed_bootdelay中,直接将该变量修改成10(正式方法应该是去修改环境变量,这会不想去研究环境变量了,先这么修改用着吧);
修改之后重新编译,使用新的编译产物进行加载调试:可以看到倒计时开启,在倒计时之前按下键盘任意键,进入shell,停止了自启动;
从打印来看,出现了找不到smc911x网口的错误信息,接下来继续解决这个问题
U-Boot 2016.11 (May 09 2022 - 23:21:51 +0800)
DRAM: 1 GiB
WARNING: Caches not enabled
Flash: 128 MiB
MMC: MMC: 0
*** Warning - bad CRC, using default environment
In: serial
Out: serial
Err: serial
Net: smc911x: Invalid chip endian 0x00000000
No ethernet found.
Hit any key to stop autoboot: 0
=>
=>
找不到smc911x的问题
从FVP手册可以看出,VE Cortex-A9模型里面的网口型号是SMSC 91C111,而上面串口打印的网口型号是smc911x,这两者的驱动程序是兼容的吗?
u-boot-2016.11/driver/net
-rw-rw-r-- 1 ubuntu ubuntu 32578 Nov 15 2016 smc91111.c
-rw-rw-r-- 1 ubuntu ubuntu 26443 Nov 15 2016 smc91111.h
-rw-rw-r-- 1 ubuntu ubuntu 6931 Nov 15 2016 smc911x.c
-rw-rw-r-- 1 ubuntu ubuntu 17053 Nov 15 2016 smc911x.h
-rw-rw-r-- 1 ubuntu ubuntu 26884 May 9 22:50 smc911x.o
-rw-rw-r-- 1 ubuntu ubuntu 852 May 9 22:50 smc911x.su
从上面可以看出,smsc911c和smc9111是分开的两个文件,且当前编译的是smc911x.c(因为生成了编译的中间产物smc911x.o);
u-boot-2016.11/driver/net/Makefile
obj-$(CONFIG_SMC91111) += smc91111.o
obj-$(CONFIG_SMC911X) += smc911x.o
从这个驱动目录下的Makefile文件可以看出,2个宏定义控制着编译使用哪种型号的网口,在u-boot的代码中搜索CONFIG_SMC911X:
(1)u-boot-2016.11/board/armltd/vexpress/vexpress_common.c
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_SMC911X
rc = smc911x_initialize(0, CONFIG_SMC911X_BASE);
#endif
return rc;
}
(2)u-boot-2016.11/include/configs/vexpress_common.h
/* SMSC9115 Ethernet from SMSC9118 family */
#define CONFIG_SMC911X 1
#define CONFIG_SMC911X_32_BIT 1
#define CONFIG_SMC911X_BASE V2M_LAN9118
针对上述2处进行修改:
(1)u-boot-2016.11/board/armltd/vexpress/vexpress_common.c
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_SMC911X
rc = smc911x_initialize(0, CONFIG_SMC911X_BASE);
#endif
#ifdef CONFIG_SMC91111
rc = smc91111_initialize(0, CONFIG_SMC91111_BASE);
#endif
return rc;
}
(2)u-boot-2016.11/include/configs/vexpress_common.h
/* SMSC9115 Ethernet from SMSC9118 family */
/*
#define CONFIG_SMC911X 1
#define CONFIG_SMC911X_32_BIT 1
#define CONFIG_SMC911X_BASE V2M_LAN9118
*/
#define CONFIG_SMC91111 1
#define CONFIG_SMC91111_BASE V2M_LAN9118
按照上述修改之后重新编译,是用新的编译产物加载测试:测试成功,网口初始化正常。
U-Boot 2016.11 (May 10 2022 - 23:20:34 +0800)
DRAM: 1 GiB
WARNING: Caches not enabled
Flash: 128 MiB
MMC: MMC: 0
*** Warning - bad CRC, using default environment
In: serial
Out: serial
Err: serial
Net: SMC91111-0
Error: SMC91111-0 address not set.
Hit any key to stop autoboot: 0
=>
=>
=>
至此,u-boot能正常启动,解决了启动过程中的错误打印。后续还会围绕u-boot进行一些其它的测试,边测试边学习。
总结
(1)上面只是展示了问题的解决方法,要解决这些问题,还是需要阅读FVP的手册,对FVP模型有一定的了解,或者去arm的网站去找相关的文档进行学习;
(2)我也使用ds对u-boot的启动过程进行了单步跟踪,所以对开始的启动流程比较熟悉了,熟悉之后也能帮助定位问题;
(3)
附录
(1)加载符号表:elf文件中包含了debug调试信息,而debug调试信息里又包含了汇编代码与源代码的行数的关系,加载符号表就是告诉DS软件,如果将汇编代码与源文件进行关联,当然debug信息还远不止这些,有兴趣的同学可以去了解一下elf文件的格式
(2)关于FVP模拟串口的原理,大家可以参考FVP的pdf手册,里面有介绍,包括网络smsc-91c111的模拟原理