aiDgePLC iec61131 虚拟机 完整使用文档

aiDgePLC 完整使用文档

aiDgePLC --- 基于 STVM 的 IEC 61131-3 ST 调试与运行环境

提供交互式 CLI 调试器、WebSocket 调试服务器,支持源码行号断点、观察点(Watchpoint)、

单步执行、变量查看、调用栈追踪,基于策略模式实现可扩展的命令分发。


对应虚拟机开源地址:https://gitee.com/galaxy_0/stvm


目录

  1. 快速开始
  2. 构建指南
  3. [CLI 调试器详解](#CLI 调试器详解)
  4. [WebSocket 调试服务器](#WebSocket 调试服务器)
  5. 策略模式命令分发架构
  6. [在项目中集成 Debugger](#在项目中集成 Debugger)
  7. FAQ

1. 快速开始

1.1 环境要求

  • OS:Windows 10/11(开发)+ WSL2 Ubuntu(构建)
  • 编译器:GCC 9+ 或 Clang 10+(支持 C++17)
  • CMake:≥ 3.15
  • 交叉编译(可选):Linaro aarch64-none-linux-gnu-gcc 9.x

1.2 一键构建

bash 复制代码
# 在 WSL 中执行
cd /mnt/e/work/dev/aiDgePLC
bash build.sh

构建产物:

文件 说明
build/aidgeplc-debug CLI 交互式调试器
build/aidgeplc-debug-server WebSocket 调试服务器
build/tests/test_debugger 单元测试

1.3 三十秒体验

bash 复制代码
# 编写一个 IEC ST 程序
cat > /tmp/hello.iec << 'EOF'
PROGRAM main
  VAR
    counter : INT := 0;
    sum     : INT := 0;
  END_VAR
  FOR counter := 1 TO 10 DO
    sum := sum + counter;
  END_FOR;
END_PROGRAM
EOF

# 启动调试器
./build/aidgeplc-debug /tmp/hello.iec

# 在调试器中:
#   break 6:L        ← 在第6行设置断点
#   run              ← 运行到断点
#   print sum        ← 查看变量
#   watch sum        ← 添加观察点
#   run              ← 继续,观察点触发时暂停
#   info watchpoints ← 查看观察点
#   quit

2. 构建指南

2.1 构建脚本

bash 复制代码
# 本地构建(Release)
./build.sh

# 指定构建类型
./build.sh Debug
./build.sh RelWithDebInfo

# 交叉编译(aarch64)
./build.sh Release aarch64

2.2 CMake 选项

选项 默认值 说明
AIDEGEPLC_BUILD_TOOLS ON 构建 CLI 工具 aidgeplc-debug
AIDEGEPLC_BUILD_TESTS ON 构建单元测试(交叉编译自动关闭)
AIDEGEPLC_BUILD_DEBUG_SERVER ON 构建 WebSocket 调试服务器
AIDEGEPLC_USE_LIBCO OFF 透传给 stvm,启用 libco 协程
AIDEGEPLC_USE_CPP_MISC_FETCH ON 从 Gitee 自动获取 cpp-misc
CPP_MISC_ROOT 离线环境:指向本地 cpp-misc 根目录
AIDEGEPLC_USE_LIBHV_FETCH ON 从 Gitee 自动获取 libhv
LIBHV_ROOT 离线环境:指向本地 libhv 根目录
CMAKE_TOOLCHAIN_FILE 指定交叉编译 toolchain

2.3 手动 CMake 构建

bash 复制代码
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel $(nproc)

2.4 外部依赖

依赖 来源 集成方式
stvm gitee.com/galaxy_0/stvm Git 子模块(兄弟目录 ./stvm
cpp-misc gitee.com/galaxy_0/cpp-misc FetchContent 或 CPP_MISC_ROOT
libhv gitee.com/galaxy_0/libhv FetchContent 或 LIBHV_ROOT

3. CLI 调试器详解

3.1 启动方式

bash 复制代码
# 启动时加载源文件
aidgeplc-debug <source.iec|source.st>

# 空壳启动,后续用 load 命令加载
aidgeplc-debug

3.2 命令一览

程序加载
命令 说明
load <file> 从文件加载 IEC ST 源码,自动编译到内存
compile "PROGRAM main ... END_PROGRAM" 内联编译 ST 代码
reset 重置执行状态(保留程序和断点)
执行控制
命令 说明
run [N] / continue [N] 运行直到断点/观察点/结束,可选最大指令数 N
step / s 单步进入(step into)
next / n 单步步过(step over)
finish 步出当前 POU(step out)
断点(按 IP 地址 / 按源码行号)
命令 说明
break <ip> 在字节码 IP 地址设断点
break <line>:L 按源码行号设断点(自动映射到字节码地址)
clear <ip> 清除 IP 断点
clear <line>:L 清除行号断点
enable <ip> 启用断点
disable <ip> 禁用断点
info breakpoints 列出所有断点

行号断点原理 :stvm 在生成字节码时同步记录每条指令对应的源码行号(line_table)。

break <line>:L 通过 line_table 将行号映射到一个或多个字节码地址,在这些地址上设置断点。

观察点(Watchpoint)
命令 说明
watch <varname> 添加观察点(变量必须已声明)
unwatch <varname> 移除观察点
info watchpoints 列出所有观察点

观察点原理 :每条指令执行后,调试器检查所有启用的观察点变量的当前值与上次记录值。

若值发生变化,自动暂停执行并记录触发变量名。适用于监控状态机切换、计数器变化等场景。

数据查看
命令 说明
vars / info variables 打印所有全局变量
print <name> / p <name> 打印单个变量值
set <name>=<value> 修改变量值
registers / info registers 打印 IP / SP / BP / Clock
stack 打印操作数栈
调用栈 / 反汇编
命令 说明
bt / backtrace 打印调用栈
list / disasm 反汇编当前 IP 上下文
disasm <start> <end> 反汇编指定范围
state 打印调试器状态
其他
命令 说明
help 显示帮助
quit / exit 退出

3.3 命令缩写

所有命令支持前缀匹配。例如:

  • bbreak
  • rrun
  • pprint
  • i binfo breakpoints
  • i winfo watchpoints

当多个命令匹配同一前缀时,调试器会提示歧义。

3.4 调试会话示例

复制代码
$ aidgeplc-debug /tmp/counter.iec
aiDgePLC 交互式调试器 [策略模式 cpp-misc]
已加载: /tmp/counter.iec
入口 IP = 0, 代码大小 = 120 字节, 符号 2 个

[aidgeplc-dbg | LOADED | ip=0] break 6:L
断点已设置 @ 行 6 (映射到 2 个 IP: 48 54)

[aidgeplc-dbg | LOADED | ip=0] run
执行结果: PAUSED (断点/限制);  IP=48

[aidgeplc-dbg | PAUSED | ip=48] print counter
counter = 1

[aidgeplc-dbg | PAUSED | ip=48] watch sum
观察点已添加: sum

[aidgeplc-dbg | PAUSED | ip=48] run
执行结果: PAUSED (断点/限制);  IP=48

[aidgeplc-dbg | PAUSED | ip=48] print sum
sum = 1

[aidgeplc-dbg | PAUSED | ip=48] info watchpoints
观察点列表 (1 个):
  sum              enabled=yes   hits=1  last=1

[aidgeplc-dbg | PAUSED | ip=48] bt
调用栈 (深度 1):
  #0  ip=48  bp=0  pou=0

[aidgeplc-dbg | PAUSED | ip=48] quit
再见。

4. WebSocket 调试服务器

4.1 概述

aidgeplc-debug-server 是基于 libhv WebSocket 模块的调试协议服务器,兼容 OpenPLC Runtime v4 调试协议,并扩展了完整的调试器控制功能码。

架构

复制代码
┌──────────────┐     WebSocket      ┌──────────────────┐
│  调试客户端   │ ←─────────────────→ │  DebugServer     │
│ (浏览器/脚本) │   ws://host:port    │                  │
└──────────────┘                     │  ┌────────────┐  │
                                     │  │ strategy   │  │ ← 按功能码分发
                                     │  │ 分发器     │  │
                                     │  └─────┬──────┘  │
                                     │        │         │
                                     │  ┌─────▼──────┐  │
                                     │  │  Debugger   │  │ ← stvm VM/Compiler
                                     │  └────────────┘  │
                                     └──────────────────┘

4.2 启动

bash 复制代码
# 默认监听 0.0.0.0:8443
./build/aidgeplc-debug-server

# 指定端口和地址
./build/aidgeplc-debug-server --port 9000 --host 127.0.0.1

# 启动时自动加载源文件
./build/aidgeplc-debug-server --load /tmp/hello.iec

WebSocket 端点ws://<host>:<port>/api/debug

4.3 协议格式

传输层:WebSocket 文本帧

报文格式:空格分隔的十六进制字符串

复制代码
请求示例:  "44 00 03 00 00 00 01 00 02"
           ^^                     ^^^^^^
           功能码                   数据

响应前缀

  • 0x7E~)= 成功
  • 0x7F = 错误(aiDgePLC 扩展),后跟 [err_len:2][err_msg]

4.4 功能码详解

核心功能码(兼容 OpenPLC v4)
0x41 DEBUG_INFO --- 获取程序信息
复制代码
请求:  41
响应:  7E [entry_ip:4][code_size:4][symbol_count:4]
       ([name_len:2][name][offset:4][type:1][array_size:4]) × symbol_count
0x42 DEBUG_SET --- 设置变量跟踪标志
复制代码
请求:  42 [name_len:2][name][flags:1]
响应:  7E

flags 的 bit 0 设置时,将该变量映射为观察点(Watchpoint)。

0x43 DEBUG_GET --- 获取单个变量值
复制代码
请求:  43 [name_len:2][name]
响应:  7E [value bytes][type_code:1]

变量值编码

type_code 类型 值字节
0x01 BOOL 1 字节
0x02 INT 8 字节(int64 小端)
0x03 REAL 8 字节(double 小端)
0x04 TIME 8 字节(int64 微秒,小端)
0x05 STR 2 字节长度 + 内容
0x00 UNKNOWN 无值字节
0x44 DEBUG_GET_LIST --- 批量获取变量值
复制代码
请求:  44 [count:2]([name_len:2][name])...
响应:  7E [count:2]([value bytes][type_code:1])...
0x45 DEBUG_GET_MD5 --- 获取程序 MD5
复制代码
请求:  45 [padding...]
响应:  7E [32字节十六进制MD5字符串]\0
aiDgePLC 扩展功能码
0x46 DEBUG_LOAD --- 载入 IEC 源文本
复制代码
请求:  46 [src_len:4][src ASCII]
响应:  7E                              ← 成功
       7F [err_len:2][err_msg]         ← 编译失败

示例 :载入 PROGRAM main VAR x:INT:=0; END_VAR x:=42; END_PROGRAM

复制代码
46 00 00 00 2F 50 52 4F 47 52 41 4D 20 6D 61 69 6E ...
   └──src_len=47──┘ └── "PROGRAM main..." ASCII ──────┘
0x47 DEBUG_RUN --- 运行
复制代码
请求:  47 [max_ins:8]     (max_ins=0 表示无限制)
响应:  7E [state:1][ip:4]

状态码

状态
0 NOT_LOADED
1 LOADED
2 RUNNING
3 PAUSED
4 FINISHED
5 ERROR
0x48 DEBUG_STEP --- 单步执行
复制代码
请求:  48 [mode:1]        (0=into, 1=over, 2=out)
响应:  7E [state:1][ip:4]
0x49 DEBUG_BRK_SET --- 设置断点
复制代码
请求:  49 [type:1][value:4]
       type=0: value = IP 地址
       type=1: value = 源码行号
响应:  7E [count:4]       (映射到的断点数量)
0x4A DEBUG_BRK_CLR --- 清除断点
复制代码
请求:  4A [type:1][value:4]
响应:  7E [count:4]       (清除的断点数量)
0x4B DEBUG_BRK_LIST --- 列出断点
复制代码
请求:  4B
响应:  7E [count:4]([ip:4][enabled:1][hits:8])...
0x4C DEBUG_STATE --- 查询状态
复制代码
请求:  4C
响应:  7E [state:1][ip:4][sp:4][bp:4]
0x4D DEBUG_RESET --- 重置执行
复制代码
请求:  4D
响应:  7E
0x4E DEBUG_CALLSTACK --- 查询调用栈
复制代码
请求:  4E
响应:  7E [depth:4]([ip:4][bp:4][pou:4])...
0x4F DEBUG_DISASM --- 反汇编
复制代码
请求:  4F [start:4][end:4]
响应:  7E [hex ASCII...]    (反汇编文本的十六进制编码)

4.5 协议交互示例

以下用 Python 演示完整的调试会话:

python 复制代码
import asyncio
import websockets

async def debug_session():
    uri = "ws://127.0.0.1:8443/api/debug"
    async with websockets.connect(uri) as ws:
        # 1. 载入程序
        src = b"PROGRAM main VAR x:INT:=0; END_VAR x:=1; x:=2; x:=3; END_PROGRAM"
        req = bytes([0x46]) + len(src).to_bytes(4, 'little') + src
        await ws.send(' '.join(f'{b:02X}' for b in req))
        resp = await ws.recv()
        print(f"LOAD: {resp}")  # 7E

        # 2. 在第4行设断点 (x:=2)
        req = bytes([0x49, 0x01]) + (4).to_bytes(4, 'little')
        await ws.send(' '.join(f'{b:02X}' for b in req))
        resp = await ws.recv()
        print(f"BRK_SET: {resp}")  # 7E 02 00 00 00 (2个断点)

        # 3. 运行
        req = bytes([0x47]) + (0).to_bytes(8, 'little')
        await ws.send(' '.join(f'{b:02X}' for b in req))
        resp = await ws.recv()
        print(f"RUN: {resp}")  # 7E 03 xx xx xx xx (PAUSED)

        # 4. 获取变量 x
        name = b"x"
        req = bytes([0x43]) + len(name).to_bytes(2, 'little') + name
        await ws.send(' '.join(f'{b:02X}' for b in req))
        resp = await ws.recv()
        print(f"GET x: {resp}")  # 7E 02 00 00 00 00 00 00 00 02

asyncio.run(debug_session())

4.6 并发安全

单个 Debugger 实例被所有 WebSocket 连接共享。命令通过 std::mutex 串行化执行,

避免并发破坏 VM 执行状态。多客户端连接时,命令按到达顺序排队处理。


5. 策略模式命令分发架构

5.1 设计动机

aiDgePLC 的 CLI 调试器和 WebSocket 调试服务器都需要命令分发功能:

  • CLI:解析用户输入的命令字符串,调用对应处理函数
  • 服务器:解析协议功能码,调用对应处理器

传统做法是用 if-else / switch-case 链,但存在以下问题:

  • 命令数量增长后代码臃肿,难以维护
  • 新增命令需要修改分发函数,违反开闭原则
  • 无法在运行时动态注册/注销命令

aiDgePLC 采用 cpp-misc

wheels::dm::strategy 模板类实现策略模式命令分发,解决上述问题。

5.2 strategy 模板类

cpp 复制代码
// 头文件: cpp-misc/include/designM/strategy.hpp
namespace wheels::dm {

template <typename Key, typename Signature>
class strategy;

// 特化:Key=std::string, Signature=void(const Context&)
template <>
class strategy<std::string, void(const Context&)> {
public:
    // 注册策略
    void add(const std::string& key, std::function<void(const Context&)> fn);

    // 精确匹配执行
    bool run(const std::string& key, const Context& ctx);

    // 获取所有已注册的 key
    std::vector<std::string> keys() const;
};

} // namespace wheels::dm

5.3 CLI 命令分发实现

5.3.1 定义命令上下文
cpp 复制代码
struct CliContext {
    Debugger&                dbg;       // 调试器引用
    bool&                    running;   // 控制主循环退出
    const std::string&       line;      // 原始输入行
    const std::vector<std::string>& args; // 切分后的参数
};
5.3.2 命令处理函数

每个命令对应一个处理函数,签名为 void(const CliContext&)

cpp 复制代码
static void cmdRun_impl(const CliContext& c) {
    uint64_t max_ins = 0;
    if (c.args.size() > 1) max_ins = std::stoull(c.args[1]);
    auto state = c.dbg.run(max_ins);
    std::printf("执行结果: %s", stateName(state));
    // ...
}

static void cmdBreak_impl(const CliContext& c) {
    if (c.args.size() < 2) { std::printf("用法: break <ip|line:L>\n"); return; }
    bool is_line; uint32_t val;
    if (!parseBreakArg(c.args[1], is_line, val)) { std::printf("无效参数\n"); return; }
    if (is_line) {
        uint32_t n = c.dbg.setBreakpointByLine(val);
        std::printf("断点已设置 @ 行 %u (映射到 %u 个 IP)\n", val, n);
    } else {
        c.dbg.setBreakpoint(val);
        std::printf("断点已设置 @ IP %u\n", val);
    }
}
5.3.3 策略表注册
cpp 复制代码
class CommandDispatcher {
    using StrategyT = wheels::dm::strategy<std::string, void(const CliContext&)>;
    StrategyT strategy_;

public:
    CommandDispatcher() {
        registerCmd("help",     cmdHelp_impl);
        registerCmd("quit",     cmdQuit_impl);
        registerCmd("load",     cmdLoad_impl);
        registerCmd("run",      cmdRun_impl);
        registerCmd("break",    cmdBreak_impl);
        registerCmd("watch",    cmdWatch_impl);
        registerCmd("print",    cmdPrint_impl);
        registerCmd("bt",       cmdBacktrace_impl);
        // ... 共 30+ 条命令
    }

    void registerCmd(const std::string& name, CmdFn fn) {
        strategy_.add(name, fn);
    }
};
5.3.4 主循环分发
cpp 复制代码
void CommandDispatcher::dispatch(const std::string& input, CliContext& ctx) {
    auto args = splitArgs(input);
    if (args.empty()) return;

    std::string cmd = args[0];

    // 1. 精确匹配
    if (strategy_.run(cmd, ctx)) return;

    // 2. 前缀匹配(支持缩写)
    std::vector<std::string> matches;
    for (const auto& key : strategy_.keys()) {
        if (cmdMatch(cmd, key)) matches.push_back(key);
    }

    if (matches.size() == 1) {
        strategy_.run(matches[0], ctx);  // 唯一匹配,执行
    } else if (matches.size() > 1) {
        std::printf("歧义命令 '%s',匹配: %s\n",
                    cmd.c_str(), join(matches).c_str());
    } else {
        std::printf("未知命令: %s (输入 help 查看命令列表)\n", cmd.c_str());
    }
}

5.4 服务器命令分发实现

调试服务器使用相同策略,但以功能码为 key:

cpp 复制代码
void DebugServer::registerHandlers() {
    // key = 功能码的 2 位十六进制字符串
    strategy_.add("41", [this](const Bytes& req){ return handleInfo(req); });
    strategy_.add("42", [this](const Bytes& req){ return handleSet(req); });
    strategy_.add("43", [this](const Bytes& req){ return handleGet(req); });
    strategy_.add("44", [this](const Bytes& req){ return handleGetList(req); });
    strategy_.add("45", [this](const Bytes& req){ return handleGetMd5(req); });
    strategy_.add("46", [this](const Bytes& req){ return handleLoad(req); });
    strategy_.add("47", [this](const Bytes& req){ return handleRun(req); });
    strategy_.add("48", [this](const Bytes& req){ return handleStep(req); });
    strategy_.add("49", [this](const Bytes& req){ return handleBrkSet(req); });
    strategy_.add("4A", [this](const Bytes& req){ return handleBrkClr(req); });
    strategy_.add("4B", [this](const Bytes& req){ return handleBrkList(req); });
    strategy_.add("4C", [this](const Bytes& req){ return handleState(req); });
    strategy_.add("4D", [this](const Bytes& req){ return handleReset(req); });
    strategy_.add("4E", [this](const Bytes& req){ return handleCallstack(req); });
    strategy_.add("4F", [this](const Bytes& req){ return handleDisasm(req); });
}

std::vector<uint8_t> DebugServer::handleCommand(const std::vector<uint8_t>& req) {
    if (req.empty()) return makeErr("empty request");

    // 功能码 -> 2 位十六进制 key
    char hex[3];
    std::snprintf(hex, sizeof(hex), "%02X", req[0]);

    std::lock_guard<std::mutex> lk(mtx_);
    std::vector<uint8_t> resp;
    if (!strategy_.run(hex, req, resp)) {
        return makeErr("unknown function code: " + std::string(hex));
    }
    return resp;
}

5.5 策略模式的优势

特性 if-else 链 strategy 模式
新增命令 修改分发函数 registerCmd() 一行注册
命令列表 手动维护 strategy_.keys() 自动获取
运行时注册 不支持 支持(动态增删)
代码组织 集中臃肿 分散在各处理函数
可测试性 需模拟整个分发器 各策略函数独立测试
前缀匹配 手动实现 遍历 keys() 即可

5.6 Fallback 机制

当 cpp-misc 不可用时(离线环境未设置 CPP_MISC_ROOT),系统自动降级为

std::map<std::string, std::function> 实现,接口完全一致:

cpp 复制代码
#ifdef AIDEGEPLC_USE_STRATEGY
    wheels::dm::strategy<std::string, void(const CliContext&)> strategy_;
#else
    std::map<std::string, CmdFn> strategy_;
#endif

通过 AIDEGEPLC_USE_STRATEGY / AIDEGEPLC_USE_SRV_STRATEGY 宏切换,

确保在无 cpp-misc 环境下仍可编译运行。


6. 在项目中集成 Debugger

6.1 CMake 集成

cmake 复制代码
# 引入 aiDgePLC 库
find_package(aiDgePLC REQUIRED)
target_link_libraries(my_app PRIVATE aidgeplc)

6.2 C++ API 使用

cpp 复制代码
#include "aidgeplc/debugger.hpp"
#include <iostream>

int main() {
    aidgeplc::Debugger dbg;
    std::string err;

    // 1. 载入 IEC ST 源码(自动编译到内存)
    if (!dbg.loadIecSource(R"IEC(
        PROGRAM main
          VAR counter : INT := 0; END_VAR
          FOR counter := 1 TO 100 DO
            counter := counter;  // 占位
          END_FOR;
        END_PROGRAM
    )IEC", err)) {
        std::cerr << "编译失败: " << err << std::endl;
        return 1;
    }

    // 2. 按源码行号设置断点
    dbg.setBreakpointByLine(5);  // 第 5 行

    // 3. 添加观察点
    dbg.addWatchpoint("counter");

    // 4. 运行到断点
    auto state = dbg.run();
    if (state == aidgeplc::DebugState::PAUSED) {
        // 5. 读取变量
        stvm::StackValue v;
        dbg.readVariable("counter", v);
        std::cout << "counter = " << v.toInt() << std::endl;

        // 6. 修改变量
        dbg.writeVariable("counter", stvm::StackValue::fromInt(50));

        // 7. 单步
        dbg.step(aidgeplc::StepMode::INTO);

        // 8. 查看调用栈
        auto frames = dbg.getCallStack();
        for (auto& f : frames) {
            std::cout << "  #" << f.frame_index
                      << " ip=" << f.return_ip
                      << " pou=" << f.pou_index << std::endl;
        }

        // 9. 继续运行
        dbg.run();
    }

    return 0;
}

6.3 DebugServer API 使用

cpp 复制代码
#include "aidgeplc/debug_server.hpp"

int main() {
    aidgeplc::DebugServer server;
    server.setPort(8443);
    server.setHost("0.0.0.0");
    server.setSourceFile("/opt/plc/program.iec");  // 可选:启动时预加载
    return server.run();  // 阻塞直到 stop()
}

6.4 核心 API 速查

Debugger
方法 说明
loadIecSource(src, err) 载入 ST 源码,自动编译
loadSourceFile(path, err) 从文件载入
run(max_ins) 运行(0=无限制)
step(mode) 单步(INTO/OVER/OUT)
reset() 重置执行状态
setBreakpoint(ip) 按 IP 设断点
setBreakpointByLine(line) 按行号设断点,返回映射数量
clearBreakpoint(ip) 清除断点
addWatchpoint(name) 添加观察点
removeWatchpoint(name) 移除观察点
readVariable(name, v) 读取变量值
writeVariable(name, v) 写入变量值
dumpVariables() 打印所有变量
getCallStack() 获取调用栈
dumpRegisters() 打印寄存器
dumpStack() 打印操作数栈
disassembleCurrent() 反汇编当前上下文
state() 获取调试器状态
currentIP() 获取当前 IP
DebugServer
方法 说明
setPort(port) 设置监听端口
setHost(host) 设置监听地址
setSourceFile(path) 设置预加载源文件
run() 启动服务器(阻塞)
stop() 停止服务器
handleCommand(req) 处理单条协议命令(供测试)

7. FAQ

Q: 如何在离线环境构建?

设置 CPP_MISC_ROOTLIBHV_ROOT 指向本地副本:

bash 复制代码
cmake .. \
  -DCPP_MISC_ROOT=/path/to/cpp-misc \
  -DLIBHV_ROOT=/path/to/libhv

Q: 交叉编译时调试服务器不可用?

libhv 的 add_subdirectory 在交叉编译时可能因依赖问题失败。

调试服务器会自动跳过构建,CLI 调试器不受影响。

Q: 行号断点映射到 0 个 IP?

该行可能没有生成任何字节码(如注释行、空行、声明行)。请尝试在赋值语句

或控制流语句所在行设置断点。

Q: 观察点没有触发?

观察点在每条指令执行后 检查。如果变量在断点暂停期间被修改(通过 set 命令),

需要在下次 run 时才会检测到变化。refreshWatchpoints() 可手动重置基线值。

Q: WebSocket 消息格式为什么用空格分隔的十六进制?

兼容 OpenPLC Runtime v4 协议。原始协议使用文本帧传输十六进制字符串,

便于调试和日志记录。hexToBytes() / bytesToHex() 工具函数处理转换。

Q: 如何扩展新的协议功能码?

  1. debug_server.hppproto 命名空间添加功能码常量
  2. DebugServer 类添加 handleXxx 处理方法
  3. registerHandlers() 中注册:strategy_.add("5x", [this](const Bytes& r){ return handleXxx(r); })

无需修改 handleCommand() 分发逻辑------策略模式自动路由。

相关推荐
小小龙学IT1 小时前
Boost.Beast 深度实战:基于 Asio 的开源 C++ HTTP/WebSocket 协议库
c++·websocket·http
不可求~2 小时前
C++ std::string_view 不是字符串:从悬空引用到安全用法
java·开发语言·c++
宇擎智脑科技2 小时前
DeepSeek Harness 架构解析:MCP 和 Skill 如何被统一为 Cordis 插件
架构·deepseek·harness·dsh
小小龙学IT3 小时前
C++ 正则表达式完全指南:从 std::regex 实战到 RE2 引擎原理(NFA/DFA/回溯陷阱)
c++·正则表达式
这个DBA有点耶4 小时前
分布式数据库到底该不该上?从判断标准到架构选型的实战思考
数据库·架构·dba
楚识科技4 小时前
破除通用瓶颈——企业级OCR定制化开发的架构思维与实战范式
架构·ocr
刘立军5 小时前
RESTful 与契约优先:规范接口定义,统一接口设计范式
后端·架构·ai编程
heimeiyingwang5 小时前
【架构实战】分布式事务:从CAP定理到Seata实战,一文讲透跨服务数据一致性
分布式·架构
独孤九剑打醒他6 小时前
RGB‑TOT 全架构系统仿真:红外波长簇光通信完整验证
架构