一般的编程语言都有调试功能,因为调试本身就比较复杂,还涉及到数据库,为了简化起见,本节v0.0.21版本就添加了一个简单的汇编级的调试功能。 本节v0.0.21版本的源代...
一般的编程语言都有调试功能,因为调试本身就比较复杂,还涉及到数据库,为了简化起见,本节v0.0.21版本就添加了一个简单的汇编级的调试功能。
本节v0.0.21版本的源代码的下载地址为:http://pan.baidu.com/share/link?shareid=216776&uk=940392313 (此为百度云盘的共享链接地址) ,访问该地址可以看到三个文件:zengl_lang_v0.0.21_forXP.rar (XP系统下的vs2008解决方案和源代码), zengl_language_v0.0.21_forLinux.tar.gz (Linux系统下的源代码和makefile) ,v0.0.21-v0.0.20-diffs.txt (v0.0.21和v0.0.20的代码变化情况)。
SourceForge.net上的仓库地址为:https://sourceforge.net/projects/zengl/files/ 从里面可以看到各个版本的代码压缩包,比如本节的zengl_lang_v0.0.21_forXP.rar ,zengl_language_v0.0.21_forLinux.tar.gz,v0.0.21-v0.0.20-diffs.txt 。
先来看下本版本的描述(在linux代码包里的usage.txt里有这段描述,在目前几个带有git版本的....-diffs.txt和git log中也有这段描述):
v0.0.21版本,该版本实现了zengl动态脚本语言的汇编级的简单调试功能。
在assemble.c中,outcode函数在输出汇编代码的时候,将汇编代码对应的源代码的文件名和源代码的行号以注释的形式加入到汇编文件中,方便调试。
在run.c中加入了__ZENGL_DEBUG宏,这样就可以在makefile中通过该宏生成zenglrun和zengldebug程 序,zenglrun只能执行汇编代码,zengldebug则可以在执行时调试目标代码,zenglrun因为不需要判断中断条件,所以执行效率比 zengldebug高些。
run.c中的调试代码目前只能简单的调试汇编代码,以后会根据需要将这些调试相关的代码分离到单独的文件中,并进一步完善调试功能。
在run.c的调试部分引入了signal.h头文件,这样就可以在执行代码前,通过signal函数将SIGTSTP信号处理函数注册为ctrl_z函 数,这样当按下ctrl+z键时,就会执行ctrl_z函数(需要注意的是windows下无效!),该函数会将gl_debugflag全局变量设为 TRUE,这样下条指令在执行前就会发生中断,并接受用户输入的调试指令。另外gl_debugPcNum变量用于记录要中断的代码位置,初始值为0,所 以在最开始第一条代码执行前就会发生中断,并接受用户的调试指令,gl_debug_next为调试指令'n'需要用到的变量。
目前一共有8条调试指令:
'p glob','p loc','p arg'分别用于打印全局变量,局部变量,参数的值。例如:p glob 1可以打印全局变量内存地址为1的值,对应汇编代码中的(1)的值,p loc 1为打印局部变量中索引为LOC寄存器加1的值,对应汇编代码中的loc(1)的值,p arg 1则为打印汇编代码中arg(1)的值。如果要打印数组中的某元素,则可以用p glob num num ...的形式,例如如果源代码中的array数组的全局内存地址为1,那么array[2,2]的值可以用p glob 1 2 2打印出来。如果array为局部变量或参数则可用p loc或p arg来打印。
至于变量在内存中的地址,可以在编译时输出的符号表中找到,或者通过汇编代码和源代码分析出来,目前只是简单的调试功能,所以没考虑用户体验方面的问题, 以后会加入数据库,将符号表,源码等信息加入到数据库中,就可以通过变量名等直接打印出结果出来,目前只是做个简单的测试。
'p regs'可以打印出所有的寄存器信息以及全局内存块信息,虚拟堆栈内存块的信息。
'p tracert'可以打印出函数调用的堆栈追踪信息。
'b'指令可以设置断点的位置,例如b 0 就是在第一条指令执行前中断,b 1 就是在第二条指令执行前中断,以此类推。
'n'指令为单步执行指令,'c'为跳出中断继续执行接下来的指令。
详情请通过git log -p 或 gitk等软件来查看。
作者:zenglong
时间:2012年5月16日
官方网站:www.zengl.com
下面是game_21_point.zl生成的game_21_point.zlc的代码片段:
0 USE "builtin"; // <game_21_def.zl> 1
1 JMP 36; // <game_21_fun.zl> 6
2 FUNARG 2;
3 PUSH LOC;
4 PUSH ARRAY_ITEM; // <game_21_fun.zl> 7
5 RESET ARRAY_ITEM;
6 MOV AX 1;
7 PUSH AX;
8 GET_ARRAY_ADDR arg(0);
9 MOV loc(0) AX;
10 PUSH ARRAY_ITEM; // <game_21_fun.zl> 8
...................................... //省略N行代码
在上面的.zlc中可以看到源代码文件名和行号信息, 如 0 USE "builtin";汇编代码对应game_21_def.zl文件的第一行源代码。1 JMP 36;汇编代码对应game_21_fun.zl文件里的第6行源代码。这样在一定程度上方便了脚本的调试。
在windows下的vs2008项目的配置管理器中添加了一个zenglrun开启命令行调试的配置项,如下所示:
选择此配置后,再运行zenglrun项目,就可以进行调试。windows下的调试界面如下:
上图中,每次在输入调试指令的上方会出现current reg_pc is ... debug pc num is ... 的调试信息,其中current reg_pc指的是当前虚拟机的PC寄存器里的值,也就是当前要执行的汇编代码的PC值。debug pc num是当前要进行调试的汇编代码的PC位置。例如上图最顶上的debug pc num是0所以就会在第一条汇编代码执行前中断,因为第一条汇编代码:0 USE "builtin";的最左边的值为0,这个0就是汇编代码的PC值。
>>>>>>几个破折号后面是用户的输入区,如最上面的b 4就是用户输入的调试指令,意思是在汇编代码PC值为4的位置处中断(本例中PC为4的位置对应的是game_21_fun.zl的第7行即游戏发牌函数 的入口位置),然后输入c继续执行,当游戏进入发牌函数的时候,就在PC为4的位置处中断了,输入p regs打印出所有虚拟机的寄存器值,如上图所示的AX寄存器为10,BX寄存器值为1,ARG参数寄存器值为10等,以及全局虚拟内存和堆栈的使用情 况,如上图所示的:globmem.size:512 globmem.count:5指虚拟机的全局内存当前大小为512,已存放了5个全局变量在里面(全局内存和堆栈是可以根据需求动态扩容的)。
上图中可以看到p arg和p tracert的调试指令的用法。因为PC值为4的位置所在源文件对应的是一个array数组,p arg 0 0就可以打印出该数组的第一个元素,这里打印出来val is User说明该元素的值为User字符串。
p tracert打印出来的是函数的堆栈追踪信息:最下面的是最原始的调用层,最上面的是当前执行的函数位置。如上图中最下面是ARG:0 LOC:0 PC:779对应game_21_point.zl里第20行即myGameStart();的下一条,说明当前在myGameStart函数调用里。中 间层ARG:3 LOC:4 PC:128对应game_21_fun.zl的第31行即myAddPoker(user,bltRandom()%13+1);的下一条,说明当前在myAddPoker 函数中。最上层ARG:10 LOC:13 PC:4对应game_21_fun.zl的第7行即count = &array[PokerCnt]; 所以调用顺序就是由game_21_point.zl调用myGameStart函数,然后在myGameStart函数中又调用myAddPoker函 数,最后到达count = &array[PokerCnt]; 并在该语句执行前发生了中断。
如果不想让调试器继续中断,可以将b设为0,因为代码为0的汇编代码已经执行过了而且不会再执行,这样就可以不再中断了。其他的指令用法在前面的版本描述中都提过了,这里就不多说了,另外在linux系统下可以使用ctrl+z在运行时进行中断,因为windows的cmd不支持该事件所以无法使用该功能。
windows下vs2008项目默认是Debug的配置,该配置下的zenglrun就没有调试功能。其实如果觉得上面的调试功能不好的话,完全可以用 print指令打印信息的方式来进行调试,就像PHP语言在没有装Zend Studio的环境下就只有用echo,print_r来进行调试一样。
具体的C文件代码部分,请结合源代码中的注释,再加上git工具以及vs2008或者eclipse+CDT插件或者gcc,gdb等工具进行分析。
最后还是老生常谈的话题:
windowsXP压缩包中的代码包括game_21_point.zl等测试脚本都是采用GBK的编码,Linux压缩包中的代码包括测试文件以及git里的信息都是UTF8的编码,所以如果哪些地方出现了乱码,请自行调整。
对于windows用户,请确保在项目属性的配置里,命令行参数配置的是game_21_point.zl(对于zengl_lang_v0.0.21的项目)或game_21_point.zlc(对于zenglrun的项目)。
另外对于vs2008的用户,我在项目属性里:[配置属性>>>>C/C++ >>>> 高级] 部分设置了禁用特定警告:4013,4715,4996 ,这几个警告会显示一些某某函数是非安全的函数,或者函数没有返回值等,这里禁用掉,防止出现过多的警告。另外还有个警告是显示某某变量没被使用过的, 这个警告我没禁用,可以不用管它。我最开始是在Linux系统中使用eclipse+CDT插件以及gcc等开发的zengl ,在我的GCC下面并没有显示过这些讨厌的警告,所以就没处理,不过还好这些警告都无关痛痒,无需理会。
还有一个地方:VS2008项目中,在[配置属性>>>> C/C++ >>>> 预处理器] 部分都设置了预处理器定义的宏:OS_IN_WINDOWS,另外在本节新加的zenglrun开启命令行调试配置中还增加了__ZENGL_DEBUG的宏 ,因为源代码既要在WINDOWS下编译,又要在LINUX下编译,所以需要通过这些宏来告诉编译器当前的环境是windows还是linux,在 windows下面,在程序结束时会执行system ("pause");这条语句(vs2008下为了能看到结果,需要暂停,否则就一闪而过,什么都看不到咯。) 而linux系统主要在bash终端下执行,不需要这条语句。
linux系统下的用户请结合usage.txt的说明,先运行make clean 将原来生成的zengl zenglrun zengldebug和 main.o parser.o assemble.o ld.o func.o run.o debug.o symbol.o builtin.o文件删除。
再运行make all (单纯的make只能生成zengl,所以需要make all来生成所有的目标)
生成zengl zenglrun zengldebug 和 main.o parser.o assemble.o ld.o func.o run.o debug.o symbol.o builtin.o。(在生成过程中如果出现一些警告,暂不管他)
最后运行 ./zengl test.zl (本节是game_21_point.zl) 查看printASTnodes函数打印抽象语法树节点的结果,以及符号表输出的变量信息以及函数信息等。(例如变量的内存地址,以及在源文件的行列号,函数的唯一标识ID等)。
接着运行./zenglrun test.zlc(本节是game_21_point.zlc)
如果要调试脚本则使用./zengldebug test.zlc(本节是game_21_point.zlc) (注意是.zlc结尾的文件名,因为zenglrun虚拟机和zengldebug调试器只能运行.zlc里的汇编代码)。
zengl语言涉及到的很多高级的编译原理都可以在《龙书》中找到。
最后的最后,如果转载请注明来源 http://www.zengl.com , OK , 先到这里,休息,休息一下 O(∩_∩)O~