【杰理】杰理SDK-Makefile-编译构建下载流程分析

【杰理】杰理SDK-Makefile-编译构建下载流程分析

前言学习理解Makefile编译构建过程,有助于新手开发者熟悉SDK文件框架构成、配置编译链接选项、增删文件编译、了解各bin文件作用及下载升级流程、进行程序空间优化等。特别是可视化SDK,其即是基于Makefile使用命令行执行编译、下载等操作,默认不使用CodeBlock IDE。

那么本文即以杰理jl701n_soundbox_release_v1.4.2/sdk为例,讲述杰理SDK-Makefile及目标程序固件的编译构建过程,其它无论是可视化或非可视化SDK均可参考此篇文章。

另外,本文主要为讲述基础原理、Makefile编译构建以及程序烧录升级过程,当然也包括部分环境及相关的配置开发介绍,但并不会详细指导如何搭建开发环境。

Makefile解析Makefile通用结构简述PS: 设计 Makefile 的目的是配置通过命令行自动执行程序,代替手动的编译操作,从而实现一键编译、下载等。

通用 Makefile 目标形式:

make、make all、make -j

命令行执行make表示默认执行第一个目标,等同于make all(相当于执行预设的编译构建下载全流程,但不是重新编译而是增量编译)make -j表示使用最大线程数并行执行默认目标,加快编译速度

make clean表示清除所有编译中间文件,先执行此条指令再make相当于 rebuild

在终端输入make clean;make -j,指示 Shell 终端串行执行此两条指令 rebuild

Makefile 通常为以下的组织形式:

各项变量集合定义,包括编译器路径、编译选项、源文件路径、链接选项等

定义构建目标的层层依赖关系,以及达成目标的命令集合(会应用到以上自动化变量)

从 Makefile 内容来看,自上而下,通常的结构为:

12345678910111213141516171819202122232425工具链路径/可执行程序路径编译参数全局宏定义头文件搜索路径集合源文件路径集合链接参数编译依赖声明集合---默认目标(all):依赖目标1 ...依赖目标n(如prebuild、固件sdk.elf等) 命令...其它目标(clean): 命令...(rm -rf *)依赖目标1(prebuild): 命令...依赖目标2(sdk.elf):依赖目标2-1 ...依赖目标2-n 命令...依赖目标n-n: 命令...

Makefile 作为一个编译构建工具,其构建原理相当于:定义构建目标作为树形结构的顶点,然后通过层层依赖关系得到最底层的所有命令行集合。执行完所有的命令依赖,即可完成目标。

编译工具链路径设置在Windows环境下,通常默认安装杰理工具链路径位于C:/JL,而701-SDK/br28内核的编译链可执行文件路径则位于C:/JL/pi32/bin,工具说明如下:

12345TOOL_DIR := C:/JL/pi32/binCC := clang.exe # CXX := clang.exe # 定义C、C++编译器为clang,用于生成.o目标文件LD := pi32v2-lto-wrapper.exe # 定义链接器,用于生成可执行文件AR := llvm-ar.exe # 定义归档工具,用于生成.a静态库

除编译工具外,还有相关的辅助构建工具,其可执行程序位于/SDK/tools/utils,通常将此部分工具添加到系统环境变量中,便于编译时调用

1234MKDIR := mkdir_win -p # mkdir_win.exe,用于递归创建多级目录RM := rm -rf # rm.exe,用于删除文件/目录FIXBAT := tools\utils\fixbat.exe # 用于处理 utf8->gbk 编码问题...

Question:为什么在SDK里指定编译器绝对路径,而不是声明到环境变量中?但辅助构建工具却是可以添加到环境变量中?

因为不同SDK对应的cpu内核可能不一致,其编译链虽都是clang,但存在差异,有pi32或pi32v2的,声明同名编译器到环境变量会导致混乱而辅助构建工具比如创建目录、删除、打包等,则是通用的命令

