这段时间在qnode项目中新增了一个叫ldb的子项目,它的作用是使用C语言实现了一个lua调试器,后面将会在qnode中嵌入对调试lua脚本的支持。

先来简单提一下ldb的用法,在ldb目录的子目录test中,有一个main.c文件,其中使用ldb库提供的API实现对lua脚本的调试演示:

#include <stdio h="">
#include "ldb.h"ldb_t *ldb;static int
c_break(lua_State *state) {ldb_step_in(state, 1);return 0;
}int main() {int i;const char *file = "my.lua";lua_State *L = lua_open();luaL_openlibs(L);lua_register(L, "c_break", c_break);ldb = ldb_new(L);luaL_dofile(L, file);ldb_destroy(ldb);return 0;
}
</stdio>

在这里,ldb提供了创建和销毁ldb库的API,分别是ldb_new和ldb_destroy。另外提供了一个API ldb_step_in,main文件中通过向lua层提供一个C api c_debug,在该函数中调用这个API,实现对lua的调试。

main函数将读取my.lua文件来执行,它的内容如下:

function test()local ab = 2034print("test")
endlocal t = {out=10}
local a = 1014
b = 2024
print("before debug")c_break()
test()
print("after  debug")

来看看ldb库当前实现的调试功能有哪些:

(ldb) h
Lua debugger written by Lichuang(2013)
cmd:help(h) : print help infoprint(p) <varname> : print var valuebacktrace(bt) : print backtrace infolist(l) : list file sourcestep(s) : one instruction exactlynext(n) :next linebreak(b) [function|filename:line] : break at function or line in a filedisable(dis) breakpoint : disable a breakpointenable(en) breakpoint : enable a breakpointdelete(del) breakpoint : delete a breakpointinfo(i) :show all break infocontinue(c) : continue execute when hit a break point
</varname>

括号中的都是该命令的缩写。当前支持添加/禁止断点,打印变量,查看文件内容,查看当前调用栈,step in模式支持逐行执行的调试,以及next模式会跳过函数执行等最基本的调试命令。

介绍完简单的使用和功能,下面来介绍一下lua中为支持调试功能提供了哪些API,以及通过这些API如何实现一个lua调试器。

1)lua提供的hook功能
lua为了支持调试,提供了hook功能,使用者可以根据需要添加不同的hook处理函数供条件触发时回调,包括以下几种:
具体包括以下几种hook类型:

#define LUA_HOOKCALL   0
#define LUA_HOOKRET 1
#define LUA_HOOKLINE    2
#define LUA_HOOKCOUNT   3
#define LUA_HOOKTAILRET 4

这里用的最多的是LUA_HOOKCALL,LUA_HOOKLINE,如果注册了这两个类型的HOOK函数,则会分别在调用某函数和执行每一行代码之后调用注册的HOOK函数。
使用lua中提供的API,可以如下方式注册HOOK函数:

  int mask;mask = lua_gethookmask(state);if (enable) {lua_sethook(state, all_hook, mask | LUA_MASKLINE, 0);} else {lua_sethook(state, all_hook, mask & ~LUA_MASKLINE, 0);}

有了HOOK函数,就可以在lua代码每次执行的时候做一些动作了。

除了HOOK函数之外,lua自身还提供了lua_Debug结构体,这个结构体包括以下成员:

source  函数的定义位置。如果函数在字符串内被定义(通过loadstring 函数),source就是该字符串,如果函数在文件中被定义,source就是带“@”前缀的文件名。short_src  source的简短版本(60个字符以内),对错误信息很有用。linedefined  source中函数被定义处的行号。what  函数类型。如果foo是普通的Lua函数,结果为“Lua”;如果是C函数,结果为“C”;如果是Lua的主代码段,结果为“main”。name  函数的名称。namewhat  name域的含义。可能的取值为:“global”、“local”、“method”、“field”,或者空字符串。空字符串意味着Lua无法找到这个函数名。nups  函数中的Upvalues的个数。func  函数本身。稍后介绍。

可以通过lua_getinfo得到一些很重要的信息,它的调用方式是:
lua_getinfo(state, params, ar),第二个参数是一个字符串,支持传入多个字母,每个字母有不同的含义:

'n':  name,namewhat
'f':   func
'S':  source,short_src,what,linedefined
'l': currentline
'u':  nup

