introduction

工欲善其事,必先利其器。lua作为一门动态语言,虽然我已经习惯了使用print来进行代码调试,但是还是有很多童鞋觉得一款好用的调试器能更好的进行lua代码编写。所以在以前接手游戏的lua结合层之后,自然就需要提供一个debuger工具了。

我们只需要的是一个能快速进行lua代码调试的工具,所以不需要gdb那种额外复杂的功能,只需要提供几种简单的功能就行了,如下:

  • c/continue 继续执行
  • bt/backtrace 列出当前堆栈
  • f/frame n 跳转到frame n
  • l/list b e 列出源代码,b为起始行,e为结束行
  • p/print v 打印v的值
  • n/next 执行,跳过下一行,包括跳过子函数
  • s/step 执行,直到碰到不同的一行
  • return 执行,直到该函数结束

虽然调试器实现的功能很简单,但是对于大多数应用来说,已经完全足够使用。

lua debug library

lua提供了一个debug library,我们就通过这个库来实现一个调试器。 首先,我们需要注册一个lua debug hook,并且绑定LUA_MASKLINE,LUA_MASKRET,LUA_MASKCALL事件,这样当lua代码执行的时候如果碰到相应的事件,则会调用我们注册的debug hook。

当debug hook调用的时候,程序就进入debug模式,这时候就可以输入对应的命令进行执行。

db

只要注册了debug hook,那么每次lua代码执行的时候碰到对应的事件就会调用注册hook,如果每次调用都进入debug模式,那是很影响程序运行的,所以我们需要一种机制,只在需要的时候进入debug模式。

在gdb里面,我们通过设置断点来进入可调式模式,虽然在lua里面也可以这么做,但这里我们采用了一种更简单的方法。我们给lua注册一个db函数,当lua执行到db函数的时候,程序才会进入debug模式。因为lua是动态语言,如果我们需要在另一个地方也进行调试,只需要再次加入db函数,重启程序即可。

所以这里我们debug hook函数内部实现是一个状态机,当没有进入db的时候,虽然lua也会进行调用该hook,但该hook内部不作任何处理。只有当执行db函数进入debug模式,hook内部才会有相应处理。

我们在debug hook里面提供了多种状态,包括none hook,step hook,next hook和return hook。

  • none hook,没有进入debug模式,该hook不做任何处理
  • step hook,进入debug的step模式,当lua代码执行到新的一行代码时候做处理
  • next hook,进入debug的next模式,当lua代码跳过下一行时候做处理
  • return hook,进入debug的return模式,当lua执行到当前函数退出时候做处理

continue

continue会让程序继续执行。该命令会让hook切入none hook状态,直到下次lua执行db函数进入debug模式。

step

step会让hook切入step hook状态,该hook会监听LUA_MASKLINE事件,当该事件发生时候,step hook进行处理,打印当前代码,并再次进入debug模式,供下次命令输入。

next

next会让hook切入next hook状态,该hook也会监听LUA_MASKLINE事件,但是next跟step最大的区别在于next是跳过一行,也就是说如果执行的lua代码下一行是一个函数调用,step会进入函数内部,而next则会执行该函数,并跳过该函数这一行直到下一行。

所以next需要进行判断LUA_MASKLINE是否进入了一个新的函数,这里我们通过函数堆栈深度来进行,当lua代码执行到一个新的函数的时候,它的函数堆栈深度会加1,所以我们只需要记录当前的堆栈深度a,next执行到下一次LUA_MASKLINE时候,获取堆栈深度b,如果a小于b,那么表明进入了一个新的函数,所以我们不需要处理,直到再次获取的堆栈深度等于a。

return

return会让hook切入return hook状态,该hook会监听LUA_MASKRET事件,当该事件发生,return hook进行处理。这里我们仍然需要进入是否进入新函数调用的判断,因为我们只想监听的是当前函数的LUA_MASKRET事件,所以我们仍需要像next那样进行堆栈深度的判断。

backtrace/frame/list

backtrace,frame,list这几个命令这里列在一起,是因为他们都跟lua_getstack,lua_getinfo这两个函数有关系。我们通过lua_getstack初始化指定栈帧的lua_Debug结构,然后在通过调用lua_getinfo获取相关栈帧信息。

print value

print value命令可能算是最复杂的一个命令,因为有多个逻辑处理。当我们通过frame定位到某一层栈帧之后,就可以通过print打印相关的对象数据,供调试使用。

当print value的时候,首先我们查找value是否在当前函数里面local变量里面,如果没有则查找该函数的upvalue,如果仍然没有,则查找global,如果都没找到,则输出nil。

code

代码在luahelper的debughelper,只是一个简单的实现,还有一些问题需要考虑。

因为lua的debug hook注册的时候只能提供一个hook,所以为了简单起见,我对DebugHelper使用了单例模式,但是最好是一个lua实例对应一个debuger。要做到这样,自己想到了两种可能方法:

  • 使用LUAI_EXTRASPACE,并通过luai_userstateopen,luai_userstateclose将debuger绑定到lua实例上面,并通过luai_userstatethread进行debuger在coroutine的迁移。这种方法需要重新编译lua代码,适合集成lua源码的项目。
  • debuger内部使用一个map来进行对应,但是在lua里面需要替换coroutine的创建,因为创建的coroutine也需要对应到同一个debuger上面。

