☰
mbedTLS移植实战:在QEMU中跑通AES/SHA-256自测
2026/10/26 6:37:01 网站建设 项目流程

简介:面向ARMv9/ARM SoC、嵌入式及芯片软件开发者,这份mbedtls移植实践笔记以PDF文档形式完整记录了将mbedtls安全库从源码获取、工程模板搭建、目录整合到编译运行的全流程。资源共1个PDF文件,大小仅199KB,内容聚焦在可复现的操作步骤上,适合当作移植参考手册。文档按实际执行顺序展开:先通过git clone拉取mbedtls源码及配套代码模板,复制出mbedtls_test工程,再拷贝library与include目录;之后修改Makefile指定交叉编译工具链,将aes.c、sha256.c加入SRCS,通过CFLAGS开启MBEDTLS_SELF_TEST宏,并在c文件中调用mbedtls_sha256_self_test完成自测;最后在QEMU环境中打包buildroot并运行程序,验证移植结果。已有333人学习,尤其适合刚入门安全库移植或想了解ARM交叉编译流程的开发者,能帮助避开Makefile配置、宏定义、工具链选择等常见坑,缩短从零到可运行的调试时间,为后续在安全启动、TEE等场景中集成mbedtls打下基础。

1. 一次 mbedTLS 移植体验:自测程序跑通才敢继续往下走

做 ARM SoC 底层软件的人,几乎都会遇到一个很平凡的诉求:安全启动要校验镜像,TEE 侧要跑 AES/SHA-256,但工程里连一个能用的密码学库都没有。自己写不现实,桌面端那套大库又太重,硬塞进约束严格的启动镜像里往往得不偿失。mbedTLS 的定位就是这个空隙——为资源受限环境设计,RSA、AES、SHA-256 都能按宏裁剪,最后编出来的是一个干净的可执行文件或静态库。

这次移植的目标不是把整个库编译进某个大系统,而是把它接进一个干净的 C 工程模板,在 QEMU 模拟环境里跑通 mbedTLS 自测程序。整个过程涉及源码目录切割、Makefile 改造、SELF_TEST 宏启用和交叉编译。适合刚接触 ARMv9 平台、想在芯片开发环境里验证 mbedTLS 的开发者。如果你之前只听说过库里有 library 和 include,不知道怎么塞进自己的工程,这篇可以帮你省掉几小时的盲试时间。

2. 移植前置:源码、模板与自测函数怎么落到本地工程

2.1 拉代码与固定版本:LTS 分支最省心

mbedTLS 的源码仓库结构很标准,根目录下分 include、library、programs、tests 等目录。真正移植时只需要两个目录:include 里放的是对外头文件和默认配置模板,library 里放的是全部算法与平台层的 C 源文件。其它目录在裁剪时可以先不看,programs 下的示例代码只在需要抄调用方式时才会打开。

先把源码和一份干净的 C 工程模板都拉到本地。我个人的习惯是先固定一个 LTS 的 tag,而不是直接用默认分支。原因很简单:2.28 和 3.x 的配置宏、API 都有差异,网上大多数旧的移植笔记基于 2.x,如果拉了 master,步骤对不上时会很浪费时间。下面的命令用占位符代替仓库地址,实际操作时把尖括号部分替换成源码仓库和模板仓库地址即可:

git clone <mbedtls 源码仓库地址> git clone <C 工程模板仓库地址> cd mbedtls git checkout v2.28.7 ls include library

这里的git checkout v2.28.7是固定版本的关键动作。2.28 是长期支持分支,安全固件和 TEE 相关场景里用得很多。ls include library只是确认目录存在,不是必须。如果 clone 下来的默认分支已经是 3.x,这一步尤其重要,否则后面配置宏的名字会对不上。模板工程不需要固定版本,它的任务只是提供一套干净的 Makefile 和 src 目录,后面我们自己脱离模板单独改编译选项。

2.2 模板拷贝与头文件落位:cp 命令的三个注意点

拿到模板后,先复制一份给 mbedTLS 专用,命名简单直接,比如 mbedtls_test。命令行最容易犯的错是忘掉-r,目录拷贝必须递归,否则报错或者拷出来是空目录:

cp native_c_template mbedtls_test -r cp mbedtls/library ./mbedtls_test/ -r cp -r mbedtls/include ./mbedtls_test/ ls mbedtls_test