2) 如何打印变量?
变量分为局部和全局变量,因此搜索某个变量的时候是从内到外的方式搜索,搜索局部变量时,使用lua提供的API lua_getlocal函数,逐个搜索,具体可以看ldb.c中的search_local_var函数。

如果在局部变量中搜索不到,则还得使用lua_getglobal函数进行全局变量的搜索,具体见ldb.c中的search_global_var函数。

但是以上的过程仅仅还只能在相应的地方查找到同名的变量,在真正需要打印变量值的时候,还需要根据变量的类型具体来打印数据,代码太多,不在这里列出,见ldb.c中的print_var函数。

3)如何查看文件的内容
查看文件的内容相对简单,因为当lua被HOOK住的时候,可以通过lua_getinfo函数得到当前lua的一些信息,比如lua文件名,行号,在C实现的Lua调试器中,会维护一个已经读取过的文件列表,如果当前所在的文件还没有被读取到内存中,那么会读取到内存中,再根据所在的行号就可以得到文件内容的信息了。

4)断点的添加
调试器的断点分为两种,一种是基于文件:行号形式的,一种则是基于函数调用形式的。
C实现的调试器中,首先需要定义一个数据结构类型,用于表示断点:

typedef struct ldb_breakpoint_t {unsigned int  available:1;char         *file;char         *func;const char   *type;int           line;unsigned int  active:1;int           index;int           hit;
} ldb_breakpoint_t;

这些信息包括断点所在的文件,行号,函数名,当前是否被激活,被触发的计数,等等。

先来看第一种形式断点的实现。这种形式的断点相对简单。做法是创建一个新的断点数据结构,保存下文件和行号,在每次HOOK函数中都去根据当前的文件和行号信息去查找是否匹配了某个断点的信息,当然,当前的实现中这个查找是线性的,可能对性能有一定影响。

来看第二种形式断点的实现。由于函数在lua中也是一种类型的变量,既然是变量那么就涉及到作用域。比如你在A模块中断点时,想给B模块的fun函数下断点,那么就不能简单的写”b func”,而应该是”b B.func”。所以在实现对函数进行断点的时候要注意这一点。另外,当给某一个函数下断点时,还需要添加LUA_HOOKCALL类型的HOOK,也就是在函数调用时被触发。之所以这么做,是因为在查找断点时,当首先使用文件名和行号都查找不到时,会判断一下当前这次的HOOK调用,是不是一个函数调用触发的HOOK,如果是的话再继续根据断点的函数名进行查找匹配。

5)如何查看当前堆栈信息
也就是模拟gdb中的bt指令的功能。lua对这个已经有支持了,可以使用lua_getstack函数,具体见ldb.c中的dump_stack函数。

6)step和next指令的实现
首先来看一个子问题,如何得到当前的函数堆栈数量,或者说当前的调用层次,通过反复调用前面提到的lua_getstack函数可以获取到:

static int
get_calldepth(lua_State *state) {int i;lua_Debug ar;for (i = 0; lua_getstack(state, i + 1, &ar ) != 0; i++);return i;
}

这两个指令的实现稍微有点难度,所以放在最后一个讲解。step就是逐行执行代码,即使调用函数的时候也会跟进该函数中,而next指令会在调用函数的时候不跟进函数的调用。所以这两者的区别在于调用时的函数堆栈,因此这两个指令的区别仅在于遇到函数的时候是不是继续跟进去。所以我的做法是新增一个变量用于保存当前的函数栈索引,当step模式时将这个值置为-1,next模式时只会保存为当前的函数栈索引,如果某个指令是调用一个函数时,这时通过get_calldepth函数获得的函数栈就会比之前的大,这样就可以知道当前的这个指令是不是调用一个函数了,next指令可以在这个时候返回不做任何处理,而step指令可以继续执行下去。

以上就是简单的原理性介绍,如果想真正了解lua调试器的实现,还是具体看看代码,自己跑一下测试demo吧。

