跳转至

第一个安全的固件改动

第一次改动只修改PX4官方示例模块的一条只读诊断文字,不接触状态估计、控制器、解锁、失联和执行器输出。目标是完整走通“固定版本→理解构建注册→修改→测试→SITL验证→回滚”,而不是展示复杂算法。

第一个安全固件改动流程

固定实验边界

本篇只适用于:

项目:PX4-Autopilot
发布:v1.17.0
提交:d6f12ad1c4f70ad3230afd7d86e971421e02fef4
目标:px4_sitl / sihsim_quadx
改动:hello示例的一条只读控制台文字
硬件:不需要真实飞控
飞行:不进行

如果当前提交、文件内容或构建目标不同,停止照抄,改用该版本官方文档重新确认。

前置条件

已经完成源码版本、开发环境与可复现构建,并满足:

  • make tests通过;
  • make px4_sitl sihsim_quadx能够进入pxh>
  • 输入hello start能够看到hello、五次Doing work...goodbye
  • git status --short为空。

基线不通过时,不进入修改。

第一步:确认源码如何进入目标

依次检查:

src/examples/hello/hello_main.cpp
src/examples/hello/hello_start.cpp
src/examples/hello/CMakeLists.txt
src/examples/hello/Kconfig
boards/px4/sitl/default.px4board

四者的关系是:

  1. hello_main.cpp定义任务执行时打印的文字;
  2. hello_start.cpp定义hello starthello stophello status命令;
  3. CMakeLists.txt通过px4_add_module()注册模块和源文件;
  4. Kconfig定义EXAMPLES_HELLO开关;
  5. SITL的default.px4board设置CONFIG_EXAMPLES_HELLO=y
  6. 构建系统把模块加入目标,运行后在pxh>注册hello命令。

可以直接验证:

rg "hello_main|EXAMPLES_HELLO|examples__hello" \
  src/examples/hello \
  boards/px4/sitl/default.px4board

第二步:只修改诊断文字

src/examples/hello/hello_main.cpp中找到:

printf("hello\n");

本次目标差异是把它改为:

printf("learning-build: read-only diagnostic v1\n");

期望差异只有这一行:

-   printf("hello\n");
+   printf("learning-build: read-only diagnostic v1\n");

可以手工编辑,也可以使用下面的补丁方法,二者只能选择一种。推荐把补丁保存在源码目录之外再应用:

EVIDENCE_DIR="../px4-evidence/first-safe-change-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$EVIDENCE_DIR"
cat > "$EVIDENCE_DIR/read-only-diagnostic.patch" <<'PATCH'
diff --git a/src/examples/hello/hello_main.cpp b/src/examples/hello/hello_main.cpp
--- a/src/examples/hello/hello_main.cpp
+++ b/src/examples/hello/hello_main.cpp
@@ -48,7 +48,7 @@ int PX4_MAIN(int argc, char **argv)
 {
    px4::init(argc, argv, "hello");

-   printf("hello\n");
+   printf("learning-build: read-only diagnostic v1\n");
    HelloExample hello;
    hello.main();

PATCH
git apply --check "$EVIDENCE_DIR/read-only-diagnostic.patch"
git apply "$EVIDENCE_DIR/read-only-diagnostic.patch"

检查:

git diff --check
git diff -- src/examples/hello/hello_main.cpp
git status --short

通过标准:

  • git diff --check退出码为0;
  • 只有目标源文件被修改;
  • 差异不包含控制、参数、消息定义或板级配置。

第三步:运行主机测试

set -o pipefail
make tests 2>&1 | tee "$EVIDENCE_DIR/tests-modified.log"
TEST_STATUS=${PIPESTATUS[0]}
printf 'tests_exit=%s\n' "$TEST_STATUS" | \
  tee "$EVIDENCE_DIR/tests-modified.exit"
test "$TEST_STATUS" -eq 0 || exit "$TEST_STATUS"

通过标准:

  • tests_exit=0
  • 没有新增失败测试;
  • 若测试输出包含原有跳过项,应记录名称,不能把跳过当成通过。

本次一行文字修改通常不会触发专门的行为测试,但全量主机测试可以发现构建和公共依赖回归。

第四步:重新构建并启动SITL

set -o pipefail
make px4_sitl sihsim_quadx 2>&1 | \
  tee "$EVIDENCE_DIR/sitl-modified.log"
SIM_STATUS=${PIPESTATUS[0]}
printf 'sitl_exit=%s\n' "$SIM_STATUS" | \
  tee "$EVIDENCE_DIR/sitl-modified.exit"
test "$SIM_STATUS" -eq 0 || exit "$SIM_STATUS"
sha256sum build/px4_sitl_default/bin/px4 | \
  tee "$EVIDENCE_DIR/px4-modified.sha256"

出现pxh>后执行:

ver all
hello start

通过标准:

  • ver all报告的源码版本来自当前工作副本;
  • 第一条应用输出精确包含learning-build: read-only diagnostic v1
  • 随后仍每两秒输出一次Doing work...,共五次;
  • 最后输出goodbye
  • 没有ERROR、崩溃或无法退出;
  • 输入shutdown后仿真正常结束。

仿真退出后脚本会记录make一侧的退出码,不能只采用tee的退出码。

若仍显示hello,说明运行的不是刚构建的产物,或者构建缓存、工作目录和启动命令不一致。

第五步:记录证据

git rev-parse HEAD > "$EVIDENCE_DIR/head.txt"
git submodule status --recursive > "$EVIDENCE_DIR/submodules.txt"
git diff --check > "$EVIDENCE_DIR/diff-check.txt"
git diff -- src/examples/hello/hello_main.cpp \
  > "$EVIDENCE_DIR/applied.patch"
git status --short > "$EVIDENCE_DIR/status-before-rollback.txt"

同时保存:

  • make tests结果;
  • SITL构建和启动结果;
  • ver all输出;
  • hello start输出;
  • 主机系统和工具链版本;
  • 测试日期。

证据目录位于源码仓库的上一级,不会让git status --short出现额外文件。回滚前先确认applied.patch内容正确且非空。

第六步:回滚并复测

先再次确认只有预期文件被修改:

git status --short
git diff -- src/examples/hello/hello_main.cpp

确认后恢复该文件:

git restore --source=HEAD -- \
  src/examples/hello/hello_main.cpp

再运行:

ROLLBACK_DIR="$EVIDENCE_DIR/rollback"
mkdir -p "$ROLLBACK_DIR"
git status --short | tee "$ROLLBACK_DIR/status.txt"
set -o pipefail
make tests 2>&1 | tee "$ROLLBACK_DIR/tests.log"
ROLLBACK_TEST_STATUS=${PIPESTATUS[0]}
printf 'tests_exit=%s\n' "$ROLLBACK_TEST_STATUS" | \
  tee "$ROLLBACK_DIR/tests.exit"
test "$ROLLBACK_TEST_STATUS" -eq 0 || \
  exit "$ROLLBACK_TEST_STATUS"
make px4_sitl sihsim_quadx 2>&1 | \
  tee "$ROLLBACK_DIR/sitl.log"
ROLLBACK_SITL_STATUS=${PIPESTATUS[0]}
printf 'sitl_exit=%s\n' "$ROLLBACK_SITL_STATUS" | \
  tee "$ROLLBACK_DIR/sitl.exit"
test "$ROLLBACK_SITL_STATUS" -eq 0 || \
  exit "$ROLLBACK_SITL_STATUS"
sha256sum build/px4_sitl_default/bin/px4 | \
  tee "$ROLLBACK_DIR/px4.sha256"

出现pxh>后:

ver all
hello start
shutdown

应等待goodbye出现后再执行shutdown

通过标准:

  • git status --short恢复为空;
  • make tests退出码为0;
  • ver all再次对应固定提交;
  • hello start再次输出原始hello
  • 模块仍能输出五次Doing work...goodbye
  • 基线、修改版和回滚版均保存了测试、SITL、版本和SHA-256记录;
  • 若基线与回滚产物不是逐字节相同,必须记录构建时间、路径或工具链等差异来源,不能只凭功能输出宣称位级可复现。

不要在存在其他未提交修改时直接执行恢复命令。先核对目标文件和差异,避免删除不属于本练习的工作。

本练习证明了什么

完成后可以证明:

  • 使用了明确的源码和子模块版本;
  • 能找到源文件、构建注册、Kconfig和目标配置之间的关系;
  • 能运行主机测试;
  • 能构建并启动SITL;
  • 能确认运行的是修改后的产物;
  • 能恢复基线并再次验证。

它不能证明:

  • 真实目标板能够启动;
  • 传感器和执行器驱动正确;
  • 目标硬件满足实时期限;
  • 控制算法发生任何改善;
  • 自编译固件已经可以带桨飞行。

进阶练习:只读时间统计

只有在完成上述闭环后,才考虑增加时间统计。必须分开记录:

  • CPU执行时间;
  • 函数开始到返回的墙钟执行时间;
  • 实际开始相对计划释放时刻的释放抖动;
  • 计划释放时刻到完成时刻的任务响应时间;
  • 传感器采样到执行器命令生效的端到端延迟。

截止期限违约应使用:

任务响应时间 = 完成时刻 - 计划释放时刻
若任务响应时间 > 相对截止期限,则记为违约

仅测量函数入口到返回的elapsed_us,无法发现任务在开始执行前已经晚释放。SITL可以验证统计逻辑,但主机调度结果不能替代目标飞控上的实时测量。

硬件扩展的前置门禁

本练习在SITL结束。若后续要刷写真实飞控,必须先完成:

  • 确认精确硬件型号和板级目标;
  • 备份原始参数、校准和已验证固件;
  • 阅读该板卡官方上传与恢复说明;
  • 准备正确的USB、引导模式或SWD恢复路径;
  • 拆除螺旋桨;
  • 使用稳定供电;
  • 定义上传成功、启动成功和回滚成功标志。

这些步骤属于后续组装与首次配置专题,不能因为SITL通过而跳过。

检查理解

  1. 为什么本练习固定到PX4 v1.17.0和具体提交?
  2. 哪些源码、构建和目标文件共同让hello进入SITL目标?
  3. 如何证明运行的是修改后的产物?
  4. 为什么SITL通过不能证明目标飞控满足实时期限?
  5. 回滚前为什么必须再次检查工作区差异?

主要参考