工具链自带的库文件、头文件路径如下:

12SYS_LIB_DIR := C:/JL/pi32/pi32v2-lib/r3-largeSYS_INC_DIR := C:/JL/pi32/pi32v2-include

SYS_LIB_DIR和SYS_INC_DIR用于编译链接时,查找系统库和头文件,通常包含了标准C库、数学库的实现以及函数声明,如SYS_LIB_DIR下包含libc.a、libm.a等,而SYS_INC_DIR下包含stdint.h、stdio.h、stdlib.h等。

以下为设置编译生成的中间文件路径,以及输出sdk.elf文件(elf描述了链接后生成的可执行文件)

12345# 输出文件设置OUT_ELF := cpu/br28/tools/sdk.elfOBJ_FILE := $(OUT_ELF).objs.txt# 编译路径设置BUILD_DIR := objs

其中sdk.elf是编译工具链输出的产物,而最终的.ufw固件是由cpu/br28/tools/download.bat对 sdk.elf 进一步处理得到的

编译参数编译参数,通常无须过多关注,保持默认即可

123456789101112131415161718192021222324CFLAGS := \ -target pi32v2 \ -mcpu=r3 \ -integrated-as \ -flto \ -Wuninitialized \ -Wno-invalid-noreturn \ -fno-common \ -integrated-as \ -Oz \ # 表示最大限度的编译优化 -g \ # 生成调试信息,g0 g1 g2 g3 g,其中-g等于g3,表示最大优化 -flto \ -fallow-pointer-null \ -fprefer-gnu-section \ -Wno-shift-negative-value \ -Wundef \ -Wframe-larger-than=256 \ -Wincompatible-pointer-types \ # 警告不兼容的指针类型转换 -Wreturn-type \ # 警告缺少返回值的函数 -Wimplicit-function-declaration \ # 缺失函数警告 -mllvm -pi32v2-large-program=true \ -fms-extensions \ -fdiscrete-bitfield-abi \ -w \ # 默认禁用所有编译警告

全局宏定义设置节选如下,用于定义全局的宏定义,有需要可以自行在此添加,但通常不建议这样做

12345678910DEFINES := \ -DSUPPORT_MS_EXTENSIONS \ -DCONFIG_RELEASE_ENABLE \ -DCONFIG_CPU_BR28 \ ... -DCONFIG_SOUNDBOX \ -DEVENT_HANDLER_NUM_CONFIG=2 \ -DEVENT_TOUCH_ENABLE_CONFIG=0 \ -DEVENT_POOL_SIZE_CONFIG=256 \ ...

头文件搜索路径、源文件设置二次业务开发时,新建的.h头文件,需要将其路径添加到如下,使得编译器能够找到包含的头文件

123456789INCLUDES := \ -Iinclude_lib \ -Iinclude_lib/driver \ ...... -Iapps/common \ -Iapps/common/device \ -Iapps/common/audio \ -Iapps/common/audio/live_audio \ ......

二次业务开发时,新建的.c源文件,需要将其路径添加到如下,使得编译器能够对源文件进行编译并链接进目标程序

12345678# 需要编译的 .c 文件c_SRC_FILES := \ apps/common/audio/amplitude_statistic.c \ apps/common/audio/audio_digital_vol.c \ apps/common/audio/audio_export_demo.c \ apps/common/audio/audio_utils.c \ apps/common/audio/decode/audio_key_tone.c \ ......

其它用户新建的源文件,如.s、.S、.cpp等,自行添加即可,但一般不会用到(.cc .cxx .cpp都是属于C++源文件扩展名)

123456789101112131415# 需要编译的 .S 文件S_SRC_FILES := \ apps/soundbox/sdk_version.z.S \# 需要编译的 .s 文件s_SRC_FILES :=# 需要编译的 .cpp 文件cpp_SRC_FILES :=# 需要编译的 .cc 文件cc_SRC_FILES :=# 需要编译的 .cxx 文件cxx_SRC_FILES :=

