简介:这份PDF面向在ARMv9与嵌入式平台上做安全通信开发的工程师,记录mbedTLS加密库的移植与自测实践,适合已具备C语言与交叉编译基础、希望把开源TLS库跑通在自研芯片或QEMU环境中的中高级开发者参考。资源共1个PDF文件,约199KB,篇幅精炼,以完整的移植流程为主线,便于快速浏览与对照操作。内容围绕源码获取、模板工程拷贝、library与include目录整合、Makefile中工具链与SRCS的修改、自测宏开关及mbedtls_sha256_self_test等接口调用展开,最终给出编译产物在QEMU中运行验证的思路,可作为加密算法裁剪、编译报错排查与自测框架搭建的参考样本。目前已有335人学习,对想打通mbedTLS从编译到运行链路的读者具有一定借鉴价值。
1. 从一次链接报错说起:mbedtls 挪进交叉环境,卡住的往往不是算法
嵌入式项目里要在 ARM SOC 上跑一次 AES 或 SHA256,mbedtls 几乎是默认答案——代码量可裁、不依赖外部库、自带自测。但把它从源码仓挪进自己的工程,很多人第一步就翻车:源码拷进来了,头文件路径也写了,make 一敲,报的却是undefined reference to 'mbedtls_sha256_self_test',回头改代码、改宏,越改越乱。这套流程本身不复杂,麻烦在于它横跨三层环境:宿主机、交叉工具链、qemu 里那份目标 rootfs,任何一层没对齐,报错都会指到别的地方去。这篇笔记适合要在 armv9 或更老的 ARM 芯片上验证 mbedtls 算法、手里已经有现成 C 工程模板的嵌入式开发,从拉源码一路记到 qemu 里跑出自测结果。
2. 版本与工具链先定死:mbedtls 2.28 LTS、aarch64 交叉编译器和 qemu 目标板
装之前先把三件事对齐:mbedtls 用哪条分支、交叉工具链用哪个三元组、qemu 里跑的是哪种 rootfs。这三样任意一个选错,后面改 Makefile 会改到怀疑人生。我一般先把它们写成一张小卡片贴在工程根目录,省得编译报错时靠猜。
2.1 mbedtls 两条分支:2.28 LTS 和 3.x,配置文件名不一样
mbedtls 现在有两条主线。2.28 是长期维护分支,配置文件在include/mbedtls/config.h,网上能搜到的移植教程绝大多数基于这一版,宏名、目录结构都对得上;3.x 之后配置文件改成了include/mbedtls/mbedtls_config.h,还多出一层config_adjust_*.h做自动裁剪,不少 low-level 接口被收进 private 头文件,照着老教程抄会一路撞墙。
我的选择很直接:从零验证算法、照着现成模板搭工程,走 2.28 LTS;已有代码库锁在 3.x 的,别为了这篇笔记降级,改宏的位置和文件名就行。拉分支的时候用-b指定,不要拉 master,避免默认分支漂到 3.x 上去。
# 2.28 长期维护分支,体积小,配置文件和大部分教程对得上 git clone -b mbedtls-2.28 --depth 1 https://github.com/ARMmbed/mbedtls.git mbedtls-src # 如果目标工程本来就绑在 3.x,用这条,注意配置文件已改名 # git clone -b v3.6 --depth 1 https://github.com/ARMmbed/mbedtls.git mbedtls-src--depth 1只拉最近一次提交,源码体积能小一大截,移植验证根本用不到提交历史。-b后面跟的是分支名,不是 tag,写错会直接报Remote branch not found,那时候别急着怀疑网络,先git ls-remote --heads看一眼分支到底叫什么。
提示:2.28 和 3.x 的
config.h路径不同,后面改宏的那一步,先确认自己手上是哪一版,别拿着 3.x 的目录结构去改 2.x 的文件。
2.2 交叉工具链:aarch64 还是 armhf,看芯片不看教程
工具链前缀选错是最隐蔽的坑。32 位 ARM 用arm-linux-gnueabihf-,64 位 aarch64(armv9 芯片基本都落在这里)用aarch64-linux-gnu-。判断依据只有一个:目标板上uname -m输出什么,或者芯片手册里核是什么架构,跟教程里写什么没关系。用错前缀的典型症状是编译能过、跑起来直接Illegal instruction,或者链接阶段报一堆 ABI 不兼容。
装完先验证工具链本身是好的,别等 Makefile 写完才发现 cc 根本不在 PATH 里:
# 宿主机上装上交叉编译器(Debian/Ubuntu 系) sudo apt install -y build-essential git make gcc-aarch64-linux-gnu # 验证:能打印版本、能找到 sysroot 才算装好 aarch64-linux-gnu-gcc -v aarch64-linux-gnu-gcc -print-sysroot-print-sysroot输出的是这套工具链自带的头文件和库根目录,后面查 libc 版本冲突时要反复用到它。如果目标板上的 rootfs 是用 buildroot 自己构建的,更稳的做法是直接用 buildroot 产出的工具链output/host/bin/aarch64-buildroot-linux-gnu-gcc,glibc 版本天然对齐,省掉后面一整类GLIBC_2.xx not found的麻烦。
2.3 宿主机依赖与目录规划:四个目录摆清楚
工程目录乱是后面所有混乱的源头。我习惯在~/work下固定四个位置:mbedtls 原始源码、通用 C 模板、最终要编译的工程、buildroot 源码树。四者互不污染,出问题直接删掉重建,不用在一个目录里翻找哪个文件是拷进来的、哪个是原生的。
| 目录 | 用途 | 能不能随便改 |
|---|---|---|
~/work/mbedtls-src | mbedtls 原始源码,只读参考 | 不改,保持干净 |
~/work/code_template | 通用 C 工程模板 | 不改,作为副本源 |
~/work/mbedtls_test | 真正要编译的工程 | 随便改 |
~/work/buildroot | 目标板 rootfs 构建树 | 只在打包时动 target 目录 |
mkdir -p ~/work && cd ~/work # 宿主机基础工具,qemu 和 buildroot 需要的那几样顺手装上 sudo apt install -y build-essential git make file tree python3file和tree这两个小工具看着无关紧要,实际排查时用得最多:file一眼看出产物是 x86 还是 aarch64,tree一眼看出 include 目录到底拷没拷全。装完cd ~/work && ls确认四个目录都在,再往下走。
3. 拉源码搭骨架:mbedtls 的 library 与 include 怎么塞进通用 C 模板
这一步的目标很明确:把 mbedtls 的源码目录和一份能编译的 C 工程骨架拼到一起,拼完能通过编译器的头文件检查。拼错了不可怕,可怕的是拼完直接写业务代码,最后报错分不清是路径问题还是源码问题。
3.1 克隆 mbedtls 与通用 C 工程模板
mbedtls 上一步已经拉好了,这里再拉一份通用 C 模板。模板的作用是提供一个已经写好的 Makefile 骨架和main.c入口,省掉从零手写编译规则的时间,后面只需要往里填工具链和源文件列表。
cd ~/work git clone --depth 1 https://gitee.com/baron_zz/code_template.git # 看一眼模板里到底有哪些子目录,别急着拷 ls code_templatels这一步别省。模板仓里通常不止一个子目录,native_c_template只是其中一个,代表“本机原生编译”的最小工程。确认名字对得上再拷,名字写错会得到一个空目录,后面 make 报No rule to make target 'main.o',又要多排查一轮。
3.2 目录怎么摆:library、include 拷进去,programs 一个别带
mbedtls 源码树里有library、include、programs、tests、scripts等一堆目录,移植只需要前两个。programs里自带几十个带main函数的示例程序,全拷过去会和自己的main.c抢符号;tests更夸张,几十兆的测试向量,对验证自测毫无帮助。
cd ~/work # 模板拷一份出来,命名成自己的工程,别在模板目录里直接改 cp -r code_template/native_c_template mbedtls_test # 只拷 library 和 include,programs/tests 一概不要 cp -r mbedtls-src/library mbedtls_test/ cp -r mbedtls-src/include mbedtls_test/ # 确认结构,正常应该看到 main.c、Makefile、library/、include/ 四项 tree -L 2 mbedtls_testtree -L 2只展开两层,输出干净。检查点有两个:include/mbedtls/下面必须有aes.h、sha256.h、config.h(2.28)或mbedtls_config.h(3.x);library/下面必须有aes.c、sha256.c。少任何一个,说明拷贝时路径写偏了,回头cp -rv重来一遍,比在编译报错里猜快得多。
注意:
cp -r拷的是目录本身,如果源路径写成mbedtls-src/library/(结尾带斜杠),行为在某些 shell 下会把目录内容铺平,结果library/aes.c变成aes.c。Makefile 里的路径就全对不上了,这类问题排查起来极其费时。
3.3 用一行最小 main 先验证头文件路径
在写任何业务代码之前,先把main.c换成极端简单的版本,只做一件事:包含头文件、打印版本号。这一步能把“头文件路径对不对”和“源码逻辑对不对”彻底分开,是我踩过几次坑之后固定下来的习惯。
#include <stdio.h> #include "mbedtls/version.h" #include "mbedtls/aes.h" #include "mbedtls/sha256.h" int main(void) { /* 只验证头文件能找到、库版本能读出来,暂不调用任何算法 */ printf("mbedtls version: %s\r\n", MBEDTLS_VERSION_STRING); return 0; }MBEDTLS_VERSION_STRING定义在include/mbedtls/version.h里,用它可以顺手确认拷进来的源码版本对不对。代码里写的是#include "mbedtls/aes.h",所以编译器的-I必须指到include这一层,而不是include/mbedtls。这是最容易写反的一个参数,写反之后报mbedtls/aes.h: No such file or directory,很多人第一反应是“文件没拷全”,其实文件好好躺在那里。
先用宿主机 gcc 编一遍,能过再换交叉工具链:
cd ~/work/mbedtls_test gcc -Iinclude main.c -o /tmp/ver_check && /tmp/ver_check能打印出版本号,说明目录结构和-I都对。这一步失败就别往下走了,后面所有报错都会是它的连锁反应。
3.4 版本号从哪读,别靠猜
拷进来的到底是 2.28 还是 3.x,不用猜。除了上面打印的宏,还可以直接看文件:
# 2.28 看这个文件里的版本宏 grep -n "MBEDTLS_VERSION_STRING" ~/work/mbedtls_test/include/mbedtls/version.h # 顺便确认配置文件叫什么名字,决定后面改哪个文件开宏 ls ~/work/mbedtls_test/include/mbedtls/ | grep -E "config"2.28 输出config.h,3.x 输出mbedtls_config.h,两个都在说明是过渡版本,改宏的时候以实际被build_info.h引用的那个为准。版本确认清楚,第 4 章开宏的位置才不会改错文件——改了没被引用的那个,编译器不会报错,宏也不会生效,属于典型的玄学问题。
4. 改 Makefile 与开宏:SRCS 裁到最小、补上隐形依赖、把自测函数接进 main
工程骨架搭好,接下来三件事:让 Makefile 认交叉工具链、把 SRCS 裁到只编用得上的模块、打开MBEDTLS_SELF_TEST并把自测调用接进main。这一步做完,make出一条 aarch64 产物,整个移植工作就完成了大半。
4.1 Makefile 里的 CROSS_COMPILE 与编译选项
模板自带的 Makefile 是给本机 gcc 用的,直接改成用变量控制工具链前缀,后面换板子只改一行。我一般保留?=,这样命令行还能临时覆盖:make CROSS_COMPILE=arm-linux-gnueabihf-。
# 交叉工具链前缀:aarch64 用 aarch64-linux-gnu-,32 位 ARM 用 arm-linux-gnueabihf- CROSS_COMPILE ?= aarch64-linux-gnu- CC := $(CROSS_COMPILE)gcc TARGET := mbedtls_demo # 只编自测用得上的模块,library 下几十个 .c 全塞进来会带进一堆无关依赖 SRCS := main.c \ library/aes.c \ library/sha256.c \ library/platform_util.c \ library/platform.c # 源码里写的是 #include "mbedtls/aes.h",-I 指到 include 这一层 INCLUDES := -Iinclude CFLAGS := -O2 -Wall -Wextra $(INCLUDES) # 自测函数只有定义了这个宏才会被编译进目标文件 CFLAGS += -DMBEDTLS_SELF_TEST OBJS := $(SRCS:.c=.o) all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(OBJS) -o $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(OBJS) $(TARGET) .PHONY: all cleanCROSS_COMPILE后面那个短横线不能省,省了会得到aarch64-linux-gnugcc这种不存在的命令。-O2是为了让自测跑得快一点,算法自测本身对优化等级不敏感;-Wall -Wextra在移植阶段一定要开,mbedtls 的有些告警(比如隐式声明)能提前暴露头文件没包含全的问题。clean里用rm -f而不是rm -rf,是为了避免手滑把路径变量写空时误删目录。
4.2 SRCS 裁剪:aes.c、sha256.c 和两个容易漏的依赖
只写aes.c和sha256.c是不够的,链接阶段十有八九会报undefined reference to 'mbedtls_platform_zeroize'。原因很直接:这两个算法文件内部调用了平台层的内存擦除函数,实现放在library/platform_util.c里。另外 2.28 默认配置里MBEDTLS_PLATFORM_C是打开的,mbedtls_printf这类符号会变成函数指针,需要library/platform.c参与链接。
怎么确认还缺哪个文件,不用猜,直接反查:
cd ~/work/mbedtls_test # 看 aes.c 和 sha256.c 里引用了哪些 mbedtls_ 开头的平台函数 grep -nE "mbedtls_platform_[a-z_]+" library/aes.c library/sha256.c | sort -u # 顺带确认这些符号到底定义在哪个 .c 里 grep -rn "mbedtls_platform_zeroize" library/*.c | grep -v "\.h"第一条命令会把mbedtls_platform_zeroize之类的调用点列出来,第二条告诉你实现在platform_util.c。这种“先找引用、再找定义”的顺序,比一个个试错加文件快得多,尤其是后面要往工程里加bignum.c、rsa.c这类依赖更深的模块时,方法完全一样。
提示:不要把
library下所有.c一把梭全编进去。能编过,但产物会大出好几倍,而且某些模块之间有隐式的配置依赖,全编容易撞上“这个宏没开、那个结构体不完整”的连环报错,排查成本比按需裁剪高得多。
4.3 MBEDTLS_SELF_TEST 宏:放命令行还是放 config.h,只能二选一
2.28 的默认配置里,MBEDTLS_SELF_TEST是注释掉的。这个宏不打开,mbedtls_aes_self_test、mbedtls_sha256_self_test这些函数根本不会被编译进目标文件,链接时直接报未定义。开头那个报错,八成就是这里。
先确认当前状态:
# 2.28 看 config.h,3.x 换成 mbedtls_config.h grep -n "MBEDTLS_SELF_TEST" ~/work/mbedtls_test/include/mbedtls/config.h输出如果是//#define MBEDTLS_SELF_TEST,说明没开。开的方式有两种:在config.h里去掉注释,或者在 Makefile 里加-DMBEDTLS_SELF_TEST。两种都行,但同时做会得到warning: "MBEDTLS_SELF_TEST" redefined,虽然只是警告,但意味着你对宏的来源失去了控制,后面改配置会分不清是哪个生效。
我的习惯是统一放 Makefile,也就是 4.1 里那行CFLAGS += -DMBEDTLS_SELF_TEST。好处是配置文件保持和上游一致,升级 mbedtls 版本时直接覆盖include目录不会丢改动;代价是所有编译入口都得记得带这个宏,所以工程里最好只留一个 Makefile 入口,别额外写编译脚本。
4.4 main 里接自测:verbose 参数和返回值怎么读
宏开了,就可以在main.c里调自测函数。mbedtls_aes_self_test(int verbose)和mbedtls_sha256_self_test(int verbose)的参数含义一样:传非 0 会把自测的每一步打印出来,传 0 只在失败时输出。调试阶段建议传 1,能看清卡在哪一组测试向量。
#include <stdio.h> #include "mbedtls/version.h" #include "mbedtls/aes.h" #include "mbedtls/sha256.h" int main(void) { int ret = 0; printf("mbedtls %s\r\n", MBEDTLS_VERSION_STRING); /* 参数 1 表示打印自测每一步,定位问题时很有用 */ ret |= mbedtls_aes_self_test(1); ret |= mbedtls_sha256_self_test(1); if (ret == 0) { printf("self test PASS\r\n"); } else { printf("self test FAIL, ret=0x%04X\r\n", ret); } /* 返回值传给 shell,方便脚本里用 $? 判断 */ return ret; }用|=而不是+=,是因为自测函数返回的是 0 或非 0,按位或能保证任何一个失败都不会被后一个的成功覆盖掉。返回值直接当进程退出码用,在 qemu 里跑完之后echo $?就能判断,不需要额外解析输出文本。这一步的写法看着简单,但它决定了后面自动化验证好不好做——手工看屏幕谁都会,能让脚本判断才算跑通。
4.5 产物自检:file 和 readelf 看两行就够
make成功不代表产物能用。编译完先看两行:
cd ~/work/mbedtls_test make clean && make # 看架构:必须是 ARM aarch64 或者 ARM, EABI5,出现 x86-64 说明还在用宿主机 gcc file mbedtls_demo # 看动态链接器路径,决定这份产物能不能在目标 rootfs 里跑起来 readelf -l mbedtls_demo | grep -A1 interpreterfile输出x86-64是最常见的翻车现场,原因通常是 Makefile 里CC被后面的规则覆盖,或者模板里硬编码了gcc没改。readelf那行输出类似/lib/ld-linux-aarch64.so.1,这个路径必须和目标 rootfs 里的实际路径一致,不一致的表现是文件明明在、权限也有,执行却报not found,非常迷惑。
5. 避坑与排查:移植 mbedtls 时最容易卡住的五个地方
下面五条都是移植过程中反复出现的,按“现象 → 原因 → 解决”记,遇到时直接对号入座,比从头捋一遍流程快。
5.1 编译期:宏和路径相关的三个坑
现象一:链接报undefined reference to 'mbedtls_sha256_self_test',但library/sha256.c明明在 SRCS 里。
原因:MBEDTLS_SELF_TEST没生效。函数体被#if defined(MBEDTLS_SELF_TEST)包着,宏没定义,整个自测函数就没编进去。另一种可能是宏加在了命令行、但你打开的是 3.x 的mbedtls_config.h而编译实际引用的是另一个配置文件。
解决:先grep MBEDTLS_SELF_TEST include/mbedtls/*.h确认宏在哪个文件被定义或注释,再确认 Makefile 里-D有没有真的传给编译器(make V=1或手动make -n看完整命令行)。
现象二:编译报warning: "MBEDTLS_SELF_TEST" redefined,或者改完宏行为没变化。
原因:config.h里取消注释了一次,Makefile 里又-D了一次。头文件里的定义和命令行定义冲突,编译器按“命令行优先、头文件覆盖”的顺序处理,实际生效的和你以为的可能不是同一个。
解决:二选一,把config.h里的定义重新注释掉,或者删掉 Makefile 里的-D。我统一走 Makefile,配置头文件保持原样。
现象三:报mbedtls/aes.h: No such file or directory,但ls include/mbedtls/aes.h文件确实存在。
原因:-I指错了层级。源码里写#include "mbedtls/aes.h",编译器会拿-I的路径去拼mbedtls/aes.h,所以-I必须是include而不是include/mbedtls。反过来,如果源码里写#include "aes.h",才需要指到include/mbedtls。
解决:统一成前者,工程里所有 mbedtls 头文件都带mbedtls/前缀,-Iinclude一行搞定。混着写迟早出问题。
5.2 链接期与运行期:libc 版本和动态链接器
现象四:qemu 里执行./mbedtls_demo报version 'GLIBC_2.29' not found,或者No such file or directory。
原因有两个方向。前者是工具链自带的 glibc 版本比目标 rootfs 里的新,产物依赖了 rootfs 里不存在的符号;后者是动态链接器路径对不上,readelf -l看到的 interpreter 路径在 rootfs 里不存在,内核找不到装载器,报错文本偏偏是“文件不存在”,误导性极强。
解决:优先用 buildroot 产出的工具链output/host/bin/aarch64-buildroot-linux-gnu-gcc,glibc 版本天然一致;实在要混用,就在链接时加-static静态链接,代价是产物变大,但验证阶段完全够用。路径问题先readelf -l看 interpreter,再ls目标 rootfs 里对应位置,两边对齐就行。
注意:
chmod +x别忘了。从宿主机拷进 rootfs 的文件经常丢掉执行位,症状也是Permission denied,和链接器问题容易混。
5.3 打包环节:拷进 output/target 却没生效
现象五:把产物拷到 buildroot 的output/target/下,重新make之后启动 qemu,发现里面还是旧文件,甚至根本没有。
原因:buildroot 的output/target/是构建中间产物,某些配置下顶层make会重新生成 rootfs 镜像,但如果你拷的位置不对(比如拷到了output/target/usr/bin而镜像用的是只读 squashfs 加 overlay),或者只跑了某个包的 rebuild 而没有重打镜像,改动就进不去。
解决:拷到output/target/root/下最稳,然后回 buildroot 顶层完整跑一次make,让它重新生成output/images/下的镜像。验证方法是看output/images/里镜像文件的时间戳有没有更新,时间戳没变说明镜像根本没重打,qemu 里当然看不到新东西。
6. 装完怎么验证:本机先过一遍,再进 qemu 看自测输出
验证分两级,顺序不能反。反过来做,一旦 qemu 里跑不出结果,你没法判断是代码问题、工具链问题还是 rootfs 问题,只能从头再来。
6.1 第一级:宿主机 gcc 先编一版跑通
同一份main.c,用宿主机 gcc 编一遍,把MBEDTLS_SELF_TEST也带上。这一步排除了交叉编译和 rootfs 的干扰,只验证 mbedtls 源码和自测调用本身没问题。
cd ~/work/mbedtls_test # 用宿主机编译器走一遍同样的源文件列表 gcc -Iinclude -DMBEDTLS_SELF_TEST \ main.c library/aes.c library/sha256.c library/platform_util.c library/platform.c \ -o /tmp/mbedtls_demo_host /tmp/mbedtls_demo_host echo "exit code: $?"自测全部通过时会打印self test PASS,退出码 0。如果这一步就失败,别急着换交叉工具链,问题一定在源码或宏配置上。我见过有人直接跳到 qemu 里调,最后发现是main.c里少包含了一个头文件,白折腾半天。
6.2 第二级:交叉编译、拷进 buildroot、qemu 里跑
本机过了,再交叉编一版。
cd ~/work/mbedtls_test make clean && make # 产物架构和动态链接器先确认,两行命令省掉后面一半的排查 file mbedtls_demo readelf -l mbedtls_demo | grep -A1 interpreter确认无误后拷进 buildroot 的 target 目录,重打镜像再启动 qemu:
# 拷到 root 用户目录下,路径短、权限简单 cp mbedtls_demo ~/work/buildroot/output/target/root/ # 回 buildroot 顶层重新打包,让它重新生成 images 下的镜像 cd ~/work/buildroot && make # 确认镜像时间戳更新了,再启动 qemu(脚本名按自己板子的 defconfig 来) ls -l output/images/ ./output/images/start-qemu.shqemu 起来之后登录,执行/root/mbedtls_demo,看到self test PASS且echo $?返回 0,整条链路就算通了。
| 检查项 | 命令 | 通过标准 |
|---|---|---|
| 产物架构 | file mbedtls_demo | 显示 ARM aarch64 或 ARM, EABI5,不出现 x86-64 |
| 动态链接器 | readelf -l mbedtls_demo | grep interpreter | 路径在目标 rootfs 中真实存在 |
| 自测结果 | ./mbedtls_demo | 打印self test PASS |
| 退出码 | echo $? | 输出 0 |
| 镜像更新 | ls -l output/images/ | 时间戳为本次打包时间 |
还有一个更省事的技巧:把mbedtls_demo挂到 qemu 的共享目录或者用 9p 直接映射宿主机目录,改完代码不用重打镜像就能跑,调试算法阶段效率高得多。最后定版的时候再走一次完整的 buildroot 打包流程,确认产物真的能进镜像。
从那以后我每次做这类移植,都强制先在本机 gcc 跑一遍自测,再动交叉工具链——这个顺序救过我太多次,也让“到底是宏没开还是架构不对”这种问题不再靠猜。这套源码、模板和 Makefile 改法都在资源里,照着走一遍基本能复现。希望帮到你。
本文还有配套的精品资源,点击获取