All Stories

Lua调试器三步走

  估计这个Lua调试器是最近几个月来遇到的最具有技术挑战性的特性了,大概要分三步走。  首先,确定使用远程调试架构。用C++实现与用户交互的操作程序和与Lua解释器交互的执行部分。现在仍然在这个阶段。昨天差不多把socket通信的部分搭起来了,这也花了我不少精力,是用boost.asio做的,实然觉得自己对boost.asio的了解增进了很多,也突然觉得其实boost.asio似乎挺好用的。但是仍然有点小问题,比如我本来希望执行部分作为C/S结构中的客户端,它能在连接上服务端后立马写点数据给服务端,可是似乎总是没写出去,要先收到些东西再写才会成功。不过现在暂时不管这个问题,今天在写与Lua解释器交互的部分,发现代码真的是越来越多喽,一个又一个新的类被创建出来,真是面向对象惹的祸呀!没写完,中间脑袋稀里糊涂的,明天继续。  接着,把与用户交互的操作程序改写成Lua,并与宿主程序集成,可以在IDE的编辑器上就让用户进行操作和反映调试器执行结果。这里要注意的是,到底到时候要放多少代码是在宿主程序中实现的,多少代码又是让Lua脚本实现的,又有多少代码是用C++写成作为Lua的扩展库的,几部分又是如何交互的。  最后,把调试执行部分改成dll,以便注入到被调试进程中去,这样就可以调试任何嵌入了Lua解释器的程序了,只要Lua解释器是个dll即可。注入后,要hook掉几个Lua的C API,包括获取lua_State,装入文件。这里应该要注意的是,使用Lua解释器的C API不能直接调用和链接了,要动态获取API地址了,这样才能使用注入进程的那个Lua解释器的dll了,不过其实可能不需要这么复杂,只要额外说明如果要用本软件调试的话,要求用我指定的Lua解释器的dll和lib即可。我觉得这点是可以做到并且在某些情况下必须做到的,因为曾经还听人说过,很多内嵌Lua的游戏,都是静态链接Lua的,所以既使我能注入,也hook不到那些C API的。

双鱼座2010年时来运转!!!

