嵌入式Linux Lua使用ZeroBrane远程调试

嵌入式Linux Lua使用ZeroBrane远程调试

概述

本文记录了在嵌入式 Linux 平台上移植 ZeroBrane 远程调试功能的过程。通过集成 MobDebug 和 LuaSocket 库,实现了对 Lua 脚本的远程调试支持,包括断点调试、单步执行、变量监视和调用堆栈等功能。

背景

目标平台是一个基于 Linux 的仪器控制系统,使用嵌入式 Lua 作为脚本引擎。硬件访问通过 UIO (Userspace I/O) 和 mmap() 实现寄存器访问。为了方便开发和调试,需要支持 ZeroBrane Studio 远程调试功能。

技术架构

ZeroBrane 调试原理

ZeroBrane 使用 MobDebug 协议进行远程调试:

复制代码
┌─────────────────┐         TCP/IP          ┌─────────────────┐
│  ZeroBrane      │◄──────────────────────►│  fan-instruments│
│  Studio         │      port: 8172         │  (Lua 脚本)     │
│  (调试器)       │                         │  (被调试端)     │
└─────────────────┘                         └─────────────────┘

协议流程:

  1. 被调试端连接到 ZeroBrane 服务器
  2. 服务器发送命令 (RUN, STEP, SETB 等)
  3. 被调试端执行并返回状态 (202 Paused 等)

依赖库

版本 说明
Lua 5.4 已有
LuaSocket 3.1.0 新增,提供 TCP 通信
MobDebug 0.805 新增,调试协议实现

移植步骤

1. 添加 LuaSocket 库

从官方仓库获取源码:

bash 复制代码
git clone --depth 1 https://github.com/lunarmodules/luasocket.git

目录结构:

复制代码
lib/luasocket/
├── *.c, *.h          # C 源码
├── lua/              # Lua 封装层
│   ├── socket.lua
│   ├── mime.lua
│   └── ltn12.lua
└── luasocket.cmake   # 构建脚本

构建脚本 lib/luasocket/luasocket.cmake

cmake 复制代码
add_library(luasocket STATIC
    lib/luasocket/auxiliar.c
    lib/luasocket/buffer.c
    lib/luasocket/compat.c
    lib/luasocket/except.c
    lib/luasocket/inet.c
    lib/luasocket/io.c
    lib/luasocket/luasocket.c
    lib/luasocket/mime.c
    lib/luasocket/options.c
    lib/luasocket/select.c
    lib/luasocket/tcp.c
    lib/luasocket/timeout.c
    lib/luasocket/udp.c
)

if(UNIX AND NOT APPLE)
    target_sources(luasocket PRIVATE lib/luasocket/usocket.c)
elseif(WIN32)
    target_sources(luasocket PRIVATE lib/luasocket/wsocket.c)
endif()

2. 添加 MobDebug 库

下载 MobDebug:

bash 复制代码
curl -o lib/mobdebug/mobdebug.lua \
    https://raw.githubusercontent.com/pkulchenko/MobDebug/master/src/mobdebug.lua

由于嵌入式环境可能没有文件系统访问,需要将 Lua 脚本编译为 C 字节数组嵌入可执行文件。

创建转换脚本 lib/mobdebug/lua_embed.py

python 复制代码
#!/usr/bin/env python3
import sys

def lua_to_c(lua_file, output_c, var_name):
    with open(lua_file, 'rb') as f:
        data = f.read()
    
    hex_values = ', '.join(f'0x{b:02x}' for b in data)
    c_code = f'const char {var_name}[] = {{ {hex_values}, 0 }};\n'
    
    with open(output_c, 'w') as f:
        f.write(c_code)

if __name__ == '__main__':
    lua_to_c(sys.argv[1], sys.argv[2], sys.argv[3])

构建脚本 lib/mobdebug/mobdebug.cmake

cmake 复制代码
function(lua_to_c LUA_FILE OUTPUT_C VAR_NAME)
    add_custom_command(
        OUTPUT ${OUTPUT_C}
        COMMAND ${Python3_EXECUTABLE} ${LUA_EMBED_SCRIPT} 
            ${LUA_FILE} ${OUTPUT_C} ${VAR_NAME}
        DEPENDS ${LUA_FILE} ${LUA_EMBED_SCRIPT}
    )
endfunction()

lua_to_c(${PROJECT_SOURCE_DIR}/lib/mobdebug/mobdebug.lua
         ${CMAKE_BINARY_DIR}/mobdebug_data.c mobdebug_lua)
lua_to_c(${PROJECT_SOURCE_DIR}/lib/luasocket/lua/socket.lua
         ${CMAKE_BINARY_DIR}/socket_data.c socket_lua)
# ... 其他 Lua 文件

add_library(mobdebug_embed STATIC
    ${CMAKE_BINARY_DIR}/mobdebug_data.c
    ${CMAKE_BINARY_DIR}/socket_data.c
    # ...
)

3. 创建调试模块

