记录移植U-boot,Linux过程中的知识点和技巧。
启动内核时出现undefined instruction
错误
启动内核时会出现各种错误,我遇到的是如下情况:
......
Image Name:linux-2.6.32.2
......
Load Address:30008000
Entry Point:30008000
Verifying Checksum...OK
XIP Kernel Image...OK
OK
Starting kernel...
undefined instruction
...
Resetting CPU...
resetting...
在解决这个问题前有必要说一下uImage是如何生成的,因为出现这样的问题肯定与uImage的正确性有密切关系。 分析uImage是如何生成的,肯定是去分析Makefile了。
- 看一下顶层中的Makefile,其中:
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
当我们执行make uImage
时,运行其中的命令行,展开其中的变量,得到:
make -f scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-s3c2410/ arch/arm/boot/uImage
上面是展开变量得到的结果,还有一种简单的方法就是利用神器grep
,执行:
#make uImage -n | grep "make -f"
...
make -f scripts/Makefile.build obj=net/sunrpc
make -f scripts/Makefile.build obj=net/unix
make -f scripts/Makefile.build obj=net/wireless
make -f scripts/Makefile.build obj=arch/arm/lib
make -f scripts/Makefile.build obj=lib
make -f scripts/Makefile.build obj=lib/zlib_deflate
make -f scripts/Makefile.build obj=lib/zlib_inflate
make -f /home/saiyn/work/linux-2.6.32.69/scripts/Makefile.modpost vmlinux.o
echo ' GEN .version'; set -e; if [ ! -r .version ]; then rm -f .version; echo 1 >.version; else mv .version .old_version; expr 0$(cat .old_version) + 1 >.version; fi; make -f scripts/Makefile.build obj=init
make -f scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-s3c2410/ arch/arm/boot/uImage
make -f scripts/Makefile.build obj=arch/arm/boot/compressed arch/arm/boot/compressed/vmlinux
上面其实就是kbuild的工作实例,kbuild使用的典型方式如下:
$(MAKE) $(build)=\<subdir\> [target]
- 下面简单地阐述一下make的工作原理。
通过-f
选项,指定Makefile为scripts目录下的Makefile.build。
而当make解释执行Makefile.build时,再将子目录中的Makefile包含到Makefile.include中来,
这就动态的组成子目录的真正的Makefile。
make始终工作于顶层目录下,所以需要跟踪编译所在的子目录,为此,kbuild定义了两个变量:src和obj。
其中,src始终指向需要构建的目录,obj指向构建的目标存放的目录。所以在Makefile.build的
一开头,变量src的值为$(obj):
/scripts/kbuild.include:
src := $(obj)
PHONY := __build
__build:
...
让我们继续回到uImage如何生成的问题上来
make -f scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-s3c2410/ arch/arm/boot/uImage
上面这句命令的效果就是去执行arch/arm/boot
中的Makefile, 目标是arch/arm/boot/uImage
,
- 现在分析
arch/arm/boot
中的Makefile(Linux2.6.32):
$(obj)/uImage: $(obj)/zImage FORCE
$(call if_changed,uimage)
@echo ' Image $@ is ready'
因为目标是arch/arm/boot/uImage
,所以首先执行的是上面部分,
uImage依赖zImage 和 FORCE,其中 FORCE是一个伪目标,作用就是不管zIamge是否最新,都要执行底下的命令行。
要弄懂$(call if_changed,uimage)
这句的意思,得清楚call
函数(见Makefile 学习笔记),以及if_changed
函数。
if_changed
定义在scripts/Kbuild.include文件中:
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
@set -e; \
$(echo-cmd) $(cmd_$(1)); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
上面的命令比较复杂,不过看到$(cmd_$(1))
这句我们大概知道就是要执行cmd_uimage
, 看一下Makefile, 果然有这么一段:
quiet_cmd_uimage = UIMAGE $@
cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A arm -O linux -T kernel \
-C none -a $(LOADADDR) -e $(STARTADDR) \
-n 'Linux-$(KERNELRELEASE)' -d $< $@
Makefile的一开始就定义了MKIMAGE
变量,MKIMAGE := $(srctree)/scripts/mkuboot.sh
,
mkuboot.sh
脚本会去寻找’mkimage’程序,mkimage
一般在U-Boot的tools
目录下。所以我们得将其拷贝到我们的/usr/local/bin
目录下。所以我们得将其拷贝到我们的/usr/local/bin
目录
后面的-A arm -O linux -T kernel -C none -a $(LOADADDR) -e $(STARTADDR) -n 'Linux-$(KERNELRELEASE)' -d $< $@
都是传递给mkimage
的参数,其中最重要的就是$(LOADADDR)
和$(STARTADDR)
,可以猜到,出现undefined instruction
错误
就是这两个参数定义出了问题。
- 分析
$(LOADADDR)
和$(STARTADDR)
这两个参数的定义
$(LOADADDR)
是定义U-Boot该从哪个地址加载Linux内核镜像,
ifeq ($(CONFIG_ZBOOT_ROM),y)
$(obj)/uImage: LOADADDR=$(CONFIG_ZBOOT_ROM_TEXT)
else
$(obj)/uImage: LOADADDR=$(ZRELADDR)
endif
因为没有定义CONFIG_ZBOOT_ROM
,所以LOADADDR=$(ZRELADDR)
ZRELADDR := $(zreladdr-y)
ifneq ($(MACHINE),)
include $(srctree)/$(MACHINE)/Makefile.boot
endif
ZRELADDR依赖zreladdr-y,而zreladdr-y就定义在$(srctree)/$(MACHINE)/Makefile.boot
中,即~/arch/arm/mach-s3c2410/Makefile.boot
root@ubuntu:~/work/linux-2.6.32.69#cat arch/arm/mach-s3c2410/Makefile.boot
zreladdr-y := 0x30008000
params_phys-y := 0x30000100
这样可以清楚的知道-a $(LOADADDR)
,其实就是-a 0x30008000
,即在0x30008000处加载内核。
清楚了$(LOADADDR),那么$(STARTADDR)就清楚了。
ifeq ($(CONFIG_THUMB2_KERNEL),y)
# Set bit 0 to 1 so that "mov pc, rx" switches to Thumb-2 mode
$(obj)/uImage: STARTADDR=$(shell echo $(LOADADDR) | sed -e "s/.$$/1/")
else
$(obj)/uImage: STARTADDR=$(LOADADDR)
endif
因为没有定义CONFIG_THUMB2_KERNEL,所以STARTADDR=$(LOADADDR),即加载地址等于入口地址,这显然是不对的,因为加载地址后的40个字节放的是
u-boot传入的参数,至此终于搞清楚为什么会出现undefined instruction
错误了。知道问题所在,那么解决起来就不难了,有2中方法:
- 1.修改Makefile
- 2.输入命令之间调用mkimage
利用busybox制作根文件系统
- 第一步:制作最新根文件系统,让内核可以正常启动工作
- 第二步:完善根文件系统,添加启动脚本