跨平台PHP调试器设计及使用方法——高阶封装
在《跨平台PHP调试器设计及使用方法——协议解析》一文中介绍了如何将pydbgp返回的数据转换成我们需要的数据。我们使用该问中的接口已经可以构建一个简单的调试器。但是由于pydbgp存在的一些问题,以及调试器需要的一些高级功能,我们还需要对这些接口进行组合和封装。(转载请指明出于breaksoftware的csdn博客)
首先我们需要做的便是一个简单的状态机。在前一文中,我们介绍了调试器会处于session(会话)和no session(无会话)阶段,在session阶段又存在如下状态
- 开始调试状态。该状态下,调试器还没有进入PHP代码层面。
- 中断状态。
- 停止中状态。该状态下,调试器已经不在PHP代码层面。
- 停止状态。该状态下,调试器调试该会话已经结束。
- 等待状态。如果PHP执行某操作很耗时,可能会在此时命中该状态。
作为一款调试器,应该隐藏一些底层的操作,而暴露给用户一些他们关心的东西。比如处在开始调试状态下,用户一般不会去关心这个状态,因为它不在PHP代码层面。这个时候我们就需要在状态机中将上述状态通过相关操作转换成用户关心的状态,比如执行Run指令,让调试器命中一些断点,从而处在用户关心的中断状态。再比如调试器处于停止中状态,用户也不会关心这个状态,状态机就会通过相关操作让调试器处于停止状态。而如果调试器处于停止状态,它也是处于不能做有意义事情的状态,状态机就让它退出session阶段,等待其他调试请求的接入。状态机我放在一个线程中执行,并通过信号量与外部通信。
def _debug_routine(self):while False == self._state_machine_stop_event.isSet():time.sleep(0.3)sessions = self._debugger_helper.do("sessions", "")for session_id in sessions:self._debug_session(session_id)if self._state_machine_stop_event.isSet():breakself._debugger_helper.do("quit","")self._reset_all_breakpoint()def _debug_session(self,session_id):select_ret = self._debugger_helper.do("select", session_id)if select_ret["ret"] == 0:print "select error"returnself._accept_user_action_event.clear()self._reset_all_breakpoint()self._add_all_breakpoint()status_ret = self._debugger_helper.do("status","")while False == self._state_machine_stop_event.isSet():if status_ret["ret"] == 0:breakif status_ret["status"] == 1:if len(self._breakpoint_list):self._debugger_helper.do("run","")else:self._debugger_helper.do("step_over","")if status_ret["status"] == 2:self._accept_user_action_event.set()time.sleep(5)if status_ret["status"] == 3:self._accept_user_action_event.clear()self._debugger_helper.do("run","")if status_ret["status"] == 4:self._accept_user_action_event.clear()breakstatus_ret = self._debugger_helper.do("status","")time.sleep(1)if status_ret["status"] != 0:self._debugger_helper.do("exit","")self._pre_variables = {}self._cur_variables = {}self._accept_user_action_event.set()
该状态机中一直通过第4行获取session状态。如果有session可以被调试,则进入_debug_session函数对其进行调试。首先调用select方法,让调试器从no session阶段进入session阶段。如果这个调试会话无法调试,则会退出_debug_session函数,继续等待其他会话的接入。如果进入调试会话,则要根据用户设置情况,对该会话设置若干断点。然后不停通过status指令获取调试器的状态。如果调试器处在开始调试状态,则查看用户设置断点的情况决定是执行run执行还是执行step_over指令。如果用户设置了断点,则我们认为用户希望程序可以直接中断在断点处,于是就直接执行run指令。如果用户没有设置断点,则可能是要从头开始调试,则我们执行step_over指令,让调试进入PHP代码层面。如果调试器处在中断状态,则通知线程外面,可以执行其他指令了。如果处在停止中状态,则直接执行run指令,让该状态直接进入停止状态。如果处于停止状态,则跳出本次调试会话。
解决了状态机问题,我们就要看断点的实现。断点是调试器非常重要的功能,一般我们都会通过断点快速定位问题。由于用户设置断点的时候,调试器可能不处在session阶段,所以没法让调试器设置断点信息。于是我们只能让其先保存在一个数组中
def add_breakpoint(self,param):update_keys = ["id", "state", "hit_value", "hit_condition", "hit_count"]param_de = base64.b64decode(param)param_json = json.loads(param_de)(breakpoint_key, breakpoint_value) = self._get_breakpoint_info(param_json)#breakpoint_set_keys = ["type", "filename", "lineno", "function", "state", "exception", "expression", "temporary", "hit_count", "hit_value", "hit_condition"]if breakpoint_key not in self._breakpoint_list.keys():self._breakpoint_list[breakpoint_key] = breakpoint_valueself._breakpoint_list[breakpoint_key]["state"] = "disable"if self._debugger_helper and self._debugger_helper.is_session():add_ret = self._debugger_helper.do("add_breakpoint", breakpoint_value)if add_ret["ret"] == 1:for item in update_keys:if item in add_ret["breakpoint"].keys():self._breakpoint_list[breakpoint_key][item] = add_ret["breakpoint"][item]return {"ret":1}else:return {"ret":0}return {"ret":1}
通过第5行调用_get_breakpoint_info,将调试器界面传入的断点信息转换成断点唯一性Key和断点信息。然后将该断点信息保存在_breakpoint_list中。如果此时处在session阶段,则调用pydbgp设置该断点,并用返回的信息更新我们保存的断点信息。这儿有个地方需要注意下,我们需要更新的断点信息的Key只是update_keys中的,而像exception和expression等都没更新,为什么?因为pydbgp有个问题,就是如果我们设置了一个条件断点或者异常断点,返回的断点信息将不再包含条件表达式或者异常名。我们只有在用户设置断点的这一个时机来保存这些不再返回的信息。
删除断点也要经过类似的过程
def remove_breakpoint(self,param):param_de = base64.b64decode(param)param_json = json.loads(param_de)print self._breakpoint_list(breakpoint_key, breakpoint_value) = self.get_breakpoint_info_by_param(param_json)print breakpoint_key, breakpoint_valueif self._debugger_helper and self._debugger_helper.is_session():if breakpoint_key in self._breakpoint_list.keys():remove_ret = self._debugger_helper.do("remove_breakpoint", breakpoint_value["id"])if remove_ret["ret"] == 1:del self._breakpoint_list[breakpoint_key]return {"ret":1}else:return {"ret":0}else:if breakpoint_key in self._breakpoint_list.keys():del self._breakpoint_list[breakpoint_key]return {"ret":1}
先将断点的Key和信息分析出来。如果当前处在session阶段,则通知pydbgp删除该断点。pydbgp删除成功后再在_breakpoint_list中删除;否则不删除。如果不处在session阶段,则直接从_breakpoint_list中删除。
断点还有很多其他的细节问题,比如断点key的生成规则,通过行号查找断点信息等本我都不在讲述,详细可以参见代码。
下一个比较实用的就是变量信息查看。在之前我们讲过,变量分为全局变量和栈上变量。如果我们每次都获取全部栈上的变量,其效率是非常低的。所以我让默认的变量信息只显示当前栈的。
def get_variables(self,param):if self._all_stack_parameters:return self._debugger_helper.do("get_variables", param)else:return self._debugger_helper.do("get_cur_stack_variables", param)
为了记录一些变量在执行某步操作前后的变化,我需要在这些步骤前后记录全部变量的值
def step_over(self,param):if self._variable_watch:self._pre_variables = self.get_variables("")ret = self._debugger_helper.do("step_over", param)self._cur_variables = self.get_variables("")else:ret = self._debugger_helper.do("step_over", param)return retdef step_in(self,param):if self._variable_watch:self._pre_variables = self.get_variables("")ret = self._debugger_helper.do("step_in", param)self._cur_variables = self.get_variables("")else:ret = self._debugger_helper.do("step_in", param)return retdef step_out(self,param):if self._variable_watch:self._pre_variables = self.get_variables("")ret = self._debugger_helper.do("step_out", param)self._cur_variables = self.get_variables("")else:ret = self._debugger_helper.do("step_out", param)return retdef run(self,param):if self._variable_watch:self._pre_variables = self.get_variables("")ret = self._debugger_helper.do("run", param)self._cur_variables = self.get_variables("")else:ret = self._debugger_helper.do("run", param)return ret
我使用_variable_watch变量控制是否在每步操作时记录这些信息。因为这些信息量非常大,非常影响调试效率,所以我使用一个配置用来开关这个功能。默认这个功能是关闭的。
还有一个功能用的也稍微多点,就是修改变量值。我们在调试时,可能遇到逻辑运算不在我们预计之内,导致执行的流程出现无法抵达的现象。这个时候我们就可以动态的修改变量的值来影响之后的执行结果。这块功能比较简单
def modify_variable(self,param):param_de = base64.b64decode(param)param_json = json.loads(param_de)if "value" not in param_json.keys() or "name" not in param_json.keys():return {"ret":0}exucte_cmd = param_json["name"] + "=" + base64.b64decode(param_json["value"])data = self._debugger_helper.do("eval", exucte_cmd)return data
最后一个是我在实践中发现的很必要的一个功能:请求记录。我们调试一个问题时,请求可能来源于我们在网页中输入数据并提交,如果一次调试出问题,想再次调试则可能要重新填写页面。这样我做了一个请求记录功能,用户可以在下次请求时发送和上次一样的请求
def save_request(self,param):param_de = base64.b64decode(param)db = request_db()if db.is_request_name_exist(param_de):return {"ret":0, "msg":"name exist"}variables = self.get_variables("")get_data_org = self._search_variable(variables, "$_GET")post_data_org = self._search_variable(variables, "$_POST")get_data_new = {}if "value" in get_data_org.keys():get_data_new = self.generate_get_request_map(get_data_org["value"], "$_GET['", "']")post_data_new = {}if "value" in post_data_org.keys():post_data_new = self.generate_get_request_map(post_data_org["value"], "$_POST['", "']")all_data = {"get":get_data_new, "post":post_data_new, "url":""}db.add_request(param_de, all_data)return {"ret":1}
经过上述封装,我们便可以在界面层通过简单的调用实现丰富的功能。下一博文我将讲解界面相关内容。
相关文章:

Oracle的口令文件(passwordfile)的讲解(摘录)
初学oracle,很多概念迷糊,今天看到这文章,让我有一个比较清晰的认识。转载[url]http://www.itpub.net/viewthread.php?tid906008&extra&page1[/url]1、os认证oracle安装之后默认情况下是启用了os认证的,这里提到的os认证…
如何优雅地使用pdpipe与Pandas构建管道?
作者 | Tirthajyoti Sarkar译者 | 清儿爸编辑 | 夕颜出品 | AI科技大本营(ID: rgznai100) 【导读】Pandas 是 Python 生态系统中的一个了不起的库,用于数据分析和机器学习。它在 Excel/CSV 文件和 SQL 表所在的数据世界与 Scikit-learn 或 Te…

第 十 天 : 添 加 硬 盘 和 分 区 挂 载 等
小Q:狼若回头,必有缘由,不是报恩,就是***; 事不三思必有败,人能百忍则无忧。今天的进度虽然慢了,但是学习状态还是一如往常,只不过今天遇到了不少新的知识点,需要好好想想…
从4个月到7天,Netflix开源Python框架Metaflow有何提升性能的魔法?
作者 | Rupert Thomas译者 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导语】Metaflow 是由 Netflix 开发,用在数据科学领域的 Python框架,于 2019 年 12 月正式对外开源。据介绍,Metaflow 解决…
SOA标准发展混乱 国内业务缺少经验
近年来,SOA已经成为国际及我国信息技术领域的重大热点之一。从2005年至今,SOA逐渐成为影响中国IT系统构建的主导思想。从2006年开始,SOA的建设方法已在我国部分行业信息化项目中开始得以越来越广泛的应用。 但热潮背后, SOA概念在…
跨平台PHP调试器设计及使用方法——界面设计和实现
一个优秀的交互设计往往会影响一个产品的命运。在设计这款调试器时,我一直在构思这款调试器该长什么样子。简单、好用是我设计的原则,于是在《跨平台PHP调试器设计及使用方法——立项》一文中,我给出了一个Demo。之后实现的效果也与之变化并不…