第一条命令把模板目录整体复制一份;第二条把 library 下所有源文件拷进新工程;第三条把 include 原样拷进去。注意第二条没有加通配符,直接拷目录可以避免漏掉隐藏文件或者子目录。

拷贝之前要确认 library 目录里没有历史编译产物。如果源码目录之前被编译过,.o和.a会被一起拷过来,在后续 make 时可能触发"文件存在但不需要重新编译"的隐蔽问题。我一般在拷之前先执行一次make clean,或者干脆只拷*.c文件。library 目录里宁可多一些没用的xxx.c,也不要漏掉关键的平台层文件,因为交叉编译时的缺失符号排查比多编几个文件更耗时间。

2.3 裁剪原则:为什么要选源码编译而不是预编译库

把 library 整体拷进来之后,需要一个明确的裁剪思路。mbedTLS 支持两种接入方式:一是编成静态库.a再链接业务代码;二是直接让业务工程把选中的.c文件编进最终可执行文件。我强烈建议嵌入式移植用第二种,原因是宏裁剪。

MBEDTLS_SELF_TEST这类宏会影响头文件里的函数声明和 C 文件里的条件编译。如果用预编译库,Makefile 里的宏和库内编译宏不一致时,链接可能正常通过,但运行起来某些功能没生效,这种问题排查起来非常玄学。把源码直接编入工程,所有编译单元共用同一组 CFLAGS,宏状态是确定的,后面自测程序才好定位问题。

这里还有一个边界要说清楚:直接编源码不代表把整个 library 目录编一遍,而是先把需要的算法文件加到 SRCS 里,缺什么符号再补什么文件。这样好处是最终可执行文件体积可控,并且自测起来路径短。坏处是库内部文件之间的依赖要自己摸清楚,这个在第 3 章会具体演示。

2.4 自测程序入口:SHA-256 与 AES 的 self_test 在哪

mbedTLS 每个算法模块都带一个_self_test函数,用来在目标板上把算法跑一遍,跟标准测试向量比对。移植完成后可以用自测先验证库本身没问题,再去找业务问题。

自测函数位于对应算法源文件中,比如sha256.c里有mbedtls_sha256_self_test,aes.c里有mbedtls_aes_self_test。它们是否被编译进去,由两个条件共同决定:算法宏和MBEDTLS_SELF_TEST宏。

源文件自测函数需要打开的头文件宏
library/sha256.cmbedtls_sha256_self_testMBEDTLS_SHA256_C, MBEDTLS_SELF_TEST
library/aes.cmbedtls_aes_self_testMBEDTLS_AES_C, MBEDTLS_SELF_TEST
library/platform_util.c无MBEDTLS_PLATFORM_C

编译时除了加-DMBEDTLS_SELF_TEST,还要确认MBEDTLS_SHA256_C和MBEDTLS_AES_C没有被手动关闭。如果使用默认配置,这两个宏默认是打开的,但如果是从其它工程复用 config,就得打开检查一遍。自测函数是移植是否成功的直接证据,所以第一步先让它跑起来,比封装上层接口更优先。

2.5 移植后的目录应该长什么样子

移植完成后的 mbedtls_test 目录结构大致如下:

mbedtls_test/ ├── Makefile ├── include/ │ └── mbedtls/ ├── library/ │ ├── aes.c │ ├── sha256.c │ ├── mbedtls_internal_sha256.c │ ├── platform_util.c │ └── ... └── src/ └── main.c

src 放业务入口,library 放第三方源码,include 直接让别人引用头文件。Makefile 在编译时只需要把-Iinclude和-Isrc加上,就能把事情做完。第三方 C 代码不额外套目录层,省得以后把 mbedTLS 升级换版本时还要改一大串 include 路径。如果团队规范要求放到 third_party,也可以整体移动,但移动之后要同步改-I路径,这一步容易在大型工程里被忽略。

3. 改 Makefile 并编译:工具链、SRCS 与 SELF_TEST 宏的配置顺序

3.1 CROSS_COMPILE 换成交叉编译工具链

由于 mbedTLS 最终要在 QEMU 模拟的 ARM SoC 环境里跑,x86 上编译出的二进制不能直接用,必须指定交叉编译工具链。不同项目工具链前缀差异很大,但变量抽象是一致的:用一个CROSS_COMPILE前缀,后面拼接 gcc、ar、strip。这样换平台时只改一行:

CROSS_COMPILE ?= arm-linux-gnueabihf- CC := $(CROSS_COMPILE)gcc AR := $(CROSS_COMPILE)ar STRIP := $(CROSS_COMPILE)strip

如果你要在 64 位的 ARMv8/ARMv9 模拟环境里跑,前缀可能是aarch64-none-elf-或者aarch64-linux-gnu-,取决于工具链和 sysroot。QEMU 用户态模拟下,arm-linux-gnueabihf-的产物通常配qemu-arm,aarch64-linux-gnu-的产物配qemu-aarch64。后面验证时会用到。使用?=不是为了花哨,而是允许命令行执行make CROSS_COMPILE=aarch64-linux-gnu-覆盖,不需要修改 Makefile。交叉编译的环境变量在命令行临时指定,通常比在 Makefile 里写死可维护性更好。

3.2 SRCS 里放了哪些文件:用到哪几个算法就放哪几个

这里要明确一点:直接编译源码不等于把整个 library 编一遍。裁剪从 SRCS 就要开始。下面这个示例是一个能跑通 SHA-256 与 AES 自测的最小集合:

SRCS := src/main.c \ library/aes.c \ library/sha256.c \ library/mbedtls_internal_sha256.c \ library/platform_util.c \ library/platform.c

逐个解释:aes.c提供 AES 自测和加解密 API;sha256.c提供 SHA-256 算法;mbedtls_internal_sha256.c是内部实现辅助文件,某些配置下必须一起编;platform_util.c提供内存清零等基础函数,很多算法文件都引用它;platform.c提供时间、打印等平台抽象。

如果在链接时报找不到mbedtls_zeroize之类的符号,基本是platform_util.c没放进来。这种缺符号是移植里最典型的连锁反应:库内部文件之间有依赖,不是加了一个算法源文件就完事。把这个现象反过来想,也能用来判断 SRCS 是否已经够用:只要链接阶段没有报 undefined reference,说明当前算法闭合了。后续添加 RSA 或 ECC 时,还要再增加rsa.c、ecp.c、bignum.c等文件,仍然按报错逐个补。

3.3 CFLAGS 与 config:SELF_TEST 宏和自定义配置头

mbedTLS 的裁剪主要靠 CFLAGS 里的宏。下面是这次工程里实际用到的三行:

CFLAGS += -Iinclude -Isrc CFLAGS += -DMBEDTLS_SELF_TEST CFLAGS += -DMBEDTLS_CONFIG_FILE="\"mbedtls_config.h\""

第一行设置头文件搜索路径;第二行打开自测宏,让算法源文件里的自测函数参与编译;第三行指定自定义配置头。第三行不写时,mbedTLS 会使用默认配置,默认配置里几乎所有算法都开着,虽然能编过,但最终体积和交叉编译时间都不理想。

这里有一个新手容易忽略的细节:-DMBEDTLS_CONFIG_FILE="\"mbedtls_config.h\""的引号转义。Makefile 解析后,传给编译器的内容是-DMBEDTLS_CONFIG_FILE="mbedtls_config.h",C 语言里的 include 指令才能正确把文件名当字符串用。如果少一个反斜杠,预处理时就报fatal error: mbedtls_config.h: No such file or directory。遇到这个报错先别改路径,绝大多数是刚才说的转义问题,而不是文件不存在。

3.4 在 main.c 里调用 SHA-256 与 AES 自测

自测程序不需要做复杂业务,直接在 main 里依次调用即可:

#include <stdio.h> #include "mbedtls/sha256.h" #include "mbedtls/aes.h" int main(void) { int ret = 0; ret = mbedtls_sha256_self_test(1); if (ret != 0) { printf("sha256 self test failed: %d\n", ret); return -1; } ret = mbedtls_aes_self_test(1); if (ret != 0) { printf("aes self test failed: %d\n", ret); return -1; } printf("all self tests passed\n"); return 0; }

mbedtls_sha256_self_test和mbedtls_aes_self_test都接收一个 verbose 参数,传 1 时会把每个测试向量是否通过打印出来,传 0 只返回最终结果。返回值 0 代表通过,其它值从负的MBEDTLS_ERR错误码到非零整数都有可能。第一次跑推荐传 1,既能确认算法工作,又能从打印信息里看到测试向量描述,后续封装业务接口时再改成传 0。

3.5 编译与检查产物:file 命令和体积参考