两种方法都懒得弄了,以后有机会去尝试一下。

end

这里只是简单的实现了一个lua debuger,但是功能我觉得足以可以在实际项目中应用了。只是越来觉得,对于动态语言,print和log才是我最喜欢的代码调试方式,因为简单而且强迫你去思考整个程序的运行流程。不过把debuger放在这里,也算是对自己以前游戏开发的一个总结吧。

一个简易版本的lua debugger实现的更多相关文章

  1. 用java语言写一个简易版本的登录页面,包含用户注册、用户登录、用户注销、修改密码等功能

    package com.Summer_0421.cn; import java.util.Arrays; import java.util.Scanner; /** * @author Summer ...

  2. 依赖注入[4]: 创建一个简易版的DI框架[上篇]

    本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...

  3. .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  4. 实现一个 WPF 版本的 ConnectedAnimation

    Windows 10 的创造者更新为开发者们带来了 Connected Animation 连接动画,这也是 Fluent Design System 的一部分.它的视觉引导性很强,用户能够在它的帮助 ...

  5. 探秘Tomcat——一个简易的Servlet容器

    即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器. 背景知识 既然说到servlet容器这个名词,我们首先要了解它到底是什么. servlet 相比你或多或少 ...

  6. C 基于UDP实现一个简易的聊天室

    引言 本文是围绕Linux udp api 构建一个简易的多人聊天室.重点看思路,帮助我们加深 对udp开发中一些api了解.相对而言udp socket开发相比tcp socket开发注意的细节要少 ...

  7. Angularjs,WebAPI 搭建一个简易权限管理系统

    Angularjs,WebAPI 搭建一个简易权限管理系统 Angularjs名词与概念(一)   1. 目录 前言 Angularjs名词与概念 权限系统原型 权限系统业务 数据库设计和实现 Web ...

  8. Opencv探索之路(二十):制作一个简易手动图像配准工具

    近日在做基于sift特征点的图像配准时遇到匹配失败的情况,失败的原因在于两幅图像分辨率相差有点大,而且这两幅图是不同时间段的同一场景的图片,所以基于sift点的匹配已经找不到匹配点了.然后老师叫我尝试 ...

  9. 使用Python创建一个简易的Web Server

    Python 2.x中自带了SimpleHTTPServer模块,到Python3.x中,该模块被合并到了http.server模块中.使用该模块,可以快速创建一个简易的Web服务器. 我们在C:\U ...

随机推荐

  1. 让互联网更快,Server Push 特性及开启方式详解

    过去 Nginx 并不支持 HTTP/2 的 Server Push 特性,幸运的是 Nginx 1.13.9 已支持该特性,详情介绍请移步 Nginx 官方博客. Server Push 这个特性是 ...

  2. git提交项目常用命令及git分支的用法

    1.第一步首先从git托管平台clone项目,我这里就使用idea为例: 填写git的url与存放本地目录名及项目名     2.如果你对项目进行了一些修改,就可以执行git命令,进行提交. 有两种方 ...

  3. 解决linux中使用git,ssh每次都要输入密码

    在linux中使用git,去提交或者下载代码都是很方便的,但是最近新配置了一套系统,发现每次git pull或者其他动作都需要输入密码. 想一想不对劲啊,我使用的是ssh的方式clone的代码,而且在 ...

  4. 【已解决】IIS搭建 asp.net core 项目后 其他电脑访问不到资源文件

    IIS搭建asp.net core 项目后,访问不到里面的资源文件(图片等),解决方法如下: 1.检查asp.net core发布文件中的资源文件是不是都放到了wwwroot名称的目录中. 2.检查a ...

  5. ACM 继续畅通工程

    Problem Description 省政府"畅通工程"的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可).现得到城镇道路统计 ...

  6. Go 语言Map(集合)

    Map 是一种无序的键值对的集合.Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值. Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它.不过,Map 是无 ...

  7. Sybase数据库实现等效的mysql中group_concat功能

    在MySQL中,如果想实现将分组之后的多个数据合并到一列,可以使用group_concat函数,如下图所示: 但是,在Sybase中没有这样的函数(别问我为什么使用Sybase,因为公司用的Sybas ...

  8. 安卓 新版本 获取wifi状态网络是否可用等

    写下这篇文章目的:当我学习的和百度看看如何获取网络状态 发现都是一些比较老的方法 API23已结过时 所以在此写下记录 ,我不明白国内为什么那么少 那么我们来看看旧方法 package com.che ...

  9. PHP 针对多用户 实现头像更换

    成品图 思路 登陆页面 表单制作 验证码制作 JavaScript刷新验证码 验证页面 验证逻辑 页面跳转 header函数 Meta标签 JavaScript 上传页面 个人主页 上传核心 最终结果 ...

  10. DJango内建模板转向jinja2的小脚本

    import re,os def org(path=os.getcwd(),fs=None,preview=True): fs = fs or [] for root,dirs,files in os ...