AJAX安全-Session做Token
个人思路,请大神看到了指点 个人理解token是防止扫号机或者恶意注册、恶意发表灌水,有些JS写的token算法,也会被抓出来被利用,个人感觉还是用会过期的Session做token更好,服务器存储,加载到客户端页面&…
跨平台PHP调试器设计及使用方法——使用
经过之前六篇博文的分析和介绍,大家应该对这套调试器有个初步的认识。本文我将讲解它的使用方法。(转载请指明出于breaksoftware的csdn博客) 上图是该软件界面的布局,我们之后的讲解也将围绕着这些功能展开。 文件夹管理 在查看一…
管理7k+工作流,月运行超10000万次,Lyft开源的Flyte平台意味着什么?
作者 | Allyson Gale译者 | 刘畅编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】Flyte 平台可以更容易的创建并发,可伸缩和可维护的工作流,从而进行机器学习和数据处理。Flyte 已有三年多的训练模型和数据处理经…

Jmeter组件执行顺序与作用域
一、Jmeter重要组件: 1)配置元件---Config Element: 用于初始化默认值和变量,以便后续采样器使用。配置元件大其作用域的初始阶段处理,配置元件仅对其所在的测试树分支有效,如,在同一个作用域…
跨平台PHP调试器设计及使用方法——拾遗
之前七篇博文讲解了跨平台PHP调试器从立项到实现的整个过程,并讲解了其使用方法。但是它们并不能全部涵盖所有重要内容,所以新开一片博文,用来讲述其中一些杂项。(转载请指明出于breaksoftware的csdn博客) 触发调试的…
召唤超参调优开源新神器:集XGBoost、TensorFlow、PyTorch、MXNet等十大模块于一身...
整理 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】Optuna是一款为机器学习任务设计的自动超参数优化软件框架,是一款按运行定义(define-by-run) 原则设计的优化软件,允许用户动态地调整搜索空间&#…

