<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Firment on MoRiv447</title><link>https://moriv447.github.io/tags/firment/</link><description>Recent content in Firment on MoRiv447</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Wed, 02 Sep 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://moriv447.github.io/tags/firment/index.xml" rel="self" type="application/rss+xml"/><item><title>让 AI 红队自己写的固件：可复现比聪明更重要</title><link>https://moriv447.github.io/posts/red-teaming-firmware/</link><pubDate>Wed, 02 Sep 2026 09:00:00 +0800</pubDate><guid>https://moriv447.github.io/posts/red-teaming-firmware/</guid><description>红队固件听起来很酷，但一个『模型这次崩给你看、下次复现不了』的漏洞等于没发现。Firment 的做法是：确定性语料打底，LLM 只负责探索。</description><content:encoded><![CDATA[<p>让 agent 攻击自己刚写出来的固件，听起来很带感。但有个残酷现实：<strong>一个不能复现的漏洞，等于没发现</strong>。模型这次把固件搞崩了，你换个温度、换个模型再跑，它复现不出来——这种 finding 毫无工程价值。</p>
<p>Firment 的红队功能整个是围绕「可复现」设计的。</p>
<h2 id="两层金字塔">两层金字塔</h2>
<ul>
<li><strong>底层：确定性变异语料</strong>。给定种子 + 一个合法基线帧，生成一串固定顺序的畸形输入（边界长度、逐位翻转、超长、格式串、分隔符混淆、数值极值）。同种子同字节序列，跨机器一致。一个漏洞的复现方式就是 <code>seed + case id</code>，<strong>不需要模型在场</strong>。</li>
<li><strong>上层：LLM 攻击 campaign</strong>。在语料跑完后，让攻击者子 agent 探索语料没覆盖的（构造结构上错但语义相关的帧、竞态两个输入）。但它的发现<strong>必须回填成语料里的一个 case</strong> 才能进报告，否则降级为 UNVERIFIED。</li>
</ul>
<h2 id="崩溃哨兵优先级很关键">崩溃哨兵：优先级很关键</h2>
<p>每个用例发完 payload，收集一段输出窗口，纯文本分类：</p>
<ol>
<li><strong>Crash</strong> —— 命中故障签名（HardFault/panic/…）</li>
<li><strong>Reboot</strong> —— 启动横幅在已有流量后重现（不该出现时又出现了）</li>
<li><strong>Hang</strong> —— 定义了心跳、窗口内没命中、且超时</li>
<li><strong>Alive</strong> —— 心跳命中，或根本没定义心跳契约</li>
</ol>
<p>Crash 压过 Reboot：崩了又被看门狗重启的目标，是 crash 不是神秘重启。沉默不等于死亡：没定义心跳时，一片安静判 Alive——你不能宣称一个从没建立过「存在」的东西缺失了。</p>
<h2 id="证据封顶">证据封顶</h2>
<p>finding 必须引用捕获文件（崩溃还会在复位前抢一份 <code>debug forensic</code> 现场快照）。<code>finalize</code> 检查这些文件真实存在，缺证据的<strong>封顶 low + UNVERIFIED</strong>。模型写的 campaign 证据更严：payload 必填、引用路径必须落在本次运行目录内、且文件里得真的有它引用的那行——防止它拿一个存在的无关文件（甚至报告自己）冒充证据。</p>
<h2 id="恢复与边界">恢复与边界</h2>
<p>崩了的目标在用例之间会被重刷/复位救回来；救不活就<strong>中止整个套件</strong>，而不是对着死板子把后面每个用例都判成假 Hang。headless 实弹必须显式 <code>--live</code>，否则只做语料彩排。</p>
<h2 id="一个诚实的局限">一个诚实的局限</h2>
<p>任何 debug 动作（包括 forensic）退出后目标都停在 halt——所以「碰过调试器之后的沉默」是攻击者自己的产物，不是 hang 的证据。这条我写进了权限闸门和 campaign 提示词里，宁可啰嗦也不让它自证。</p>
<hr>
<p>红队的价值不在「聪明地找到 bug」，在「找到之后别人能复现、能回归」。确定性打底、LLM 探索，这个顺序不能反。</p>
]]></content:encoded></item><item><title>用逻辑分析仪给 AI Agent 当地面真值</title><link>https://moriv447.github.io/posts/logic-analyzer-ground-truth/</link><pubDate>Tue, 01 Sep 2026 22:30:00 +0800</pubDate><guid>https://moriv447.github.io/posts/logic-analyzer-ground-truth/</guid><description>让 AI 判断『这条 UART 真的是 115200 吗』，不能靠它看串口助手的截图，得靠波形测出来。记录 Firment 里 la 工具的几个设计取舍。</description><content:encoded><![CDATA[<p>AI agent 做固件，最难的不是写代码，是<strong>让它知道自己写对了没有</strong>。运行级证据（串口日志）能覆盖一部分，但很多物理事实——PWM 频率对不对、SPI 时序、总线上真实跑的字节——日志里看不到。逻辑分析仪就是补这一层的。</p>
<p>Firment 的 <code>la</code> 工具接的是 sigrok-cli。几个值得记的设计取舍：</p>
<h2 id="1-外部子进程绝不链接-libsigrok">1. 外部子进程，绝不链接 libsigrok</h2>
<p>sigrok/libsigrok 是 GPL。链接进一个 MIT 程序会传染许可证。所以 <code>la</code> 把 sigrok-cli 当<strong>外部二进制</strong>用 argv 数组调用（和 probe-rs 同一模式），跨进程边界读 stdout，GPL 传染不到。代价是要自己处理进程生命周期，换来的是干净 + 覆盖面广（fx2lafw 克隆机、Saleae、几十种设备走同一驱动层）。</p>
<h2 id="2-频率只给区间不给伪精确值">2. 频率只给区间，不给伪精确值</h2>
<p>边沿只能定位到 ±1 个采样，所以周期带 ±2 采样的误差。报一个 <code>1.33 Hz</code> 是假精确。<code>la measure</code> 一律输出 <code>hz_low .. hz_high</code> 区间，且欠采样（每周期 &lt;4 采样）时降到低置信度。</p>
<h2 id="3-读回设备真实采样率而不是你请求的那个">3. 读回设备真实采样率，而不是你请求的那个</h2>
<p>fx2lafw 这类只支持离散采样率（6/12/24/48 MHz），你请求 8m 它可能跳到 12m。如果用请求值算频率会系统性偏差。所以采集后跑一次 <code>--show</code> 把<strong>真实采样率和通道数</strong>读回来写进元数据。</p>
<h2 id="4-通道名要按-sigrok-的现实来">4. 通道名要按 sigrok 的现实来</h2>
<p>sigrok-cli 按<strong>通道名</strong>匹配，主流驱动叫 <code>D0..D7</code> 不是 <code>0..7</code>。我一开始按索引写示例，真机上直接 <code>unknown channel</code>。这种坑只有对着 sigrok 源码或上板才发现得了。</p>
<h2 id="5-半截数据比报错更糟">5. 半截数据比报错更糟</h2>
<p>采集导出成原始位流时，如果进程被超时/取消打断，可能留下一个写了一半的 <code>.bin</code>。测量层必须查 <code>has_binary</code> + 尺寸校验，宁可报错也不能拿截断波形算出一个自信的假频率。</p>
<h2 id="6-挂进证据阶梯">6. 挂进证据阶梯</h2>
<p>HIL 里 <code>kind = &quot;la&quot;</code> 步骤可以断言 <code>expect_frequency_hz</code> / <code>expect_duty</code> / <code>expect_edges</code> / <code>expect_decoded</code>，规则和其它物理观测一致：<strong>低置信度永远过不了断言</strong>，dry-run 强制 FAIL。</p>
<p>一句话：把「波形测出来」变成 agent 能消费、且<strong>不会说谎</strong>的证据。</p>
]]></content:encoded></item><item><title>固件开发的最后一公里：我为什么用 Rust 写了个会自己烧板子的 AI Agent</title><link>https://moriv447.github.io/posts/firment-last-mile/</link><pubDate>Tue, 01 Sep 2026 21:00:00 +0800</pubDate><guid>https://moriv447.github.io/posts/firment-last-mile/</guid><description>市面上的 coding agent 都停在『帮你写代码』，但固件开发真正耗时的最后一公里——能不能烧进去、跑起来、日志对不对、资源超没超——没人管。Firment 就是冲着这段做的。</description><content:encoded><![CDATA[<p>做过单片机的人都懂那种碎：一个功能闭环要横跨五六个工具。代码在编辑器里改，编译切命令行，烧录开 STM32CubeProgrammer 或 probe-rs，看日志再开一个串口助手，最后想知道固件占了多少 Flash、哪个函数把栈撑爆了，还得翻 <code>.map</code> 或者手动加 <code>-fstack-usage</code> 重编一遍。</p>
<p>每一步都是独立的手工操作。而一个「AI 编码助手」如果只停在「帮你写代码」这一步，对嵌入式工程师的帮助是残缺的——因为<strong>固件开发的最后一公里恰恰是最容易翻车的部分</strong>，通用 coding agent 完全没覆盖。</p>
<p>这就是我做 <a href="https://github.com/MoRiv447/Firment">Firment</a> 的原因。</p>
<h2 id="一句话把整条链路装进同一个对话">一句话：把整条链路装进同一个对话</h2>
<p>Firment = Firmware + Agent（名字取自 firmament，故意少一个 a）。它是一个用 Rust 写的通用编码 Agent，但带着一个嵌入式优先的工具链闭环：</p>
<blockquote>
<p>你说「给 UART1 加一个 DMA 接收的驱动，烧到板子上跑一下，看看日志」，它自己走完 <strong>写码 → 编译 → 烧录 → 串口观测 → ELF 分析</strong>，而不是丢给你一段代码就完事。</p>
</blockquote>
<h2 id="关键不是会写代码是对结果负责">关键不是「会写代码」，是「对结果负责」</h2>
<p>让模型写一段 <code>main.c</code> 不难，难的是让它<strong>诚实地判断这件事到底成没成</strong>。所以我没有依赖模型的自觉，而是搭了一套机械强制的<strong>证据阶梯</strong>，五级：</p>
<ol>
<li><strong>代码级</strong> —— 文件按预期改了</li>
<li><strong>构建级</strong> —— 真实工具链编译通过</li>
<li><strong>部署级</strong> —— 固件烧进了目标</li>
<li><strong>运行级</strong> —— 串口输出 / 寄存器符合预期</li>
<li><strong>物理级</strong> —— 观测到真实的外部效果</li>
</ol>
<p>规则很硬：<strong>高级不自动蕴含低级，编译通过也不自动等于物理正确</strong>。三层闸门把它落到机器上：</p>
<ul>
<li><strong><code>verify_command</code> 闸门</strong>：配了校验命令，回合结束前必须过，不过就继续干活；</li>
<li><strong>ELF 回归闸门</strong>：每次编辑回合后自动重分析产物，flash/RAM 涨了、某函数栈深暴增这类「编译能过但其实是回归」的问题，超阈值就卡住完成；</li>
<li><strong>HIL 端到端套件</strong>：<code>build → flash → monitor（带断言）→ elf_analyze</code> 串成一次可回放、可 dry-run 的验证。</li>
</ul>
<h2 id="物理级证据不靠视觉模型靠确定性测量">物理级证据：不靠视觉模型，靠确定性测量</h2>
<p>这是 v0.7/v0.8 我花最多力气的地方。让 agent「看到」板子的真实行为，但<strong>不用大模型看图</strong>（不可复现、还贵），而是本地确定性算法：</p>
<ul>
<li><strong><code>observe</code></strong> —— 对目标板的照片做 CV：LED 亮没亮、连拍里有没有东西动、闪不闪且多快（频率只给区间不给伪精确值）、改动前后像素变没变。每个结论带<strong>置信度</strong>，HIL 步骤可以断言它——<strong>低置信度永远过不了断言</strong>。</li>
<li><strong><code>la</code>（逻辑分析仪）</strong> —— 经 sigrok-cli 采集真实波形，测频率/占空比/脉宽/波特率，协议解码（uart/spi/i2c/CAN…）交给 sigrok 自带解码器。于是「这条线真的是 115200 吗」「这个 PWM 真的是 1 kHz 吗」从「模型说了算」变成「波形测出来」。</li>
</ul>
<h2 id="红队让-agent-攻击自己写的固件">红队：让 agent 攻击自己写的固件</h2>
<p>最新的一块。固件写完，另一个「攻击者」子 agent 拿<strong>确定性变异语料</strong>（种子固定，同种子同字节序列）往运行中的固件灌畸形输入，用崩溃哨兵（故障签名 / 启动横幅重现 / 心跳丢失）判定，产出<strong>必须引用捕获证据</strong>的漏洞报告——缺证据就封顶 low/UNVERIFIED。复现一个漏洞只需 <code>seed + case id</code>，不需要再问一次模型。</p>
<h2 id="安全模型嵌入式工程师对ai-删东西烧错固件格外敏感">安全模型：嵌入式工程师对「AI 删东西/烧错固件」格外敏感</h2>
<p>所以默认行为围绕这点设计：写/改/shell 默认要权限确认；<code>rm</code>/<code>format</code>/<code>git reset --hard</code>/强推一律拦截；<code>edit_file</code> 用 SHA-256 内容寻址锚定，同一处编辑只生效一次；路径沙箱；<code>web_fetch</code> 带 SSRF 防护。</p>
<h2 id="三端一个内核">三端一个内核</h2>
<p>monorepo，CLI/TUI（Rust + ratatui）是事实标准，GUI 客户端（Tauri + React）共用同一个 Rust 内核，Web 端（Next.js）是 TS 重实现、靠一份提交的工具规格快照保持同步。MIT 开源，一行安装。</p>
<hr>
<p>如果你也在琢磨「AI 到底能不能对硬件负责」，或者就是嵌入式 + agent 这个交叉方向感兴趣，欢迎去看看 <a href="https://github.com/MoRiv447/Firment">Firment</a>，或者拿你的板子试试。这个博客会持续记录我踩的坑和背后的设计取舍。</p>
]]></content:encoded></item></channel></rss>