Antkillerfarm Hacking V7.0

uboot, Linux镜像文件, 嵌入式Linux, OpenWrt(三), diff&patch

2022-01-01

uboot

从uboot到Linux

这里以uboot 2014年11月的主线代码为例分析从uboot到linux的全过程。之所以写这篇文章,是由于网上的资料多数都很陈旧,诸如start_armboot之类的函数在新的代码里根本找不到了。由于uboot支持的CPU以及Board非常的多,所以本文仅以Samsung exynos为例来介绍这个过程。

从上电到uboot启动:

1./arch/arm/cpu/armv7/start.S: reset——uboot的汇编入口

2./arch/arm/lib/crt0.S: _main

3./arch/arm/lib/board.c: board_init_f——初始化第一阶段

4./arch/arm/lib/board.c: board_init_r——初始化第二阶段

5./common/main.c: main_loop——uboot主循环

uboot启动Linux

1.uboot中有个bootd的命令选项,执行该命令会进入/common/cmd_bootm.c: do_bootd

2.common/cli.c: run_command,传入bootcmd命令作为参数。

3.common/cmd_bootm.c: do_bootm

4.arch/arm/lib/bootm.c: do_bootm_linux

5.arch/arm/lib/bootm.c: do_jump_linux——跳转到Linux内核的入口地址

uImage格式是专为uboot开发的格式,主要解决了uboot和linux在嵌入式设备的存储上共存的问题。

uboot命令处理流程

从main_loop到命令处理:

1./common/main.c: main_loop

2./common/cli.c: cli_loop

3./common/cli_simple.c: cli_simple_loop

4./common/cli.c: run_command_repeatable

5./common/cli_simple.c: cli_simple_run_command

6./common/cli_simple.c: cmd_process

7./common/command.c: cmd_call

上面的流程仅是主循环如何调用命令回调函数的过程。下面介绍一下命令是如何声明、存储和查询的。

首先查看链接脚本,uboot使用的链接脚本文件名为u-boot.lds。根据cpu和board的不同,u-boot.lds也有所差异。例如Samsung exynos所用的u-boot.lds在arch\arm\cpu下。

其中有个.u_boot_list段就是用来存储命令数据的。它的表述如下所示:

.u_boot_list : {
		KEEP(*(SORT(.u_boot_list*)));
	}

命令的声明,通常使用U_BOOT_CMD宏。这个宏最终展开为:

_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
		__attribute__((unused,				\
		section(".u_boot_list_2_"#_list"_2_"#_name)))

这也就是.u_boot_list*的来历了。

可以使用/common/command.c: find_cmd函数在命令列表中,根据名称查找命令数据。

环境变量

/common/cmd_nvedit.c: setenv–这个函数用于设置环境变量的值。它的原理是:

1.首先在环境变量数组default_environment中,更改相应内容的值。

2.然后调用saveenv,保存default_environment的值,到具体的硬件中。例如NAND设备的代码在/common/env_nand.c中。

在linux内核层面也可以修改uboot的环境变量。通常的做法步骤如下:

1.uboot代码中有个tools/env文件夹。编译改代码可以得到fw_printenv文件。编译的命令是:

make env

2.将fw_printenv放到linux系统的/usr/sbin路径下,并创建符号链接fw_setenv。此处的符号链接并不是可有可无的,这里有个编程小技巧:如何用同一个可执行文件执行不同的功能呢?

除了最常用的使用参数区分的方法之外,还可以采用如下方法:

int main(int argc, char *argv[])

这是main函数的声明,其中argv是参数数组,而argv[0]是输入的命令本身,因此可以使用这个作为判断依据,来区分不同的用途。这时候符号链接也就派上用场了。

tftpsrv

有些uboot提供了tftpsrv的功能,用于从网口传输文件(主要是烧写用的镜像文件)。

该tftpsrv默认监听的ip地址保存在uboot的ip环境变量中。如果需要的话,可进行必要的修改并重启。

客户端传输镜像文件时,需要采用二进制模式。命令如下:

tftp 10.3.9.161 -m binary -c put <file name>

Linux镜像文件

vmlinux

这是源代码直接生成的镜像文件。以x86平台为例:

arch\x86\kernel\vmlinux.lds.S–这是链接脚本的源代码,经过C语言的宏预处理之后会生成vmlinux.lds,使用这个脚本,链接即可得到vmlinux,其过程与普通应用程序并无太大区别,也就是个elf文件罢了。

image

vmlinux使用objcopy处理之后,生成的不包含符号表的镜像文件。这是linux默认生成的结果。

zImage

zImage = 使用gzip压缩后的image + GZip自解压代码。使用make zImage或者make bzImage创建。两者的区别是zImage只适用于大小在640KB以内的内核镜像。

uImage

uImage = uImage header + zImage。使用uboot提供的mkimage工具创建。

以上的这些镜像文件的关系可参见:

http://www.cnblogs.com/armlinux/archive/2011/11/06/2396786.html

http://www.linuxidc.com/Linux/2011-02/32096.htm

Flash镜像

一般来说,一个完整的linux系统,不仅包括内核,还包括bootloader和若干分区。这些镜像文件散布,不利于批量生产的进行。这时就需要将之打包,并生成一个可直接用于生产烧写的Flash镜像。

可使用mtd-utils库中的ubinize工具生成Flash镜像。

mtd-utils的官网是:

http://www.linux-mtd.infradead.org/

安装方法:

sudo apt install mtd-utils

mtd-utils还可用于烧写分区。例如如下命令:

mtd write xyz.uimage linux

其中xyz.uimage是镜像文件名,linux是分区名称。

参考:

http://blog.csdn.net/andy205214/article/details/7390287

利用mkfs.ubifs和ubinize两个工具制作UBI镜像

从代码来查看板子的MTD分区方案,主要是搜索mtd_partition类型的使用定义。比如mini2440板子的分区方案可在mini2440_default_nand_part数组中查到。

MTD(memory technology device):内存技术设备,是linux用于描述ROM,NAND,NOR等内存设备的子系统的抽象。

除了MTD之外,常用的还有SPI Flash方案。

嵌入式Linux

Linaro

Linaro虽然名义上是一家非营利性质的开放源代码软件工程公司。然而,它所提供的ARM工具链,基本上已经是ARM御用级别的了。

官网:

https://www.linaro.org/

Yocto

Yocto是一个开源协作项目,它提供了一些模板、工具和方法来支持面向嵌入式产品的自定义Linux系统。

官网:

https://www.yoctoproject.org/

它也提供了一套工具链,依附于旗下的子项目:Poky Linux。似乎NXP用的较多。

参考:

https://www.ibm.com/developerworks/cn/linux/l-yocto-linux/index.html

使用Yocto Project构建自定义嵌入式Linux发行版

32/64位编译

大致的原则是:

1.使用对应的bit的工具链。

2.链接的库也需要是对应bit的。

32bit float:

-march=armv7-a -mfloat-abi=hard -mfpu=neon

OpenWrt

OpenWrt网络配置

这里主要参考以下文档:

http://ju.outofmemory.cn/entry/176659

为防遗失,摘录如下:

上图为OpenWrt的网络接口图。

有线网络配置文件:/etc/config/network

无线网络配置文件:/etc/config/wireless

pppoe-wan:虚拟设备,拨号上网接口。

lo:虚拟设备,自身的回环网设备。

ra0 rai0:无线设备,它们各自对应一个SSID,分别是2.4G和5G。

iptables设置

iptables是Openwrt的防火墙软件命令,可参见:

http://wiki.openwrt.org/doc/howto/netfilter

这里将主要用法示例如下:

iptables -I zone_wan_input 4 -p tcp --dport 22 -j ACCEPT # allow SCP on WAN port

扩展包

大多数情况下,我们并不需要亲手编写OpenWrt模块,因为日常使用中的绝大部分功能,OpenWrt项目都提供了相应的扩展包。因此,如何寻找、安装扩展包,就成为一个经常性的问题。

官方扩展包可以在代码根目录下的feeds.conf.default文件中找到。

从这里可以看出,OpenWrt项目早期的代码托管在openwrt.org下,而最近的开发已经迁移至github。因此,如果有的包在源代码中没有看到的话,可以到openwrt.org下碰碰运气。

一个软件包可以生成独立的ipk安装文件(Modularizes),也可以直接打包进img中(Built-in)。这个生成选项在make menuconfig的选项菜单中,选择Y就是Built-in,选择M就是Modularizes。

使用GDB调试

由于完整的GDB尺寸太大(~1.5MB),因此通常使用GDBServer进行调试。两者的代码都在gdb软件包中。

参考文档:

http://wiki.openwrt.org/doc/devel/gdb

http://h4x3rotab.github.io/blog/2014/02/27/openwrtxia-de-gdbyuan-cheng-diao-shi/

OpenWRT下的GDB远程调试

除了上面列出的内容之外,我还遇到了一个问题:我所用平台的SDK将-Os作为全局的编译选项。这在平时自然没什么,但调试的时候就有问题了。如何将-Os换成-O0呢?可参见以下示例:

TARGET0_CFLAGS:=$(filter-out -Os,$(TARGET_CFLAGS))
TARGET_CFLAGS:= -O0 $(TARGET0_CFLAGS) -ggdb3

这里解释一下:

1.filter-out是make提供的过滤函数,可去除字符串A中包含的特定字符串B。

2.定义TARGET0_CFLAGS的原因在于:make不支持变量的递归定义,需要中间变量暂存之。

diff&patch

diff/patch这对工具在数学上来说,diff是对2个集合求差,patch是求和。

diff -uNr A B > C #生成A和B的diff文件C,-uNr为最常用的选项
patch A C #给A打上diff文件得到B
patch -R B C #B还原为A

给目录应用patch。

patch -p1 <1.patch

这种情况适合1.patch中包含对多个文件的修改时。

批量应用patch

有的时候,patch不是一个patch文件,而是一个目录中的若干个patch文件。这时可用如下办法:

find . -name "*.patch">1.txt

sort 1.txt | xargs cat >2.patch

patch -p1 <2.patch

参考:

https://mp.weixin.qq.com/s/VlQnDAD4dGQpmHNil7uPuQ

如何在Linux上使用xargs命令

https://mp.weixin.qq.com/s/HchAIWJmqPNTqEEwb3TbAg

Linux下xargs命令

Fork me on GitHub