在之前v1.0.5版本的时候,为了方便开发一些小游戏,曾写过一个简单的调试功能,不过那时候写出来的调试功能破坏了代码的结构,比较凌乱,不方便维护,功能也很有限,所以从v1.2.0嵌入式版本开始,就一直没做调试功能...

    在之前v1.0.5版本的时候,为了方便开发一些小游戏,曾写过一个简单的调试功能,不过那时候写出来的调试功能破坏了代码的结构,比较凌乱,不方便维护,功能也很有限,所以从v1.2.0嵌入式版本开始,就一直没做调试功能。

    当前v1.4.0版本所采用的调试变量信息的方法是在当前虚拟机里,再新建一个专用于调试的虚拟机,比如要查看obj.key成员的信息,就是将obj.key这段字符串传递给新建的调试虚拟机,在该虚拟机里对这段字符串脚本进行解析,构建语法树,生成对应的汇编指令,然后执行,将执行后的结果存储到新增的DEBUG调试寄存器里,这样外部的程序就可以通过相应的API接口从DEBUG调试寄存器里提取出obj.key的值来。

    由于这种方式是在独立的调试虚拟机里运行的,所以不会和原虚拟机发生冲突,善后清理工作比较容易,也不会破坏原有的C代码结构,调试有关的功能绝大部分都放在新增的zenglDebug.c文件里,该文件里的C函数名都是以zenglDebug_开头的,方便维护调试相关的代码。

    另外v1.0.5的调试器只能设置一个断点,而当前v1.4.0的调试器则可以设置任意多的断点,还可以设置条件断点,日志断点,以及实现单步执行,执行到返回等功能。

    v1.4.0版本的下载地址如下:

    github项目地址为:https://github.com/zenglong/zengl_language/  可以选择右侧的Download Zip或Clone in Desktop下载源码。

    百度盘共享链接地址:http://pan.baidu.com/s/1bn7XI6V 这是一个zip压缩包,解压后可以看到linux ,windows,mac和android目录,分别为三个PC操作系统和Android系统下的源码和编译器配置,windows目录中既包含用于vs2008的sln解决方案,还包含用于vc6的dsw工作空间,android目录中包含android工程文件, 需要android ndk来编译和调试,该zip压缩包中还包括apk安装包,安装该apk后,可以在手机或平板(平板没测试)上编写运行简单的zengl脚本(只有几个基础的模块函数,其他的模块函数需要自行在C源码中添加)。

    sourceforge.net上的项目地址为:https://sourceforge.net/projects/zengl/files/ (里面有所有历史版本的压缩包,包括v1.4.0的zip包,该包里也含有apk安装包)。

    在linux目录中的makefile有两种编译方式,一种动态库的编译方式:默认的make生成的就是动态库,由于生成动态库libzengl.so后,会copy这个.so文件到/usr/lib目录中,所以动态库的方式需要root权限,还有一种是make static来生成静态库,静态库就不需要root权限。(使用详情参考linux目录里的usage.txt)

    mac目录中只包含makefile文件和测试用的zengl脚本文件,所以在mac下直接make即可,需要清理中间文件时,可以使用make clean来清理。(使用详情参考mac目录里的usage.txt)

    有关android的NDK编译执行方法可以参考v1.3.1里的文章 (因为android是从1.3.1开始引入的)

    在windows,linux,mac下运行脚本时,可以在最后加入-d参数来开启调试(程序会在开始执行时生成一个调试shell,可以输入调试命令,如可以用h命令来查看帮助信息等),例如linux下可以输入:./zengl test.zl -d

    下面看下v1.4.0所做的修改(这些信息可以在readme或linux的usage.txt中查看到,也可以直接在上面的github链接中查看到):

    zengl v1.4.0 , 添加
    zenglApi_Debug,zenglApi_GetDebug,
    zenglApi_DebugSetBreak,zenglApi_DebugSetBreakEx,
    zenglApi_DebugGetBreak,zenglApi_DebugDelBreak,
    zenglApi_DebugSetBreakHandle,zenglApi_DebugSetSingleBreak,
    zenglApi_DebugGetTrace
    一共9个和调试有关的接口,通过这些接口实现了调试变量信息,设置断点,查看脚本函数的堆栈调用信息,设置条件断点,设置日志断点,
    设置断点次数,单步步入,单步步过,执行到返回等交互式调试功能。

    调试接口的具体使用方法可以参考main.c里的代码。另外还添加了zenglApi_ReAllocMem和zenglApi_makePathFileName两个辅助接口。

    将脚本变量的整数类型由int改为long,这样在64位linux系统里,long就是和指针一样的64位的大小,方便64位移植,防止GCC编译出现指针到int类型大小转换的警告。

    修复数组元素或类成员没初始化时,加加减减无效的BUG。

    由该版本和SDL库,得到一个zengl_SDL项目,可以用zengl编写一些简单的类似俄罗斯方块,21点之类的小游戏,详情见zengl_SDL项目(项目地址:https://github.com/zenglong/zengl_SDL)。另外,zengl_SDL项目里的游戏脚本在windows下是GBK编码的,linux下是utf8编码的,和之前v1.0.5与v1.0.6的不一样(这两个版本的游戏脚本在两个系统下都是UTF8编码),windows下使用GBK编码就不会在cmd终端里出现乱码。

    作者:zenglong
    时间:2014年1月29日
    官网:www.zengl.com

    上面提到了新增的9个调试相关的API接口函数,可以在zengl_exportfuns.h头文件里查看到这些接口的函数声明和简单的注释信息:

/*API接口,调试接口*/
ZL_EXPORT ZL_EXP_INT zenglApi_Debug(ZL_EXP_VOID * VM_ARG,ZL_EXP_CHAR * debug_str);

/*API接口,获取调试寄存器里的调试结果*/
ZL_EXPORT ZL_EXP_INT zenglApi_GetDebug(ZL_EXP_VOID * VM_ARG,ZENGL_EXPORT_MOD_FUN_ARG * retval);

/*API接口,设置调试断点*/
ZL_EXPORT ZL_EXP_INT zenglApi_DebugSetBreak(ZL_EXP_VOID * VM_ARG,ZL_EXP_CHAR * filename,ZL_EXP_INT line,
											ZL_EXP_CHAR * condition,ZL_EXP_CHAR * log,ZL_EXP_INT count,ZL_EXP_BOOL disabled);

/*API接口,设置调试断点的扩展函数,直接根据指令PC值来进行设置*/
ZL_EXPORT ZL_EXP_INT zenglApi_DebugSetBreakEx(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT pc,ZL_EXP_CHAR * condition,ZL_EXP_CHAR * log,ZL_EXP_INT count,ZL_EXP_BOOL disabled);

/*API接口,获取index索引对应的断点信息*/
ZL_EXPORT ZL_EXP_INT zenglApi_DebugGetBreak(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT index,ZL_EXP_CHAR ** filename,ZL_EXP_INT * line,
											ZL_EXP_CHAR ** condition,ZL_EXP_CHAR ** log,ZL_EXP_INT * count,ZL_EXP_BOOL * disabled,ZL_EXP_INT * pc);

/*API接口,删除index索引对应的断点*/
ZL_EXPORT ZL_EXP_INT zenglApi_DebugDelBreak(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT index);

/*API接口,设置调试断点触发时调用的用户自定义函数*/
ZL_EXPORT ZL_EXP_INT zenglApi_DebugSetBreakHandle(ZL_EXP_VOID * VM_ARG,ZL_EXP_VOID * handle,ZL_EXP_VOID * conditionErrorHandle,ZL_EXP_BOOL break_start,ZL_EXP_BOOL OutputDebugInfo);

/*API接口,设置单步中断,isStepIn参数不为0则为单步步入,否则为单步步过*/
ZL_EXPORT ZL_EXP_INT zenglApi_DebugSetSingleBreak(ZL_EXP_VOID * VM_ARG,ZL_EXP_BOOL isStepIn);

/*API接口,获取脚本函数的堆栈调用信息*/
ZL_EXPORT ZL_EXP_INT zenglApi_DebugGetTrace(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT * argArg,ZL_EXP_INT * argLOC,ZL_EXP_INT * argPC,
											ZL_EXP_CHAR ** fileName,ZL_EXP_INT * line,ZL_EXP_CHAR ** className,ZL_EXP_CHAR ** funcName);



    在zenglApi.c文件的底部可以看到这些接口函数的具体定义。例如第一个zenglApi_Debug接口的定义如下:

/*API接口,调试接口*/
ZL_EXPORT ZL_EXP_INT zenglApi_Debug(ZL_EXP_VOID * VM_ARG,ZL_EXP_CHAR * debug_str)
{
	ZENGL_VM_TYPE * VM = (ZENGL_VM_TYPE *)VM_ARG;
	ZENGL_VM_TYPE * DebugVM = ZL_NULL;
	ZL_INT retcode;
	ZL_CHAR * ApiName = "zenglApi_Debug";
	if(VM->signer != ZL_VM_SIGNER) //通过虚拟机签名判断是否是有效的虚拟机
		return -1;
	switch(VM->ApiState)
	{
	case ZL_API_ST_OPEN:
	case ZL_API_ST_RESET:
		VM->run.SetApiErrorEx(VM_ARG,ZL_ERR_VM_API_INVALID_CALL_POSITION, ApiName , ApiName);
		return -1;
		break;
	}
	if(VM->debug.DeubugVM == ZL_NULL)
	{
        /*** 
            下面用zenglApi_Open接口新建一个专用于调试的虚拟机,返回的
            虚拟机指针存放在父虚拟机VM里的debug调试器里的DeubugVM成员里,
            本来应该写为DebugVM的,但是写代码时没注意,写成了DeubugVM
            (那就将错就错吧,就用DeubugVM名字来表示调试器新建的虚拟机好了) 
        ***/
		DebugVM = VM->debug.DeubugVM = (ZENGL_VM_TYPE *)zenglApi_Open();
		if(DebugVM == ZL_NULL)
		{
			VM->run.SetApiErrorEx(VM_ARG,ZL_ERR_VM_API_DEBUGVM_OPEN_FAILED, ApiName , ApiName);
			return -1;
		}
		//设置调试器编译时需要的存在于父虚拟机中的资源
        /*** 
            下面拷贝父虚拟机里的HASH表资源,全局符号表,局部符号表资源,
            类信息等资源到调试器里
            这样调试器的虚拟机才能构建出正确的汇编指令字节码出来
        ***/
		ZENGL_SYS_MEM_COPY(&DebugVM->compile.HashTable,&VM->compile.HashTable,ZL_SYM_HASH_TOTAL_SIZE * sizeof(ZL_INT));
		DebugVM->compile.def_table = VM->compile.def_table;
		DebugVM->compile.SymGlobalTable = VM->compile.SymGlobalTable;
		DebugVM->compile.SymLocalTable = VM->compile.SymLocalTable;
		DebugVM->compile.SymClassTable = VM->compile.SymClassTable;
		DebugVM->compile.SymClassMemberTable = VM->compile.SymClassMemberTable;
		//将编译过程中的查询符号信息的函数重定向到调试器自定义的函数
        /***
            由于调试器在编译时,某些信息还是需要从父虚拟机里获取,
            因此需要将编译过程里的一些查询函数重定向到调试器自定义的函数,
            通过将compile编译器里的查询函数的函数指针定义为debug调试器里
            的函数指针,从而完成重定向。
        ***/
		DebugVM->compile.ReplaceDefConst = DebugVM->debug.ReplaceDefConst;
		DebugVM->compile.lookupDefTable = DebugVM->debug.lookupDefTable;
		DebugVM->compile.SymLookupID = DebugVM->debug.SymLookupID;
		DebugVM->compile.SymLookupID_ForDot = DebugVM->debug.SymLookupID_ForDot;
		DebugVM->compile.SymLookupClass = DebugVM->debug.SymLookupClass;
		DebugVM->compile.SymLookupClassMember = DebugVM->debug.SymLookupClassMember;
		DebugVM->run.LookupModFunTable = DebugVM->debug.LookupModFunTable;
	}
	if(debug_str == ZL_NULL)
	{
		VM->run.SetApiErrorEx(VM_ARG,ZL_ERR_VM_API_INVALID_DEBUG_STR, ApiName , ApiName);
		return -1;
	}
        /***
            将debug调试器的DeubugPVM成员设置为VM父虚拟机的指针
            这样调试器就可以通过DeubugPVM成员来获取到父虚拟机里的
            相关信息了
        ***/
	DebugVM->debug.DeubugPVM = VM;
	ZENGL_SYS_MEM_COPY(DebugVM->vm_main_args,VM->vm_main_args,sizeof(ZENGL_EXPORT_VM_MAIN_ARGS));
        /***
            下面调试器里的output_debug_info成员用于控制
            是否输出符号表等调试信息到用户自定义函数
            该成员可以在zenglApi_DebugSetBreakHandle接口中进行设置
        ***/
	if(VM->debug.output_debug_info == ZL_FALSE)
		DebugVM->vm_main_args->flags &= ~(ZL_EXP_CP_AF_OUTPUT_DEBUG_INFO);
	else
		DebugVM->vm_main_args->flags |= (ZL_EXP_CP_AF_IN_DEBUG_MODE | ZL_EXP_CP_AF_OUTPUT_DEBUG_INFO);
	DebugVM->isinApiRun = ZL_TRUE;
	if(DebugVM->run.mempool.isInit == ZL_FALSE)
		DebugVM->run.init(DebugVM); //编译器中需要对解释器输出汇编指令,所以在此初始化解释器
	DebugVM->compile.source.run_str = debug_str;
	DebugVM->compile.source.run_str_len = ZENGL_SYS_STRLEN(debug_str);
        /***
            下面通过SetFunInfo函数设置调试器所在的函数信息,
            通过这些信息,就可以调试函数或类函数里的局部变量信息
        ***/
	DebugVM->debug.SetFunInfo(DebugVM); //设置调试所在的函数或类函数环境
	DebugVM->compile.AsmGCStackPush(DebugVM,DebugVM->compile.SymClassTable.global_classid,ZL_ASM_STACK_ENUM_FUN_CLASSID);
        /***
            通过debug调试器自定义的Compile函数来编译debug_str对应的
            调试字符串
        ***/
	retcode = DebugVM->debug.Compile(DebugVM,ApiName,DebugVM->vm_main_args);
	DebugVM->compile.AsmGCStackPop(DebugVM,ZL_ASM_STACK_ENUM_FUN_CLASSID,ZL_TRUE);
	if(retcode == 0) //如果编译成功,则进入解释器
	{
		DebugVM->ApiState = ZL_API_ST_RUN; //设置为RUN状态
        /***
            通过debug调试器自定义的Run函数来运行debug_str对应的调试字符串
            debug自定义的函数如这里的Run和上面的Compile函数都定义在
            zenglDebug.c文件里
        ***/
		DebugVM->debug.Run(DebugVM);
		DebugVM->ApiState = ZL_API_ST_AFTER_RUN; //设置为AFTER_RUN状态
	}
	if(retcode == -1)
	{
		VM->run.SetApiErrorEx(VM_ARG, ZL_ERR_VM_API_DEBUG_ERR, zenglApi_GetErrorString(DebugVM));
		zenglApi_Close(DebugVM);
		VM->debug.DeubugVM = ZL_NULL;
		return -1;
	}
        /***
            debug_str调试字符串编译运行完后,通过zenglApi_Close来
            释放调试器虚拟机的资源
        ***/
	zenglApi_Close(DebugVM);
	VM->debug.DeubugVM = ZL_NULL;
	return 0;
}

    上面代码里棕色的注释是这里额外添加的用于进行更详细的说明用的,在源代码里并不存在。

    在main.c里可以查看到上面提到的9个调试API接口的用法,在该文件的main入口函数里,当在脚本名字后面添加了-d参数时,就会调用zenglApi_DebugSetBreakHandle接口来设置当脚本的断点发生中断时,会调用的用户自定义的调试函数:

/**
	用户程序执行入口。
*/
int main(int argc,char * argv[])
{
..................................
    if(argc >= 3 && strcmp(argv[2],"-d") == 0)
	zenglApi_DebugSetBreakHandle(VM,main_debug_break,main_debug_conditionError,ZL_EXP_TRUE,ZL_EXP_FALSE); //设置调试API

    if(zenglApi_Run(VM,argv[1]) == -1) //编译执行zengl脚本
	main_exit(VM,"错误:编译<%s>失败:%s\n",argv[1],zenglApi_GetErrorString(VM));
..................................
}

    上面代码里的zenglApi_DebugSetBreakHandle用于设置用户自定义的断点处理句柄,该接口的第二个参数为断点中断时会调用的用户自定义函数,这里为main_debug_break函数,第三个参数为当条件断点里的条件表达式执行失败时会调用的用户自定义函数,此处为main_debug_conditionError,第四个参数用于判断是否要在脚本的第一条指令处发生中断,第五个参数用于控制调试器的output_debug_info成员,表示是否要在调试器新建的虚拟机里输出语法树,符号表等调试信息。

    main_debug_break函数在main.c文件里的定义如下:

ZL_EXP_INT main_debug_break(ZL_EXP_VOID * VM_ARG,ZL_EXP_CHAR * cur_filename,ZL_EXP_INT cur_line,ZL_EXP_INT breakIndex,ZL_EXP_CHAR * log)
{
...............................................
    /***
        通过while循环处理用户输入的调试命令
    ***/
    while(!exit)
    {
        printf(">>> debug input:");
        /***
            通过getchar函数来获取用户在终端输入的命令
        ***/
        ch = getchar();
        for(i=0;ch!='\n';i++)
        {
        ....................................
            ch = getchar();
        }
        ....................................
        switch(command[0])
        {
        case 'p':
            {
                arg = getDebugArg(str,&start,ZL_EXP_FALSE);
                tmplen = arg != ZL_EXP_NULL ? strlen(arg) : 0;
                if(arg != ZL_EXP_NULL && tmplen > 0)
                {
                    if(arg[tmplen - 1] != ';' && str_count < str_size - 1)
                    {
                        arg[tmplen] = ';';
                        arg[tmplen+1] = STRNULL;
                    }
            /***
                通过zenglApi_Debug接口编译执行p命令后面的表达式
                (调试字符串)
            ***/
                    if(zenglApi_Debug(VM_ARG,arg) == -1)
                    {
                        printf("p调试错误:%s\n",zenglApi_GetErrorString(VM_ARG));
                        continue;
                    }
            /***
                main_print_debug函数里会调用zenglApi_GetDebug接口
                来获取前面zenglApi_Debug接口运行表达式后的结果,
                然后将结果输出到终端显示出来
            ***/
                    main_print_debug(VM_ARG,arg);
                }
                else
                    printf("p命令缺少参数\n");
            }
            break;
        case 'b':
            ...........................................
            /***
                通过zenglApi_DebugSetBreak接口来设置断点
                断点位置为filename文件里的line行,
                count参数为断点次数
            ***/
            if(zenglApi_DebugSetBreak(VM_ARG,filename,line,ZL_EXP_NULL,ZL_EXP_NULL,count,ZL_EXP_FALSE) == -1)
                printf("b命令error:%s\n",zenglApi_GetErrorString(VM_ARG));
            else
                printf("设置断点成功\n");
            }
            break;
        case 'B':
            ...........................................
            /***
                通过zenglApi_DebugGetBreak接口来获取断点的相关信息
                根据第二个参数i即断点索引,来得到该断点的filename文件名,
                line行号,condition条件断点的条件表达式,
                log日志断点的日志表达式,
                count断点次数,disabled是否禁用的信息。
                当i为-1时,该接口将返回内部断点动态数组的size尺寸信息,
                根据该size信息,通过循环就可以得到断点列表,
                该接口最后一个参数为断点所在的指令PC信息,
                这里为ZL_EXP_NULL表示暂没获取
            ***/
            if(zenglApi_DebugGetBreak(VM_ARG,i,&filename,&line,&condition,&log,&count,&disabled,ZL_EXP_NULL) == -1)
                continue;
            ...........................................
            break;
        case 'T':
            ...........................................
            /***
                通过zenglApi_DebugGetTrace接口来获取脚本函数的堆栈调用信息
                第二个参数arg,第三个参数loc及第四个参数pc用于迭代获取
                所有的堆栈信息。
                当arg,loc及pc都为-1时,该接口将得到脚本函数堆栈的第一层信息,
                并设置对应的fileName文件名,
                line行号,className所在的类名,funcName所在的函数名信息,
                同时会得到下一层信息的arg,loc,pc值,
                接着就可以使用新的arg,loc,pc值来循环得到其他层的堆栈信息了。
            ***/
            ret = zenglApi_DebugGetTrace(VM_ARG,&arg,&loc,&pc,&fileName,&line,&className,&funcName);
            ...........................................
            break;
        case 'r':
            ...........................................
            /***
                通过zenglApi_DebugSetBreakEx接口根据指令PC值来设置断点
            ***/
            if(zenglApi_DebugSetBreakEx(VM_ARG,pc,ZL_EXP_NULL,ZL_EXP_NULL,1,ZL_EXP_FALSE) == -1)
                printf("%s",zenglApi_GetErrorString(VM_ARG));
            else
                exit = 1;
            break;
        case 'd':
            {
                ...........................................
                /***
                    通过zenglApi_DebugDelBreak接口来删除index索引对应的断点
                    index索引信息可以通过B命令显示的断点列表中查看到
                ***/
                if(zenglApi_DebugDelBreak(VM_ARG,index) == -1)
                    printf("d命令error:无效的断点索引");
                else
                    printf("删除断点成功");
                ...........................................
            }
            break;
        case 'D':
            {
                ...........................................
                /***
                将zenglApi_DebugSetBreak接口的最后一个参数设为ZL_EXP_TRUE即1,
                则表示禁用filename文件里line行对应的断点
                ***/
                if(zenglApi_DebugSetBreak(VM_ARG,filename,line,condition,log,count,ZL_EXP_TRUE) == -1)
                    printf("D命令禁用断点error:%s",zenglApi_GetErrorString(VM_ARG));
                else
                    printf("D命令禁用断点成功");
                printf("\n");
                ...........................................
            }
            break;
        case 'C':
            {
                ...........................................
                /***
                    zenglApi_DebugSetBreak接口的第4个参数设为
                    newCondition字符串指针
                    (该字符串为用户输入的C命令后面的有效脚本表达式),
                    表示将filename文件里的line行设为条件断点,
                    条件表达式为newCondition对应的字符串
                    C命令的第一个参数是index对应的断点索引,
                    第二个参数才是条件表达式
                    第一个index断点索引加上zenglApi_DebugGetBreak接口
                    就可以得到zenglApi_DebugSetBreak所需的
                    filename和line信息
                ***/
                if(zenglApi_DebugGetBreak(VM_ARG,index,&filename,&line,&condition,&log,&count,&disabled,ZL_EXP_NULL) == -1)
                {
                    printf("C命令error:无效的断点索引\n");
                    continue;
                }
                else
                {
                    if(zenglApi_DebugSetBreak(VM_ARG,filename,line,newCondition,log,count,disabled) == -1)
                        printf("C命令设置条件断点error:%s",zenglApi_GetErrorString(VM_ARG));
                    else
                        printf("C命令设置条件断点成功");
                    printf("\n");
                }
            }
            break;
        case 'L':
            {
                ...........................................
                /***
                    zenglApi_DebugSetBreak接口的第5个参数设为
                    newLog字符串指针
                    (该字符串为用户输入的L命令后面的有效脚本表达式),
                    表示将filename文件里的line行设为日志断点,
                    日志表达式为newLog对应的字符串,
                    L命令的第一个参数是index对应的断点索引,
                    第二个参数才是日志表达式
                    第一个index断点索引加上zenglApi_DebugGetBreak接口
                    就可以得到zenglApi_DebugSetBreak所需的
                    filename和line信息
                ***/
                if(zenglApi_DebugGetBreak(VM_ARG,index,&filename,&line,&condition,&log,&count,&disabled,ZL_EXP_NULL) == -1)
                {
                    printf("L命令error:无效的断点索引\n");
                    continue;
                }
                else
                {
                    if(zenglApi_DebugSetBreak(VM_ARG,filename,line,condition,newLog,count,disabled) == -1)
                        printf("L命令设置日志断点error:%s",zenglApi_GetErrorString(VM_ARG));
                    else
                        printf("L命令设置日志断点成功");
                    printf("\n");
                }
            }
            break;
        case 'N':
            {
                ...........................................
                /***
                    通过zenglApi_DebugSetBreak接口的第6个参数newCount
                    就可以设置filename文件里line行对应断点的断点次数
                ***/
                if(zenglApi_DebugSetBreak(VM_ARG,filename,line,condition,log,newCount,disabled) == -1)
                    printf("N命令设置断点次数error:%s",zenglApi_GetErrorString(VM_ARG));
                else
                    printf("N命令设置断点次数成功");
                printf("\n");
                ...........................................
            }
            break;
        case 's':
            /***
                通过zenglApi_DebugSetSingleBreak接口来设置单步执行
                第二个参数为ZL_EXP_TRUE表示单步步入
                (即遇到函数时,是否追踪到函数里面去)
            ***/
            zenglApi_DebugSetSingleBreak(VM_ARG,ZL_EXP_TRUE);
            exit = 1;
            break;
        case 'S':
            /***
                通过zenglApi_DebugSetSingleBreak接口来设置单步执行
                第二个参数为ZL_EXP_FALSE表示单步步过
                (即遇到函数时,不会进入到函数里面去)
            ***/
            zenglApi_DebugSetSingleBreak(VM_ARG,ZL_EXP_FALSE);
            exit = 1;
            break;
        case 'c':
            exit = 1;
            break;
        case 'h':
            /***
                h命令来查看帮助
            ***/
            printf(" p 调试变量信息 usage:p express\n"
                " b 设置断点 usage:b filename lineNumber\n"
                " B 查看断点列表 usage:B\n"
                " T 查看脚本函数的堆栈调用信息 usage:T\n"
                " d 删除某断点 usage:d breakIndex\n"
                " D 禁用某断点 usage:D breakIndex\n"
                " C 设置条件断点 usage:C breakIndex condition-express\n"
                " L 设置日志断点 usage:L breakIndex log-express\n"
                " N 设置断点次数 usage:N breakIndex count\n"
                " s 单步步入 usage:s\n"
                " S 单步步过 usage:S\n"
                " r 执行到返回 usage:r\n"
                " c 继续执行 usage:c\n");
            break;
        default:
            printf("无效的命令\n");
            break;
        }
    } //while(!exit)
    if(str != ZL_EXP_NULL)
        zenglApi_FreeMem(VM_ARG,str);
    return 0;
}

    上面代码里对主要的调试接口进行了详细说明,这里棕色的注释是额外添加的,在源代码里没有。

    在-d参数下,调试的情况如下所示:


图1

    上图里用h查看帮助信息,T显示当前断点所在的函数的堆栈追踪信息,用p rc4来查看rc4变量的当前值,类变量本质上就是数组,所以在调试变量信息时,数组和类变量的值都是相同的数组形式。

    v1.4.0版本里变量如果是整数的话,在32位系统下为32位的尺寸,在64位GCC下就是64位的大小,这是因为该版本的变量的整数运行时在zengl内部对应的是long类型,而不是以前的int类型,在32位系统下:./zengl test.zl的结果如下:
 

图2

    可以看到,在变量的运行时为整数类型时,当超过32位大小,就会发生溢出。

    在ubuntu 64位系统下:./zengl test.zl的结果如下:
 

图3

    上图显示在64位系统下,整数类型的变量就有64位的大小,就没有发生溢出。

    有关该版本的其他改动请查看github里相关的diff信息。

    OK,到这里,休息,休息一下 o(∩_∩)o~~
上下篇

下一篇: zengl v1.4.1 及zengl_SDL新增的Android工程

上一篇: zengl v1.3.2 编译静态库 Bug和向下兼容处理

相关文章

zengl编程语言v1.0.6 问号冒号选择运算符,endswitch,endclass

zengl v1.6.0-v1.7.1, zenglServer v0.1.0

zengl编程语言v0.0.23,SDL游戏开发,类定义

zengl编程语言v0.0.15变量引用

zengl编程语言v0.0.20 inc加载脚本

zengl v1.8.0 缓存内存中的编译数据,跳过编译过程