自己动手实现Lua调试器相关推荐

  1. lua调试器与编辑器开源项目luacode(源自decoda)

    目前项目开发一直是用decoda进行调试,使用sublime编辑,本没有太大问题,都用了大半年. 一直都比较忙,这段时间有空在decoda的源码基础上进行修改,就是想将调试器与编辑器融合起来,改成我期 ...

  2. 【ZeloEngine】Lua调试器

    [ZeloEngine]Lua调试器 Lua没有非常强势的IDE和调试器方案,基本上都是专用方案造的轮子 尝试了几个方案(按时间顺序) Decoda LuaPerfect EmmyLua(Clion) ...

  3. x-studio(Lua调试器,粒子编辑器,UI编辑器,代码编辑器,csb恢复工具)

    最新版本:x-studio 10.0.9000.29(2020年4月14日更新) 官网: https://x-studio.net 官方教程: https://docs.x-studio.net x- ...

  4. Lua 调试(Debug)

    Lua 提供了 debug 库用于提供创建我们自定义调试器的功能.Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试器代码. Lua 中 debug 库包含以下函数: 序号 方法 ...

  5. 自己动手开发调试器 01

    背景:     在做XXX编译器检证时经常需要区分是代码端错误,还是编译器端错误,因此对代码进行调试是必不可少的.但是狗日的甲方并没有提供对应的调试器XXXDB,而用GDB调试XXX生成的可执行程序很 ...

  6. linux的静态编译elf无法调试,[翻译]自己动手编写一个Linux调试器系列之4 ELF文件格式与DWARF调试格式 by lantie@15PB...

    自己动手编写一个Linux调试器系列之4 ELF文件格式与DWARF调试格式 by lantie@15PB 在上一节中,你已经听说了DWARF调试格式,它是程序的调试信息,是一种可以更好理解源码的方式 ...

  7. eac 反调试_自己动手制作一个过保护调试器

    一.起因 本人是新手第一次接触驱动开发的小白,事情是这样的,一个星期前突发奇想想做一个调试器保护程序用于调试游戏,既然要调试驱动保护的程序,自然也要深入驱动底层.做调试器必须要hook api去隐藏调 ...

  8. 开源项目-基于Intel VT技术的Linux内核调试器

    本开源项目将硬件虚拟化技术应用在内核调试器上,使内核调试器成为VMM,将操作系统置于虚拟机中运行,即操作系统成为GuestOS,以这样的一种形式进行调试,最主要的好处就是调试器对操作系统完全透明.如下 ...

  9. 使用GDB命令行调试器调试C/C++程序

    编译自:http://xmodulo.com/gdb-command-line-debugger.html 作者: Adrien Brochard 原创:LCTT https://linux.cn/a ...

最新文章

  1. echarts.js 做图表的插件
  2. mybatis多产数_freeCodeCamp杰出贡献者–我们如何选择,认可和奖励多产的志愿者
  3. 收藏 | 自监督视觉Transformer
  4. 【英语学习】【English L06】U01 Breakfast L6 Make at home vs. eat out
  5. mysql索引 实验_“索引”实验小例
  6. Spring4.x(16)--SpringEL-正则表达式
  7. java报错空指针异常_分析使用Spring Boot进行单元测试时,报出空指针异常
  8. Uninstalling ASP.NET MVC 1.1 after installing Visual Studio 2010 beta 2
  9. Objective-C 相关Category
  10. Fragstats|单一土地利用类型景观格局指数
  11. couchbase java view_couchbase 相关
  12. 深度学习 DEEP LEARNING 1-2章
  13. CTF挑战赛-合天网安实验室
  14. WP应用程序磁贴设置
  15. Qt解决资源文件中无法显示图片的问题
  16. 【洛谷P2184】贪婪大陆【线段树】
  17. Livy的CDH环境parcel和csd制作
  18. php在线备忘录,PHP设计模式 - 备忘录模式
  19. 在qt中使用opengl绘制图形动画
  20. 中国神童13岁免试上大学,极端荣耀后却选择出家为僧!

热门文章

  1. 【错误记录】Android Studio 编译报错 ( Cannot use connection to Gradle distribution . as it has been stopped. )
  2. 【Flutter】Dart 面向对象 ( get 方法 | set 方法 | 静态方法 )
  3. Redis 你该懂的点
  4. 线程的创建 验证线程之间共享数据 守护线程 线程进程效率对比 锁 死锁 递归锁...
  5. 【bzoj3105】新Nim游戏
  6. Selenium_python自动化环境搭建篇
  7. 403错误代码导致网站样式全部乱了
  8. Redis 启动与授权
  9. 浏览器插件 火狐插件
  10. 英语单词 voltage simulation synthesize junction asynchronous mega optimize