Makefile 改好后,编译动作本身很简单:

make clean make 2>&1 | tee build.log file mbedtls_selftest_demo ls -lh mbedtls_selftest_demo

make clean是为了清掉模板工程里可能残留的旧对象文件。make 2>&1 | tee build.log让错误和标准输出都进日志,排查问题不用反复重开终端。file命令用来确认产物架构,如果输出ELF 32-bit LSB executable, ARM说明交叉编译生效;如果输出x86-64,说明 Makefile 里CC变量没有生效,回到 3.1 检查前缀。

体积方面,只编 SHA-256 和 AES 的最小自测程序,去掉调试信息后通常在几十 KB 量级。如果编译出来一两百 KB 甚至更大,优先怀疑默认配置的宏开太多。此时可以用$(STRIP)对产物瘦身,但更根本的问题是裁剪没到位,后面集成进安全启动镜像时,镜像体积会直接卡你。

3.6 编译后核对自测符号:别等到运行才发现

编译通过后,先用交叉工具链的 nm 看一眼产物里是否有自测函数符号,避免把问题留到 QEMU 运行阶段:

arm-linux-gnueabihf-nm mbedtls_selftest_demo | grep -E "self_test"

如果输出里有mbedtls_sha256_self_test和mbedtls_aes_self_test,说明宏生效。如果什么都没有,即使编译通过了,运行时也不会走到自测流程。这个检查比直接跑模拟器更快。自测函数会不会被链接优化掉,取决于编译选项,比如-ffunction-sections配合链接时未引用不保留,因此 nm 检查能确认可执行文件里是否存在该符号,但要确认是否真的被调用,还需要回到 main.c 看调用链。

这个技巧在交叉编译里很实用,因为目标板上跑起来成本高,先用静态检查把明显的问题拦下来。实际上我每次移植完都会跑一遍这个命令,特别是在更换工具链版本之后。

4. 移植避坑:五个典型翻车现场与排查路径

移植 mbedTLS 的最大特点,是你以为编译过了就没事,真正的问题其实都藏在配置宏和源文件的咬合关系里。下面按出现频率,整理这次移植中踩过的五个典型问题。

4.1 config 头文件找不到:现象、原因、解决

现象:编译时第一行就报fatal error: mbedtls_config.h: No such file or directory,或者config.h找不到。

原因:默认配置头在新旧版本里名字不一样。旧工程里叫config.h,新版本叫mbedtls_config.h。Makefile 里MBEDTLS_CONFIG_FILE指到了一个不存在的文件,头文件搜索路径没指到include也会是同样表现。

解决:先用ls include/mbedtls/确认配置文件具体名,再把CFLAGS -I指到include目录。如果配置宏里指定了文件,把反斜杠和双引号的转义写对。这个错误最常见的原因其实是路径写成了-Iinclude/mbedtls,而 mbedTLS 源文件里的写法是#include "mbedtls/xxx.h",头文件实际搜索路径应该从工程根目录下开始,多了或少了层级都会挂。

4.2 SELF_TEST 宏没生效:函数有声明但链接不到

现象:main.c 里调用了mbedtls_sha256_self_test,编译不报错,链接时报undefined reference to mbedtls_sha256_self_test。或者更隐蔽:链接过了,但运行输出显示程序直接跳过了自测。

原因:宏只在主程序里定义了,算法文件在另一个编译命令里编译,CFLAGS 不一致。Makefile 中有的目标使用旧变量,没有追加宏,导致算法 .c 文件编译时MBEDTLS_SELF_TEST没开,自测函数被条件编译掉了。

解决:把宏统一加到全局CFLAGS,不要只加在某一个 rule 上。用make V=1打印每条编译命令,肉眼检查算法文件是否带上了宏。这是排查宏不统一最快的方式,不要只看链接错误去补函数。

4.3 链接缺失内部符号:platform_util 引发的连锁反应

现象:加上 aes.c 和 sha256.c 后,链接报undefined reference to mbedtls_platform_zeroize或mbedtls_platform_gmtime_r。

原因:mbedTLS 的算法层不是孤立的。比如 AES 内部清除密钥,调的是平台层提供的安全清零函数;时间相关功能依赖平台抽象。裁剪时如果 Makefile 只放了两个算法 .c 文件,这些基础符号就会缺。