好运的部份:2010年终於开始有苦尽甘来的感觉了,木星将从1月中进入双鱼座的命宫。你会觉得自己是属於你个人的,可以摆脱身不由己的感觉,而且生命是全新的。有许多的新资源会在今年奇迹式的展现,帮助你实现你的梦想,让你的才能得到发挥,感情也会遇到你生命中非常重要的那个人,请好好把握,因为今年是你「对」的一年,不要在今年闹情绪或任性,把这些幸运都丢掉了,要记住,2010年只要是你愿意丢掉不幸的心情,迎接幸福,都可以心想事成,除非是你自己愿意不幸。 厄运的部份:经过土星行经下降位置的锻链,双鱼座的人历经亲密关系的离开,因此,当土星进入第8宫,双鱼座的人对於自己的欲望,会变得有不敢奢望任何好事的情况存在,有可能因此面对喜欢的人,自身的行动都是悲观和消极的。以及由於土星与海王星都在双鱼座的健康宫位(8宫与12宫),双鱼座会有3年身体上相当虚弱的问题存在,体力会显得比较差,会常有一些疾病让你苦恼,皮肤、骨头、牙齿会常常有一些疼痛,睡眠依然会有常常做梦、无法熟睡的情况发生。以及千万注意不要帮人作保、不要欠债与贷款,这3年都容易背负庞大的债务,以及有可能家中会有需要你长期照顾的长者。这都会让双鱼座的健康运势下滑,可以的话,双鱼座应该开始锻链你的身体了,储存多一些的体力,预防健康上会发生的问题。这些问题你现在(2009年11月)应该已经有所感觉了。这种情况会持续3年,请一定要好好注意养身,可以从事一些修养身心灵的运动。 工作运:双鱼座的人今年会开始有机会实现自己的梦想,只要是你想做的工作,想发展的志愿,都可以奇迹式的获得机会发展,甚至是无心插柳的一个想法,都会获得很大的回响。上半年的工作十分忙碌,可能还蛮多麻烦事的,不过双鱼一定要把握今年的好运势,把你过去想做却被束缚的想法全部付诸实现,你将会在6、7月的时候发现,你对工作上的努力会化成大量的金钱回馈给你。 是相当幸运的一年。 感情运:2009年鱼的感情运历经了相当严苛的考验,如果还能继续延续的感情,真的是得来不易的一段感情,请好好珍惜。2010年双鱼座的桃花旺盛的狂开,尤其是2、3月过生日的时候,还有金星加持,并且没有不良的相位影响,感情可以说是心想事成,比较伤脑筋的部份是,今年双鱼座的人心情上会比较悲观,反而会让很多好机会溜走了,4、5、6月的时候,土星会逆行回处女座,是让双鱼决定一段感情的关键期。到了下半年,一切都会变得稳定与明朗了,如果是有喜欢的对象,双鱼座的人要大方一些,不要搞暧昧,今年会遇到对你生命中非常重要的那个人,千万不要错过了。 健康运:双鱼座唯一比较糟糕的部份是健康运势不太好,如果可以的话,请现在赶紧找出治疗你身心灵的活动,因为你即将有3年体能不佳的情况存在。大小病痛都会不断,就算是身体底子好的双鱼,也会有睡眠障碍、精神不济的情况存在,请好好注意身体健康、稳定心灵,不要情绪化,保持心灵平静,对你的健康有益。 整体运:(5颗星) 今年是双鱼座非常重要的一年,梦想与感情都会心想事成,幸福决定权全在你自己手上,你必须要清楚明白自己要什麽,不要什麽,那些幸福才可以掌握,如果你迷迷糊糊,对於自身的感情与梦想无法确定的话,就会浪费2010年这麽好的能量了,可以的话,现在就开始计画吧!迎接属於你的2010年。

回顾2009,规划2010

  2009过得很快,真的好快,感觉没做什么事,却又真的发生了些什么事。  不过我的记忆只到2009年的春节。在春节前,我仍然做着那看不到尽头的一体化平台项目,哦,后来有个正式的名字叫流程集成管理平台。春节后,经过各方的努力,我被调回自己项目组内做Ruby IDE,本以为这回是自己感兴趣又熟悉而富有想法的事了,可以大展拳脚了,可是现实老是跟理想偏差很远。一切都显得那样自以为是的混乱而不求上进。这样的状况一直持续到9月底辞职。  是的,辞职,我终于辞职了。记得清明放假回老家的时候,遇到一个看相的女人,说我今年工作会变动,当然她说的语气要难听一点,说是我工作丢掉了。当时我还是有点嗤之以鼻的,我是个比较向往真有神之类的存在的,但同时我却又是个比较坚定的无神论者,所以我觉得这种神棍的话,好听的可以听听当作鼓励,难听的就要过滤掉了。现在回想起来,我也不清楚到底当时是怎么下定决心辞职的,其实我很是怀念在深圳在华为的生活的。正是因为对在华为的生活方式的不舍,去年腾迅给我的offer我都拒绝了,当然还有其他各方面的原因。又想想,如果去年真的去了腾迅,我是不是会一直在那里干下去了呢。  辞职的时候,需要跟四级部门主管开始往下的各级领导沟通。所以能遇得到的最高级别的领导就是四级部门主管了,当然我果真见识到高级别的人眼光和视野确实是更锐利和明亮一些。给我印象比较深刻的是他当时问我“难道你就真的对自己以后在华为的发展这么没有信心么”,一句话说中了我辞职的最大的原因。是的,没信心,一点都没有,所以我只能离开,寻找自己的理想,追求自己的野望!  说实话,我一直没有很明确的思路,自己以后要怎么做的思路,我只有一个大致的目标和方向,甚至于这个目标和方向都时不时地产生模糊的变化。不过辞职后,我有了大把的自由支配的时间,我越来越觉得如果不辞职,根本不可能支撑起我想做这些事的欲望。但是,尽管每天的时间全都给了我自己,时间仍然是不充足的,我仍然要花大量的时间去堕落和颓废。辞职前,我曾经想着好好看一些书,好好学一些东西,结果到现在仍然没有进展。  2009已经过去,充满机会的2010已经开始。曾经数次列入新年规划的计划,每年都没有得到有效的执行,所以仍然要列入2010规划之中。我希望,2010是我事业上能够明显看到成功的一年,我需要组建一个小公司,招收一些人马来实现我的理想!