lib/lua_debug.h

c 复制代码
#ifndef LUA_DEBUG_H
#define LUA_DEBUG_H

#include <lua.hpp>

extern "C" {
int luaopen_socket_core(lua_State *L);
int luaopen_mime_core(lua_State *L);
}

int lua_debug_init(lua_State *L);
int lua_debug_start(lua_State *L, const char *host, int port);
void lua_debug_stop(lua_State *L);

#endif

lib/lua_debug.cpp 关键实现:

cpp 复制代码
// 注册 C 模块
static int register_c_module(lua_State *L, const char *name, lua_CFunction openfn)
{
    luaL_requiref(L, name, openfn, 1);
    lua_setfield(L, -2, name);
    return 0;
}

// 初始化调试模块
int lua_debug_init(lua_State *L)
{
    // 注册 socket.core 和 mime.core (C 模块)
    register_c_module(L, "socket.core", luaopen_socket_core);
    register_c_module(L, "mime.core", luaopen_mime_core);
    
    // 加载 Lua 封装层 (嵌入的字节码)
    load_embedded_lua(L, "socket", socket_lua);
    load_embedded_lua(L, "mobdebug", mobdebug_lua);
    
    return 0;
}

// 启动调试会话
int lua_debug_start(lua_State *L, const char *host, int port)
{
    lua_getglobal(L, "mobdebug");
    lua_getfield(L, -1, "start");
    lua_pushstring(L, host);
    lua_pushinteger(L, port);
    lua_pcall(L, 2, 1, 0);
    // ...
}

4. 修改主程序

添加命令行参数支持:

cpp 复制代码
// main.cpp
int main(int argc, char *argv[])
{
    // 解析参数
    int debug_mode = 0;
    std::string debug_host = "localhost";
    int debug_port = 8172;
    
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--debug") == 0) {
            debug_mode = 1;
        } else if (strcmp(argv[i], "--debug-host") == 0 && i + 1 < argc) {
            debug_host = argv[++i];
        } else if (strcmp(argv[i], "--debug-port") == 0 && i + 1 < argc) {
            debug_port = atoi(argv[++i]);
        }
    }
    
    // 初始化调试模块
    if (debug_mode) {
        lua_debug_init(L);
    }
    
    // 运行脚本
    if (script_arg > 0) {
        if (debug_mode) {
            lua_debug_start(L, debug_host.c_str(), debug_port);
        }
        run_script(L, argv[script_arg]);
        if (debug_mode) {
            lua_debug_stop(L);
        }
    }
}

5. 启用 Lua 标准库

MobDebug 依赖 ioos 模块,需要修改 lib/lua/linit.c

c 复制代码
static const luaL_Reg loadedlibs[] = {
  {LUA_GNAME, luaopen_base},
  {LUA_LOADLIBNAME, luaopen_package},
  {LUA_COLIBNAME, luaopen_coroutine},
  {LUA_TABLIBNAME, luaopen_table},
  {LUA_IOLIBNAME, luaopen_io},      // 启用
  {LUA_OSLIBNAME, luaopen_os},      // 启用
  {LUA_STRLIBNAME, luaopen_string},
  {LUA_MATHLIBNAME, luaopen_math},
  {LUA_UTF8LIBNAME, luaopen_utf8},
  {LUA_DBLIBNAME, luaopen_debug},
  {NULL, NULL}
};

遇到的问题

问题 1: module 'io' not found

原因 : linit.cioos 库被注释禁用。

解决 : 启用 LUA_IOLIBNAMELUA_OSLIBNAME

问题 2: module 'socket.core' not found

原因 : LuaSocket 的 Lua 封装层 (socket.lua) 需要 C 核心模块 (socket.core),但只加载了 Lua 层。

解决 : 使用 luaL_requiref() 注册 C 模块到 package.loaded

cpp 复制代码
luaL_requiref(L, "socket.core", luaopen_socket_core, 1);
lua_setfield(L, -2, "socket.core");

问题 3: module 'mime.core' not found

原因 : 同上,mime.lua 依赖 mime.core

解决 : 同样注册 mime.core

问题 4: require "mobdebug" 失败

原因 : mobdebug 只注册为全局变量,未注册到 package.loaded

解决 : 同时注册到 package.loaded 和全局表:

cpp 复制代码
lua_setfield(L, -2, "mobdebug");  // package.loaded.mobdebug
lua_getfield(L, -1, "mobdebug");
lua_setglobal(L, "mobdebug");      // _G.mobdebug

测试验证

测试脚本

test_debug.lua

lua 复制代码
print("=== ZeroBrane Debug Test ===")

-- 检查模块
local modules = {"socket", "socket.core", "mime", "mime.core", "mobdebug"}
for _, mod in ipairs(modules) do
    local ok, m = pcall(require, mod)
    print(ok and "[OK]" or "[FAIL]", mod)
end

