diff --git a/.gitignore b/.gitignore
index 62da2bd..e9d6d8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,5 +14,14 @@ venv/
.ruff_cache/
htmlcov/
.coverage
-debugforge.toml
.claude/
+
+# Sensitive config (contains credentials)
+debugforge.toml
+*.env
+.env.*
+
+# IDE
+.idea/
+.vscode/
+*.swp
diff --git a/README.md b/README.md
index cb08184..324b530 100644
--- a/README.md
+++ b/README.md
@@ -1,513 +1,190 @@
-
-
-
+# DebugForge
-DebugForge
+将 TRACE32 接入 AI Agent,实现自动化调试、Debug 定位的 MCP Server 工具。支持本地和远程环境。
-
- Bridge Lauterbach TRACE32 to AI Agents via MCP
-
-
-
-
-
-
-
-
-
-
- DebugForge is an MCP server that connects Lauterbach TRACE32 debuggers to AI agents.
- It gives Claude Code, Codex, Qwen and other AI agents direct access to your hardware debugger
- — enabling them to autonomously read target state, locate software bugs, and drive the fix-debug cycle end-to-end.
-
-
----
-
-## Highlights
-
-- **59 MCP Tools** — Full TRACE32 access for AI agents: execution control, breakpoints, memory, registers, variables, symbols, build, and more
-- **Autonomous Debug Loop** — AI agents can build, flash, debug, locate root causes, fix code, and re-verify — the full edit-compile-debug cycle
-- **Debug Skills** — Accumulate reusable debugging knowledge; AI agents learn from past issues and apply proven strategies
-- **Project-Aware** — Configure once via `debugforge.toml`, your AI agent automatically knows your ELF paths, flash scripts, toolchain, and TRACE32 setup
-- **Real Hardware** — Battle-tested on TC397 TriCore via USB. Your AI agent controls actual silicon, not a simulator
-- **Advanced Breakpoints** — Conditional, data watchpoints, hit-count, task-specific, action triggers, and temporary breakpoints
-- **Deep Inspection** — AI agents can read call stacks with locals, expand structs, view disassembly, inspect peripherals
-- **Any MCP Agent** — Works with Claude Code, Codex CLI, Qwen, or any MCP-compatible AI assistant
-- **Zero Lock-in** — MIT licensed, open source, no vendor dependencies beyond TRACE32 itself
-
-## Architecture
+## 架构
```
-┌─────────────────┐ MCP (stdio) ┌──────────────┐ PYRCL/TCP ┌──────────────┐
-│ │◄───────────────────────────►│ │◄─────────────────────►│ │
-│ AI Agent │ JSON-RPC tool calls │ DebugForge │ Remote Control API │ TRACE32 │
-│ (Claude Code, │ (59 debugging tools) │ MCP Server │ (lauterbach-trace32 │ PowerView │
-│ Codex, etc.) │ │ │ -rcl) │ │
-│ │◄───────────────────────────►│ │◄─────────────────────►│ │
-└─────────────────┘ Results └──────────────┘ Hardware └──────┬───────┘
- │
- ┌──────▼───────┐
- │ Target MCU │
- │ (e.g. TC397) │
- └──────────────┘
+┌─────────────────────────────────────────────────────────────┐
+│ 本地 Linux (Agent) │
+│ - 源码/ELF/CMM │
+│ - AI Agent (PYRCL 客户端) │
+│ - DebugForge MCP Server │
+└────────────────┬────────────────────────┬───────────────────┘
+ │ SCP (端口 22) │ PYRCL (端口 20000)
+ │ 传输 ELF/CMM │ 远程控制 TRACE32
+ ▼ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ 远程 Windows (your-windows-host) │
+│ - OpenSSH Server │
+│ - TRACE32 │
+│ - 硬件板子 (TC38x/TC39x) │
+└─────────────────────────────────────────────────────────────┘
```
-## Quick Start
-
-```bash
-# 1. Install DebugForge
-pip install debugforge
-
-# 2. Install TRACE32 Python package (from your TRACE32 installation)
-pip install /demo/api/python/rcl/dist/lauterbach_trace32_rcl-*.whl
-
-# 3. Add to your AI agent's MCP config (e.g. .claude/settings.json)
-```
-
-```json
-{
- "mcpServers": {
- "debugforge": {
- "command": "debugforge"
- }
- }
-}
-```
-
-```bash
-# 4. Start TRACE32 with API port enabled, then ask your AI agent:
-# "Connect to TRACE32, load the firmware, find why the system crashes at boot"
-```
-
-## Installation
-
-### Prerequisites
-
-| Requirement | Details |
-|-------------|---------|
-| Python | 3.10 or higher |
-| TRACE32 | PowerView running with Remote API enabled |
-| PYRCL | `lauterbach-trace32-rcl` package from your TRACE32 installation |
+## 快速开始
-### Step 1: Install DebugForge
+### 1. 安装
```bash
-pip install debugforge
-```
-
-Or install from source:
-
-```bash
-git clone https://github.com/YangPan2020/debugforge.git
-cd debugforge
pip install -e .
```
-### Step 2: Install TRACE32 Python Library
+### 2. 配置
-The PYRCL wheel is bundled with your TRACE32 installation:
+复制配置模板并填入实际信息:
```bash
-pip install /demo/api/python/rcl/dist/lauterbach_trace32_rcl-*.whl
-```
-
-> Replace `` with your TRACE32 installation directory (e.g., `/opt/t32`, `C:\T32`).
-
-### Step 3: Enable TRACE32 Remote API
-
-Add these lines to your TRACE32 configuration file (`.t32` or `config.t32`):
-
-```
-RCL=NETTCP
-PORT=20000
-```
-
-Then restart TRACE32 PowerView.
-
-## Configuration
-
-### MCP Client Setup
-
-All MCP-compatible AI agents use the same configuration — register `debugforge` as an MCP server:
-
-```json
-{
- "mcpServers": {
- "debugforge": {
- "command": "debugforge"
- }
- }
-}
-```
-
-| Agent | Config File | Notes |
-|-------|-------------|-------|
-| Claude Code | `.claude/settings.json` | Also supports `env` field for auto-connect |
-| Gemini CLI | `.gemini/settings.json` | |
-| GitHub Copilot | `.vscode/mcp.json` | VS Code extension |
-| Antigravity CLI | MCP config | |
-| Codex CLI | MCP config | |
-| DeepSeek | MCP config | |
-| Hermes Agent | MCP config | |
-
-**Auto-connect example** (skip manual `connect()` call):
-
-```json
-{
- "mcpServers": {
- "debugforge": {
- "command": "debugforge",
- "env": {
- "T32_AUTO_CONNECT": "true",
- "T32_PORT": "20000"
- }
- }
- }
-}
+cp debugforge.toml.example debugforge.toml
```
-### Project Configuration (`debugforge.toml`)
-
-Create a `debugforge.toml` in your project root to tell your AI agent about your debugging environment:
+编辑 `debugforge.toml`:
```toml
-[trace32]
-install_path = "/opt/t32" # TRACE32 installation directory
+[mode]
+mode = "remote" # 或 "local"
[connection]
-node = "localhost" # TRACE32 host address
-port = 20000 # API port (match your .t32 config)
-protocol = "TCP" # TCP or UDP
-auto_connect = true # Connect when server starts
-
-[project]
-elf = "output/build/firmware.elf" # ELF file with debug symbols
-map = "output/build/firmware.map" # MAP file (optional)
+node = "192.168.1.100" # TRACE32 所在主机 IP
+port = 20000
-[toolchain]
-compiler_path = "/opt/gcc-arm/bin" # Compiler toolchain path
-objdump = "arm-none-eabi-objdump" # Disassembly tool
-readelf = "arm-none-eabi-readelf" # ELF analysis tool
-nm = "arm-none-eabi-nm" # Symbol table tool
+[remote]
+host = "192.168.1.100"
+winrm_user = "user@domain.local"
+winrm_password = "your_password"
+ssh_user = "username"
+staging_dir = "D:\\T32\\debugforge"
-[build]
-command = "make -j8" # Build command
-clean_command = "make clean" # Clean command
-working_dir = "." # Build working directory
+[project]
+elf = "/path/to/your/firmware.elf"
[scripts]
-flash = "tools/Trace32/flash.cmm" # Flash programming script
-init = "tools/Trace32/startup.cmm" # Target init script
-
-[debug]
-skills_dir = ".debugforge/skills" # Debug knowledge base directory
+flash = "/path/to/your/flash_script.cmm"
```
-All relative paths are resolved from the directory containing `debugforge.toml`.
-
-See [`debugforge.example.toml`](debugforge.example.toml) for a complete template.
-
-### Environment Variables
-
-Environment variables override `debugforge.toml` values (highest priority):
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `T32_INSTALL_PATH` | — | TRACE32 installation directory |
-| `T32_NODE` | `localhost` | TRACE32 host address |
-| `T32_PORT` | `20000` | TRACE32 API port |
-| `T32_PROTOCOL` | `TCP` | Communication protocol (TCP/UDP) |
-| `T32_AUTO_CONNECT` | `false` | Auto-connect on server start |
-| `DEBUGFORGE_CONFIG` | `./debugforge.toml` | Path to config file |
-
-**Priority:** Environment Variables > `debugforge.toml` > Built-in Defaults
-
-## Available Tools (59)
-
-### Connection & Configuration
-
-| Tool | Description |
-|------|-------------|
-| `connect` | Connect to a TRACE32 PowerView instance |
-| `disconnect` | Disconnect from TRACE32 |
-| `status` | Get connection status, TRACE32 version, and system state |
-| `get_project_config` | Get the loaded project configuration (paths, scripts, settings) |
-
-### Execution Control
-
-| Tool | Description |
-|------|-------------|
-| `go` | Start/continue program execution |
-| `step` | Single-step execution (into, over, or out) |
-| `halt` | Stop program execution |
-| `reset` | Reset the target CPU |
-| `get_state` | Get current CPU execution state |
-| `go_till` | Run until a specific address (temporary breakpoint) |
-| `go_up` | Run until current function returns to caller |
-| `go_return` | Run to the return instruction of current function |
-
-### Breakpoints
-
-| Tool | Description |
-|------|-------------|
-| `set_breakpoint` | Set a program/read/write/readwrite breakpoint |
-| `list_breakpoints` | List all active breakpoints |
-| `delete_breakpoint` | Delete a breakpoint at a specific address/symbol |
-| `clear_all_breakpoints` | Delete all breakpoints at once |
-| `toggle_breakpoint` | Enable/disable a breakpoint without deleting |
-
-### Advanced Breakpoints
+> **注意**: `debugforge.toml` 包含敏感凭据信息,已被 `.gitignore` 排除,不会被提交到仓库。
-| Tool | Description |
-|------|-------------|
-| `set_conditional_breakpoint` | Breakpoint with HLL condition (e.g., `i > 100`) |
-| `set_data_breakpoint` | Trigger on memory access with optional value match |
-| `set_count_breakpoint` | Stop after N-th hit (loop debugging) |
-| `set_task_breakpoint` | Trigger only for a specific OS task/thread |
-| `set_action_breakpoint` | Execute a TRACE32 command on hit |
-| `set_temporary_breakpoint` | Auto-deletes after first hit |
+### 3. 配置远程 Windows
-### Memory
+参考 [WINDOWS_SSH_SETUP.md](WINDOWS_SSH_SETUP.md) 安装 OpenSSH Server。
-| Tool | Description |
-|------|-------------|
-| `read_memory` | Read target memory (hex dump format) |
-| `write_memory` | Write data to target memory |
-| `data_set` | Write a value to a memory address using Data.Set |
+### 4. 运行 MCP Server
-### Registers
+```bash
+debugforge
+```
-| Tool | Description |
-|------|-------------|
-| `read_register` | Read a single CPU register |
-| `read_registers` | Read multiple registers at once |
-| `write_register` | Write a value to a CPU register |
+或通过 examples 中的脚本直接测试:
-### Variables
+```bash
+python examples/remote_debug.py
+python examples/remote_debug.py --pyrcl
+```
-| Tool | Description |
-|------|-------------|
-| `read_variable` | Read a C/C++ variable by symbol name |
-| `write_variable` | Write a value to a C/C++ variable |
-| `var_view` | View a variable/struct/array with full expansion |
-| `var_set` | Set a C/C++ variable to a new value |
+## 工作流
-### Symbols
+### Remote 模式
-| Tool | Description |
-|------|-------------|
-| `symbol_by_name` | Look up symbol address by name |
-| `symbol_by_address` | Look up symbol name by address |
+1. **文件传输**: SCP/WinRM 传输 ELF + CMM 到 Windows staging 目录
+2. **CMM Wrapper**: 生成 wrapper CMM,将 ELF 路径替换为 Windows 路径
+3. **PYRCL 连接**: 通过 TCP 连接远程 TRACE32
+4. **执行 CMM**: 远程 TRACE32 执行 CMM 烧录 ELF
+5. **源码映射**: 设置 `Symbol.SourcePATH.Translate` 让 TRACE32 找到源码
-### Commands & Scripting
+### Local 模式
-| Tool | Description |
-|------|-------------|
-| `execute_command` | Execute any TRACE32 PRACTICE command |
-| `run_practice` | Run a PRACTICE (.cmm) script with timeout |
-| `evaluate` | Evaluate a TRACE32 expression or function |
-
-### Debug Views
-
-| Tool | Description |
-|------|-------------|
-| `get_callstack` | Get call stack with function names |
-| `get_locals` | Get call stack with all local variables per frame |
-| `get_data_dump` | Formatted memory dump (hex + ASCII) |
-| `get_register_view` | Full register view with all flags |
-| `get_disassembly` | Disassembly listing at address or current PC |
-| `get_source_listing` | Source code around current execution point |
-| `get_window` | Get text content of any TRACE32 window command |
-
-### System & OS Awareness
-
-| Tool | Description |
-|------|-------------|
-| `get_task_list` | List OS tasks/threads |
-| `get_task_stack` | Get stack info for OS tasks |
-| `get_peripheral_view` | View peripheral register contents |
-
-### Build & Binary Analysis
-
-| Tool | Description |
-|------|-------------|
-| `build_project` | Execute the configured build command |
-| `clean_project` | Execute the configured clean command |
-| `disassemble` | Disassemble a function or address range (objdump) |
-| `analyze_map` | Analyze MAP file for memory layout and symbols |
-| `analyze_elf` | Analyze ELF file structure (readelf) |
-
-### Workflow Orchestration
-
-| Tool | Description |
-|------|-------------|
-| `flash_and_run` | Flash firmware → reset → run to breakpoint |
-| `build_flash_run` | Build → flash → reset → run (full edit-compile-debug loop) |
-
-### Debug Skills (Knowledge Base)
-
-| Tool | Description |
-|------|-------------|
-| `list_debug_skills` | List all debug skills in the knowledge base |
-| `get_debug_skill` | Get full content of a specific debug skill |
-| `search_debug_skills` | Search skills by keywords matching symptoms |
-| `save_debug_skill` | Save a new debug skill from a successful debug session |
-
-## Usage Examples
+1. **PYRCL 连接**: 连接本地 TRACE32
+2. **执行 CMM**: 直接执行本地 CMM 脚本
-### Basic Debug Session
+## 配置详解
-```
-You: "Connect to TRACE32 and help me find why the system crashes after boot"
-
-AI Agent workflow:
- 1. get_project_config() → learns your ELF path and scripts
- 2. connect() → connects to TRACE32
- 3. execute_command("SYStem.Up") → powers up the target
- 4. execute_command("Data.LOAD.Elf /path/to/firmware.elf")
- 5. set_breakpoint("main")
- 6. go() → runs to main
- 7. step("over") → steps through code
- 8. get_callstack() → analyzes the call stack
- 9. read_variable("error_code") → checks variables
- → "Found it: error_code = -1 because init_hardware() fails at line 84"
-```
+参考 `debugforge.toml.example` 获取完整配置项说明。
-### Flash and Debug
+### 模式选择
-```
-You: "Flash the new firmware and verify it boots correctly"
-
-AI Agent workflow:
- 1. get_project_config() → gets flash script path
- 2. connect()
- 3. run_practice("tools/flash.cmm") → flashes firmware
- 4. reset()
- 5. set_breakpoint("main")
- 6. go()
- 7. get_state() → confirms target stopped at main
- → "Firmware flashed and verified — target stopped at main() successfully"
+```toml
+[mode]
+mode = "remote" # "local" 或 "remote"
```
-### Advanced Debugging
+### 远程连接
-```
-You: "The buffer overflow happens somewhere in process_packet(). Set a watchpoint."
-
-AI Agent workflow:
- 1. symbol_by_name("rx_buffer") → gets buffer address
- 2. set_data_breakpoint(address="0xD0001000", access="write", size=256)
- 3. go()
- 4. get_callstack() → sees who wrote beyond the buffer
- 5. get_disassembly() → examines the offending instruction
- 6. read_memory("0xD0001000", 512) → shows the corrupted data
- → "Overflow at process_packet+0x4C: memcpy writes 320 bytes into 256-byte buffer"
+```toml
+[remote]
+host = "192.168.1.100" # 远程 Windows IP
+winrm_port = 5985 # WinRM 端口
+winrm_user = "user@domain" # WinRM 用户名
+winrm_password = "password" # WinRM 密码
+ssh_user = "username" # SSH 用户名
+ssh_password = "" # SSH 密码 (留空使用 key)
+ssh_port = 22 # SSH 端口
+staging_dir = "D:\\T32\\debugforge" # 文件传输目标目录
```
-### Autonomous Fix Cycle
+### 源码路径映射
+```toml
+[[remote.source_translates]]
+from = "/home/user/project"
+to = "D:\\project"
```
-You: "The watchdog is resetting the MCU. Find and fix the root cause."
-
-AI Agent workflow:
- 1. search_debug_skills("watchdog timeout reset") → finds matching skill
- 2. get_debug_skill("watchdog-timeout") → reads proven strategy
- 3. build_flash_run() → build + flash + run to main
- 4. set_conditional_breakpoint("wdt_reset_handler", condition="reset_count > 0")
- 5. go() → runs until watchdog fires
- 6. get_callstack() → finds stuck function
- 7. get_locals() → sees infinite loop condition
- ... fixes code ...
- 8. build_flash_run() → rebuild, reflash, verify
- 9. save_debug_skill(...) → captures new knowledge
- → "Fixed: infinite loop in spi_wait_ready() when peripheral clock is disabled"
-```
-
-## Supported AI Agents
-
-| Agent | Status | Configuration |
-|-------|--------|---------------|
-| [Claude Code](https://claude.com/claude-code) | ✅ Tested | `.claude/settings.json` |
-| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | ✅ Compatible | `.gemini/settings.json` |
-| [GitHub Copilot](https://github.com/features/copilot) | ✅ Compatible | `.vscode/mcp.json` |
-| [Codex CLI](https://github.com/openai/codex) | ✅ Compatible | MCP stdio transport |
-| [Antigravity CLI](https://github.com/antigravity-official/antigravity-cli) | ✅ Compatible | MCP stdio transport |
-| [DeepSeek](https://www.deepseek.com/) | ✅ Compatible | MCP stdio transport |
-| [Hermes Agent](https://github.com/hermes-agent/hermes) | ✅ Compatible | MCP stdio transport |
-| [Qwen Agent](https://github.com/QwenLM/Qwen-Agent) | ✅ Compatible | MCP stdio transport |
-| Any MCP Client | ✅ Compatible | Standard MCP protocol |
-## Development
+让 TRACE32 在 debug 时能找到源码文件。需要先将源码复制到 Windows 对应目录。
-### Setup
+## 依赖
```bash
-git clone https://github.com/YangPan2020/debugforge.git
-cd debugforge
-pip install -e ".[dev]"
+pip install paramiko scp pywinrm
```
-### Run Tests
+## 文件说明
-```bash
-pytest tests/ -v
+```
+debugforge/
+├── src/debugforge/ # MCP Server 核心代码
+├── examples/ # 示例脚本
+│ ├── remote_debug.py # 远程调试示例 (WinRM + PYRCL)
+│ ├── remote_debug_tc38x.py # TC38x 远程调试示例
+│ ├── test_all_tools.py # 全工具测试
+│ └── debug_tc397_live.py # TC397 完整调试工作流
+├── debugforge.toml.example # 配置模板
+├── WINDOWS_SSH_SETUP.md # Windows OpenSSH 配置指南
+└── README.md # 本文档
```
-### Code Style
+## 故障排查
-This project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting:
+### SSH 连接失败
-```bash
-ruff check src/ tests/
-ruff format src/ tests/
+```
+[FATAL] 连接失败: SSH connection failed
```
-### Contributing
-
-See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
-
-## Autonomous Debug Workflow
+- 检查 Windows OpenSSH Server 是否安装并运行
+- 检查防火墙是否允许端口 22
+- 测试: `ssh @`
-DebugForge supports a full autonomous debug loop. Include [`debugforge-workflow.md`](debugforge-workflow.md) in your AI agent's instructions to enable:
+### PYRCL 连接失败
```
-Problem Description → Understand Code → Build → Flash → Debug → Locate Bug → Fix → Rebuild → Verify → Done
+[FATAL] 连接失败: Connection refused
```
-**Phases:**
-1. **Understand** — Parse symptoms, search debug skills for matching patterns
-2. **Analyze** — Read source, inspect MAP/ELF, disassemble critical functions
-3. **Debug on Hardware** — Build/flash/run, set breakpoints, inspect state
-4. **Fix & Verify** — Modify code, rebuild, reflash, confirm fix
-5. **Capture Knowledge** — Save new debug patterns as reusable skills
-
-### Debug Skills
+- 检查远程 TRACE32 是否启动
+- 检查 TRACE32 配置: `RCL=NETTCP PORT=20000`
+- 检查防火墙是否允许端口 20000
+- 测试: `telnet 20000`
-Debug skills are reusable Markdown files in `.debugforge/skills/` that capture proven debugging strategies:
+### CMM 执行失败
-```bash
-.debugforge/skills/
-├── stack-overflow.md
-├── hardfault-analysis.md
-└── peripheral-init-failure.md
+```
+CMM 异常: ...
```
-AI agents search these automatically when debugging, and save new skills after resolving novel issues.
-
-## Roadmap
-
-- [ ] HTTP/SSE transport for remote debugging
-- [ ] Auto-start/manage TRACE32 lifecycle
-- [ ] Multi-core debugging (multiple simultaneous connections)
-- [ ] Trace data analysis and visualization
-- [ ] Flash programming tools (dedicated, beyond script execution)
+- 检查 ELF 文件是否成功传输到 staging_dir
+- 检查 CMM 中的路径是否正确
+- 查看远程 TRACE32 窗口错误信息
## License
-[MIT](LICENSE) — free for personal and commercial use.
-
----
-
-
- Built with ❤️ for the embedded debugging community
-
+MIT
diff --git a/WINDOWS_SSH_SETUP.md b/WINDOWS_SSH_SETUP.md
new file mode 100644
index 0000000..5cc6141
--- /dev/null
+++ b/WINDOWS_SSH_SETUP.md
@@ -0,0 +1,112 @@
+# Windows OpenSSH 配置指南
+
+## 1. 安装 OpenSSH Server
+
+### Windows 10/11 (1809+)
+
+1. 打开 **设置** → **应用** → **可选功能**
+2. 点击 **添加功能**
+3. 搜索 **OpenSSH 服务器**,安装
+
+### 或使用 PowerShell (管理员)
+
+```powershell
+# 检查是否已安装
+Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
+
+# 安装
+Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
+
+# 启动服务
+Start-Service sshd
+
+# 设置为自动启动
+Set-Service -Name sshd -StartupType Automatic
+
+# 配置防火墙
+New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
+```
+
+## 2. 配置 SSH
+
+编辑 `C:\ProgramData\ssh\sshd_config`:
+
+```powershell
+notepad C:\ProgramData\ssh\sshd_config
+```
+
+确保以下配置存在且未注释:
+
+```
+Port 22
+PubkeyAuthentication yes
+PasswordAuthentication yes
+```
+
+重启服务:
+
+```powershell
+Restart-Service sshd
+```
+
+## 3. 测试连接
+
+从 Linux 测试:
+
+```bash
+ssh @
+```
+
+如果成功,说明 OpenSSH Server 已就绪。
+
+## 4. 配置 SSH Key (推荐)
+
+### 在 Linux 生成密钥
+
+```bash
+ssh-keygen -t rsa -b 4096
+```
+
+### 复制公钥到 Windows
+
+```bash
+ssh-copy-id @
+```
+
+或手动复制:
+
+```bash
+cat ~/.ssh/id_rsa.pub | ssh @ "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
+```
+
+### Windows 端权限设置
+
+```powershell
+# 修复 authorized_keys 权限
+icacls C:\Users\\.ssh\authorized_keys /inheritance:r /grant ":(R)" /grant "SYSTEM:(R)"
+```
+
+## 5. 验证 SCP
+
+```bash
+scp test.txt @:D:\\T32\\debugforge\\
+```
+
+如果成功,说明文件传输已就绪。
+
+## 6. 常见问题
+
+### 连接超时
+
+- 检查防火墙是否允许端口 22
+- 检查 sshd 服务是否运行: `Get-Service sshd`
+
+### 权限拒绝
+
+- 检查 `authorized_keys` 文件权限
+- 检查 `sshd_config` 中 `PubkeyAuthentication yes`
+
+### 中文用户名/密码
+
+- 确保 Windows 用户名是英文 (如 `username`)
+- 密码建议使用英文字符
diff --git a/debugforge.toml.example b/debugforge.toml.example
new file mode 100644
index 0000000..97f6565
--- /dev/null
+++ b/debugforge.toml.example
@@ -0,0 +1,50 @@
+# DebugForge Configuration Template
+# Copy this file to debugforge.toml and fill in your actual values.
+# debugforge.toml is gitignored and will NOT be committed.
+
+[mode]
+mode = "remote" # "local" or "remote"
+
+[trace32]
+install_path = "C:\\T32"
+
+[connection]
+node = "localhost" # TRACE32 host (use remote.host for remote mode)
+port = 20000 # TRACE32 RCL port
+protocol = "TCP"
+auto_connect = false
+
+[remote]
+host = "192.168.1.100" # Remote Windows IP
+winrm_port = 5985 # WinRM port
+winrm_user = "user@domain.local" # WinRM username (domain account)
+winrm_password = "your_password" # WinRM password
+winrm_transport = "ntlm" # WinRM transport: ntlm, kerberos, basic
+ssh_user = "username" # SSH username for file transfer
+ssh_password = "" # SSH password (leave empty for key auth)
+ssh_port = 22 # SSH port
+staging_dir = "C:\\T32" # Remote staging directory
+
+[[remote.source_translates]]
+from = "/home/user/project"
+to = "D:\\project"
+
+[project]
+elf = "/path/to/your/firmware.elf"
+
+[scripts]
+flash = "/path/to/your/flash_script.cmm"
+
+[toolchain]
+compiler_path = ""
+objdump = "tricore-elf-objdump"
+readelf = "tricore-elf-readelf"
+nm = "tricore-elf-nm"
+
+[build]
+command = "make -j8"
+clean_command = "make clean"
+working_dir = ""
+
+[debug]
+skills_dir = ""
diff --git a/examples/debug_tc397_live.py b/examples/debug_tc397_live.py
new file mode 100644
index 0000000..0c7d4d2
--- /dev/null
+++ b/examples/debug_tc397_live.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python3
+"""
+Example: TC397 automated debug workflow (full pipeline)
+
+Demonstrates complete debug workflow:
+1. Connect Trace32 via PYRCL
+2. Flash download via CMM script
+3. Set breakpoints and run
+4. Inspect registers, variables, memory
+5. Callstack and source analysis
+6. OS awareness (NuttX tasks)
+7. Step execution with variable tracking
+8. Peripheral register read
+
+Usage:
+ 1. Copy debugforge.toml.example to debugforge.toml
+ 2. Configure connection and project paths
+ 3. Run: python examples/debug_tc397_live.py
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+import time
+import traceback
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
+
+from lauterbach.trace32.rcl import connect as t32_connect
+from debugforge.config import load_config
+from debugforge.state import state
+
+
+def banner(title: str):
+ print(f"\n{'='*60}")
+ print(f" {title}")
+ print(f"{'='*60}")
+
+
+def _read_window(dbg, command: str, max_size: int = 32768) -> str:
+ content = bytearray()
+ offset = 0
+ while True:
+ try:
+ length, chunk = dbg._get_window_content(command, 4096, offset, "ASCII")
+ except Exception:
+ break
+ if length == 0 or not chunk:
+ break
+ content += chunk[:length]
+ offset += length
+ if offset >= max_size:
+ break
+ return content.decode("utf-8", errors="replace")
+
+
+def cmd(dbg, c: str):
+ print(f" > {c}")
+ try:
+ dbg.fnc.error_occurred()
+ dbg.cmd(c)
+ print(f" OK")
+ except Exception as e:
+ print(f" [WARN] {e}")
+
+
+def sym_addr(dbg, name: str) -> int | None:
+ try:
+ s = dbg.symbol.query_by_name(name)
+ if s.address and s.address.value is not None:
+ return s.address.value
+ except Exception:
+ pass
+ return None
+
+
+def pc_to_func(dbg, pc: int) -> str:
+ try:
+ addr = dbg.address.from_string(f"0x{pc:08X}")
+ sym = dbg.symbol.query_by_address(addr)
+ return sym.name or "(unknown)"
+ except Exception:
+ return "(unknown)"
+
+
+def phase1_connect(cfg):
+ banner("Phase 1: Connect Trace32")
+ print(f" Config: node={cfg.node}, port={cfg.port}, protocol={cfg.protocol}")
+ print(f" ELF: {cfg.elf}")
+ print(f" Flash script: {cfg.scripts.get('flash', '(none)')}")
+
+ dbg = t32_connect(node=cfg.node, port=cfg.port, protocol=cfg.protocol)
+ ver = dbg.fnc.software_version()
+ print(f" Connected, version: {ver}")
+
+ state.debugger = dbg
+ state.node = cfg.node
+ state.port = cfg.port
+ state.protocol = cfg.protocol
+
+ return dbg
+
+
+def phase2_flash(dbg, cfg):
+ banner("Phase 2: Flash Download & JTAG Connect")
+
+ flash_script = cfg.scripts.get("flash", "")
+ elf_path = cfg.elf
+
+ if not flash_script:
+ print(" [SKIP] No flash script configured, loading symbols only")
+ cmd(dbg, f'Data.LOAD.Elf "{elf_path}" /NoCODE')
+ return
+
+ print(f" Flash script: {flash_script}")
+ print(f" ELF: {elf_path}")
+
+ try:
+ dbg.cmm(f'"{flash_script}"', timeout=120)
+ print(f" Flash script executed")
+ except Exception as e:
+ print(f" [WARN] cmm() exception: {e}")
+ dbg.cmd(f'CD.DO "{flash_script}"')
+ time.sleep(15)
+
+ print(f"\n Verifying symbols:")
+ found = 0
+ for name in ["core_main", "nx_start", "core0_main", "rtfw_init"]:
+ addr = sym_addr(dbg, name)
+ if addr is not None:
+ print(f" {name:<40s} @ 0x{addr:08X}")
+ found += 1
+ print(f" Found {found} key symbols")
+
+
+def phase3_breakpoints_and_run(dbg, cfg):
+ banner("Phase 3: Set Breakpoints & Run")
+
+ cmd(dbg, "Break.Delete /ALL")
+
+ breakpoints = ["core_main", "core0_main", "nx_start"]
+ for bp_name in breakpoints:
+ addr = sym_addr(dbg, bp_name)
+ if addr is not None:
+ cmd(dbg, f"Break.Set {bp_name}")
+ else:
+ print(f" {bp_name}: symbol not found, skipping")
+
+ print("\n Go (run)...")
+ cmd(dbg, "Go")
+ time.sleep(3)
+
+ print(" Break (halt)...")
+ dbg.break_()
+ time.sleep(0.5)
+
+ try:
+ pc = dbg.register.read_by_name("PC").value
+ func = pc_to_func(dbg, pc)
+ print(f" PC = 0x{pc:08X} -> {func}")
+ except Exception as e:
+ print(f" State: {e}")
+
+
+def phase4_inspect_state(dbg):
+ banner("Phase 4: Inspect Runtime State")
+
+ print("\n [4a] Core registers:")
+ for rname in ["PC", "SP", "A0", "A1", "D0", "D1", "D2", "D3"]:
+ try:
+ reg = dbg.register.read(rname)
+ val = reg.value
+ if isinstance(val, int):
+ print(f" {reg.name:<8s} = 0x{val:08X}")
+ else:
+ print(f" {reg.name:<8s} = {val}")
+ except Exception as e:
+ print(f" {rname:<8s} = [error: {e}]")
+
+ print("\n [4b] Memory (CPU0 DSPR):")
+ try:
+ addr = dbg.address.from_string("D:0xD0000000")
+ data = dbg.memory.read(addr, length=64)
+ for offset in range(0, len(data), 16):
+ chunk = data[offset:offset+16]
+ hex_str = " ".join(f"{b:02X}" for b in chunk)
+ print(f" +{offset:04X}: {hex_str}")
+ except Exception as e:
+ print(f" [error: {e}]")
+
+
+def phase5_step(dbg):
+ banner("Phase 5: Step Execution")
+ for i in range(5):
+ print(f"\n --- Step {i+1} ---")
+ try:
+ if dbg.fnc.state_run():
+ dbg.break_()
+ time.sleep(0.3)
+ dbg.cmd("Step")
+ pc = dbg.register.read_by_name("PC").value
+ func = pc_to_func(dbg, pc)
+ print(f" PC = 0x{pc:08X} -> {func}")
+ except Exception as e:
+ print(f" [error: {e}]")
+ break
+
+
+def main():
+ cfg = load_config()
+
+ print("=" * 60)
+ print(" DebugForge: TC397 Automated Debug Workflow")
+ print(" Target: Infineon AURIX TC397 (TriCore)")
+ print("=" * 60)
+
+ try:
+ dbg = phase1_connect(cfg)
+ phase2_flash(dbg, cfg)
+ phase3_breakpoints_and_run(dbg, cfg)
+ phase4_inspect_state(dbg)
+ phase5_step(dbg)
+
+ banner("Done")
+ cmd(dbg, "Break.Delete /ALL")
+ print(" All breakpoints cleared")
+
+ except ConnectionError as e:
+ print(f"\n[FATAL] Connection failed: {e}")
+ print("Check:")
+ print(" 1. Trace32 PowerView is running")
+ print(" 2. config.t32 has RCL=NETTCP PORT=20000")
+ print(" 3. Target board connected via JTAG")
+ sys.exit(1)
+ except Exception as e:
+ print(f"\n[FATAL] Unexpected error: {e}")
+ traceback.print_exc()
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/remote_debug.py b/examples/remote_debug.py
new file mode 100644
index 0000000..7e643fd
--- /dev/null
+++ b/examples/remote_debug.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+"""
+Example: Remote debug workflow via WinRM + PYRCL
+
+Demonstrates:
+1. WinRM upload ELF + CMM to remote Windows
+2. WinRM start Trace32
+3. PYRCL connect and execute debug commands
+
+Usage:
+ 1. Copy debugforge.toml.example to debugforge.toml
+ 2. Fill in your remote connection details
+ 3. Run: python examples/remote_debug.py
+"""
+
+import os
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
+
+from debugforge.config import load_config
+
+
+def run_winrm_cmd(session, cmd: str, label: str = ""):
+ """Execute a PowerShell command and print results."""
+ if label:
+ print(f"\n{label}")
+
+ r = session.run_ps(cmd)
+ out = r.std_out.decode('utf-8', errors='replace').strip()
+ err = r.std_err.decode('utf-8', errors='replace').strip()
+
+ if out:
+ print(f" stdout: {out}")
+ if err and "CLIXML" not in err:
+ print(f" stderr: {err[:500]}")
+ print(f" exit: {r.status_code}")
+
+ return r.status_code == 0
+
+
+def main():
+ import winrm
+
+ cfg = load_config()
+ remote = cfg.remote
+
+ if not remote.host:
+ print("Error: remote.host not configured in debugforge.toml")
+ sys.exit(1)
+
+ elf_path = cfg.elf
+ flash_script = cfg.scripts.get("flash", "")
+
+ if not elf_path or not os.path.exists(elf_path):
+ print(f"Error: ELF file not found: {elf_path}")
+ sys.exit(1)
+
+ print("=" * 60)
+ print("DebugForge: Remote Debug via WinRM + PYRCL")
+ print("=" * 60)
+ print(f" Host: {remote.host}")
+ print(f" WinRM: {remote.winrm_url}")
+ print(f" Staging: {remote.staging_dir}")
+
+ # Connect WinRM
+ print(f"\nConnecting WinRM: {remote.winrm_url}")
+ session = winrm.Session(
+ remote.winrm_url,
+ auth=(remote.winrm_user, remote.winrm_password),
+ transport=remote.winrm_transport,
+ )
+
+ # 1. Create remote directory
+ run_winrm_cmd(
+ session,
+ f'New-Item -ItemType Directory -Path "{remote.staging_dir}" -Force | Out-Null; Write-Output "Directory ready"',
+ "Create remote directory",
+ )
+
+ # 2. Start Trace32
+ t32_cmd = r'''
+$t32 = "C:\T32\bin\windows64\t32mtc.exe"
+$config = "C:\T32\config.t32"
+$proc = Get-Process t32mtc -ErrorAction SilentlyContinue
+if ($proc) {
+ Write-Output "Trace32 already running (PID: $($proc.Id))"
+} else {
+ Start-Process -FilePath $t32 -ArgumentList "-c", $config
+ Start-Sleep -Seconds 5
+ Write-Output "Trace32 started"
+}
+'''
+ run_winrm_cmd(session, t32_cmd, "Check/Start Trace32")
+
+ # 3. Verify Trace32 RCL port
+ run_winrm_cmd(
+ session,
+ f'Test-NetConnection -ComputerName {remote.host} -Port {cfg.port} -WarningAction SilentlyContinue | Select-Object TcpTestSucceeded',
+ "Verify Trace32 port",
+ )
+
+ print(f"\nTrace32 ready at {remote.host}:{cfg.port}")
+
+ # 4. Optional: connect via PYRCL
+ if '--pyrcl' in sys.argv:
+ print(f"\nConnecting via PYRCL...")
+ from lauterbach.trace32.rcl import connect
+ dbg = connect(node=remote.host, port=str(cfg.port), protocol=cfg.protocol)
+ print(f"Connected! Version: {dbg.fnc.version_software()}")
+
+ if flash_script:
+ remote_cmm = f"{remote.staging_dir}\\{os.path.basename(flash_script)}"
+ print(f"Executing CMM: {remote_cmm}")
+ dbg.cmd(f'DO "{remote_cmm}"')
+
+ for translate in remote.source_translates:
+ src = translate.get("from", "")
+ dst = translate.get("to", "")
+ if src and dst:
+ dbg.cmd(f'SYmbol.SourcePATH.Translate "{src}" "{dst}"')
+
+ print("Done!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/remote_debug_tc38x.py b/examples/remote_debug_tc38x.py
new file mode 100644
index 0000000..d8c7fd5
--- /dev/null
+++ b/examples/remote_debug_tc38x.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+"""
+Example: Remote debug TC38x via network drive + PYRCL
+
+Demonstrates direct file access via mapped network drive (Z:) and
+safe Trace32 shutdown via QUIT command.
+
+Safety measures:
+- Must execute dbg.cmd('QUIT') before exit to cleanly close Trace32
+- Force-killing the process can cause hardware errors
+- All exit paths (normal/exception/signal) execute QUIT
+
+Usage:
+ 1. Copy debugforge.toml.example to debugforge.toml
+ 2. Fill in your connection details
+ 3. Run: python examples/remote_debug_tc38x.py
+"""
+
+import os
+import sys
+import signal
+import logging
+import time
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
+
+from lauterbach.trace32.rcl import connect
+from debugforge.config import load_config
+
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s [%(levelname)s] %(message)s',
+ datefmt='%H:%M:%S'
+)
+
+dbg_connection = None
+
+
+def safe_quit_trace32(dbg):
+ """Safely close Trace32 via QUIT command."""
+ if not dbg:
+ return
+ try:
+ logging.info('[T32] Sending QUIT command to safely close Trace32 PowerView...')
+ dbg.cmm('QUIT')
+ logging.info('Trace32 closed safely')
+ time.sleep(2)
+ except Exception as e:
+ logging.error(f'Error closing Trace32: {e}')
+
+
+def signal_handler(signum, frame):
+ sig_name = signal.Signals(signum).name
+ logging.warning(f'Received signal {sig_name}, shutting down Trace32...')
+ global dbg_connection
+ if dbg_connection:
+ safe_quit_trace32(dbg_connection)
+ sys.exit(128 + signum)
+
+
+def main():
+ global dbg_connection
+
+ signal.signal(signal.SIGINT, signal_handler)
+ signal.signal(signal.SIGTERM, signal_handler)
+
+ cfg = load_config()
+ remote = cfg.remote
+ host = remote.host or cfg.node
+ port = str(cfg.port)
+
+ logging.info("=" * 70)
+ logging.info("Remote Debug TC38x")
+ logging.info(f" Host: {host}:{port}")
+ logging.info("=" * 70)
+
+ dbg = None
+ try:
+ logging.info(f"[1/4] Connecting Trace32 ({host}:{port})...")
+ dbg = connect(node=host, port=port, protocol='TCP', timeout=120)
+ dbg_connection = dbg
+ logging.info("Connected")
+
+ logging.info("[2/4] Verifying connection...")
+ version = dbg.fnc.version_software()
+ logging.info(f"Trace32 version: {version}")
+
+ cmm_path = cfg.scripts.get("flash", "")
+ elf_path = cfg.elf
+
+ if cmm_path:
+ logging.info(f"[3/4] Executing CMM: {cmm_path}")
+ logging.info(f" Loading ELF: {elf_path}")
+ result = dbg.cmm(cmm_path)
+ logging.info(f"CMM execution complete")
+ time.sleep(3)
+
+ logging.info("[4/4] Verifying ELF load...")
+ try:
+ pc_value = dbg.register.read("PC")
+ logging.info(f"ELF loaded, PC: 0x{pc_value.value:08X}")
+ except Exception as e:
+ logging.warning(f"Cannot read PC: {e}")
+
+ logging.info("Debug session complete, shutting down...")
+
+ except ConnectionError as e:
+ logging.error(f"Connection failed: {e}")
+ logging.error("Check: 1) Trace32 running 2) RCL=NETTCP PORT=20000 3) Firewall")
+ return 1
+ except Exception as e:
+ logging.error(f"Error: {e}")
+ import traceback
+ traceback.print_exc()
+ return 1
+ finally:
+ safe_quit_trace32(dbg)
+ dbg_connection = None
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/examples/test_all_tools.py b/examples/test_all_tools.py
new file mode 100644
index 0000000..d8bd1c7
--- /dev/null
+++ b/examples/test_all_tools.py
@@ -0,0 +1,248 @@
+#!/usr/bin/env python3
+"""
+Example: Test all DebugForge debug tools via PYRCL
+
+Comprehensive test of all TRACE32 debug capabilities:
+- Connection, execution, registers, memory
+- Breakpoints, symbols, variables, commands
+- Views, multicore, system config, trace
+- Benchmark, notifications, watchpin, OS awareness
+
+Usage:
+ 1. Copy debugforge.toml.example to debugforge.toml
+ 2. Ensure Trace32 is running with RCL enabled
+ 3. Run: python examples/test_all_tools.py
+"""
+
+import os
+import sys
+import time
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
+
+from lauterbach.trace32.rcl import connect
+from debugforge.config import load_config
+
+results = []
+
+
+def test(name, func):
+ print(f"\n[{name}]", flush=True)
+ try:
+ result = func()
+ print(f" PASS: {result}", flush=True)
+ results.append((name, "PASS", str(result)[:200]))
+ return result
+ except Exception as e:
+ print(f" FAIL: {type(e).__name__}: {e}", flush=True)
+ results.append((name, "FAIL", f"{type(e).__name__}: {e}"))
+ return None
+
+
+def main():
+ cfg = load_config()
+ host = cfg.remote.host or cfg.node
+ port = str(cfg.port)
+
+ # ========== CONNECT ==========
+ print("=" * 70, flush=True)
+ print("Phase 1: Connect to Trace32", flush=True)
+ print("=" * 70, flush=True)
+
+ print(f"Connecting to {host}:{port}...", flush=True)
+ dbg = connect(node=host, port=port, protocol=cfg.protocol, timeout=30)
+ print("Connected!", flush=True)
+ ver = dbg.fnc.version_software()
+ print(f" Version: {ver}", flush=True)
+
+ # ========== LOAD ELF ==========
+ print("\n" + "=" * 70, flush=True)
+ print("Phase 2: Load ELF", flush=True)
+ print("=" * 70, flush=True)
+
+ cmm_path = cfg.scripts.get("flash", "")
+ if cmm_path:
+ print(f"Executing: {cmm_path}", flush=True)
+ try:
+ dbg.cmm(cmm_path)
+ print("CMM executed", flush=True)
+ except Exception as e:
+ print(f"CMM error: {e}", flush=True)
+ print("Continuing anyway...", flush=True)
+ time.sleep(3)
+ else:
+ print("No flash script configured, skipping", flush=True)
+
+ # ========== CONNECTION TOOLS ==========
+ print("\n" + "=" * 70, flush=True)
+ print("Phase 3: Test All Debug Tools", flush=True)
+ print("=" * 70, flush=True)
+
+ print("\n--- Connection Tools ---", flush=True)
+
+ def t_status():
+ v = dbg.fnc.version_software()
+ cpu = dbg.fnc.system_cpu()
+ up = dbg.fnc.system_up()
+ return f"v={v}, cpu={cpu}, up={up}"
+ test("connection.status", t_status)
+
+ # ========== EXECUTION TOOLS ==========
+ print("\n--- Execution Tools ---", flush=True)
+
+ def t_halt():
+ dbg.break_()
+ time.sleep(0.5)
+ halt = dbg.fnc.state_halt()
+ return f"Halted: {halt}"
+ test("execution.halt", t_halt)
+
+ def t_state():
+ halt = dbg.fnc.state_halt()
+ run = dbg.fnc.state_run()
+ target = dbg.fnc.state_target()
+ power = dbg.fnc.state_power()
+ return f"halt={halt}, run={run}, target={target}, power={power}"
+ test("execution.state", t_state)
+
+ def t_step():
+ dbg.step()
+ time.sleep(0.2)
+ pc = dbg.register.read("PC")
+ return f"Stepped to PC=0x{pc.value:X}"
+ test("execution.step", t_step)
+
+ def t_go():
+ dbg.go()
+ time.sleep(1)
+ run = dbg.fnc.state_run()
+ dbg.break_()
+ time.sleep(0.3)
+ return f"Running was: {run}"
+ test("execution.go", t_go)
+
+ # ========== REGISTERS TOOLS ==========
+ print("\n--- Register Tools ---", flush=True)
+
+ def t_reg_read():
+ pc = dbg.register.read("PC")
+ sp = dbg.register.read("SP")
+ return f"PC=0x{pc.value:X}, SP=0x{sp.value:X}"
+ test("registers.read", t_reg_read)
+
+ def t_reg_write():
+ d0 = dbg.register.read("D0")
+ old = d0.value
+ dbg.register.write("D0", 0x12345678)
+ d0_new = dbg.register.read("D0")
+ dbg.register.write("D0", old)
+ return f"D0: 0x{old:X} -> 0x{d0_new.value:X} -> restored"
+ test("registers.write", t_reg_write)
+
+ # ========== MEMORY TOOLS ==========
+ print("\n--- Memory Tools ---", flush=True)
+
+ def t_mem_read():
+ pc = dbg.register.read("PC")
+ data = dbg.memory.read(pc.value, 16)
+ hexs = " ".join(f"{b:02X}" for b in data)
+ return f"@0x{pc.value:X}: {hexs}"
+ test("memory.read", t_mem_read)
+
+ def t_mem_read32():
+ pc = dbg.register.read("PC")
+ val = dbg.memory.read_uint32(pc.value)
+ return f"@0x{pc.value:X}: uint32=0x{val:X}"
+ test("memory.read_uint32", t_mem_read32)
+
+ # ========== BREAKPOINTS TOOLS ==========
+ print("\n--- Breakpoint Tools ---", flush=True)
+
+ def t_bp_set():
+ pc = dbg.register.read("PC")
+ addr = pc.value + 0x10
+ dbg.breakpoint.set(addr)
+ bps = dbg.breakpoint.list()
+ return f"Set BP @0x{addr:X}, total={len(bps)}"
+ test("breakpoints.set", t_bp_set)
+
+ def t_bp_list():
+ bps = dbg.breakpoint.list()
+ if bps:
+ info = [(f"0x{bp.address:X}" if hasattr(bp, 'address') else str(bp)) for bp in bps[:5]]
+ return f"{len(bps)} BP(s): {info}"
+ return "0 breakpoints"
+ test("breakpoints.list", t_bp_list)
+
+ def t_bp_delete():
+ bps = dbg.breakpoint.list()
+ if bps:
+ dbg.cmd("BREAK.DELETE")
+ return "All breakpoints deleted"
+ return "No BP to delete"
+ test("breakpoints.delete", t_bp_delete)
+
+ # ========== SYMBOLS TOOLS ==========
+ print("\n--- Symbol Tools ---", flush=True)
+
+ def t_sym_query_name():
+ syms = dbg.symbol.query_by_name("main")
+ if syms:
+ s = syms[0]
+ attrs = {a: getattr(s, a, '?') for a in ['name', 'address', 'size'] if hasattr(s, a)}
+ return f"'main' found: {attrs}"
+ return "No 'main' symbol"
+ test("symbols.query_by_name", t_sym_query_name)
+
+ def t_sym_exist():
+ exist = dbg.fnc.symbol_exist("main")
+ return f"symbol_exist('main') = {exist}"
+ test("symbols.exist", t_sym_exist)
+
+ # ========== MULTICORE TOOLS ==========
+ print("\n--- Multicore Tools ---", flush=True)
+
+ def t_mc_cpu():
+ cpu = dbg.fnc.system_cpu()
+ return f"CPU: {cpu}"
+ test("multicore.cpu", t_mc_cpu)
+
+ # ========== SYSTEM CONFIG TOOLS ==========
+ print("\n--- System Config Tools ---", flush=True)
+
+ def t_sys_mode():
+ mode = dbg.fnc.system_mode()
+ return f"Mode: {mode}"
+ test("system_config.mode", t_sys_mode)
+
+ # ========== TRACE TOOLS ==========
+ print("\n--- Trace Tools ---", flush=True)
+
+ def t_trace_state():
+ state = dbg.fnc.trace_state()
+ return f"Trace state: {state}"
+ test("trace.state", t_trace_state)
+
+ # ========== SUMMARY ==========
+ print("\n" + "=" * 70, flush=True)
+ print("TEST SUMMARY", flush=True)
+ print("=" * 70, flush=True)
+
+ passed = sum(1 for _, s, _ in results if s == "PASS")
+ failed = sum(1 for _, s, _ in results if s == "FAIL")
+
+ print(f"\nTotal: {len(results)} | Passed: {passed} | Failed: {failed}", flush=True)
+
+ if failed > 0:
+ print(f"\nFailed tests:", flush=True)
+ for name, s, msg in results:
+ if s == "FAIL":
+ print(f" FAIL {name}: {msg}", flush=True)
+
+ print("\n" + "=" * 70, flush=True)
+ print("DONE", flush=True)
+ print("=" * 70, flush=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/remote_rcl_config.t32 b/remote_rcl_config.t32
new file mode 100644
index 0000000..ce04b4d
--- /dev/null
+++ b/remote_rcl_config.t32
@@ -0,0 +1,19 @@
+; ============================================================
+; remote_rcl_config.t32
+; 远程机器上的 TRACE32 配置文件
+;
+; 使用方法 (在远程机器上):
+; t32mtc -c /path/to/remote_rcl_config.t32
+;
+; 或者追加到现有的 config.t32 文件中:
+; 复制 RCL= 和 PORT= 部分到 config.t32 的空行之间
+; ============================================================
+
+; 必须: RCL 前后各一个空行
+
+RCL=NETTCP
+PORT=20000
+PACKLEN=4096
+
+; 允许多个客户端同时连接 (可选)
+; RCLMULTIPLE=ENABLE
diff --git a/setup_remote_t32.sh b/setup_remote_t32.sh
new file mode 100755
index 0000000..4ad1cdb
--- /dev/null
+++ b/setup_remote_t32.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# ============================================================
+# setup_remote_t32.sh
+# 在远程机器上配置和启动 TRACE32 (RCL 模式)
+#
+# 使用方法:
+# 1. 拷贝此脚本到远程机器
+# 2. ssh user@remote "bash setup_remote_t32.sh"
+# 或直接 scp 后远程执行
+#
+# 前置条件:
+# - 远程机器已安装 TRACE32 (t32mtc 在 PATH 或 /opt/t32_*/)
+# - JTAG 硬件已连接
+# ============================================================
+
+set -e
+
+T32_INSTALL="${T32_INSTALL:-/opt/t32_202509}"
+T32_BIN="${T32_INSTALL}/bin"
+RCL_PORT="${RCL_PORT:-20000}"
+WORK_DIR="/tmp/debugforge"
+
+echo "============================================"
+echo " DebugForge: Remote TRACE32 Setup"
+echo " T32 Install: ${T32_INSTALL}"
+echo " RCL Port: ${RCL_PORT}"
+echo "============================================"
+
+# 1. 查找 t32mtc
+if [ -x "${T32_BIN}/t32mtc" ]; then
+ T32MT="${T32_BIN}/t32mtc"
+elif command -v t32mtc &>/dev/null; then
+ T32MT=$(command -v t32mtc)
+else
+ echo "[ERROR] t32mtc not found!"
+ echo " Tried: ${T32_BIN}/t32mtc"
+ echo " Set T32_INSTALL to your TRACE32 installation path"
+ exit 1
+fi
+echo " t32mtc: ${T32MT}"
+
+# 2. 创建工作目录
+mkdir -p "${WORK_DIR}/elf"
+mkdir -p "${WORK_DIR}/cmm"
+mkdir -p "${WORK_DIR}/tmp"
+echo " Work dir: ${WORK_DIR}"
+
+# 3. 生成 RCL 配置
+CONFIG_FILE="${WORK_DIR}/config_rcl.t32"
+cat > "${CONFIG_FILE}" </dev/null; then
+ if ss -tlnp 2>/dev/null | grep -q ":${RCL_PORT} "; then
+ echo " [WARN] Port ${RCL_PORT} already in use!"
+ echo " Another TRACE32 instance may be running"
+ echo " Kill it first or use a different port"
+ fi
+elif command -v netstat &>/dev/null; then
+ if netstat -tlnp 2>/dev/null | grep -q ":${RCL_PORT} "; then
+ echo " [WARN] Port ${RCL_PORT} already in use!"
+ fi
+fi
+
+# 5. 启动 TRACE32 (后台模式)
+echo ""
+echo " Starting TRACE32 in background..."
+echo " Command: ${T32MT} -c ${CONFIG_FILE}"
+echo ""
+echo " 如需前台运行 (看 GUI):"
+echo " ${T32MT} -c ${CONFIG_FILE}"
+echo ""
+echo " 如需后台运行:"
+echo " nohup ${T32MT} -c ${CONFIG_FILE} &"
+echo ""
+echo " 测试连接 (从本地):"
+echo " python remote_debug.py --host $(hostname -I | awk '{print $1}')"
+echo ""
+echo " ✓ Setup complete!"
+echo "============================================"
diff --git a/src/debugforge/config.py b/src/debugforge/config.py
index 08c7e10..1d5b665 100644
--- a/src/debugforge/config.py
+++ b/src/debugforge/config.py
@@ -16,6 +16,29 @@
tomllib = None
+@dataclass
+class RemoteConfig:
+ """Remote connection configuration (WinRM/SSH)."""
+
+ host: str = ""
+ mode: str = "local"
+ winrm_port: int = 5985
+ winrm_user: str = ""
+ winrm_password: str = ""
+ winrm_transport: str = "ntlm"
+ ssh_user: str = ""
+ ssh_password: str = ""
+ ssh_port: int = 22
+ staging_dir: str = ""
+ source_translates: list[dict[str, str]] = field(default_factory=list)
+
+ @property
+ def winrm_url(self) -> str:
+ if not self.host:
+ return ""
+ return f"http://{self.host}:{self.winrm_port}/wsman"
+
+
@dataclass
class DebugForgeConfig:
"""DebugForge project configuration."""
@@ -34,7 +57,7 @@ class DebugForgeConfig:
map: str = ""
# Scripts (resolved to absolute)
- scripts: dict[str, str] = field(default_factory=dict)
+ scripts: dict[str, str | list[str]] = field(default_factory=dict)
# Toolchain settings
compiler_path: str = ""
@@ -50,6 +73,9 @@ class DebugForgeConfig:
# Debug settings
skills_dir: str = ""
+ # Remote settings
+ remote: RemoteConfig = field(default_factory=RemoteConfig)
+
# Base directory for resolving relative paths
_base_dir: str = field(default="", repr=False)
@@ -128,7 +154,10 @@ def load_config(config_path: str | None = None) -> DebugForgeConfig:
# [scripts]
scripts_section = data.get("scripts", {})
for key, val in scripts_section.items():
- cfg.scripts[key] = _resolve_path(val, base_dir)
+ if isinstance(val, str):
+ cfg.scripts[key] = _resolve_path(val, base_dir)
+ elif isinstance(val, list):
+ cfg.scripts[key] = [_resolve_path(v, base_dir) for v in val if isinstance(v, str)]
# [toolchain]
tc_section = data.get("toolchain", {})
@@ -155,6 +184,37 @@ def load_config(config_path: str | None = None) -> DebugForgeConfig:
if "skills_dir" in debug_section:
cfg.skills_dir = _resolve_path(debug_section["skills_dir"], base_dir)
+ # [mode]
+ mode_section = data.get("mode", {})
+ if "mode" in mode_section:
+ cfg.remote.mode = mode_section["mode"]
+
+ # [remote]
+ remote_section = data.get("remote", {})
+ if "host" in remote_section:
+ cfg.remote.host = remote_section["host"]
+ if "winrm_port" in remote_section:
+ cfg.remote.winrm_port = int(remote_section["winrm_port"])
+ if "winrm_user" in remote_section:
+ cfg.remote.winrm_user = remote_section["winrm_user"]
+ if "winrm_password" in remote_section:
+ cfg.remote.winrm_password = remote_section["winrm_password"]
+ if "winrm_transport" in remote_section:
+ cfg.remote.winrm_transport = remote_section["winrm_transport"]
+ if "ssh_user" in remote_section:
+ cfg.remote.ssh_user = remote_section["ssh_user"]
+ if "ssh_password" in remote_section:
+ cfg.remote.ssh_password = remote_section["ssh_password"]
+ if "ssh_port" in remote_section:
+ cfg.remote.ssh_port = int(remote_section["ssh_port"])
+ if "staging_dir" in remote_section:
+ cfg.remote.staging_dir = remote_section["staging_dir"]
+ if "port" in remote_section:
+ cfg.port = int(remote_section["port"])
+ cfg.remote.host = cfg.remote.host or cfg.node
+ if "source_translates" in remote_section:
+ cfg.remote.source_translates = remote_section["source_translates"]
+
# Environment variable overrides (highest priority)
env_install = os.environ.get("T32_INSTALL_PATH", "")
if env_install:
diff --git a/src/debugforge/server.py b/src/debugforge/server.py
index a1d5c9b..3e1f1f9 100644
--- a/src/debugforge/server.py
+++ b/src/debugforge/server.py
@@ -46,6 +46,12 @@ async def lifespan(server: FastMCP):
from debugforge.tools import build # noqa: E402, F401
from debugforge.tools import workflow # noqa: E402, F401
from debugforge.tools import skills # noqa: E402, F401
+from debugforge.tools import trace # noqa: E402, F401
+from debugforge.tools import multicore # noqa: E402, F401
+from debugforge.tools import benchmark # noqa: E402, F401
+from debugforge.tools import system_config # noqa: E402, F401
+from debugforge.tools import watchpin # noqa: E402, F401
+from debugforge.tools import notifications # noqa: E402, F401
def main():
diff --git a/src/debugforge/tools/benchmark.py b/src/debugforge/tools/benchmark.py
new file mode 100644
index 0000000..9d2e9b4
--- /dev/null
+++ b/src/debugforge/tools/benchmark.py
@@ -0,0 +1,219 @@
+"""BenchMark Counter (BMC) performance analysis tools for TriCore.
+
+Provides access to on-chip hardware performance counters for profiling
+cache hits/misses, instruction counts, branch predictions, and other
+architectural events.
+
+Reference: debugger_tricore.pdf "CPU specific BMC Commands" section.
+"""
+
+from __future__ import annotations
+
+from debugforge.server import mcp
+from debugforge.state import state
+from debugforge.tools.views import _read_window
+
+
+@mcp.tool()
+async def bmc_configure(counter: str, event: str) -> str:
+ """Configure a BenchMark Counter to count a specific hardware event.
+
+ Maps a performance event to a counter slot. The counter accumulates
+ while the target runs. Read with bmc_read() after halting.
+
+ Args:
+ counter: Counter slot — "M1CNT", "M2CNT", "M3CNT", "M4CNT", etc.
+ event: Hardware event to count. Common TriCore events:
+ "CYCLECOUNT" — CPU clock cycles
+ "INSTRUCTIONCOUNT" — instructions executed
+ "DATA_X_HIT" — data cache/buffer hits
+ "DATA_X_CLEAN" — data cache/buffer misses
+ "DATA_READ" — data read accesses
+ "DATA_WRITE" — data write accesses
+ "PROGRAM_X_HIT" — instruction cache hits
+ "PROGRAM_X_MISS" — instruction cache misses
+ "JUMP" — jump/branch instructions
+ "BRANCH_PREDICTED" — correctly predicted branches
+ "BRANCH_NOT_PREDICTED" — mispredicted branches
+
+ Returns:
+ Confirmation of counter configuration
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd(f"BMC.{counter}.EVENT {event}")
+ return f"BMC configured: {counter} → counting {event}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error configuring BMC {counter}: {e}"
+
+
+@mcp.tool()
+async def bmc_read(counters: list[str] | None = None) -> str:
+ """Read current BenchMark Counter values.
+
+ Target must be halted. Returns raw counter values since last reset.
+
+ Args:
+ counters: List of counter names to read (e.g., ["M1CNT", "M2CNT"]).
+ If empty/None, reads all available counters.
+
+ Returns:
+ Counter names and their current values
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "BMC.state")
+ if not result.strip():
+ return "BMC state not available (counters may not be configured)"
+ return f"BMC State:\n{result}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error reading BMC: {e}"
+
+
+@mcp.tool()
+async def bmc_reset() -> str:
+ """Reset all BenchMark Counters to zero.
+
+ Clears all counter values for a fresh measurement.
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ # Reset each common counter
+ for cnt in ["M1CNT", "M2CNT", "M3CNT", "M4CNT"]:
+ try:
+ dbg.cmd(f"BMC.{cnt}.EVENT OFF")
+ except Exception:
+ pass
+ # Re-open state to clear
+ dbg.cmd("BMC.state")
+ return "All BMC counters reset"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error resetting BMC: {e}"
+
+
+@mcp.tool()
+async def bmc_cache_analysis(
+ function: str = "",
+ run_duration_ms: int = 1000,
+) -> str:
+ """Run a quick cache performance analysis.
+
+ Configures counters for cache hit/miss ratio, runs the target briefly,
+ then reports the results. Useful for identifying cache bottlenecks.
+
+ Args:
+ function: Function to analyze (empty = current execution point)
+ run_duration_ms: How long to run in milliseconds (default: 1000)
+
+ Returns:
+ Cache hit/miss statistics
+ """
+ dbg = state.require_connection()
+ try:
+ results = ["Cache Performance Analysis:"]
+
+ # Configure counters for cache analysis
+ dbg.cmd("BMC.M1CNT.EVENT DATA_X_HIT")
+ results.append(" M1CNT: Data cache hits")
+ dbg.cmd("BMC.M2CNT.EVENT DATA_X_CLEAN")
+ results.append(" M2CNT: Data cache misses")
+ dbg.cmd("BMC.M3CNT.EVENT PROGRAM_X_HIT")
+ results.append(" M3CNT: Instruction cache hits")
+ dbg.cmd("BMC.M4CNT.EVENT PROGRAM_X_MISS")
+ results.append(" M4CNT: Instruction cache misses")
+
+ # Set a breakpoint if function specified
+ if function:
+ dbg.cmd(f"Break.Set {function} /Program")
+ dbg.cmd("Go")
+ results.append(f" Running to {function}...")
+ else:
+ dbg.cmd("Go")
+ results.append(" Running...")
+
+ # Wait for the specified duration
+ import asyncio
+ await asyncio.sleep(run_duration_ms / 1000.0)
+
+ # Halt and read
+ dbg.cmd("Break")
+ results.append(" Halted. Reading counters...")
+
+ # Read BMC state
+ bmc_result = _read_window(dbg, "BMC.state")
+ if bmc_result.strip():
+ results.append(f"\n{bmc_result}")
+
+ return "\n".join(results)
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error in cache analysis: {e}"
+
+
+@mcp.tool()
+async def bmc_set_atob(counter: str, enabled: bool = True) -> str:
+ """Enable or disable A-to-B mode on a BMC counter.
+
+ In A-to-B mode, the counter only counts events between the Alpha
+ and Bravo marker breakpoints. Use set_breakpoint with actions
+ "alpha"/"bravo" to set markers.
+
+ Args:
+ counter: Counter name (e.g., "M1CNT")
+ enabled: True to enable A-to-B mode, False to disable
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ state_str = "ON" if enabled else "OFF"
+ dbg.cmd(f"BMC.{counter}.ATOB {state_str}")
+ return f"BMC {counter} A-to-B mode: {state_str}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting A-to-B mode: {e}"
+
+
+@mcp.tool()
+async def bmc_profile_chart(counters: list[str] | None = None) -> str:
+ """Display a BMC profile chart with counter data mapped to instruction flow.
+
+ Shows which functions/addresses consumed the most events (cycles,
+ cache misses, etc.). Requires BMC counters to have been recorded
+ during execution.
+
+ Args:
+ counters: Counter names to include (e.g., ["M1CNT", "M2CNT"]).
+ Empty = all configured counters.
+
+ Returns:
+ Profile chart showing event distribution across code
+ """
+ dbg = state.require_connection()
+ try:
+ if counters:
+ cnt_str = " ".join(counters)
+ cmd = f"SNOOPer.PROfileChart.COUNTER %Up {cnt_str}"
+ else:
+ cmd = "SNOOPer.PROfileChart.COUNTER %Up M1CNT M2CNT M3CNT"
+
+ result = _read_window(dbg, cmd)
+ if not result.strip():
+ return "No profile data available (record BMC data first using bmc_cache_analysis)"
+ return result
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting BMC profile chart: {e}"
diff --git a/src/debugforge/tools/breakpoints.py b/src/debugforge/tools/breakpoints.py
index 47e8734..3c6eb2f 100644
--- a/src/debugforge/tools/breakpoints.py
+++ b/src/debugforge/tools/breakpoints.py
@@ -143,3 +143,22 @@ async def toggle_breakpoint(address: str, enabled: bool) -> str:
except Exception as e:
action = "enabling" if enabled else "disabling"
return f"Error {action} breakpoint at {address}: {e}"
+
+
+@mcp.tool()
+async def get_breakpoint_count() -> str:
+ """Get the total number of breakpoints currently set.
+
+ Returns:
+ Count of all breakpoints (enabled and disabled)
+ """
+ dbg = state.require_connection()
+ try:
+ bps = dbg.breakpoint.list()
+ count = len(bps) if bps else 0
+ enabled_count = sum(1 for bp in bps if bp.enabled) if bps else 0
+ return f"Total breakpoints: {count} (enabled: {enabled_count}, disabled: {count - enabled_count})"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting breakpoint count: {e}"
diff --git a/src/debugforge/tools/execution.py b/src/debugforge/tools/execution.py
index 9ce1a9e..0ae49bc 100644
--- a/src/debugforge/tools/execution.py
+++ b/src/debugforge/tools/execution.py
@@ -1,9 +1,10 @@
-"""Execution control tools for TRACE32."""
+"""Execution control tools for TRACE32 — run, step, halt, reset, source location."""
from __future__ import annotations
from debugforge.server import mcp
from debugforge.state import state
+from debugforge.tools.views import _read_window
@mcp.tool()
@@ -33,22 +34,35 @@ async def step(mode: str = "into") -> str:
New program counter address after step
"""
dbg = state.require_connection()
+ mode_norm = mode.lower().strip()
cmd_map = {
"into": "Step",
"over": "Step.Over",
"out": "Step.Out",
+ "into_hll": "Step.Hll",
+ "over_hll": "Step.HllOver",
+ "next_line": "Step.HllOver",
+ "next_source_line": "Step.HllOver",
+ "next_assembly": "Step.Over",
+ "source_line": "Step.Hll",
}
- cmd = cmd_map.get(mode)
+ cmd = cmd_map.get(mode_norm)
if cmd is None:
- return f"Invalid step mode '{mode}'. Use: into, over, out"
+ return (
+ f"Invalid step mode '{mode}'. Use: into, over, out, into_hll, "
+ "over_hll (step one source line), next_line, next_assembly, source_line"
+ )
try:
dbg.cmd(cmd)
try:
pc = dbg.fnc.register_pc()
- return f"Stepped ({mode}). PC = 0x{pc:08X}"
+ loc = _current_source_location(dbg)
+ if loc:
+ return f"Stepped ({mode_norm}). PC = 0x{pc:08X}\nSource: {loc}"
+ return f"Stepped ({mode_norm}). PC = 0x{pc:08X}"
except Exception:
- return f"Stepped ({mode}). Use 'read_register' to check PC."
+ return f"Stepped ({mode_norm}). Use 'read_register' to check PC."
except Exception as e:
return f"Error during step: {e}"
@@ -110,3 +124,217 @@ async def get_state() -> str:
return info
except Exception as e:
return f"Error getting state: {e}"
+
+
+def _current_source_location(dbg) -> str:
+ """Return current source location as 'file:line (function)' or empty."""
+ try:
+ result = dbg.eval('sYmbol.FUNCtion(PP())')
+ except Exception:
+ result = ""
+ try:
+ src = dbg.eval('sYmbol.SOURCE(PP())')
+ except Exception:
+ src = ""
+ if src:
+ loc = f"{src}"
+ if result:
+ loc += f" in {result}"
+ return loc
+ return result if result else ""
+
+
+@mcp.tool()
+async def get_source_location() -> str:
+ """Get the current source file and line of the halted CPU.
+
+ Returns the HLL source location (file:line) and enclosing function name
+ for the current PC. The target must be halted and symbols must be loaded.
+
+ Returns:
+ Formatted source location (file, line, function, module)
+ """
+ dbg = state.require_connection()
+ try:
+ pc = dbg.fnc.register_pc()
+ loc = _current_source_location(dbg)
+ if loc:
+ return f"PC = 0x{pc:08X}\n{loc}"
+ return f"PC = 0x{pc:08X} (no source location available)"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting source location: {e}"
+
+
+@mcp.tool()
+async def step_mode_list() -> str:
+ """List all supported step modes and their behavior.
+
+ Returns:
+ Table of step modes with descriptions
+ """
+ rows = [
+ ("into", "Step into function calls (assembly-level)"),
+ ("over", "Step over function calls (assembly-level)"),
+ ("out", "Step out of current function to caller"),
+ ("into_hll", "Step into function calls (source-line)"),
+ ("over_hll", "Step over one HLL source line (next source line)"),
+ ("next_line", "Alias for over_hll — next source line"),
+ ("next_assembly", "Alias for over — next assembly instruction"),
+ ("source_line", "Alias for into_hll — one source line"),
+ ]
+ lines = ["Supported step modes:", ""]
+ for name, desc in rows:
+ lines.append(f" {name:20s} {desc}")
+ return "\n".join(lines)
+
+
+@mcp.tool()
+async def get_current_function() -> str:
+ """Get the name of the function the CPU is currently executing.
+
+ Returns:
+ Function name (or address if no debug symbols)
+ """
+ dbg = state.require_connection()
+ try:
+ result = dbg.eval('sYmbol.FUNCtion(PP())')
+ if result:
+ return f"Current function: {result}"
+ pc = dbg.fnc.register_pc()
+ return f"Current function: unknown (no symbol at PC = 0x{pc:08X})"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting current function: {e}"
+
+
+@mcp.tool()
+async def run_to_line(file: str, line: int) -> str:
+ """Run until execution reaches a specific source file line.
+
+ Sets a temporary breakpoint at the given source location and resumes
+ execution. The target stops when that line is reached.
+
+ Args:
+ file: Source file name (can be partial, e.g., 'main.c' or full path)
+ line: Line number in the source file
+
+ Returns:
+ Confirmation of temporary breakpoint
+ """
+ dbg = state.require_connection()
+ try:
+ # TRACE32 syntax: Break.Set "file"\line /Program /Temp
+ escaped_file = file.replace('"', '""')
+ dbg.cmd(f'Break.Set "{escaped_file}"\\{line} /Program')
+ dbg.cmd("Go")
+ return f"Running to {file}:{line}..."
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting run-to-line at {file}:{line}: {e}"
+
+
+@mcp.tool()
+async def get_run_stats() -> str:
+ """Get quick run/halt statistics for the target.
+
+ Combines CPU state, current function, source location, and elapsed
+ runtime (if available) in one call.
+
+ Returns:
+ Aggregated run statistics
+ """
+ dbg = state.require_connection()
+ try:
+ parts = ["# Run Statistics", ""]
+ try:
+ run_state = dbg.fnc.state_run()
+ parts.append(f"Running: {run_state}")
+ except Exception as e:
+ parts.append(f"State check failed: {e}")
+
+ try:
+ pc = dbg.fnc.register_pc()
+ parts.append(f"PC: 0x{pc:08X}")
+ except Exception:
+ pass
+
+ loc = _current_source_location(dbg)
+ if loc:
+ parts.append(f"Location: {loc}")
+
+ try:
+ sys_mode = dbg.fnc.system_mode()
+ parts.append(f"System mode: {sys_mode}")
+ except Exception:
+ pass
+
+ return "\n".join(parts)
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting run stats: {e}"
+
+
+@mcp.tool()
+async def get_practice_state() -> str:
+ """Get the state of any running PRACTICE (.cmm) script.
+
+ Shows whether a PRACTICE script is active, which script is running,
+ and the current line being executed.
+
+ Returns:
+ PRACTICE script state
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "PRactice.state")
+ if not result.strip():
+ return "No PRACTICE script running"
+ return f"PRACTICE State:\n{result}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting PRACTICE state: {e}"
+
+
+@mcp.tool()
+async def abort_practice() -> str:
+ """Abort the currently running PRACTICE script.
+
+ Stops the active .cmm script execution immediately.
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd("PRactice.ABORT")
+ return "PRACTICE script aborted"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error aborting PRACTICE script: {e}"
+
+
+@mcp.tool()
+async def get_message_line() -> str:
+ """Read the current TRACE32 message line content.
+
+ The message line is the status bar at the bottom of the TRACE32 window
+ that displays status messages from commands and scripts.
+
+ Returns:
+ Current message line text
+ """
+ dbg = state.require_connection()
+ try:
+ result = dbg.eval('MSG.line()')
+ return f"Message line: '{result}'" if result else "Message line: (empty)"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error reading message line: {e}"
diff --git a/src/debugforge/tools/memory.py b/src/debugforge/tools/memory.py
index c15b9fe..416892f 100644
--- a/src/debugforge/tools/memory.py
+++ b/src/debugforge/tools/memory.py
@@ -80,3 +80,73 @@ async def write_memory(
return f"Invalid hex data '{data}': {e}"
except Exception as e:
return f"Error writing memory at {address}: {e}"
+
+
+@mcp.tool()
+async def read_memory_cached(
+ address: str,
+ length: int,
+ width: int = 32,
+) -> str:
+ """Read memory using cache-aware access (D: prefix).
+
+ Shows data as the CPU sees it through its data cache, not the stale
+ value on the bus. Essential for debugging code that modifies cached
+ memory (e.g., shared variables in LMU on TC39x).
+
+ Args:
+ address: Memory address as hex string (e.g., "0x80000000")
+ length: Number of bytes to read
+ width: Access width in bits — 8, 16, 32, or 64 (default: 32)
+
+ Returns:
+ Hex dump of cached memory contents
+ """
+ return await read_memory(address, length, width, access="D:")
+
+
+@mcp.tool()
+async def read_memory_physical(
+ address: str,
+ length: int,
+ width: int = 32,
+) -> str:
+ """Read physical memory bypassing the CPU cache.
+
+ Reads the actual value on the memory bus, which may differ from what
+ the CPU sees if the data cache has not been flushed.
+
+ Args:
+ address: Memory address as hex string (e.g., "0x80000000")
+ length: Number of bytes to read
+ width: Access width in bits — 8, 16, 32, or 64 (default: 32)
+
+ Returns:
+ Hex dump of physical memory contents
+ """
+ return await read_memory(address, length, width, access="")
+
+
+@mcp.tool()
+async def list_access_classes() -> str:
+ """List all available memory access classes for TRACE32.
+
+ Shows the different access class prefixes that can be used to read
+ memory through different paths (cache, bus, peripheral, etc.).
+
+ Returns:
+ Table of access classes with descriptions
+ """
+ rows = [
+ ("(none)", "Default access — bus-level (may show stale cache data)"),
+ ("D:", "Data access — shows CPU's view through data cache"),
+ ("P:", "Program access — instruction memory space"),
+ ("DC:", "Data cache direct — read data cache contents"),
+ ("IC:", "Instruction cache direct — read I-cache contents"),
+ ("PER:", "Peripheral access — SFR/peripheral registers"),
+ ("DUALPORT:", "Dual-port RAM access (if supported)"),
+ ]
+ lines = ["Memory Access Classes:", ""]
+ for prefix, desc in rows:
+ lines.append(f" {prefix:12s} {desc}")
+ return "\n".join(lines)
diff --git a/src/debugforge/tools/multicore.py b/src/debugforge/tools/multicore.py
new file mode 100644
index 0000000..bf2bc4d
--- /dev/null
+++ b/src/debugforge/tools/multicore.py
@@ -0,0 +1,208 @@
+"""Multicore debugging tools for TriCore AURIX — SMP, AMP, iAMP.
+
+Covers core selection, multicore configuration, cross-core synchronization,
+and chip stepping detection.
+
+Reference: debugger_tricore.pdf "Multicore Debugging" section.
+"""
+
+from __future__ import annotations
+
+from debugforge.server import mcp
+from debugforge.state import state
+from debugforge.tools.views import _read_window
+
+
+@mcp.tool()
+async def select_core(core: int) -> str:
+ """Select the active CPU core for debugging.
+
+ In multicore setups, switches the debugger focus to the specified core.
+ Subsequent commands (breakpoints, register reads, etc.) target this core.
+
+ Args:
+ core: Core number (0-based, e.g., 0, 1, 2 for TC397)
+
+ Returns:
+ Confirmation with current core state
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd(f"SYStem.CPU {core}.")
+ # Query current state
+ run_state = dbg.fnc.state_run()
+ if run_state:
+ return f"Core {core} selected — Running"
+ else:
+ try:
+ pc = dbg.fnc.register_pc()
+ return f"Core {core} selected — Stopped at PC = 0x{pc:08X}"
+ except Exception:
+ return f"Core {core} selected — Stopped"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error selecting core {core}: {e}"
+
+
+@mcp.tool()
+async def get_chip_info() -> str:
+ """Get the detected chip/device information.
+
+ Returns chip stepping, device variant, and CPU configuration.
+ Useful for verifying the correct CPU is selected.
+
+ Returns:
+ Chip stepping, CPU type, and stepping information
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "SYStem.CPU")
+ info_parts = ["Chip Information:"]
+
+ # Try to get chip stepping via PRACTICE function
+ try:
+ stepping = dbg.eval('CHIP.STEPping()')
+ info_parts.append(f" Chip Stepping: {stepping}")
+ except Exception:
+ pass
+
+ # Get system mode
+ try:
+ sys_mode = dbg.fnc.system_mode()
+ info_parts.append(f" System Mode: {sys_mode}")
+ except Exception:
+ pass
+
+ if result.strip():
+ info_parts.append(f"\n CPU Configuration:\n{result}")
+
+ return "\n".join(info_parts)
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting chip info: {e}"
+
+
+@mcp.tool()
+async def configure_multicore(core_count: int) -> str:
+ """Configure multicore topology for AURIX devices.
+
+ Mounts multiple cores into one chip for shared resources (trace, MCDS).
+ Must be called before SYStem.Up in multicore scenarios.
+
+ Based on SYStem.CONFIG.CORE command from debugger_tricore.pdf:
+ SYStem.CONFIG.CORE maps each core to a chip position.
+
+ Args:
+ core_count: Number of cores to configure (e.g., 6 for TC397, 3 for TC377)
+
+ Returns:
+ Configuration results per core
+ """
+ dbg = state.require_connection()
+ try:
+ results = []
+ for i in range(core_count):
+ core_id = i + 1 # SYStem.CONFIG.CORE uses 1-based numbering
+ chip_pos = 1 # All cores in same chip
+ dbg.cmd(f"SYStem.CONFIG.CORE {core_id}. {chip_pos}.")
+ results.append(f" Core {i} → Chip position {chip_pos}, core slot {core_id}")
+
+ return f"Multicore configured ({core_count} cores):\n" + "\n".join(results)
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error configuring multicore: {e}"
+
+
+@mcp.tool()
+async def sync_cores(action: str = "go") -> str:
+ """Synchronize Go/Step/Break across multiple cores.
+
+ Uses the SYnch command to coordinate execution state between cores
+ in SMP or synchronized AMP mode.
+
+ Args:
+ action: Synchronization action:
+ "go" — run all synchronized cores
+ "break" — halt all synchronized cores
+ "step" — single-step all synchronized cores
+
+ Returns:
+ Confirmation of sync operation
+ """
+ dbg = state.require_connection()
+ try:
+ cmd_map = {
+ "go": "SYnch.Go",
+ "break": "SYnch.Break",
+ "step": "SYnch.Step",
+ }
+ cmd = cmd_map.get(action)
+ if not cmd:
+ return f"Invalid sync action '{action}'. Use: go, break, step"
+
+ dbg.cmd(cmd)
+ return f"Synchronized {action} across all cores"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error syncing cores ({action}): {e}"
+
+
+@mcp.tool()
+async def get_all_cores_state() -> str:
+ """Get the execution state of all CPU cores.
+
+ Queries each core's running/stopped status and PC value.
+ Useful in multicore debugging to understand the global system state.
+
+ Returns:
+ Table showing state of each core
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "SYStem.CONFIG.state")
+ if result.strip():
+ return f"System Configuration:\n{result}"
+
+ # Fallback: try to get state of current core
+ run_state = dbg.fnc.state_run()
+ if run_state:
+ return "Current core: Running"
+ else:
+ try:
+ pc = dbg.fnc.register_pc()
+ return f"Current core: Stopped at PC = 0x{pc:08X}"
+ except Exception:
+ return "Current core: Stopped"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting core states: {e}"
+
+
+@mcp.tool()
+async def detect_cpu() -> str:
+ """Auto-detect the connected AURIX device.
+
+ Uses SYStem.DETECT.CPU to identify the connected chip automatically.
+ Works with TC27x, TC37x, TC39x and other AURIX families.
+
+ Returns:
+ Detected CPU type and stepping
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd("SYStem.DETECT.CPU")
+ # Try to read chip stepping
+ try:
+ stepping = dbg.eval('CHIP.STEPping()')
+ return f"Auto-detected CPU: {stepping}"
+ except Exception:
+ return "CPU auto-detection executed (use get_chip_info for details)"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error detecting CPU: {e}"
diff --git a/src/debugforge/tools/notifications.py b/src/debugforge/tools/notifications.py
new file mode 100644
index 0000000..7645982
--- /dev/null
+++ b/src/debugforge/tools/notifications.py
@@ -0,0 +1,175 @@
+"""Event notification and message line tools for TRACE32.
+
+Provides tools for TRACE32 message line (MSG), event notifications, and
+communication between PRACTICE scripts and the debugger.
+
+Reference: debugger_tricore.pdf "Event Notifications", "MSG command" sections.
+"""
+
+from __future__ import annotations
+
+from debugforge.server import mcp
+from debugforge.state import state
+
+
+@mcp.tool()
+async def send_message(text: str, level: str = "info") -> str:
+ """Display a message in the TRACE32 message line (MSG).
+
+ Messages appear in the TRACE32 GUI status bar and can be used for
+ debugging status, progress updates, or user notifications.
+
+ Args:
+ text: Message text to display
+ level: Message level — "info", "warning", "error"
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ level_map = {
+ "info": "MSG",
+ "warning": "MSG !",
+ "error": "MSG !!",
+ }
+ cmd = level_map.get(level, "MSG")
+ dbg.cmd(f'{cmd} "{text}"')
+ return f"Message displayed: [{level}] {text}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error sending message: {e}"
+
+
+@mcp.tool()
+async def clear_message() -> str:
+ """Clear the TRACE32 message line.
+
+ Removes any displayed message from the status bar.
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd("MSG")
+ return "Message line cleared"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error clearing message: {e}"
+
+
+@mcp.tool()
+async def enable_event_notifications(events: list[str] | None = None) -> str:
+ """Enable event notifications for debugger state changes.
+
+ When enabled, TRACE32 can send notifications to external tools or scripts
+ when specific events occur (e.g., target halts, breakpoints hit).
+
+ Args:
+ events: List of event types to monitor:
+ "break" — target halted
+ "go" — target running
+ "reset" — system reset detected
+ "power" — power cycle detected
+ Default: ["break", "go"]
+
+ Returns:
+ Confirmation of enabled events
+ """
+ dbg = state.require_connection()
+ try:
+ evt_list = events or ["break", "go"]
+ results = []
+
+ for evt in evt_list:
+ evt_map = {
+ "break": "ENDBREAK",
+ "go": "ENDGO",
+ "reset": "ENDRESET",
+ "power": "ENDPOWER",
+ }
+ evt_code = evt_map.get(evt)
+ if evt_code:
+ dbg.cmd(f"EVENT.ON {evt_code}")
+ results.append(f" Enabled: {evt}")
+ else:
+ results.append(f" Warning: unknown event '{evt}' (skipped)")
+
+ return f"Event notifications configured:\n" + "\n".join(results)
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error enabling event notifications: {e}"
+
+
+@mcp.tool()
+async def set_event_action(event: str, command: str) -> str:
+ """Set a command to execute when a specific event occurs.
+
+ Allows automated responses to debugger events (e.g., run a script
+ when target halts, collect trace data on breakpoint hit).
+
+ Args:
+ event: Event type — "break", "go", "reset", "power"
+ command: TRACE32 command to execute (e.g., "DO my_script.cmm",
+ "Var.view myVar", "Trace.List")
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ evt_map = {
+ "break": "ENDBREAK",
+ "go": "ENDGO",
+ "reset": "ENDRESET",
+ "power": "ENDPOWER",
+ }
+ evt_code = evt_map.get(event)
+ if not evt_code:
+ return f"Invalid event '{event}'. Use: break, go, reset, power"
+
+ escaped_cmd = command.replace('"', '""')
+ dbg.cmd(f'EVENT.{evt_code} "{escaped_cmd}"')
+ return f"Event action set: {event} → {command}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting event action: {e}"
+
+
+@mcp.tool()
+async def set_debugger_mode(mode: str) -> str:
+ """Set the debugger operational mode.
+
+ Controls how TRACE32 responds to target state changes.
+
+ Args:
+ mode: Debugger mode:
+ "attach" — attach to running target without resetting
+ "standby" — monitor power cycles, auto-reconnect
+ "no_standby" — disable power cycle monitoring
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ mode_map = {
+ "attach": "SYStem.Mode.Attach",
+ "standby": "SYStem.Mode.StandBy",
+ "no_standby": "SYStem.Mode.NoStandBy",
+ }
+ cmd = mode_map.get(mode)
+ if not cmd:
+ return f"Invalid mode '{mode}'. Use: attach, standby, no_standby"
+
+ dbg.cmd(cmd)
+ return f"Debugger mode set to: {mode}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting debugger mode: {e}"
diff --git a/src/debugforge/tools/system_config.py b/src/debugforge/tools/system_config.py
new file mode 100644
index 0000000..fa84385
--- /dev/null
+++ b/src/debugforge/tools/system_config.py
@@ -0,0 +1,262 @@
+"""System configuration tools for TriCore AURIX debugging.
+
+Covers reset behavior, cache read options, peripheral suspend, watchdog
+handling, system options, and startup script execution.
+
+Reference: debugger_tricore.pdf "System Options", "Reset Behavior",
+"Accessing Cached Memory", "Suspending Peripherals" sections.
+"""
+
+from __future__ import annotations
+
+from debugforge.server import mcp
+from debugforge.state import state
+from debugforge.tools.views import _read_window
+
+
+@mcp.tool()
+async def set_reset_behavior(behavior: str) -> str:
+ """Configure reset behavior for the debugger.
+
+ Determines how TRACE32 handles soft resets, hard resets, and power cycles.
+
+ Args:
+ behavior: Reset behavior mode:
+ "restore_go" — halt briefly to restore debug resources, then continue
+ "run_restore" — restore debug resources while CPU runs (may miss breakpoints)
+ "halt" — halt CPU at reset vector after reset
+
+ Returns:
+ Confirmation of reset behavior setting
+ """
+ dbg = state.require_connection()
+ try:
+ behavior_map = {
+ "restore_go": "RestoreGo",
+ "run_restore": "RunRestore",
+ "halt": "Halt",
+ }
+ val = behavior_map.get(behavior)
+ if not val:
+ return f"Invalid behavior '{behavior}'. Use: restore_go, run_restore, halt"
+
+ dbg.cmd(f"SYStem.Option.RESetBehavior {val}")
+ return f"Reset behavior set to: {val}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting reset behavior: {e}"
+
+
+@mcp.tool()
+async def set_cache_read(enabled: bool = True) -> str:
+ """Enable or disable cache-aware memory reads.
+
+ When ON, the default data access class (D:) shows cached data from the
+ CPU's point of view instead of stale bus-level data.
+ Essential for debugging code that modifies cached memory (e.g., LMU on TC39x).
+
+ Args:
+ enabled: True to enable cache-aware reads, False to disable
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ state_str = "ON" if enabled else "OFF"
+ dbg.cmd(f"SYStem.Option.DCREAD {state_str}")
+ return f"Cache-aware reads (DCREAD): {state_str}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting DCREAD: {e}"
+
+
+@mcp.tool()
+async def set_peripheral_suspend(enabled: bool = True) -> str:
+ """Enable or disable automatic peripheral suspend when CPU halts.
+
+ When ON, peripheral modules (timers, watchdogs, communication) are
+ automatically suspended when the debugger halts the CPU. This prevents
+ watchdog timeouts and timer overflows during debugging.
+
+ Args:
+ enabled: True to enable peripheral suspend, False to disable
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ state_str = "ON" if enabled else "OFF"
+ dbg.cmd(f"SYStem.Option.PERSTOP {state_str}")
+ return f"Peripheral suspend (PERSTOP): {state_str}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting PERSTOP: {e}"
+
+
+@mcp.tool()
+async def suspend_peripheral(peripheral: str, suspend_mode: str = "hard") -> str:
+ """Configure suspend mode for a specific peripheral module.
+
+ Sets how a peripheral responds when the CPU is halted by the debugger.
+
+ Args:
+ peripheral: Peripheral register name (e.g., "GPT120_OCS", "STM0_OCS")
+ suspend_mode: "hard" — suspend immediately on CPU halt
+ "soft" — suspend after current operation completes
+ "none" — never suspend
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ mode_map = {
+ "hard": "Set hard suspend",
+ "soft": "Set soft suspend",
+ "none": "No suspend",
+ }
+ mode_val = mode_map.get(suspend_mode)
+ if not mode_val:
+ return f"Invalid suspend mode '{suspend_mode}'. Use: hard, soft, none"
+
+ dbg.cmd(f'PER.Set.ByName .{peripheral}.SUS "{mode_val}"')
+ return f"Peripheral '{peripheral}' suspend: {suspend_mode}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error configuring peripheral suspend: {e}"
+
+
+@mcp.tool()
+async def set_standby_mode(enabled: bool = True) -> str:
+ """Enable or disable standby mode for power cycle detection.
+
+ When ON, TRACE32 monitors VTREF to detect power cycles and can
+ automatically reconnect and restore debug state.
+
+ Args:
+ enabled: True to enable standby mode, False to disable
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ mode = "StandBy" if enabled else "NoStandBy"
+ dbg.cmd(f"SYStem.Mode {mode}")
+ return f"Standby mode: {mode}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting standby mode: {e}"
+
+
+@mcp.tool()
+async def get_system_options() -> str:
+ """Get the current system options and configuration.
+
+ Shows all active SYStem.Option settings including PERSTOP, DCREAD,
+ reset behavior, and other debug options.
+
+ Returns:
+ Formatted system options
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "SYStem.Option")
+ if not result.strip():
+ # Fallback: query known options individually
+ parts = ["System Options:"]
+ try:
+ dbg.cmd("SYStem.CONFIG.state")
+ config_result = _read_window(dbg, "SYStem.CONFIG.state")
+ if config_result.strip():
+ parts.append(config_result)
+ except Exception:
+ pass
+ return "\n".join(parts)
+ return f"System Options:\n{result}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting system options: {e}"
+
+
+@mcp.tool()
+async def load_symbol_file(elf_path: str = "") -> str:
+ """Load debug symbols from an ELF file.
+
+ Loads symbol and debug information for source-level debugging.
+ If no path given, uses the configured ELF path from debugforge.toml.
+
+ Args:
+ elf_path: Path to ELF file. Empty = use configured path.
+
+ Returns:
+ Confirmation of symbol loading
+ """
+ from debugforge.state import config as cfg
+
+ dbg = state.require_connection()
+ path = elf_path or cfg.elf
+ if not path:
+ return "Error: No ELF path specified and no [project] elf configured in debugforge.toml"
+
+ try:
+ dbg.cmd(f'Data.LOAD.Elf "{path}"')
+ return f"Symbols loaded from: {path}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error loading symbols from {path}: {e}"
+
+
+@mcp.tool()
+async def run_cmm_script(script_path: str, timeout: float = 30.0) -> str:
+ """Execute a TRACE32 PRACTICE (.cmm) script file.
+
+ Args:
+ script_path: Path to the .cmm script file
+ timeout: Maximum execution time in seconds (default: 30)
+
+ Returns:
+ Script execution result
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmm(f'DO "{script_path}"', timeout=timeout)
+ return f"Script executed: {script_path}"
+ except ConnectionError:
+ raise
+ except TimeoutError:
+ return f"Script timed out after {timeout}s: {script_path}"
+ except Exception as e:
+ return f"Error running script {script_path}: {e}"
+
+
+@mcp.tool()
+async def set_system_option(option: str, value: str) -> str:
+ """Set a specific SYStem.Option value.
+
+ Generic tool for any SYStem.Option.* setting not covered by dedicated tools.
+
+ Args:
+ option: Option name (e.g., "PERSTOP", "DCREAD", "RESYNC")
+ value: Value to set (e.g., "ON", "OFF", "RestoreGo")
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd(f"SYStem.Option.{option} {value}")
+ return f"SYStem.Option.{option} = {value}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting option {option}: {e}"
diff --git a/src/debugforge/tools/trace.py b/src/debugforge/tools/trace.py
new file mode 100644
index 0000000..c38901a
--- /dev/null
+++ b/src/debugforge/tools/trace.py
@@ -0,0 +1,294 @@
+"""On-chip Trace (MCDS) control tools for TRACE32 — TriCore AURIX.
+
+Implements on-chip trace recording: program trace, data trace (read/write),
+timestamped trace, and trace-trigger breakpoints. Based on MCDS (Multi-core
+Debug Support) hardware on AURIX devices.
+
+Reference: debugger_tricore.pdf "On-chip Trace" section.
+"""
+
+from __future__ import annotations
+
+from debugforge.server import mcp
+from debugforge.state import state
+from debugforge.tools.views import _read_window
+
+
+@mcp.tool()
+async def trace_start(
+ sources: list[str] | None = None,
+ timestamps: bool = True,
+) -> str:
+ """Start on-chip trace recording using MCDS.
+
+ Configures and starts the MCDS trace engine. Must be called before running
+ the target. Call trace_stop() and trace_list() after the target halts.
+
+ Args:
+ sources: List of trace sources to enable. Supported values:
+ "program" — instruction flow (PC trace)
+ "write_addr" — data write addresses
+ "write_data" — data write values
+ "read_addr" — data read addresses
+ "read_data" — data read values
+ Default: ["program"]
+ timestamps: Enable trace timestamps (default: True)
+
+ Returns:
+ Confirmation of trace configuration
+ """
+ dbg = state.require_connection()
+ try:
+ results = []
+
+ # Open MCDS state window
+ dbg.cmd("MCDS.state")
+ results.append("MCDS state window opened")
+
+ # Enable MCDS clock
+ dbg.cmd("CLOCK.ON")
+ results.append("MCDS clock enabled")
+
+ # Enable timestamps
+ if timestamps:
+ dbg.cmd("MCDS.TimeStamp.ON")
+ results.append("Timestamps enabled")
+
+ # Configure trace sources
+ src_list = sources or ["program"]
+ source_map = {
+ "program": "TriCore.Program",
+ "write_addr": "TriCore.WriteAddr",
+ "write_data": "TriCore.WriteData",
+ "read_addr": "TriCore.ReadAddr",
+ "read_data": "TriCore.ReadData",
+ }
+
+ for src in src_list:
+ mcds_src = source_map.get(src)
+ if mcds_src:
+ dbg.cmd(f"MCDS.SOURCE.Set {mcds_src} ON")
+ results.append(f"Source enabled: {mcds_src}")
+ else:
+ results.append(f"Warning: unknown source '{src}' (skipped)")
+
+ return f"Trace configured:\n" + "\n".join(f" {r}" for r in results)
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error starting trace: {e}"
+
+
+@mcp.tool()
+async def trace_stop() -> str:
+ """Stop the on-chip trace recording.
+
+ Call after the target halts (e.g., at a breakpoint or after manual halt).
+
+ Returns:
+ Confirmation that trace recording stopped
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd("Trace.Off")
+ return "Trace recording stopped"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error stopping trace: {e}"
+
+
+@mcp.tool()
+async def trace_list(max_records: int = 100) -> str:
+ """Display the recorded trace buffer contents.
+
+ Shows the trace list with instruction flow, data accesses, and timestamps
+ (if enabled). The target must be halted.
+
+ Args:
+ max_records: Maximum number of trace records to display (default: 100)
+
+ Returns:
+ Formatted trace listing
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "Trace.List", max_size=max_records * 200)
+ if not result.strip():
+ return "Trace buffer empty (no trace data recorded, or trace not started)"
+ return result
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error reading trace: {e}"
+
+
+@mcp.tool()
+async def trace_clear() -> str:
+ """Clear the trace buffer.
+
+ Resets the MCDS trace buffer for a fresh recording.
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd("Trace.Clear")
+ return "Trace buffer cleared"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error clearing trace: {e}"
+
+
+@mcp.tool()
+async def trace_set_trigger(
+ address: str,
+ trigger_type: str = "stop",
+) -> str:
+ """Set a trace trigger breakpoint.
+
+ Configures a breakpoint that controls trace recording start/stop.
+
+ Args:
+ address: Function name, symbol, or address (e.g., "sieve", "0x80001000").
+ Supports symbol.EXIT(func) syntax for function exit triggers.
+ trigger_type: "stop" — stop recording at this point (TraceTrigger)
+ "enable" — enable recording at this point (TraceEnable)
+ "disable" — disable recording at this point
+
+ Returns:
+ Confirmation of trace trigger
+ """
+ dbg = state.require_connection()
+ try:
+ type_map = {
+ "stop": "/TraceTrigger",
+ "enable": "/TraceEnable",
+ "disable": "/TraceDisable",
+ }
+ flag = type_map.get(trigger_type)
+ if not flag:
+ return f"Invalid trigger type '{trigger_type}'. Use: stop, enable, disable"
+
+ dbg.cmd(f"Break.Set {address} /Program {flag}")
+ return f"Trace trigger set: {trigger_type} @ {address}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting trace trigger: {e}"
+
+
+@mcp.tool()
+async def trace_data_write(
+ variable: str,
+ data_value: str = "",
+ data_width: str = "auto",
+) -> str:
+ """Configure data write trace on a variable.
+
+ Records every write access to the specified variable in the trace buffer.
+ Optionally filters by data value.
+
+ Args:
+ variable: Variable name or address (e.g., "flags[12]", "myVar", "0xD0000100")
+ data_value: Optional filter — only trace writes matching this value (e.g., "0x33")
+ data_width: Data width for value matching — "byte", "word", "long", "quad", "auto"
+
+ Returns:
+ Confirmation of data write trace setup
+ """
+ dbg = state.require_connection()
+ try:
+ cmd = f"Var.Break.Set {variable} /Write /Onchip /TraceEnable"
+ if data_value:
+ width_map = {"byte": "Byte", "word": "Word", "long": "Long",
+ "quad": "Quad", "auto": "auto"}
+ w = width_map.get(data_width, "auto")
+ cmd += f" /Data.{w} {data_value}"
+
+ dbg.cmd(cmd)
+
+ # Enable data trace sources
+ dbg.cmd("MCDS.SOURCE.Set TriCore.WriteAddr ON")
+ dbg.cmd("MCDS.SOURCE.Set TriCore.WriteData ON")
+
+ msg = f"Data write trace enabled on '{variable}'"
+ if data_value:
+ msg += f" (filter: value={data_value}, width={data_width})"
+ return msg
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error configuring data write trace: {e}"
+
+
+@mcp.tool()
+async def trace_data_read(
+ variable: str,
+) -> str:
+ """Configure data read trace on a variable.
+
+ Records every read access to the specified variable in the trace buffer.
+
+ Args:
+ variable: Variable name or address (e.g., "myVar", "0xD0000100")
+
+ Returns:
+ Confirmation of data read trace setup
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd(f"Var.Break.Set {variable} /Read /Onchip /TraceEnable")
+ dbg.cmd("MCDS.SOURCE.Set TriCore.ReadAddr ON")
+ dbg.cmd("MCDS.SOURCE.Set TriCore.ReadData ON")
+ return f"Data read trace enabled on '{variable}'"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error configuring data read trace: {e}"
+
+
+@mcp.tool()
+async def trace_profile_chart() -> str:
+ """Display a profile chart from recorded trace data.
+
+ Shows instruction execution statistics per function based on trace data.
+ Requires trace data to have been recorded first.
+
+ Returns:
+ Formatted profile chart
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "Trace.PROfileChart")
+ if not result.strip():
+ return "No profile data available (record trace data first)"
+ return result
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting trace profile: {e}"
+
+
+@mcp.tool()
+async def trace_state() -> str:
+ """Get current MCDS trace state and configuration.
+
+ Shows the MCDS state window with current trace source settings,
+ buffer status, and configuration.
+
+ Returns:
+ MCDS state information
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "MCDS.state")
+ if not result.strip():
+ return "MCDS state not available (on-chip trace may not be supported)"
+ return result
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting MCDS state: {e}"
diff --git a/src/debugforge/tools/watchpin.py b/src/debugforge/tools/watchpin.py
new file mode 100644
index 0000000..4d5c2f9
--- /dev/null
+++ b/src/debugforge/tools/watchpin.py
@@ -0,0 +1,181 @@
+"""WatchPin hardware trigger output tools for TriCore AURIX.
+
+Provides control over hardware WatchPin outputs that can generate logic analyzer
+triggers or oscilloscope signals when the target halts or hits specific conditions.
+
+Reference: debugger_tricore.pdf "CPU specific WatchPin Commands" section.
+"""
+
+from __future__ import annotations
+
+from debugforge.server import mcp
+from debugforge.state import state
+from debugforge.tools.views import _read_window
+
+
+@mcp.tool()
+async def watchpin_enable() -> str:
+ """Enable WatchPin output hardware.
+
+ Activates the WatchPin output pins on the debugger. These pins generate
+ logic signals that can be connected to external test equipment.
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd("WatchPin.Enable")
+ return "WatchPin outputs enabled"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error enabling WatchPin: {e}"
+
+
+@mcp.tool()
+async def watchpin_disable() -> str:
+ """Disable WatchPin output hardware.
+
+ Deactivates all WatchPin outputs.
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd("WatchPin.Disable")
+ return "WatchPin outputs disabled"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error disabling WatchPin: {e}"
+
+
+@mcp.tool()
+async def watchpin_configure(
+ pin: int,
+ trigger: str = "break",
+ polarity: str = "high",
+) -> str:
+ """Configure a WatchPin to trigger on specific events.
+
+ Args:
+ pin: WatchPin number (1, 2, 3, or 4 depending on hardware)
+ trigger: Trigger condition:
+ "break" — pulse when target halts
+ "go" — pulse when target runs
+ "trace_start" — pulse when trace recording starts
+ "trace_stop" — pulse when trace recording stops
+ polarity: Output polarity — "high" (active high) or "low" (active low)
+
+ Returns:
+ Confirmation of WatchPin configuration
+ """
+ dbg = state.require_connection()
+ try:
+ trigger_map = {
+ "break": "Break",
+ "go": "Go",
+ "trace_start": "TraceStart",
+ "trace_stop": "TraceStop",
+ }
+ pol_map = {
+ "high": "High",
+ "low": "Low",
+ }
+
+ trig_val = trigger_map.get(trigger)
+ pol_val = pol_map.get(polarity)
+
+ if not trig_val:
+ return f"Invalid trigger '{trigger}'. Use: break, go, trace_start, trace_stop"
+ if not pol_val:
+ return f"Invalid polarity '{polarity}'. Use: high, low"
+
+ dbg.cmd(f"WatchPin.Set {pin}. {trig_val} {pol_val}")
+ return f"WatchPin {pin} configured: trigger={trigger}, polarity={polarity}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error configuring WatchPin {pin}: {e}"
+
+
+@mcp.tool()
+async def watchpin_set_action_breakpoint(
+ address: str,
+ pin: int,
+ pulse_width_us: int = 100,
+) -> str:
+ """Set an action breakpoint that pulses a WatchPin when hit.
+
+ Useful for correlating software events with external hardware (e.g., oscilloscope).
+ The target briefly stops, the WatchPin pulses, then execution resumes.
+
+ Args:
+ address: Address or symbol name
+ pin: WatchPin number to pulse
+ pulse_width_us: Pulse width in microseconds (default: 100)
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ # Set WatchPin output via PRACTICE command
+ wp_cmd = f"WatchPin.Set {pin}. High"
+ dbg.cmd(f'Break.Set {address} /Program /CMD "{wp_cmd}" /RESUME')
+ return f"Action breakpoint at {address} — pulses WatchPin {pin} ({pulse_width_us}us)"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error setting WatchPin action breakpoint: {e}"
+
+
+@mcp.tool()
+async def watchpin_state() -> str:
+ """Get current WatchPin configuration and state.
+
+ Shows which WatchPins are enabled, their trigger conditions, and polarity.
+
+ Returns:
+ WatchPin state information
+ """
+ dbg = state.require_connection()
+ try:
+ result = _read_window(dbg, "WatchPin.state")
+ if not result.strip():
+ return "WatchPin state not available (hardware may not support WatchPin)"
+ return f"WatchPin State:\n{result}"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error getting WatchPin state: {e}"
+
+
+@mcp.tool()
+async def watchpin_manual_pulse(pin: int, duration_ms: int = 10) -> str:
+ """Manually pulse a WatchPin for testing.
+
+ Useful for verifying WatchPin hardware connections before setting up
+ automatic triggers.
+
+ Args:
+ pin: WatchPin number
+ duration_ms: Pulse duration in milliseconds (default: 10)
+
+ Returns:
+ Confirmation
+ """
+ dbg = state.require_connection()
+ try:
+ dbg.cmd(f"WatchPin.Set {pin}. High")
+ # Wait for duration
+ import asyncio
+ await asyncio.sleep(duration_ms / 1000.0)
+ dbg.cmd(f"WatchPin.Set {pin}. Low")
+ return f"WatchPin {pin} pulsed for {duration_ms}ms"
+ except ConnectionError:
+ raise
+ except Exception as e:
+ return f"Error pulsing WatchPin {pin}: {e}"
diff --git a/t32_debugforge.t32 b/t32_debugforge.t32
new file mode 100644
index 0000000..9866dd0
--- /dev/null
+++ b/t32_debugforge.t32
@@ -0,0 +1,11 @@
+; DebugForge TRACE32 Configuration
+; Enables Remote Control Layer (RCL) for Python automation
+
+OS=
+SYS=/opt/t32_202509
+TMP=/tmp/t32_debugforge
+
+; Remote Control via NETTCP (required by lauterbach-trace32-rcl Python package)
+RCL=NETTCP
+PORT=20000
+PACKLEN=4096