嵌入式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 小时前
Ansible 详解:group模块、vars_files变量、user模块实战
linux·运维·ansible
aini_lovee2 小时前
基于STM32的光电感烟火灾报警器设计
stm32·单片机·嵌入式硬件
独隅2 小时前
Linux 系统下 ADB 环境 的详细安装步骤和基础设置指南
linux·运维·adb
码农爱学习2 小时前
使用cJosn读写配置文件
java·linux·网络
拒朽2 小时前
51单片机学习(六)模块化编程和LCD调试工具
嵌入式硬件·学习·51单片机
自然常数e2 小时前
预处理讲解
java·linux·c语言·前端·visual studio
哼?~2 小时前
Linux线程同步
linux
tumeng07112 小时前
Linux(CentOS)安装 Nginx
linux·nginx·centos
cyber_两只龙宝2 小时前
【Docker】Docker的原生网络介绍
linux·运维·docker·云原生·容器