简介:面向ARM嵌入式开发与芯片验证人群的mbedtls移植实操记录,完整演示了从源码仓库获取、工程模板创建,到库目录拷贝、Makefile编译选项修改及自测函数调用的全流程。文档以PDF文件呈现,包内仅1个文件,压缩后约199KB,轻量易用,适合快速查阅。目前已累计有330人学习浏览。内容重点覆盖mbedtls在armv9架构下的典型移植步骤,包括将library、include目录接入自定义工程,开启MBEDTLS_SELF_TEST等编译宏,以及通过添加mbedtls_sha256_self_test()调用完成自测。文档还结合qemu模拟环境,演示了如何将这些程序打包进buildroot并运行验证,便于读者在无真实硬件条件下完成功能检查。对想了解嵌入式安全库基础移植流程、希望在真实项目中进行安全组件集成的开发者,这是一份简洁但步骤清晰的参考笔记。
1. mbedtls 还停留在“看看源码”?嵌入式 ARM 芯片上把它跑起来才算移植入门
做嵌入式 ARM 开发的人大概都遇到过这种场景:新拿到的 SOC 板卡,工具链刚从供应商那边拷过来,还没想好先跑什么程序验证一下环境。很多人的第一反应是点个 LED,但点灯只能证明 GPIO 通了,证明不了 CPU、中断、内存和工具链都正常。我现在的习惯是直接跑一遍 mbedtls 的自测程序——AES 和 SHA-256 这类算法对指令周期和内存布局非常敏感,能一路跑通,说明芯片、交叉编译环境和运行链路基本是通的。mbedtls 移植不是新鲜事,但真正把它从源码拉下来、编进自己的工程模板、再放到 QEMU 里跑起来,中间有一堆细节值得记一笔。这篇笔记就是我从零走通整个流程的记录,包含完整的操作步骤、Makefile 修改方法和几个栽过跟头的地方,适合所有拿到新 ARM 板卡想快速验证软件栈的嵌入式工程师。
2. 移植前先别急着敲 make:模板、源码和目录结构一次理清
2.1 先分清“库移植”和“应用集成”
很多第一次做 mbedtls 移植的人会卡在一个概念上:到底是要把 mbedtls 编成一个独立的 .so 或 .a 库,还是直接把源码编进自己的固件里?这两条路差异很大。如果你是做嵌入式 Linux 用户态程序,比如在 ARMv8 或者 armv9 的平台上跑安全启动相关工具,常见做法是编译链接成动态库,上层应用通过头文件调用;但如果你是在裸机环境或者 RTOS 上做,那源码级嵌入几乎是唯一选择。
这份移植笔记走的是第二条路线:把 mbedtls 的 library 和 include 目录直接拷贝到应用工程里,随主程序一起编。好处是没有额外的库依赖,烧进板子也好排查;坏处是编译时间会长一点,而且 Makefile 必须把参与编译的源文件列清楚。我建议刚开始做移植时也选这种方式,因为你能直接看到每个模块的编译过程,出错了可以精准定位到是哪个 .c 文件的问题,而不是在黑匣子一样的静态库里猜。
2.2 拉源码和模板工程,版本别选错
第一步是下载 mbedtls 源码。直接到代码托管平台把官方仓库 clone 下来,这一步没什么好说的。但有两点需要提醒:第一,clone 完之后别着急用最新代码,除非你想替社区做测试。我一般会先看一遍 release 分支或维护中的稳定分支,至少保证你踩到的坑是别人踩过的,而不是某个新提交刚引入的。第二,源码目录里有大量测试代码和文档,移植时用不到,别一股脑往工程里搬。
第二步是准备一个空的 C 语言工程模板。模板的作用是省掉你从头写 Makefile、启动文件和目录结构的时间。我自己维护了一个非常朴素的 native_c_template 模板,里面就是一个可执行的 C 工程骨架:src 目录下放主程序,顶层放 Makefile,编译产物输出到 out 目录。这份移植笔记里用的就是这种通用模板,你完全可以用自己现有的工程替代,关键是目录职责要清楚,后面拷贝 mbedtls 时才不会乱。
模板拿到手之后,执行拷贝命令生成一个独立的移植工程,比如叫 mbedtls_test:
cp native_c_template mbedtls_test -r cd mbedtls_test这一步看似简单,但-r参数偶尔会被忽略,尤其是在你习惯用cp native_c_template mbedtls_test不加参数的情况下。如果没加-r,目录内容根本拷不过去,后续所有操作都会扑空。拷完之后建议先看一眼目录结构,确认模板内容完整,再进入下一步。
2.3 目录拷贝,拷完先做一次结构体检
接下来把 mbedtls 里的 include 和 library 两个目录拷到工程里。这一步的命令同样很简单:
# 假设你 clone 的 mbedtls 在 mbedtls_src 目录下 cp -r mbedtls_src/include mbedtls_test/ cp -r mbedtls_src/library mbedtls_test/ cd mbedtls_test拷贝完成后,我强烈建议先跑一下tree或者find确认目录结构长什么样:
tree -L 2正常情况你应该看到 include/mbedtls 下有一大堆头文件,library 下有 aes.c、sha256.c、sha512.c 等几十个源文件。这里有一个非常隐蔽的问题:mbedtls 的配置头也在 include 目录里,不同版本的配置头文件名不一样。早期版本叫 config.h,后来的版本改成了 mbedtls_config.h。如果你拷贝的版本和你参考的文档对不上,编译时第一个报错就是找不到头文件。我那次就因为这个浪费了半天时间,后来养成了先看目录再动手的习惯。
另外,library 里有些文件依赖特定的平台宏才能编译,比如和时间、文件系统相关的模块。这些对于裸机或最小化移植来说可能用不上,你可以先不管,等编译报错时再回头检查配置。真正需要做的,是把 include 路径加入 Makefile 的搜索路径,以及决定把哪几个源文件加进编译列表。这两个问题,下一章具体讲。
3. 让 Makefile 替你干活:工具链、源文件与宏开关的配合
3.1 指定交叉编译工具链,先解决“用哪个编译器”
模板工程默认用的可能是本机 gcc,如果直接在 ARM 目标平台上运行,那没问题;但绝大多数情况是我们用 x86 的电脑交叉编译出 ARM 架构的程序,再拷到板子或者 QEMU 镜像里执行。所以 Makefile 第一件事就是修改编译工具链前缀。
打开模板工程的 Makefile,一般会有类似这样的定义:
# 交叉编译工具链前缀,根据你的 SOC 供应商提供的工具链修改 CROSS_COMPILE = aarch64-none-linux-gnu- CC = $(CROSS_COMPILE)gcc LD = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar把CROSS_COMPILE改成你实际使用的工具链前缀即可。比如有些 SOC 厂商给的是arm-none-eabi-(裸机),有些给的是aarch64-linux-gnu-(Linux 用户态),这决定了你编译出来的程序是跑在 Linux 系统上还是直接跑在硬件上。如果你不确定,先用$(CROSS_COMPILE)gcc -v验证一下工具链能否正常运行。这一步出错的现象通常是command not found,原因多半是前缀写错或者工具链没加入 PATH。
3.2 SRCS 别漏文件,SHA-256 自测缺了它就报链接错
工具链配好后,最关键的改动是 Makefile 里的 SRCS 变量。模板工程默认只有 main.c,我们要把 mbedtls 需要用到的源文件加进去。以跑 SHA-256 和 AES 自测为例,至少要包含:
SRCS = src/main.c \ library/aes.c \ library/sha256.c这里有个坑要提前说:aes.c 本身会依赖一些公共模块,比如platform.c或者memory_buffer_alloc.c,具体依赖哪些要看你的配置宏。如果你只加了源文件但不加依赖,编译能过,链接时会报undefined reference to ...,那一串错误跟天书一样,特别打击信心。我的建议是第一次跑最小化自测时,直接在 library 目录下用通配符把常用的算法源文件全部加进来:
SRCS = src/main.c \ $(wildcard library/*.c)这个写法的好处是省事,缺点是些用不到的模块也编进去了。但既然是移植验证,先把体积和编译时间放一边,全量编译能最快排除源文件缺失导致的问题。等自测跑通了,再按照你的实际需求裁剪,逐个去掉不需要的文件。
对应的 include 路径也要加进 Makefile:
CFLAGS += -Iinclude3.3 打开 SELF_TEST 宏,主函数里一个调用跑通全流程
mbedtls 的设计很有意思,默认情况下自测代码是被编译进库里的,但要不要执行,取决于主函数里是否调用xxx_self_test函数。这个函数不是平台相关的,不会因为你换了芯片就去掉。不过它受一个宏控制,如果编译时没开启MBEDTLS_SELF_TEST,相关函数的实现会被预编译指令排除掉,你调用了也只能得到一个undefined reference错误。
在 Makefile 里加宏:
CFLAGS += -DMBEDTLS_SELF_TEST然后在 main.c 里添加调用。以 SHA-256 为例:
#include <stdio.h> #include "mbedtls/sha256.h" int main(void) { int ret = mbedtls_sha256_self_test(1); if (ret != 0) { printf("SHA-256 self test failed: %d\n", ret); return 1; } printf("SHA-256 self test passed.\n"); mbedtls_aes_self_test(1); // 如果你也把 aes.c 编进来了 return 0; }注意mbedtls_sha256_self_test的入参是一个整型,传 1 表示打印详细的运行信息,传 0 则只返回结果。第一次跑建议传 1,能看到每一步的输入、输出和期望值,排查起来直观很多。如果你用的是较新的版本,函数名前缀可能会有细微差异,比如mbedtls_sha256_self_test在多数版本下是一致的,但建议打开头文件确认一下,避免复制代码后编译报错。
编译命令就是传统三板斧:
make clean make编译成功后,out 目录下会生成可执行文件。第一次跑通时那种“多个 .c 文件真的编进去了”的感觉还是很踏实的。但编译通过只是第一步,真正的麻烦往往在后面。
4. 移植避坑:自测通过不代表万事大吉
4.1 现象:头文件找不到,报fatal error: mbedtls/config.h: No such file or directory
原因是多方面的:最常见的是 include 目录没拷全,或者 Makefile 里的-Iinclude路径没加对;还有一种情况是版本差异,新版的配置头叫mbedtls_config.h,源码里#include "mbedtls/config.h"在预处理时会被替换,如果目录里只有新文件,就找不到了。
解决路径分两步。第一,在工程根目录执行find include -name "*.h" | head,确认所有头文件都到位;第二,在 Makefile 里把 CFLAGS 的-I路径写成绝对路径,防止因为 make 的工作目录不同导致搜索路径失效。如果问题还在,用gcc -E -Iinclude src/main.c -v手动展开预处理,看看到底卡在哪个 include 路径上。这一步能看到完整的头文件搜索顺序,比瞎改 Makefile 高效得多。
4.2 现象:函数能编译通过,但自测程序没有任何输出
这个坑更隐蔽。编译和链接都成功,说明源文件都编进去了,但运行起来屏幕上一行字都没有。第一次遇到时我还以为是 printf 的问题,查了半天发现是优化等级惹的祸。当-O2或更高的优化等级被打开时,编译器可能把没有副作用的自测调用给优化掉了,因为self_test(1)的返回值没有被使用,编译器觉得这段代码可以“不执行”。
解决方法是显式使用返回值,并且在编译时降低优化等级。比如把主函数写成:
int ret = mbedtls_sha256_self_test(1); if (ret != 0) { return 1; }同时在 Makefile 里加:
CFLAGS += -O0对于移植验证阶段,-O0是最保险的,既不会被优化掉,也方便调试。等稳定性验证完了再开优化,避免优化带来的时序差异干扰你的判断。
4.3 现象:移植到 ARM 平台后,自测输出一堆错误,但同源码在 PC 上正常
这是最让人崩溃的问题,以为移植环境的差异导致了算法错误,但实际上大概率跟字节序有关。mbedtls 默认支持大小端,但部分平台或者配置宏组合不对时,内部字节序处理会出问题。尤其当你跑在 SOC 模拟器或者某些自定义平台上,架构指令集和字节序组合可能跟你预想的不一样。
排查顺序很关键:先看自测输出有没有算出一个“可能”的值,再确认平台架构。通常在 Makefile 里加上-DMBEDTLS_PLATFORM_C会解决一部分平台相关的初始化问题,同时检查配置头里有没有误开了MBEDTLS_NO_DEFAULT_ENTROPY之类的硬开关。还有一个快速验证方法是只跑 SHA-256,因为哈希算法对字节序的处理相对简单,如果连 SHA-256 都错,基本可以确定是配置宏问题而不是算法实现问题。
4.4 现象:在 QEMU 环境运行起来就崩溃,段错误或者直接无响应
编译链接都正常,拷进 QEMU 镜像后一运行就崩,这是新手最容易误判为“代码写错”的情况。实际上大多数原因是运行环境的栈空间太小。mbedtls 的自测程序会分配较大的临时缓冲区,如果你的链接脚本或者启动参数把栈设为 4KB、8KB,跑 AES 轮函数时可能直接越界。
解决思路是在链接脚本中把栈空间调到至少 256KB,或者如果是用户态程序,在启动脚本用ulimit -s看一下栈限制。另一个常见做法是调用mbedtls_platform_set_printf这类平台函数,把输出重定向到调试串口,否则 printf 可能因为缓冲机制卡死。这一步容易被忽略,但它就是 QEMU 上“运行正常但控制台没输出”的直接原因。
4.5 现象:链接时出现一堆重复符号,或者行为诡异
当你把 library 整个目录的通配符加进 SRCS 之后,重复定义的概率大幅上升。比如某些平台版本的 library 里同时存在platform.c和platform_util.c,它们的函数会有重叠。再说一个容易混淆的场景:你的系统库或者第三方库可能早就提供过 SHA-256 的同名函数,或者你之前编过的某个静态库里也有 mbedtls 的符号,导致链接器不知道该用哪个实现。
处理方案是编译后用 nm 检查生成的符号来源:
nm out/mbedtls_test | grep mbedtls_sha256看符号是 T 类型(位于代码段)还是 U 类型(未解析),如果是 U,说明你的程序链接的是外部的实现,不是 mbedtls 自带的。这时候要么给 mbedtls 的实现统一加前缀,要么确认链接顺序,把自己的 library 目录放在系统库之前。这个技巧在嵌入式环境里尤其管用,能省掉很多肉眼找半天也发现不了的“同名污染”。
5. 上 QEMU 验证:把自测程序装进镜像,串口里看输出
5.1 打包进 rootfs,重新生成镜像
编译产物在 out 目录下,按这份笔记的操作,可执行文件名会是 mbedtls_test,或者你自己起的名字。我们要把它放进 QEMU 启动的镜像里,才能拿到目标环境里运行。我的常见做法是先把可执行文件拷到 buildroot 的 out 目录,然后再走一遍 buildroot 的打包流程,生成最终的启动镜像:
cp out/mbedtls_test /path/to/buildroot/output/target/root/ make -C /path/to/buildroot注意拷贝的目标路径不要放在系统目录,比如/usr/bin之类,那可能被后续的清理规则覆盖。放到/root或者/home下更安全。打包完成后启动 QEMU,进入 shell 后直接执行:
./mbedtls_test如果自测输出正常打印出 SHA-256/AES 的测试结果,说明整个工具链、运行时和镜像打包流程全部打通了。这个信号比点灯强得多。
5.2 进阶技巧:用返回码做批量回归
我后来在验证不同 SOC 软件栈时,会顺手把自测程序包装成一个简单的回归脚本,效果不错。修改 main.c 让返回值直接对应测试结果,然后在 QEMU 里用一个脚本连续运行多组测试:
./mbedtls_test -sha256 ./mbedtls_test -aes echo $?把$?的判断加进脚本,就能在环境搭建时快速筛出问题。这个技巧看起来简单,但真的能帮你省掉很多“手动看屏幕输出”的低效时间。尤其是 ARMv8/armv9 这种多核平台,每个核的内存命名空间和启动方式不同,用脚本批量跑远比肉眼靠谱。
这套流程走完之后,我发现移植 mbedtls 不再是心里发怵的事。从那以后,我每次拿到新的 ARM 板卡或者切换到 armv9 安全相关项目时,都会强制走一遍“拉源码、拷目录、改 Makefile、跑自测、装 QEMU 验证”这个流程。它像一块试金石,能把环境问题、工具链问题、运行问题一次性暴露出来。希望帮到你。
本文还有配套的精品资源,点击获取