Android 编译介绍序列文章

1. boot.img 编译

1.1 boot.img 组成

Android 产品中,内核格式是Linux标准的zImage,根文件系统采用ramdisk格式。这两者在Android下是直接合并在一起取名为boot.img,会放在一个独立分区当中。这个分区格式是Android自行制定的格式。

Android开发时,最标准的做法是重新编译于内核和根文件系统,然后调用Android给的命令行文件mkbootimg来打包。
常用的就是打包和解包工具:unpackbootimg 和 mkbootimg

1.1.1 linux 内核镜像文件介绍

linux内核镜像几种形式:vmlinux、vmlinux.bin、vmlinuz、zImage、bzImage。

(1) vmlinux:这是编译linux内核直接生成的原始镜像文件,它是由静态链接之后生成的可执行文件,文件类型是linux接受的可运行文件格式之中的一个(ELF、COFF 或 a.out),但是它一般不作为最终的镜像使用,不能直接boot启动,用于生成vmlinuz,可以debug的时候使用。

(2) vmlinuz:vmlinuz是可引导的,压缩的linux内核,“vm”代表的“virtual memory”。
vmlinuz是vmlinux经过gzip和objcopy(*)制作出来的压缩文件。vmlinuz不仅是一个压缩文件,并且在文件的开头部分内嵌有gzip解压缩代码。所以不能用gunzip 或 gzip –dc解包vmlinuz。是可以引导boot启动内核的最终镜像。vmlinuz通常被放置在/boot目录,/boot目录下存放的是系统引导需要的文件,同时vmlinuz文件解压出的vmlinux不带符号表的目标文件,所以一般 /boot 目录下会带一个符号表 System.map 文件。

vmlinuz是一个统称。有两种详细的表现形式:zImage和bzImage(big zImage)。

(3) zImage:这是小内核的旧格式,有指令make zImage生成,仅适用于只有640K以下内存的linux kernel文件。

(4) bzImage: big zImage,需要注意的是这个 bz 与 bzip 没有任何关系,适用于更大的 linux kernel 文件,bzImage是1M以上。现代处理器的 linux 镜像有一部分是生成 bzImage 文件。

(5) vmlinux.bin:与vmlinux相同,但采用可启动的原始二进制文件格式。丢弃所有符号和重定位信息。通过 objcopy -O binary vmlinux vmlinux.bin 从vmlinux生成。
https://www.cnblogs.com/hellokitty2/p/15943388.html

(6) uImage 是在zImage的头部添加了64字节信息,包含了内核版本,加载位置,生成时间,大小信息等,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的 uboot 已经支持了 zImage 启动!所以已经很少用到 uImage 了,除非你用的很古老的 uboot。
使用“ make”、“ make all”、“ make zImage”这些命令就可以编译出 zImage 镜像,具体内容见 arch/arm/Makefile。

1.2 mkbootimg 工具介绍

boot.img 编译时主要相关Makefile文件
include build/make/core/main.mk
build/core/Makefile
including out_vnd/soong/xxx.mk
通过build/core/Makefile中添加打印可以找到mkbootimg对应的参数:

ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
$(foreach b,$(INSTALLED_BOOTIMAGE_TARGET), $(eval $(call add-dependency,$(b),$(call bootimage-to-kernel,$(b)))))
$(INSTALLED_BOOTIMAGE_TARGET): $(recoveryimage-deps)
        $(call pretty,"Target boot image from recovery: $@")
        $(call build-recoveryimage-target, $@, $(PRODUCT_OUT)/$(subst .img,,$(subst boot,kernel,$(notdir $@))))
endif # BOARD_USES_RECOVERY_AS_BOOT

$(INSTALLED_RECOVERYIMAGE_TARGET): $(recoveryimage-deps)
        $(call build-recoveryimage-target, $@, \
          $(if $(filter true, $(BOARD_EXCLUDE_KERNEL_FROM_RECOVERY_IMAGE)),, $(recovery_kernel)))

示例如下:

out/host/linux-x86/bin/mkbootimg \
--kernel out/target/product/$(project_name)/kernel \
--ramdisk out/target/product/$(project_name)/ramdisk-recovery.img \
--cmdline "bootopt=xxx buildvariant=user"  \
--base 0x60000000 --board JUST-TEST-EXAMLE  \
--dtb out_vnd/target/product/$(project_name)/dtb.img \
--os_version 12 \
--os_patch_level 2022-09-16  \
--kernel_offset 0x00060000  \
--ramdisk_offset 0x20000000 \
--tags_offset 0x08000000 \
--header_version 2  \
--dtb_offset 0x08000000 \
--output  out/target/product/$(project_name)/boot.img

1.2.1 unpack_bootimg 工具介绍

下面命令把boot.img解包,所有文件输出到out目录下

unpack_bootimg --boot_img out/target/product/mt6853/boot.img --out boot
boot magic: ANDROID!
kernel_size: 13447872
kernel load address: 0x40060000
ramdisk size: 15558924
ramdisk load address: 0x62100000
second bootloader size: 0
second bootloader load address: 0x00000000
kernel tags load address: 0x49a80000
page size: 2048
os version: 12.0.0
os patch level: 2021-09
boot image header version: 2
product name: CY-X7866-T773-A
command line args: bootopt=64S3,32N2,64N2 buildvariant=userdebug
additional command line args:
recovery dtbo size: 0
recovery dtbo offset: 0x0000000000000000
boot header size: 1650
dtb size: 170514
dtb address: 0x0000000057b80000

参考连接

编译指定目录(mmm)命令,只要有XXX.mk文件都可以编译:
mmm packages/apps/setting/ 2>&1 | tee build.log

1.2.2 kernel编译

编译命令:(-B、-j是可选项,-B表示强制编译,-j表示开的线程数,进行快速编译)

mmm kernel-4.19:kernel -j16 2>&1 | tee kernel.log
mmm kernel-4.19:kernel-menuconfig (生成的.config 在out\target\product\[project]\obj\KERNEL_OBJ)
mmm kernel-4.19:clean-kernel

1.2.3 dtb 反编译

当前驱动代码一般都是在probe函数中解析dts设备树节点来进行驱动注册,所以如何确认编译生成的dtb中是否包含自己所需要的设备节点就很重要,主要分为以下几点来进行确认:
1)找到dtc 工具所在的目录

./../kernel/prebuilts/kernel-build-tools/linux-x86/bin/dtc
  1. 找到所生成的dtb所在目录
 /.../obj/KERNEL_OBJ/arch/arm64/boot/dts/mediatek/x7820_t773.dtb

3)使用dtc工具将 x7820_t773.dtb 生成 A_dts.dts 方便查看

/.../linux-x86/bin/dtc  -I dtb -O dts -o A_dts.dts x7820_t773.dtb

note: 使用 dtc -I dts -O dtb -o B_dtb.dtb A_dts.dts 可以将 A_dts 生成B_dtb.dtb

1.2.4 Makefile 编译注意事项

1.2.4.1 ko 快速编译

很多时候我们只需要将某个驱动编译成一个 .ko,然后 adb push 进去,在insmod 即可,并不需要全部编译kernel或者boot.img。这个时候就需要自己写个Makefile,然后直接make即可,在make 编译之前需要 source build/env.sh lunch xxx来配置好环境, Makefile的配置可以参考下面内容:

ifneq ($(KERNELRELEASE),)
KBUILD_EXTRA_SYMBOLS=$(PWD)/Module.symvers
ccflags-y := -I$(PWD)/linux -I$(PWD)/common
ccflags-y += -I$(PWD)/../linux/include/kernel

obj-m := test.o
test_demo-objs := common/test1.o linux/test2.o \
        linux/test3.o
else
PWD ?= $(shell pwd)
CLANG_TRIPLE=aarch64-linux-gnu-
MAKE=$(ANDROID_BUILD_TOP)/kernel/build/build-tools/path/linux-x86/make
CROSS_COMPILE=$(ANDROID_BUILD_TOP)/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-