链接参数设置(添加静态库时用到)节选如下

1234567891011121314151617# 链接参数LFLAGS := \ --plugin-opt=-pi32v2-always-use-itblock=false \ --plugin-opt=-enable-ipra=true \ --gc-sections \ # 删除未使用的section,减少程序占用 --start-group \ # cpu/br28/liba/cpu.a \ cpu/br28/liba/system.a \ ... # 添加静态库时,按需求添加在--start-group和--end-group之间 ... cpu/br28/liba/res.a \ --end-group \ # 与--start-group搭配 -Tcpu/br28/sdk.ld \ # 指定链接脚本 sdk.ld,控制内存布局和段定位 -M=cpu/br28/tools/sdk.map \ # 配置生成map文件,记录链接信息,用于调试分析 --plugin-opt=mcpu=r3 \ --plugin-opt=-mattr=+fprev1 \

核心点是注意--start-group和--end-group,此组参数的作用是保证顺序链接、控制不同库文件中的同名函数/符号优先级、避免多个库中同名符号冲突

进行库添加时,规范库的链接顺序如,打补丁需要添加新的静态库时,须注意。顺序不当,有可能会导致链接报错。

比如如下,链接器会优先从 liba.a 中查找 函数/变量 实现,然后是 libb.a,最后是 libc.a

12345--start-groupliba.alibb.alibc.a--end-group

预构建流程在Makefile所在目录终端,输入命令make -j,即开始编译,其中all是默认目标,而pre_build是其依赖的第一个目标,即每开始正式编译前,都会先进行一次预构建过程

123456789101112all: pre_build $(OUT_ELF) $(info +POST-BUILD) $(QUITE) $(RUN_POST_SCRIPT) sdk# 预构建pre_build: $(info +PRE-BUILD) $(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/sdk_used_list.c -o cpu/br28/sdk_used_list.used $(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/sdk_ld.c -o cpu/br28/sdk.ld $(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/tools/download.c -o $(POST_SCRIPT) $(QUITE) $(FIXBAT) $(POST_SCRIPT) $(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/tools/isd_config_rule.c -o cpu/br28/tools/isd_config.ini

预构建流程是杰理SDK编译前的必要步骤,其主要作用包括以下:

处理sdk_used_list.c得到sdk_used_list.used,在链接参数中会引用此文件,主要是起到记录、声明作用

链接参数--plugin-opt=-used-symbol-file=cpu/br28/sdk_used_list.used表示链接器会去匹配寻找该文件里的符号(函数/变量声明),将这个符号在库中的实现链接到sdk.ld指定的地址。

处理cpu/br28/tools/sdk_ld.c得到sdk.ld,主要是生成链接脚本,控制内存布局

处理cpu/br28/tools/download.c得到download.bat,生成顶层下载脚本

从sdk.elf提取不同节,得到相应的二进制文件,并组合成app.bin等操作调用子脚本download.bat,进行二进制文件打包和程序下载流程

$(FIXBAT) $(POST_SCRIPT) 调用fixbat.exe对download.bat处理编码转换问题

处理 cpu/br28/tools/isd_config_rule.c 得到 isd_config.ini

子脚本download.bat 调用 isd_download.exe 进行实际的flash下载(isd_config.ini为下载配置文件)如:..\..\isd_download.exe ..\..\isd_config.ini -tonorflash -dev br28 -boot 0x120000 -div8 -wait 300 -uboot ..\..\uboot.boot -app ..\..\app.bin -res tone.cfg ..\..\cfg_tool.bin ..\..\eq_cfg_hw.bin p11_code.bin -uboot_compress

源文件构建流程

使用通配符和转换规则,使c_OBJS, S_OBJS, s_OBJS, cpp_OBJS, cxx_OBJS, cc_OBJS分别映射包含所有源文件编译生成对应的 .o 目标文件

