在之前v1.0.5版本的时候,为了方便开发一些小游戏,曾写过一个简单的调试功能,不过那时候写出来的调试功能破坏了代码的结构,比较凌乱,不方便维护,功能也很有限,所以从v1.2.0嵌入式版本开始,就一直没做调试功能...
/*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); |
/*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; } |
/** 用户程序执行入口。 */ 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)); .................................. } |
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; } |