解决:直接在报错信息里找符号名,再到 library 目录里搜定义。一般最先缺的是platform_util.c,后续可能还会缺constant_time.c,取决于版本和配置。补齐之后重新 make,直到没有未定义符号。这个补文件的过程不需要一次猜对,报一个补一个,反而是最稳的。

4.4 QEMU 里自测跑飞:栈对齐和字节序的坑

现象:在模拟环境里执行./mbedtls_selftest_demo,AES 自测输出到一半程序崩溃,或者 SHA-256 的结果和标准向量不一致,但编译平台上完全没问题。

原因:常见情况有三种。第一是交叉编译工具链与 QEMU 模拟的 CPU 不匹配,比如用带 VFP 的gnueabihf工具链,QEMU 默认 CPU 较旧时遇到非法指令崩溃;第二是栈指针在入口处没有 8 字节对齐,C 库或向量处理有时依赖对齐;第三是字节序配置错,ARM 大多数场景是小端,但模拟环境启动参数偶尔会把 CPU 指定成大端。

解决:先换一种模拟方式缩小问题范围。用qemu-arm -cpu cortex-a9或qemu-aarch64 -cpu cortex-a53指定与工具链匹配的 CPU,加上-L指向工具链 sysroot。栈对齐问题从启动文件和链接脚本里查 sp 初始化与对齐要求,必要时调整链接参数。字节序则用file输出和lscpu类指令确认。这类问题很难一步到位,通常做法是先跑一个只有 SHA-256 的最小 demo,再逐步加 AES,二分定位。

4.5 不要迷信默认配置:裁剪宏与自测宏的关系

现象:程序能编译能运行,但自测返回-1,或者函数返回MBEDTLS_ERR_SHA256_BAD_INPUT_DATA。

原因:默认配置文件把大部分功能都打开了,算法文件之间存在大量交叉耦合。有些算法宏开着但对应源文件没有编进 SRCS,运行时调用的接口找不到类型定义或没有实现,表现为错误码,而不一定是崩溃。

解决:自测环境不要用默认配置跑全功能。先把需要的算法宏在配置头里列清楚,其它关掉。裁剪宏和MBEDTLS_SELF_TEST要同时出现。这个环节没有捷径,编译一次查一次依赖,比网上找现成配置可靠得多。后面要加 RSA 或 ECC 时,也需要按同样的方式把新宏和源文件配对。

5. QEMU 上验证移植结果:两个技巧和一个固定检查习惯

5.1 先用 qemu-user 快速验证自测

打包完整根文件系统之前,可以先在用户态模拟里跑一次。静态编译的可执行文件不需要额外装载器,只要有对应的系统调用模拟就能跑:

qemu-arm -L /usr/arm-linux-gnueabihf ./mbedtls_selftest_demo

-L指定工具链的 sysroot 路径,用于加载动态库和解析路径。如果前面用静态编译,-L甚至可以省掉,但加上更保险。对应 64 位环境,命令换成qemu-aarch64 ./mbedtls_selftest_demo。这一步跑通后,基本可以确认算法和编译参数没问题,再进完整镜像,能省掉大量反复打包的时间。

5.2 再进根文件系统镜像跑完整流程

用户态模拟验证的是库本身,完整镜像验证的是集成结果。把编译产物放到根文件系统目录,重新打包镜像,启动 QEMU 之后执行即可:

cp mbedtls_selftest_demo output/target/root/ make qemu-system-arm -M vexpress-a9 -nographic -kernel <kernel_image> -sd <rootfs_image>

启动后直接进入 shell,运行./mbedtls_selftest_demo。这里有一个容易忽略的细节:可执行文件尽量静态编译,否则 rootfs 里要带上对应的动态库和链接器,缺失时启动命令没有任何提示,程序直接找不到入口。runtime 阶段的报错大多不是代码问题,而是文件系统里少了东西。验证时优先看串口输出,自测函数传了 verbose=1,会在串口打出一长串测试向量状态,看到[ OK ]说明算法确实在目标环境里跑通了。

从那以后,我每次移植完第三方 C 库,都强制走一遍自带 self_test,再封装业务接口,省得后面应用层跑挂了说不清楚是库的问题还是我的问题。这套流程如果用现成的移植模板,二十来分钟可以走完,我把模板和 Makefile 整理进了资源包,拿到手按第 2 章的顺序核对目录结构,再照着第 3 章配置宏就行。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询