定义OBJS包含所有类型的目标文件;定义DEP_FILES包含所有的.d依赖文件,用于增量编译判断用。

修改 OBJS 和 DEP_FILES 的值,增加BUILD_DIR前缀,使得所有构建过程输出都位于BUILD_DIR目录下

VERBOSE控制编译过程输出详细日志,默认为0,可设置为1,如make VERBOSE=1; LINK_AT用于决定是否使用file函数

定义构建伪目标all、pre_build、clean,规则、编译、依赖文件包含等1234567all依赖于pre_build和OUT_ELF(即sdk.elf)(层层目标依赖,直至源文件->目标文件的编译)直接输入make时,默认执行make all。其执行完编译链接流程后,最后调用脚本`cpu\br28\tools\download.bat`进行下载pre_build用于执行预处理步骤,如生成配置文件、链接脚本、下载脚本clean用于清理构建过程产生的中间文件,如rm -rf BUILD_DIR xxx

链接脚本、固件打包程序、下载脚本解析sdk_ld.c分析maskrom_stubs.ld

涉及到 update_flag VM区 等的地址链接定位

涉及到 配置 代码放RAM 的链接配置

cpu/brxx/tools/download.c分析暂无

说明如何打包固件等

子脚本download.bat分析在可视化SDK中,子脚本download.bat 已经合并进 download.c 了

执行实际的下载流程

下载/升级固件构成分析由download.c以及子脚本download.bat可得到,实际下载到芯片的flash构成,以及各固件的关系

1234567单备份: jl_isd.bin + VM区 + 蓝牙配置区 = 程序flash占用空间jl_isd.bin = (isd_download.exe) uboot.boot + app.bin + tone_zh.cfg + cfg_tool.bin + p11_code.bin + stream.binapp.bin = text.bin + data.bin + data_code.bin + aec.bin + aac.bin + ps_ram_data_code.binupdate.ufw = jl_isd.ufw = ufw_make.exe (jl_isd.fw + ota.bin)

VM区大小须满足 大于 提示各种升级所需的最小空间其中 VM区 和 蓝牙配置区 的大小,可以在isd_config.ini中查看。

压缩代码方法: 配置能关则关,音频编解码能关尽关, 提示音文件可以改成 16bit-16KHz-单声道的mp3文件

哪些文件是烧录的,哪些文件是用于ota的,哪些文件是干嘛的?

思考与拓展杰理CodeBlock工程是如何进行构建编译的?与SDK根目录的Makefile构建方式的具体异同?哪个效率更优?暂无解答

make与codeblock-IDE的构建工具 差异对比?

归根到底无论是IDE还是Makefile,都是调用同一个编译器对源文件进行编译链接,编译速度理论上无差异。但构建组织速度存在差异

Code::Blocks可以配置使用Makefile,也可以使用其内置的构建系统。另,可以通过配置ccache工具加速编译构建

Maskrom的程序跟下载的固件有相关性吗?Maskrom是出厂自带的只读程序,主要用于引导启动、集成固件烧录功能等?

暂不了解 Maskrom uboot app 的详细流程

可以用C++编写杰理程序吗?理论上可以,只要编译器支持即可

毕竟编程语言只是程序流程描述,编译器负责将其解析其适合于芯片cpu的机器码。但越是高级的语言,其附带的特性就越多,因此会占用更多的资源。不建议用于资源紧张的嵌入式开发

什么情况下需要rebuild?全局宏配置、功能的开启/关闭,有可能会影响到预构建相关的文件sdk_used_list.c、sdk_ld.c、download.c、isd_config_rule.c里预编译条件变化,需要rebuild。当链接静态库有更新时,必须要 rebuild。如果静态库有引用到相关的配置宏的,也需要 rebuild。

常规的.c源文件、.h头文件修改,不需要rebuild。因为在Makefile中,包含进编译源文件的路径集合,已经被全部声明了依赖关系。