Lua源代码阅读迫在眉睫

  今天在群里有个人问,他初始化Lua解释器后,用C代码执行一下lua_replace然后崩溃了,报没有环境。我觉得比较奇怪,看代码我觉得应该没有问题,但人家确确实实崩溃了。后来他解决了这个问题后,在群里公布解决方法。他看到一个老外的帖子,然后照他们说的,把调用lua_replace的那个C函数放到Lua中进行调用,一切都OK了!我仍然没明白其中的所以然,后来另一个人贴了个网址,是他自己写的一篇blog,其中解释了为什么在调用lua_replace时会崩溃。经过他的解释,是因为Lua的调用栈要求不为空,否则lua_replace就会崩溃,至于为什么要这样他也不明白。实际代码的解决办法就是在lua_replace前call一下Lua函数,比如前面说的,放在Lua中调用,那么理所当然调用栈中至少有本函数嘛。  我又看了几遍那篇blog,其中说到要嵌入的Lua解释器中要初始化标准库时,要用luaL_openlibs,如果用luaopen_base之类的直接调用就不行。这时我才渐渐地有点头绪。当年在嵌入Lua时,最早的时候用luaopen_base之类的是没问题的,后来确实不行了,在lua的maillist上有人告诉我要用luaL_openlibs,我还说应该不是这个原因,但他们说这是5.1.x之后才这么做的。现在回想起来,大概就是因为5.1.x修改了这部分的实现,而且看了一下luaL_openlibs的代码,确实就是把luaopen_base这些函数让给Lua来调用了,其它的没有区别了。  通过这件事,让我又一次深刻地认识到阅读分析Lua源代码的重要性,Lua本身的资料仍然太少,从源代码中可以得到很多undocumented的知识!

