嵌入式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 脚本的开发和调试。

参考资料

相关推荐
小此方3 小时前
Re:Linux系统篇(十三)特别篇: 实现Linux第⼀个系统程序−进度条
linux·运维·服务器
夏日听雨眠11 小时前
LInux(逻辑地址与物理地址的区别,文件描述符,lseek函数)
linux·运维·网络
时空自由民.11 小时前
STM32配置Timer+DMA读取ADC数据
stm32·单片机·嵌入式硬件
华普微HOPERF12 小时前
数字隔离器,如何确保MCU不受储能系统中的高电压、大电流影响?
单片机·嵌入式硬件
qq_5425154112 小时前
Ubuntu 22.04.4 LTS安装ToDesk最新版打不开,无响应?旧版本4.7.2_277版本分享
linux·ubuntu·todesk
火车叼位13 小时前
替代 Tiny Win10 的 Linux 方案:Debian XFCE 精简桌面搭建
linux·运维
小麦嵌入式13 小时前
FPGA入门(四):时序逻辑计数器原理与 LED 闪烁实现
linux·驱动开发·stm32·嵌入式硬件·fpga开发·硬件工程·dsp开发
搁浅小泽14 小时前
常用电子元器件
单片机·嵌入式硬件·可靠性工程师
皮卡蛋炒饭.14 小时前
传输层协议UDP
linux·网络协议·udp
zhaoshuzhaoshu14 小时前
嵌入式开发之时钟树解析-SMT32平台
嵌入式硬件