但静态库文件、预构建文件这部分没有被声明依赖关系的文件,即使有更新,make也不会检测到。需要rebuild,清除已有的编译中间文件,重新进行编译链接

什么情况下需要擦除flash和VM?需要清除记忆的配置信息,如蓝牙配对信息、用户信息等,则擦除VM

OTA升级前后的程序VM区大小配置不一致,会导致升级失败?旧程序配置了VM为48K,新程序配置为64K,验证了仅有此部分差异情况下,OTA升级不成功。

可以通过分析链接脚本,查看程序的内存排布。

VM区的大小配置,会影响到全局程序区的地址偏移链接,导致对指定地址区域的升级文件校验失败?

配置 Makefile 下载时默认擦除所有: 在download.bat增加 -format all ?

编译工具链 addr2line 的使用由上文解释可知,sdk.elf是链接后生成的elf文件,其包含所有编译的源文件、编译的函数、编译的变量等,通过addr2line工具,可以直接反编译出指定地址的函数名、变量名等。

ps: map、lst 文件都是由elf文件衍生的

addr2line工具用于将内存地址转换为源代码中的函数名和行号

参数: -e 指定 elf文件,-a 显示地址,-f 显示函数名,-i 显示行号

在终端(如 PowerShell )输入命令执行示例如下:addr2line -e sdk.elf -a -f 0x0011BE9E 0x00119A40 0x00119A9A 0x06003AF8

可视化SDK界面点击编译、擦除VM、擦除flash等,具体流程是怎样的?特别地,在可视化SDK根目录下,也存在一个download.bat文件

如下:

12345678910111213141516171819@echo off:: 将命令的第一个参数作为xx路径SET PROJ_DOWNLOAD_PATH=%1:: 第二个参数用于定义环境变量是否擦除VM、FLASHif %2==format_flash ( SET FORMAT_ALL_ENABLE=1 SET FORMAT_VM_ENABLE=0)if %2==format_vm ( SET FORMAT_VM_ENABLE=1 SET FORMAT_ALL_ENABLE=0)if %2==download ( SET FORMAT_ALL_ENABLE=0 SET FORMAT_VM_ENABLE=0)cpu\br27\tools\download.bat

最终都会执行下载流程,区别在于设置当前终端的临时环境变量

Makefile 嵌套调用与递归调用通过嵌套 Makefile,可以将大型项目分解成多个Makefile文件,提高可维护性和可读性:

主Makefile:位于项目的根目录下,负责整体的构建流程

子Makefile:可以有多个,位于项目的子目录下,负责特定模块或组件的构建

主Makefile可以通过 include 指令来包含合并子Makefile的内容,也可以通过 $(MAKE) 指令递归调用子Makefile的目标。

参考站点

相关推荐

揭秘女生打你的真实含义【正文】
日博365官网手机版

揭秘女生打你的真实含义【正文】

📅 08-20 👁️ 9028
机械硬盘寿命一般多久?机械硬盘寿命解析(从理论到现实的全面指南)!
365网络科技有限公司是做什么的

机械硬盘寿命一般多久?机械硬盘寿命解析(从理论到现实的全面指南)!

📅 06-29 👁️ 4395
vue为什么那么难?
365网络科技有限公司是做什么的

vue为什么那么难?

📅 10-13 👁️ 5801
45寸电视长宽多少厘米(了解电视尺寸与屏幕比例)
日博365官网手机版

45寸电视长宽多少厘米(了解电视尺寸与屏幕比例)

📅 07-10 👁️ 4504
辛弃疾风格(辛弃疾风格流派)
365体育投注下载

辛弃疾风格(辛弃疾风格流派)

📅 07-19 👁️ 4146
一般一个晚上几次性生活才是正常的
日博365官网手机版

一般一个晚上几次性生活才是正常的

📅 07-09 👁️ 2991