Linux下的Silverlight:Moonlight 1.0 Beta 1发布了
Moonlight是微软Silverlight的一个开源实现,其目标平台是Linux与Unix/X11系统。自从2007年9月开始,Moonlight就在Mono项目下进行了开发,它是由Novell发起并资助的。现在,Moonlight 1.0 Beta 1已经向公众发布了。 Novell和Mono宣布…
在visual studio 2010中调用ffmpeg
转自:http://blog.sina.com.cn/s/blog_4178f4bf01018wqh.html 最近几天一直在折腾ffmpeg,在网上也查了许多资料,费了不少劲,现在在这里和大家分享一下。 一、准备工作本来是想自己在windows下编译ffmpeg生成lib、dll等库文件的&am…
无线路由器与无线AP的区别
摆脱线缆的羁绊,手捧一杯香醇的咖啡在家中的任何角落都可以无拘无束和网友谈天说地──这就是无线的魅力!在无线网络迅猛发展的今天,无线局域网(Wireless Local-Area Network,简称WLAN)已经成为许多SOHO家庭…

Simple Dynamic Strings(SDS)源码解析和使用说明一
SDS是Redis源码中一个独立的字符串管理库。它是由Redis作者Antirez设计和维护的。一开始,SDS只是Antirez为日常开发而实现的一套字符串库,它被使用在Redis、Disque和Hiredis等作者维护的项目中。但是作者觉得这块功能还是比较独立的,应该让其…
“不会Linux,到底有多危险?”骨灰级成程序员:基本等于自废武功!
说起程序员的必备技能,我想大家都可以说很多,比如:算法、数据结构、数学、编程语言等等。对于程序员来讲,这些底层能力固然重要,但是,工具同样也是如此,比如常被大家所忽视的:Linux。…