-- 测试连接
local socket = require("socket")
local tcp = socket.tcp()
tcp:settimeout(2)
local ok, err = tcp:connect("localhost", 8172)
if ok then
    print("[OK] Connected to debugger!")
    tcp:close()
end

-- 测试循环 (设置断点)
for i = 1, 5 do
    local x = i * 2
    print("iteration " .. i .. ": x=" .. x)
end

Mock 调试服务器

test_debug_server.py

python 复制代码
#!/usr/bin/env python3
import socket

def debug_server(port=8172):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('0.0.0.0', port))
    server.listen(1)
    
    print(f"Waiting for connection on port {port}...")
    conn, addr = server.accept()
    print(f"*** CONNECTION SUCCESSFUL! ***")
    
    # 发送 RUN 命令让脚本继续执行
    conn.send(b"RUN\n")
    
    conn.close()
    server.close()

if __name__ == '__main__':
    debug_server()

测试结果

bash 复制代码
# 终端 1
$ python3 test_debug_server.py
Waiting for connection on port 8172...
*** CONNECTION SUCCESSFUL! ***

# 终端 2
$ ./build/fan-instruments --debug test_debug.lua
socket.core registered
mime.core registered
MobDebug module initialized
Debugger connected to localhost:8172
=== ZeroBrane Debug Test ===
[OK] socket
[OK] socket.core
[OK] mime
[OK] mime.core
[OK] mobdebug
[OK] Connected to debugger!
iteration 1: x=2
iteration 2: x=4
...

使用方法

命令行参数

参数 说明 默认值
--debug 启用调试模式 -
--debug-host HOST 调试服务器地址 localhost
--debug-port PORT 调试服务器端口 8172

ZeroBrane Studio 配置

  1. 启动 ZeroBrane Studio

  2. 打开 Lua 脚本文件

  3. 设置断点 (点击行号左侧)

  4. 菜单: Project → Start Debugger Server

  5. 在目标设备上运行:

    bash 复制代码
    ./fan-instruments --debug --debug-host <PC_IP> script.lua

调试功能

  • 断点调试: 在 ZeroBrane 中设置断点,程序会暂停
  • 单步执行: Step Into (F11), Step Over (F10), Step Out (Shift+F11)
  • 变量监视: 在 Watch 窗口添加表达式
  • 调用堆栈: 查看 Stack 窗口

文件清单

复制代码
lib/
├── lua_debug.h              # 调试模块头文件
├── lua_debug.cpp            # 调试模块实现
├── lua/
│   └── linit.c              # 修改: 启用 io/os 库
├── luasocket/               # 新增: LuaSocket 库
│   ├── *.c, *.h
│   ├── lua/*.lua
│   └── luasocket.cmake
└── mobdebug/                # 新增: MobDebug 库
    ├── mobdebug.lua
    ├── mobdebug.cmake
    └── lua_embed.py

main.cpp                     # 修改: 添加调试参数
CMakeLists.txt               # 修改: 添加依赖

test_debug.lua               # 测试脚本
test_debug_server.py         # Mock 调试服务器

上板测试

总结

本次移植主要工作:

  1. 集成 LuaSocket: 提供底层 TCP 通信能力
  2. 集成 MobDebug: 实现调试协议
  3. 嵌入 Lua 脚本: 将 Lua 文件编译为 C 数组,避免文件系统依赖
  4. 注册 C 模块 : 正确注册 socket.coremime.core
  5. 启用标准库 : 开启 ioos 模块

移植后的系统支持 ZeroBrane Studio 远程调试,方便嵌入式 Lua 脚本的开发和调试。

参考资料

相关推荐
小此方2 分钟前
Re:Linux系统篇(五)指令篇 ·四:shell外壳程序及其工作原理
linux·运维·服务器
其实防守也摸鱼23 分钟前
sqlmap下载和安装保姆级教程(附安装包)
linux·运维·服务器·测试工具·渗透测试·攻防·护网行动
jingyu飞鸟1 小时前
Linux系统发送邮件,解决信誉等级低问题 docker compose修改启动一键使用
linux·运维·docker
Lumos_7771 小时前
Linux -- exec 进程替换
linux·运维·chrome
ElfBoard2 小时前
飞凌精灵(ElfBoard)技术贴|如何在RK3506开发板上实现UART功能复用
大数据·linux·人工智能·驱动开发·单片机·嵌入式硬件·物联网
HackTorjan2 小时前
AI驱动的制品库高效管理:智能分类、自动化追踪与全生命周期优化
linux·人工智能·分类·自动化
.千余2 小时前
【Linux】进程概念
linux·服务器·开发语言·学习
蜡台2 小时前
centos 8 安装 nginx-1.29.8 及相关配置教程
linux·nginx·centos
爱学习的小囧2 小时前
ESXi性能历史怎么监控?2种方法,图形化+命令行全覆盖
java·linux·运维·服务器·网络·esxi·esxi8.0
踏着七彩祥云的小丑2 小时前
嵌入式——认识电子元器件——二极管系列
单片机·嵌入式硬件