Lua调试器研究续

  本来以为自己对如何实现一个Lua调试器已经有了足够的知识储备,于是今天就提枪上马,结果无奈地发现,说是一回事,自己做是另一回事。我最终的目标是要达到Decoda那样的效果,但一开始只能慢慢一步一步来,先实现类似lldebug的debuggee部分。但是就算是这样小步开始,也仍然困难重重,很多问题其实我都没有吃透。  首先是发现在sethook时,只有按line回调是正常的,而call和return都没有回调,这让我感到很迷惑,无论是用C代码的hook还是Lua代码的hook,都是存在这个问题,而且用lldebug是正常的。所以我最早怀疑跟Lua代码有关的猜测也不成立了,只好试图从lldebug的源代码中寻找答案,当然寻找到的lldebug源代码跟我的代码没有本质的区别。最后过了很久才隐约记起来,我用的是LuaJIT,好像它对debug库的支持是有些跟官方Lua不同的地方。于是立马换回官方Lua,发现果然无论是C代码还是Lua代码都照预期的执行了。  之后是发现,hook的回调是能正常执行了,但程序还是自顾自地跑下去了,那怎么处理单步和断点呢。然后是回头看lldebug代码,发现是在hook回调函数中,处理完对应的事件后,应该要等待命令。一开始我还想不通,要怎么等待命令啊,后来突然明白过来了,比如我这个是控制台操作的,那么在这里接收一个控制台的输入就行了,不就变成运行一下,停一下了嘛!  我写了两个测试脚本,用于观察Lua的debug库三种hook回调的行为。发现Lua在执行一个脚本文件时,首先是将整个脚本文件看作一个整体,用Lua的话说,应该是一个chunk,而它执行这个chunk是个call操作,所以执行一个脚本文件会先有一个call回调,在整个文件执行完后,会有一个return回调。如果一个脚本文件中又执行了另一个脚本文件,那么就可以到嵌入call/return回调。当执行的代码是一个函数的定义时,会先在函数的end行有一个line回调,然后在函数的function行有一个line回调。当执行的代码是一次函数调用,那么会先在函数体(参数列表后面)的第一行有一次call回调,然后再在该行有一次line回调,在结束函数时,先在函数end行有一次line回调,再在该行有一次return回调。  再之后是开始考虑要支持哪些调试命令,以及怎么实现。简单地看了一下别的调试器的实现,我就暂定要支持step into,step over,run to cursor,breakpointp这几个操作了,step into大概是最容易实现的,就是debug库中按line回调即可。step over可能稍微麻烦一点,应该先假设是按line回调来处理,如果从该操作开始第一次回调的是call,那么应该记录下call的次数,并计数return回调的次数,直到return回调的次数跟call回调次数相同,再下一次line回调就达到step over的效果了。run to cursor也比较简单,按line回调直到当前光标所在行即可。breakpoint跟run to cursor的处理方法一样。  除此之外要注意的是coroutine的创建,会返回一个lua_State,所以需要拦截这个创建函数,对这个新创建出来的lua_State也sethook。因此在调试器中还得为不同的lua_State保存各自的调试上下文。  以上都是不考虑执行效率的做法,在云风的blog上有一篇文章,讲到他设计的一个提高调试器执行脚本效率的方案,似乎是勉强能提高一点性能,但要求用户修改Lua脚本来定期轮循调试器是否有新命令下发。其实在这个基础上,我们可以更进一步,修改Lua脚本的工作由调试器完成,对用户透明。而且定期轮循可以修改成只要下了断点的行的最前面自动插入一个调试器定义的函数调用,也就是将执行控制权再次转移到调试器中。这就比较像C/C++调试器中插入int3的做法了,如果断点是固定的话,那么调试器执行脚本的效率将几乎是无损耗的。除了效率上的提升外,各个调试命令的实现也将变得更加容易。但这个方案有一个问题是,这插入断点指令的操作必然是在脚本文件在被执行前完成的,如果在调试运行过程中有新增断点,那么仍然得退出原来的那种靠debug库line回调的方式了,除非重新启动调试。

Lua调试器研究

  准备自己实现一个Lua的调试器,以前没做过这方面的工作,所以只好拿现成的开源代码来研究了。  比较容易找到的调试器代码有好几个,比如lldebug,RemDebug以及wxLua中的那个调试器,各自的实现方式有所不同,但万变不离其宗的是都使用了Lua自己的debug库sethook功能。还有个共同点就是远程调试的架构,而且都是用socket实现的。  RemDebug完全用Lua写成,一个只有两个文件,加在一起代码才500多行。其中一个文件中实现的是调试引擎,负现各种调试功能的实现,另一个文件是用于实现人机交互的接口。  lldebug是个日本人写的,简单看了一下,也是跟RemDebug类似分成两部分实现。它的人机交互部分就比较高级,用wxWidgets实现了一个GUI,可以直接在上面打断点,单步,查看回调栈,查看变量等等,可算是debugger部分,另外实现一个单独的程序用于执行Lua脚本,这里可称为debuggee,其实也就是在执行Lua脚本前设置一下调试选项,以及拦截一些Lua的C API。  wxLua带了一个调试功能,它跟lldebug比较像的一点,有一个独立的程序执行Lua脚本作为debuggee。另一部分则是用C/C++实现的Lua接口,可以由用户自己用Lua脚本来实现debugger,这是它相较于前两者比较独特的一点。  通过sethook实现的调试器,要特别关注coroutine的处理。  我一直比较羡慕的是Decoda的那种功能,简单说来就是它可以直接调试其他嵌入了Lua解释器的应用程序执行的Lua脚本。当时我一直想不清楚这是怎么实现的,只是看到它向被调试的应用程序进程注入了一个dll,于是猜测它拦截了应用程序对Lua的C API的调用,而拦截了C API后要做些什么我就不知道了。现在想起来,似乎也就是只要在应用程序执行Lua脚本前,对lua_State设置hook,于是就跟前面提到的那些传统调试器一样了。但另外要注意的问题是,抢在什么时候将这个dll注入,应该拦截哪些C API。如果能在应用程序创建Lua解释器前注入,那么拦截lua_open就可以了,通过修改PE文件的导入表,基本可以达到这个目的。如果不能保证注入的时机,那么比较合适的被拦截C API应该是lua_load,但要注意的是拦截后设置hook时应该注意一个lua_State只要设置一次hook就行了。