KERNELDIR ?= $(ANDROID_BUILD_TOP)/out_vnd/target/product/x6820_h773/obj/KERNEL_OBJ/

LD=$(ANDROID_BUILD_TOP)/kernel/prebuilts-master/clang/host/linux-x86/clang-r416183b/bin/ld.lld
CC=$(ANDROID_BUILD_TOP)/kernel/prebuilts-master/clang/host/linux-x86/clang-r416183b/bin/clang

.PHONY: modules clean

modules:
        $(MAKE) CC=$(CC) CLANG_TRIPLE=$(CLANG_TRIPLE) ARCH=arm64 LD=$(LD) CROSS_COMPILE=$(CROSS_COMPILE) \
        -C $(KERNELDIR) -I./linux/ M=$(PWD)  modules

clean:
        @rm -rf *.o *.order *.symvers *.mod *.mod.* .*.o.cmd .*.mod.o.cmd .*.ko.cmd\
                .tmp_versions *.ko *.symversions 
endif

在当前文件夹下make 的时候,KERNELRELEASE 是没有定义的,就会走 else 分支,给一些编译工具宏进行赋值,然后去 KERNELDIR 根目录下去编译,根目录是top目录,会定义KERNELRELEASE,再次执行到这里就会直接 obj-m 指定要编译的模块即。

1.2.4.2 Makefile 添加打印方法

在使用make命令进行编译的时候经常会遇到需要打印出某些变量的的信息,来定位问题,在Makefile中可以使用下面几种方式来进行添加打印:

SRC := ./src/test1.c
SRC += ./src/test2.c
INC := ./inc/test.h

$(info SRC = $(SRC))
$(warning INC = $(INC))
# 注意:执行到error会直接stop退出

$(error INC = $(INC))
test:
	@echo SRC=$(SRC)
	@echo INC=$(INC)
	$(info SRC = $(SRC))
	$(warning INC = $(INC))
	#注意:执行到 error 会直接 stop 退出
	$(error INC = $(INC))
1.2.4.3 Makefile 编译工具链确认

在编译模块的时候,我们需要确认当前使用的工具链所在得路径,比如GCC, Clang等,可以再 kernel 目录下的Android.mk
中添加打印即可看到相关信息,如:

kernel-4.14/Android.mk
...
ifeq ($(wildcard $(TARGET_PREBUILT_KERNEL)),)
KERNEL_MAKE_DEPENDENCIES := $(shell find $(KERNEL_DIR) -name .git -prune -o -type f | sort)
KERNEL_MAKE_DEPENDENCIES := $(filter-out %/.git %/.gitignore %/.gitattributes,$(KERNEL_MAKE_DEPENDENCIES))

