内容
# 您的AI助手对固件一无所知。以下是解决方法。
**fw-context赋予Claude、OpenCode、Cursor等AI助手对嵌入式C/C++代码库的编译器精确理解。无需云服务,无需猜测。只需与实际编译结果相符的答案。**
---
> *"我询问Claude谁调用了`uart_init`。它读取了四个错误的文件,忽略了回调函数,并花费了8,000个代币得到了一个半正确的答案。使用fw-context,同样的问题只需要一次工具调用和200个代币——答案是编译器验证的。*"
> — 创造这个工具的经历
---
## 10秒钟的介绍
您的AI助手足够聪明,可以理解您的固件。它只是无法正确地**看到**它。
给Claude或OpenCode一个Python问题,它会读取三个文件,了解导入信息,并给您一个精确的答案。给它关于Zephyr项目的相同问题,它会遍历它不应该读取的头文件,忽略隐藏在函数指针后面的回调函数,并花费数千个代币在错误的`#ifdef`分支上。
**模型不是问题。信息它可以访问才是。**
fw-context是一个MCP服务器,位于您的AI助手和代码库之间。助手不是盲目地读取源文件,而是查询由解析项目时使用的编译命令.json中的确切标志构建的**编译器精确索引**。相同的`-I`路径。相同的`-D`定义。相同的`CONFIG_*`符号与实际构建相同。
您的AI从遍历文本转变为查询**实际编译的内容**的数据库。
[60秒内开始 →](#getting-started-in60-seconds)
---
## LLM Alone vs LLM + fw-context — 差异在一张表中
让我们具体一些。下面是使用嵌入式开发问题时的实际情况——先使用LLM Alone,然后使用相同的LLM配合fw-context。
| 问题 | LLM Alone | LLM + fw-context |
|----------|-----------|-----------------|
| *"谁调用了`spi_transfer`?"* | 遍历`spi_transfer` → 47个匹配项在供应商HAL、声明和注释中 → 手动筛选 → 仍然不确定回调函数 | `find_callers("spi_transfer")` → 4个直接调用者在确切的文件:行,验证为编译后的AST |
| *"如果我更改此签名,会发生什么?"* | 手动读取每个调用者 → 遍历每个文件中的函数名称 → 希望不会错过回调函数注册 → 信心不足 | `find_all_callers_recursive("spi_transfer", max_depth=5)` → 12个调用者在8个文件中的传递树,详尽无遗 |
| *"此函数的作用是什么?"* | 读取文件 → 跟踪包含项以了解类型 → 查询宏 → 手动读取调用者 | `get_symbol_context("boiler_control")` → 函数体 + 调用者 + 被调用者 + 预计算的纯英文分析 → 一次调用 |
| *"有死代码吗?"* | 生成函数列表 → 遍历每个函数 → 筛选入口/ISR/构造函数 → 验证候选函数 → 对于大型代码库来说不切实际 | `find_dead_code(project_only=true)` → 候选函数在确切的文件:行,具有`dead`/`possibly_dead`分类 |
| *"电池数据如何到达加热逻辑?"* | 读取modbus_poll → 读取handleData → 搜索batt_soc引用 → 跟踪到boiler_control → 精神上拼凑 | `find_references("rt_data")` → 5个函数中25个引用,跨越4个文件 → 完整的数据流映射 |
| *"查找与电源管理相关的所有代码"* | 遍历`power`、`sleep`、`low_power`、`pm_`、`suspend`、`resume` → 300+个命中,大多数在供应商头文件中 → 过载 | `semantic_search("power management low power sleep modes")` → 按照相似性排名的前10个概念相关符号 |
| *"给我这个4,300行的文件概述"* | `读取文件` → 4,300行 → 一个文件上的数千个代币 → 仍然不知道结构 | `get_file_map("modem_msg.cpp")` → 426个符号按类别分组(方法、字段、枚举、嵌套类型) |
| *"main()的完整依赖树是什么?"* | 读取main → 查找每个调用的函数 → 读取每个函数 → 查找它们的调用者 → 指数爆炸式阅读 | `find_callees_recursive("loop", max_depth=3)` → 深度1-3的30个调用者,项目 + 框架 |
### 代币经济学
这不仅仅是时间的问题。还有**哪些问题实际上是值得问的**。
| 指标 | LLM Alone | LLM + fw-context |
|--------|-----------|----------------|
| 了解一个函数所需的代币 | 3,000–8,000(读取文件 + 包含项) | 200–500(一次`get_symbol_context`调用) |
| 跟踪调用者(3级)的代币 | 15,000–40,000(指数文件读取) | 300–800(一次`find_all_callers_recursive`调用) |
| 查找变量的所有引用的代币 | 5,000–15,000(遍历 + 读取每个文件) | 200–400(一次`find_references`调用) |
| 对话中问题数量(上下文填满前) | 3–5 | 15–25 |
配合fw-context的AI不仅能更快地回答问题——它还能回答没有它就无法提出的问题,因为提问的代币成本将在上下文窗口耗尽之前耗尽。
---
## 为什么LLM在嵌入式C/C++方面挣扎
问题不是模型能力。Claude、GPT-4、Gemini——它们都能读取和理解C代码。问题是**读取源文件是理解编译固件的错误方式**。
### 1. LLM读取文本。编译器读取翻译单元。
当您的AI打开`main.c`时,它会看到200行代码。当您的编译器编译`main.c`时,它会看到15,000+行——每个`#include`都已解析,每个宏都已扩展,每个`#ifdef`分支都折叠为每个板的确切路径。
您的AI根据它读取的200行做出答案。您的板运行的是它没看到的15,000行现实。
**症状:** AI解释了`gpio_pin_set()`在通用HAL中的工作原理。但在**您的**板上,使用**您的**设备树,该函数解析为`nct38xx_gpio_set()`——一个具有时序特性的特定驱动程序,如果您在紧密循环中切换,则该特性很重要。AI的答案对于通用情况是正确的。对于您的板来说是错误的。
**使用fw-context:** `get_source("gpio_pin_set")`返回编译后的函数体——您的板实际使用的特定驱动程序。`explain_symbol("gpio_pin_set")`描述了实际运行的内容。
### 2. LLM看到所有`#ifdef`分支。您的板只运行一个。
```c
void uart_send(const uint8_t *data, size_t len) {
#if CONFIG_UART_ASYNC_API
return uart_async_send(data, len); // nRF52, STM32H7
#elif defined(CONFIG_UART_INTERRUPT_DRIVEN)
return uart_int_send(data, len); // STM32F4, some NXP
#elif defined(SOC_FAMILY_NRF)
return nrf_uarte_send(data, len); // nRF91 fallback
#else
return uart_poll_send(data, len); // everything else
#endif
}
```
LLM看到所有四个实现作为死文本。它不知道哪个被编译。它可能会描述轮询实现,因为它在底部,或者异步实现,因为它在顶部——无论哪种方式,它都在猜测。
**使用fw-context:** libclang应用您的`-DCONFIG_UART_ASYNC_API=1`来自`compile_commands.json`。`get_source("uart_send")`仅返回`uart_async_send`分支——因为这编译了。无需猜测。
### 3. LLM看不到回调函数、中断服务例程(ISR)或函数指针
```cpp
// 嵌入式代码的实际连接方式:
MB.onDataHandler(&handleData); // Modbus回调函数
k_work_init(&work, sensor_handler); // RTOS工作队列
IRQ_DIRECT_CONNECT(UART0_IRQn, isr); // 中断向量
dev->api->send(data, len); // 驱动程序虚拟表
// 这些对grep不可见。
// 当LLM搜索“谁调用了handleData?”时,它们不会出现。
```
**使用fw-context:** `find_references("handleData")`在`modbus_setup:6`找到注册——即使它不是直接调用,**它是一个引用**。`find_callers("handleData")`返回空——这本身就是答案:“此函数通过回调函数机制调用,而不是直接调用。”
空结果是数据,而不是失败。
### 4. 每个LLM问题都从零开始
询问 *"谁调用了`spi_transfer`?"* → LLM遍历代码库。
然后 *" `spi_transfer`调用了什么?"* → LLM再次遍历。相同的头文件。相同的包含项。相同的代币。
就像询问同事后续问题,让他们忘记整个前面的对话一样。LLM没有索引,所以它没有代码库结构的记忆。
**使用fw-context:** 索引在查询之间持续存在。`search_code` → `get_symbol_context` → `find_callees_recursive` — 每个查询都建立在索引之上,而不是重新读取文件。SQLite数据库是共享内存。
### 5. 供应商代码淹没应用程序代码
在一个典型的Zephyr项目中,编译代码的90%以上是框架、HAL和RTOS。遍历`gpio_set`会得到300个匹配项——290个在供应商目录中。
LLM读取ESP-IDF的GPIO驱动程序(400行)之前,它在`src/buttons.c:45`找到您的一个调用。它不知道什么是“您的代码”,什么是“平台”——它们都是文件。
**使用fw-context:** `find_callers("gpio_set", project_only=true)`仅返回来自您的`src/`和`lib/`目录的调用。`search_code`在结果评分中将项目路径(1.2倍)提升到供应商路径(0.85倍)。您的代码优先显示。
---
## fw-context如何增强您的AI
### 架构
```
您的项目构建(west / pio / bear)
│
▼
compile_commands.json ← 每个.c文件的确切编译器标志
│
▼
libclang解析每个文件 ← 与编译器相同的-I、-D、--target
│
▼
磁盘上的SQLite索引 ← 符号 + 调用图 + FTS5 + 嵌入式
│ + 预计算的LLM分析
▼
29个MCP工具 ← 您的AI查询索引,而不是文件
│
▼
您的AI助手 ← Claude、OpenCode、Cursor、Codex、Kilo Code
```
**关键设计选择:** 索引使用libclang从`compile_commands.json`构建——与clangd和Xcode使用的解析器相同。它应用您的确切编译器标志。您的板使用的`#ifdef`分支已解析。您的`-D`标志定义的宏已扩展。您的`-I`标志指定的包含路径已遵循。
当您的AI询问“显示`uart_init`”,它看到编译器编译的内容。
不是磁盘上的文本。
### 索引内容
| 层 | 技术 | 存储内容 |
|-------|-----------|---------------|
| **符号** | SQLite | 每个函数/方法/类/枚举/变量的名称、类型、文件、行、签名、文档字符串 |
| **全文** | FTS5 | 名称、限定名称、令牌、摘要的反向索引——前缀和短语搜索 |
| **调用图** | refs表 | 每个调用边:谁调用谁,谁读取哪个变量 |
| **向量** | sqlite-vec | 语义/概念搜索的1024维嵌入式向量 |
| **分析** | llm_analysis表 | 每个函数的预计算的纯英文解释 |
### 27种工具
您的AI助手通过查询索引获得这些功能——不是通过读取文件:
**🔍 搜索和发现** — 无需遍历即可查找任何内容
- `search_code` — FTS5全文搜索,自动尝试5种策略
- `lookup_symbol` — 按名称或前缀查找,优先显示定义
- `smart_search` — 自然语言 → FTS5 via Ollama(`"modem如何连接?"`)
- `semantic_search` — 按概念查找代码,而不是关键字(`"电源节省模式"`)
**📖 代码理解** — 以编译器精度读取代码
- `get_source` — 通过libclang extent(不是行号猜测)获取确切的函数体
- `get_symbol_context` — 函数体 + 调用者 + 被调用者 + LLM分析,一次调用
- `get_file_map` — 符号目录(4,300行文件 → 426个符号按类别分组)
- `explain_symbol` — 预计算的纯英文描述
- `get_file_analysis` — 每文件摘要(`"实现锅炉系统的控制逻辑..."`)
**🕸️ 调用图和依赖关系** — 了解什么依赖于什么
- `find_callers` — 直接调用者,带有确切的文件:行
- `find_all_callers_recursive` — 传递调用者(BFS,最大深度5)
- `find_callees_recursive` — 什么依赖于什么,直接和传递?
- `find_call_path` — A如何到达B? 调用图中的最短路径
- `find_references` — 每个符号的读取、写入和调用
- `find_dead_code` — 定义但从未调用(带有项目/供应商过滤)
- `find_hotspots` — 按调用者计数排名的最调用函数
- `find_wrapper_callers` — 哪些包装类调用哪些驱动程序方法?
- `find_indirect_call_sites` — 函数指针字段/变量在何处实际调用?
- `find_indirect_targets` — 哪些函数分配给回调函数字段/参数?
- `trace_data_flow` — 跟随类型通过函数签名到目标
**🏗️ C++分析** — 导航类和模板
- `get_inheritance_chain` — 基类、派生类、传递层次结构
- `get_class_members` — 按类别分组的完整API表面
- `get_method_overrides` — 虚拟调度链
- `get_template_instances` — 模板的所有具体实例化
**🔧 维护** — 保持索引健康
- `get_active_build` — 健康检查:索引是否过时?
- `reindex_file` — 编辑后更新一个文件
- `reset_index` — 删除并重建
- `list_projects` — 此机器上索引了什么?
- `check_ollama` — Ollama是否可用于`smart_search`?
---
## 真实的提示,真实的结果
以下是使用fw-context支持的AI时的实际情况。这些是真实的提示、工具调用和HA_Boiler项目(ESP32加热控制器,约700行C++)的真实响应。
### 提示:“显示`boiler_control`并解释它如何融入系统**
**LLM Alone:** 读取`control.cpp`(63行)。不知道谁调用它。不知道它调用什么。需要手动搜索`boiler_control`引用。消耗~3,000个代币。
**LLM + fw-context:**
```
→ get_symbol_context("boiler_control")
返回一次调用:
✓ 函数体(42行,libclang-exact extent)
✓ 调用者:[ha_update at ha_mqtt.cpp:109]
✓ 被调用者:[modbus_is_stale at modbus.cpp:99, digitalRead, digitalWrite]
✓ LLM分析: "根据电池状态、温度读数和时间控制锅炉系统。调整继电器状态(BOILER2_PIN)以控制加热,基于电池满度、温度阈值和基于时间规则。副作用:更新batFull标志、读取继电器状态、确定新状态、切换BOILER2_PIN、记录到Serial。"
代币:~500。 AI需要的所有上下文。一次调用。
### 提示:“如果我更改`modbus_poll`的签名会发生什么?"
**LLM Alone:** 遍历`modbus_poll` → 找到定义和`loop()`中的调用。认为这就是全部。不了解时序依赖或待处理请求检查。答案:“只有`loop()`调用它,应该安全。” **错误** — 更改时序行为会影响Modbus轮询频率。
**LLM + fw-context:**
```
→ find_callers("modbus_poll")
→ loop at main.cpp:134
→ get_symbol_context("modbus_poll")
→ 函数体显示:内部2s定时(MODBUS_INTERVAL_MS),WiFi检查,MODBUS_MAX_PENDING guard,两个addRequest调用,具有特定的代币
→ find_references("MODBUS_INTERVAL_MS")
→ 仅在modbus_poll中使用(内部定时)
→ find_references("MODBUS_REG_SOC")
→ 在modbus_poll(发送者)和handleData(接收路由)中使用(MODBUS_REG_SOC和MODBUS_REG_VOLTAGE)
→ 更改签名会影响基于令牌的路由
AI的答案:"`modbus_poll`仅从`loop()`调用。但是,改变其签名会影响:(a)内部2秒的轮询频率,(b)令牌值用于路由Modbus响应(MODBUS_REG_SOC和MODBUS_REG_VOLTAGE),(c)待处理请求保护。令牌常量用于`modbus_poll`发送者和`handleData`接收者——它们形成协议。更改一侧而更改另一侧会破坏响应路由。”
代币:~1,200个代币,跨4个工具调用。答案:完整且正确。
### 提示:“项目中是否有死代码?"
**LLM Alone:** 没有实用方法回答。需要读取每个文件,枚举每个函数,遍历每个函数名,过滤假阳性——不可能在单个对话中。最好的做法:“我没有方法彻底检查死代码。”
**LLM + fw-context:**
```
→ find_dead_code(project_only=true)
返回:“未发现死代码——每个定义的函数至少有一个调用者。”
代币:~150。答案:详尽,编译器验证。
此结果是真实的——HA_Boiler项目是700行,维护良好,没有死函数。那是有价值的信息。 在较大的或旧的项目中,您将获得一份候选人列表供您调查。
这结果是真实的——HA_Boiler项目仅700行,维护良好,没有死函数。这是有价值的信息。在更大的或更老的项目中,您将获得一个需要调查的候选人列表。
### 提示:"追踪电池SOC数据从Modbus寄存器到锅炉继电器决策的流程"
**仅限LLM:** 读取`modbus.cpp` → 读取`control.cpp` → 手动拼凑流程。可能会忽略过时检测(`modbus_is_stale`),因为它是一个单独的函数。可能会忽略`config.h`中的滞后常数。
**LLM + fw-context:**
```
→ find_references("rt_data")
→ 25个引用,跨越5个函数,4个文件
→ get_symbol_context("handleData")
→ 正文 + 说明:无调用者(它是一个回调函数)
→ explain_symbol("modbus_is_stale")
→ "如果自上次Modbus成功后超过MODBUS_STALE_MS毫秒,则返回true"
→ get_source("boiler_control")
→ 显示modbus_is_stale()是**首要**检查 — 过时数据 → batFull = false
AI的数据流答案:
1. modbus_poll() 每2秒发送寄存器266(SOC)的请求
2. eModbus库接收TCP响应 → 调用handleData()回调函数
3. handleData()验证范围(0–1000),存储到rt_data.batt_soc
4. boiler_control() 读取rt_data.batt_soc(第28行,第31行)
5. 首要检查:modbus_is_stale() — 如果>5分钟无响应 → batFull=false
6. 电池充满检查:batt_soc >= BATT_SOC_THRESHOLD(80.0%)
7. 滞后:低于BATT_SOC_THRESHOLD - BATT_SOC_HYSTERESIS(78.0%)之前,batFull转为false
8. batFull决定使用哪个温度传感器和逻辑分支
9. 最终输出:digitalWrite(BOILER2_PIN, ...) 带有30秒最短切换保护
代币:~2000。答案:完整的带有滞后、过时检测和继电器保护的数据流。每个步骤可追溯到特定的文件:行。
```
---
## 之前和之后:一个真实的调查
这里是同一个任务 — 了解一个不熟悉的嵌入式代码库 — 以两种方式完成。
### 没有fw-context:手动侦探
```
打开main.cpp → 找到loop() → 看到ha_update()被调用。
搜索ha_update → 打开ha_mqtt.cpp → 看到它调用boiler_control()。
打开control.cpp → 读取boiler_control() — 42行,5个条件。
搜索BATT_VOLTAGE_THRESHOLD → config.h。
搜索TEMP_SENTINEL → config.h。
搜索modbus_is_stale → modbus.cpp。
读取modbus_poll() — 发送SOC和电压请求。
读取handleData() — 验证和存储Modbus响应。
读取getTemp() — DS18B20带有RunningMedian过滤器。
尝试拼凑:传感器 → rt_data → boiler_control → 继电器。
但是谁调用handleData? Grep找不到。肯定是回调函数。
搜索onDataHandler → 在modbus_setup()中找到。回调函数在那里注册。实际调用者在eModbus库内部。
→ 最终对数据流有了一个心理模型。
总计:读取8个文件,多次搜索整个代码库
问题答案:"boiler_control做什么以及数据如何流动?"
信心:中等 — 可能遗漏了引用或宏
```
### 使用fw-context:查询索引
```
get_symbol_context("boiler_control")
→ 正文 + 调用者 + 被调用者 + LLM分析。一次调用。
→ 回答"这个函数做什么以及它在什么位置?"
find_references("rt_data")
→ 25个引用,跨越5个函数,4个文件。
→ 完整的数据流图。
explain_symbol("modbus_poll")
→ 预编译的纯英文解释。
→ "轮询Modbus服务器的电池SOC和电压..."
find_callers("handleData")
→ 空 — 它是一个回调函数。
→ 这就是答案:基于回调,由Modbus库调用。
find_call_path("loop", "handleData")
→ 未找到路径。
→ 确认回调边界。
get_file_analysis("src/control.cpp")
→ "实现了锅炉系统的控制逻辑..."
→ 整个模块的上下文。
总计:6次工具调用,零文件读取
问题答案:完整的数据流、架构、时序、边缘情况
信心:高 — 每个引用都是编译器验证的,每个边缘来自AST
```
### 变更内容
| | 仅限LLM | LLM + fw-context |
|---|----|----|
| 读取文件 | 8(整个文件) | 0(仅索引查询) |
| 回调发现 | 手动grep + 运气 | `find_callers`返回空 — 显式信号 |
| 宏解析 | 手动读取config.h | libclang已经解析 |
| 数据流信心 | 精神模型,可能遗漏引用 | 25个引用枚举,每个引用都有账号 |
| **您可以问的问题** | 受代币预算限制 | 受工具覆盖范围限制(29种工具,广泛) |
---
## 对于AI助手用户:日常变更
如果您使用Claude Code、OpenCode、Cursor或另一个MCP-capable助手,
添加fw-context会改变实际操作:
### 变得实用的问题
- *"在重构这个驱动程序之前,请显示所有调用者,包括传递调用者。*
- *"是否有一个函数在这个代码库中不再被调用?*
- *"这个全局变量在哪里被写入?我需要每个写入站点。*
- *"从主循环到最低级别的UART字节发送,调用路径是什么?*
- *"查找项目中与电池管理相关的所有内容 — 跨所有文件。*
- *"哪些函数被调用最多?我想要关注测试。*
- *"给我这个5,000行文件的结构概述,然后再阅读它。*
### 变得更快的问题
- "这个函数做什么?" → 1次调用,不需要1个文件读取
- "谁调用这个?" → 1次调用,不需要grep + 手动过滤
- "这个常量在哪里使用?" → 1次调用,不需要grep整个供应商头文件
- "X如何到达Y?" → 1次调用,不需要读取每个中间函数
### 变得*正确*的问题
带有fw-context的AI根据编译的内容进行回答。不是根据文本文件。
`#ifdef`分支您的板子使用。包含路径您的构建指定。宏您的`-D`标志定义。
当您的AI说*"`spi_transfer` 在 `drivers/flash.c:142` 处被 `flash_write` 调用"*, 这不是grep结果。 这是从AST编译器验证的边缘。
---
## 60秒开始使用
### 1. 安装
```bash
git clone git@github.com:turbyho/fw-context-mcp.git ~/.fw-context/src
cd ~/.fw-context/src && make install
fw-context init # registers with Claude Code, OpenCode, Cursor, etc.
```
**前提条件:** Python 3.11+,`bear`,`libclang-dev`。 Ollama可选。
[完整安装指南 →](https://github.com/turbyho/fw-context-mcp/blob/main/docs/installation.md)
### 2. 索引您的项目
```bash
cd your-firmware-project
fw-context index
```
自动检测Zephyr、PlatformIO、Mbed OS,或用`bear`包装任何构建。
首次索引构建完整的数据库。后续运行是增量 — 仅更改的文件被重新解析。
### 3. 开始提出实际问题
```
"显示boiler_control — 正文、调用者、被调用者和它的功能。"
"查找读取或写入电池电压的每个函数。"
"如果我更改spi_transfer的签名,影响范围是什么?"
"项目中是否有任何死代码?仅限项目代码。"
"数据如何从Modbus寄存器流到继电器输出?"
"哪些函数被调用最多?我想要优先测试。"
```
您的AI现在查询索引而不是grep文件。
### 4. 保持新鲜
```bash
fw-context reindex src/control.cpp # 编辑后 — 增量,快速
fw-context index # 合并后
# 或继续工作 — 自动索引检测查询时过时的文件
```
### 支持的AI助手
| 助手 | 注册 |
|-----------|-------------|
| **Claude Code** | `~/.claude/mcp.json` |
| **OpenCode** | `~/.config/opencode/mcp.json` |
| **Cursor** | `.cursor/mcp.json` |
| **Codex** | `~/.codex/mcp.json` |
| **Kilo Code** | 继承自Claude Code |
### 支持的构建系统
| 生态系统 | 如何 |
|-----------|-----|
| **PlatformIO** | `pio run --target compiledb` |
| **Zephyr RTOS** | `west build` (基于CMake,自动) |
| **Mbed OS** | `bear -- mbed compile` |
| **ESP-IDF** | `idf.py build` + bear |
| **Arduino** | 通过PlatformIO包装 |
| **CMake** | `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` |
| **裸金属 / Make** | `bear -- make` |
---
## 诚实的局限性
每个工具都有边界。这里是fw-context的边界 — 清晰说明。
### 回调和函数指针
fw-context在三个阶段跟踪函数指针:
1. **注册** — `find_callers`和`find_references`检测函数指针赋值 (`driver.onData = &handler`),调用参数 (`register_cb(&handler)`),变量初始化 (`void (*fp)(int) = &handler`),和指定结构体初始化 (`{.onReady = &handler}`)。这些作为`"间接"`调用边出现。
2. **调用** — `find_indirect_call_sites`查找函数指针实际调用的位置,无论是通过字段 (`driver.onData(buf, len)`),变量 (`stored_callback(42)`),还是参数 (`cb(args)`).
3. **链接** — `find_indirect_targets`通过字段的USR连接赋值站点到调用站点,回答哪些函数可以通过`onData`调用,带有精确的编译器验证结果。
通过存储在库中的指针(例如在eModbus中)注册,通过预编译代码中的实际调用站点,调用图边缘仍然缺失。
### 中断服务程序(ISRs),RTOS原语,消息队列
中断向量,`k_work_init()`,`k_msgq_put()/get()` — 这些连接函数,但不出现在调用图谱中。通过消息队列流动的数据没有静态调用路径。
### 预编译库
Nordic SoftDevice,TI BLE堆栈,预编译WiFi blobs — 没有源码索引。 调用它们将有缺失的被调用者边缘。
### 不支持编译_commands.json的专有IDE
IAR,Keil MDK,一些Segger配置 — 这些不支持生成`compile_commands.json`本地。解决方法:用`bear`包装编译器,它拦截来自任何调用命令行编译器的IDE的编译请求。
### 宏密集代码
如果宏通过标记粘贴(X-Macros)生成函数名称,生成的符号被索引,但生成它们的宏丢失。扩展形式存在;元编程不存在。
### 间接调用站点(调用方)
调用方现在被覆盖 — `find_indirect_call_sites`检测函数指针何时通过字段、变量或参数调用,`find_indirect_targets`将它们链接到赋值站点。动态索引数组 (`handlers[i](args)`), `void*`-擦除回调和通过中间变量的多跳指针链仍然超出静态分析的范围。
---
## 何时使用什么
fw-context并不是用来替换现有的工具。它填补了它们无法覆盖的空白:**为AI助手提供编译器准确的对其代码库的理解。**
| 工具 | 最适合 | AI原生? |
|------|----------|------------|
| **clangd** | 代码补全、跳转到定义、编辑器内诊断 | ❌ (LSP) |
| **grep/rg** | 快速文本搜索、查找字符串字面量 | ❌ |
| **ctags/cscope** | 基于索引的Vim/Emacs导航 | ❌ |
| **Sourcegraph** | 组织范围的代码搜索,带有服务器基础设施 | ❌ (REST API) |
| **fw-context** | **AI辅助代码探索、理解和影响分析** | ✅ (MCP) |
**工作流程:** 编辑器中的clangd用于编辑。 AI助手中的fw-context用于探索和理解。两者都使用libclang。 两者都看到相同的编译现实。 它们适用于开发周期中的不同时刻。
---
## 常见问题
### 这会将我的固件发送到云吗?
**绝不会。** 所有操作都在本地运行:libclang,SQLite,Ollama(如果使用)。 无需遥测。 无需API密钥。 安装后无需网络。 您的专有固件从未离开您的机器。
### 我需要GPU或特殊硬件吗?
不需要。 Ollama在CPU上运行,如果没有GPU。 对于嵌入式模型 (`mxbai-embed-large`,~670 MB)和分析模型 (`qwen2.5-coder:14b`,~9 GB),CPU推理较慢但适用于索引(一次完成)和偶尔的智能搜索查询。
### 我可以完全不使用Ollama吗?
是的。 所有27种工具都无需Ollama即可工作。 `smart_search`回落到词分FTS5。 `semantic_search`回落到`search_code`。 `explain_symbol`返回源码 + 提示,由您的AI自行处理。 索引和调用图在没有任何LLM依赖的情况下完全功能正常。
### 如果我的索引损坏了怎么办?
```bash
fw-context reset # 删除
fw-context index # 重建
```
索引只是一个SQLite文件。 无需永久状态。
### 它是否适用于C++模板和虚函数?
是的。 模板实例化被跟踪 (`get_template_instances`),虚函数重写被映射 (`get_method_overrides`),继承链可遍历 (`get_inheritance_chain`)。 所有这些都需要引用索引(默认启用)。
### 它能处理多大的项目?
已测试的项目高达100,000+符号。 参考索引已验证1.2百万个引用边。 初始索引时间大致线性扩展到翻译单元计数。 增量重新索引始终快速。
---
## 结论
您的AI助手足够智能,可以理解您的固件。 它**无法**仅通过读取源文件来做到这一点 — 因为源文件是编译器依赖、不完整的宏模糊的编译和运行在您的板上的实际代码的影子。
fw-context解决了信息问题。 它为您的AI提供与编译器相同的代码视图。 从这个基础上,一切都改变了:答案精确,对话更短,代币成本降低一个数量级,您曾经因为超出上下文窗口而无法提问的问题变得实用。
**模型很好。 给它更好的数据。**
---
## 文档
| 文档 | 涵盖 |
|----------|--------|
| **[技术概述](docs/README.md)** | 架构图、目录布局、功能、支持的生态系统 — 原始README |
| **[安装指南](docs/installation.md)** | 先决条件、安装、升级、Ollama设置、AI助手集成 |
| **[工具参考](docs/tools.md)** | 所有29种MCP工具、10种CLI命令、搜索管道内部 |
| **[配置](docs/configuration.md)** | `.fw-context/config.toml` + `local.toml` — 共享项目配置和本地开发人员覆盖 |
| **[MCP服务器](README-MCP.md)** | JSON-RPC协议、工具模式、错误处理、调试 |
---
<div align="center">
**[⭐ GitHub上标星](https://github.com/turbyho/fw-context-mcp)**
|
**[📖 文档](https://github.com/turbyho/fw-context-mcp/blob/main/docs/)**
|
**[🐛 问题](https://github.com/turbyho/fw-context-mcp/issues)**
**开源(MIT)。无云。无守护进程。无API密钥。**
*您的固件从未离开您的机器。*
</div>
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器