插件打包

  前几天,把所有的插件以插件为单位打包成zip了。不过不知道wxWidgets中的zip虚拟文件系统如何支持密码,至少是没找到相关的选项。现在的情况是验证了方案可行性,可以确认功能上的不缺失以及性能上的损耗在可接受的范围内,但真正实用是一定要有密码的,也就是为了保护插件中的源代码。以前还以为带了密码就不方便第三方开发插件了,现在想通了,其实只要我提供一个插件开发环境,其中自带插件打包功能,这样就可以用同一个密码了。既然wxWidgets没有简单方便的接口来支持密码zip,那就只好自己写一个这样的功能了,好在zip实在是一个很大众化的格式,源代码很容易找到。

《西梅北》

  昨天脑袋发热,去买了个新凯越,心里很难过,死要面子活受罪。  今天早上醒来,心情更加的压抑和沉重,深深的孤独感和失落感以及挫败感。晚上做了个梦,梦见那个小姑娘,叫我帮她的同学(?)远程协助考试,考试的范围是一篇叫《西梅北》的文章。我无比清晰地记着这个题目,想起那个小姑娘,那个让我很后悔的小姑娘。  看到《间客》中的一句话:“如果将来这个联邦要收拾你……我很想在联邦之外给你留条后路。”不禁热泪盈眶。

基本搞定语法树视图

  今天在宿主程序中做了修改,并添加了插件,可以在适当的时候触发刷新语法树视图。  刷新语法树的时机其实并不好选择,总的说来,应该在这两个时刻进行刷新:一,文档刚刚打开;二,做出了影响语法树内容的修改。第一点,比较容易做到,但第二点就比较困难了,它包括当前活动文档的切换,以及当前活动文档被修改。切换也是比较容易响应的,但怎样判断一个修改是否影响了语法树内容,就不简单了。我现在的简单做法是,每当有新的行时,就进行刷新,笨了点,但勉强能用。  刷新语法树的方式,也是需要仔细实现的,我现在的做法完全是为了简单起见,先停止刷新窗口,然后清空整个树,再把整个树的内容都重新添加一遍,再刷新窗口。这样的缺点是在刷新时,能明显地看到整个树视图的闪烁。如果要规避这个问题,应该比较当前树视图中的内容和待刷新的数据,在树视图中逐个判断有新增的,或需要删除的节点,这样就比较流畅了。但这个算法就相对来说复杂,而且可能更耗资源。  目前我只是让Lua中的函数定义作为语法树视图中的唯一的元素,目前看来效果还可以。其实单就Lua来讲,还可以添加变量的定义以及表的构造。但是眼前可以预见的问题是,变量可能有很多,全列出来可能干扰视线。而表的构造表达方式可以很复杂,而且表是可以在程序中动态增删元素的,这要如何处理。