“Uncaught TypeError: string is not a function”
http://www.cnblogs.com/haitao-fan/archive/2013/11/08/3414678.html 今天在js中写了一个方法叫做search(),然后点击按钮的时候提示: “Uncaught TypeError: string is not a function” 百思不得其解啊,我的js木有问题啊啊.... 后来才发现酱…

关于Nikon Ai AF 28mm F1.4D遮光罩的问题
-- 好不容易找到百变妖,确实比较妖!!遮光罩不好找,原厂推荐的HK-7基本属于古董中的古董。 爬文很久,终于找到一篇国外的介绍,说可以用HK-4代替,比HK-7效果更好,而且可以用85mm 1.4D-…

Simple Dynamic Strings(SDS)源码解析和使用说明二
在《Simple Dynamic Strings(SDS)源码解析和使用说明一》文中,我们分析了SDS库中数据的基本结构和创建、释放等方法。本文将介绍其一些其他方法及实现。(转载请指明出于breaksoftware的csdn博客) 字符串连接 SDS库提供下面两种方法进行字符串…
亚马逊机器学习服务:深入研究AWS SageMaker
作者 | Manish Manalath译者 | Shawn编辑 | Carol出品 | AI科技大本营(ID: rgznai100) 机器学习是一个从数据中发现模式的强大概念。但是,如果您尝试过从零开始构建机器模型,那么您一定知道设计一个可扩展的机器学习工作流是多大的…

Java Timer 定时器的使用
一、延时执行首先,我们定义一个类,给它取个名字叫TimeTask,我们的定时任务,就在这个类的main函数里执行。 代码如下:package test;import java.util.Timer;public class TimeTaskTest { public static void main(Str…
Redis源码解析——前言
今天开启Redis源码的阅读之旅。对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事。但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它。除了一些高大上的技术,我们…

asp.net客户端脚本验证小技巧
通用的客户端脚本验证 Code//验证客户端function checkclient() { var list document.all; for(var i0 ;i<list.length; i) { var h list[i].hint; if(h ! null && h ! "") { if(list[i].isdrop"…
5个可以帮助你提高工作效率的新AI工具
作者 | Kyrylo Lyzanets译者 | 火火酱编辑 | Carol出品 | AI科技大本营(ID: rgznai100) 毫无意义的新闻、故事和活动会占用你每天多少的工作时间?假如你是一名需要高绩效的高管或专业人士,如果在工作中可以不分心,那你…

Centos6.5更换163源 epel源
想必大家都遇到过,安装新的centos系统,使用yum去安装软件的时候,要么找不到,要么慢的让人发疯。网上其实办法很多,直接更换163源就ok,但是基本所有的文章都是直接wget下163的源,但是不知道为什么…
图模型+Bert香不香?完全基于注意力机制的图表征学习模型Graph-Bert
作者 | Jiawei Zhang、Haopeng Zhang、Congying Xia、Li Sun译者 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】本文提出了一种全新的图神经网络 Graph-Bert,仅仅基于 Attention 机制而不依赖任何类卷积或聚合操作…

闭关纪要17.Google app engine的简单应用
在上面用了十一篇博客的文章详细的介绍了,Step1账户登录系统之后,从现在开始,继续写闭关纪要,因为Step1账户登录系统也是闭关工作的一部分,因此保留序号,这篇纪要在上次的闭关纪要5.WML,UTF-8,BOM,签名及其…
Redis源码解析——内存管理
在《Redis源码解析——源码工程结构》一文中,我们介绍了Redis可能会根据环境或用户指定选择不同的内存管理库。在linux系统中,Redis默认使用jemalloc库。当然用户可以指定使用tcmalloc或者libc的原生内存管理库。本文介绍的内容是在这些库的基础上&#…

poj_2479 动态规划
题目大意 给定一列数,从中选择两个不相交的连续子段,求这两个连续子段和的最大值。 题目分析 典型的M子段和的问题,使用动态规划的方法来解决。 f[i][j] 表示将A[1...i] 划分为j个不相交连续子串,且A[j]属于第i个子串,…