$(TARGET_KERNEL_CONFIG): PRIVATE_DIR := $(KERNEL_DIR)
$(TARGET_KERNEL_CONFIG): $(KERNEL_CONFIG_FILE) $(LOCAL_PATH)/Android.mk
$(TARGET_KERNEL_CONFIG): $(KERNEL_MAKE_DEPENDENCIES)
        $(info ######## schan ############)
        $(warning) mkdir -p $(dir $@)
        $(warning $(PREBUILT_MAKE_PREFIX)$(MAKE) -C $(PRIVATE_DIR) $(KERNEL_MAKE_OPTION) $(KERNEL_DEFCONFIG))

$(BUILT_DTB_OVERLAY_TARGET): $(KERNEL_ZIMAGE_OUT)

.KATI_RESTAT: $(KERNEL_ZIMAGE_OUT)
$(KERNEL_ZIMAGE_OUT): PRIVATE_DIR := $(KERNEL_DIR)
$(KERNEL_ZIMAGE_OUT): $(TARGET_KERNEL_CONFIG) $(KERNEL_MAKE_DEPENDENCIES)
        $(warning) mkdir -p $(dir $@)
        $(warning $(PREBUILT_MAKE_PREFIX)$(MAKE) -C $(PRIVATE_DIR) $(KERNEL_MAKE_OPTION))
...

然後在源碼目錄下執行make bootimage -j16 2>&1 | tee build.log 編譯即可,編譯完成后查看build.log,可以發現類似下面
的内容:

/.../prebuilts/build-tools/linux-x86/bin/make -j32 -C  kernel-4.14 O=/.../obj/KERNEL_OBJ \
ARCH=arm64 CROSS_COMPILE=aarch64-linux-androidkernel- CLANG_TRIPLE=aarch64-linux-gnu-  LD=ld.lld \
LD_LIBRARY_PATH=/.../prebuilts/... \
NM=llvm-nm OBJCOPY=llvm-objcopy  CC=clang  ROOTDIR=/.../android_src \ 
PATH=/.../prebuilts/clang/host/linux-x86/clang-r383902/bin:...

上面打印出来的信息 “-C kernel-4.1.4” 其中 -C 表示进入到kernel-4.14目录执行(解析)其中的makefile, 所以这个时候就是去编译kernel了。

1.2.4.4 Makefile不同的赋值符号

= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值

其中 =:= 都是给变量赋值,他们之间的区别是什么呢?
代码在make的时候,是将整个 Makefile 展开之后再去决定变量的值,因此获取的变量的值会是它最终的赋值。
但是使用 := 符号的话,获得的变量值就是它当时的变量值。
举个例子说明:

x=kitty
y=hello $(x)
x=mickey

最终 y 的值是 hello mickey;

而如果是下面的情况

x:=kitty
y:=hello $(x)
x:=mickey

则 y 的最终值是 hello kitty
https://blog.csdn.net/bluemickey/article/details/17681115

1.2.4.5 Makefile外部模块头文件的检索

当编译的目标模块依赖多个头文件时,kbuild 对头文件的搜索位置有如下规定:
(1) 直接放置在 Makefile 同级的目录下,在编译时当前目录会被添加到头文件搜索目录。
(2) 放置在系统目录,这个系统目录是源代码目录中的 include。
(3) 与通用的 Makefile 一样,使用 -I$(DIR) 来指定,不同的是,代表编译选项的变量是固定的,为 ccflag,因为当前 Makefile 相当于是镶嵌到 Kbuild 系统中进行编译,所以必须遵循 Kbuild 中的规定.
一般的用法是这样的:

ccflags-y += -I$(DIR)/include
1.2.4.6 Makefile 编译选项

在之前的版本中,编译的选项由 EXTRA_CFLAGS, EXTRA_AFLAGS 和 EXTRA_LDFLAGS 修改成了 ccflags-y asflags-y 和 ldflags-y.

(1) ccflags-y asflags-y 和 ldflags-y
这三个变量的值分别对应编译、汇编、链接时的参数。
同时,所有的 ccflags-y asflags-y 和 ldflags-y 这三个变量只对有定义的 Makefile 中使用,简而言之,这些flag在Makefile树中不会有继承效果,Makefile之间相互独立。

(2) subdir-ccflags-y, subdir-asflags-y
这两个编译选项与ccflags-y和asflags-y效果是一致的,只是添加了subdir-前缀,意味着这两个编译选项对本目录和所有的子目录都有效。
https://www.cnblogs.com/hellokitty2/p/15943388.html

1.2.4.7 Makefile 通配符 wildcard

wildcard 用来明确表示通配符。因为在 Makefile 里,变量实质上就是 C/C++ 中的宏,也就是说,如果一个表达式如 objs = *.o ,则 objs 的值就是 *.o ,而不是表示所有的 .o 文件。

如果要使用通配符,那么就要使用 wildcard 来声明 * 这个符号,使 * 符号具有通配符的功能。
如下:
建立两个 *.c 文件,如 test1.c 和 test2.c ,Makefile 如下:

src = $(wildcard *.c /test/src/*.c)

all:
        @echo $(src)

运行make后会输出 test1.c test2.c 和 、test/src 目录下的所有 C 文件。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主公CodingCos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值