我们先看一下C/C++和java的调试原理
C/C++调试原理:目前比较流行的调试工具是 GDB 和微软的 Visual Studio 自带的 debugger,在这种 debugger 中,首先,需要编译一个“ debug ”模式的程序,将调试语句编译到该程序中。其次,在调试过程中,debugger 将会深层接入程序的运行,掌握和控制运行态的一些信息,并将这些信息及时返回。
Java调试原理:Java的调试使用Java 虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI)提供的调试接口。包括进入一个函数,进入一个语句的事件,得到当前上下文的API等等,你只要注册了这些事件,调用这些API即可。
从上面可以看出,调试的方式主要有两种,一种是自己写编辑,在目标文件中加入调试代码,由调试代码来做调试工作,另一种是运行该语言的容器本身提供了调试接口。
我们再来看javascript的调试。
目前的javascript调试工具也是用的第二种调试方式,IE和firefox分别提供了javascript的调试接口。
以firefox为例,它提供的调试接口是:jsdIDebuggerService,我们可以向它注入一些调试的钩子,来实现调试,它提供的调试钩子主要有:
钩子类 | 说明 |
breakpointHook | Called when the engine encounters a breakpoint. |
debuggerHook | Called when the engine encounters the debugger keyword. |
debugHook | Called when the errorHook returns false. |
errorHook | Called when an error or warning occurs. |
functionHook | Called before and after a function is called. |
interruptHook | Called before the next PC is executed. |
scriptHook | Called when a jsdIScript is created or destroyed. |
throwHook | Called when an exception is thrown (even if it will be caught.) |
topLevelHook | Called before and after a toplevel script is evaluated. |
包括firebug,venkman,ATF,aptana等调试工具都是实现这些钩子类来做调试的,譬如要做错误定位,就要实现errorHook,errorHook有一个方法
onError(message, fileName, lineNo, pos, flags, errnum, exc)就给出了错误信息,错误文件,行号,位置,等信息。只要向jsdIDebuggerService上注册errorHook,firefox发生js错误时就会调用该方法。
这种调试技术目前已经成熟,但是却都是只能在单个浏览器上调试,我们要在其它浏览器上调试还是比较困难的。
开源项目Javascript Debug Toolkit用另一种原理做了跨浏览器调试javascript的工具,和C/C++的调试一样,它先把javascript编译为带调试代码的目标文件,在浏览器中运行,只要这些调试代码能够做到跨浏览器,整个调试工具就能跨浏览器了。
我们看一下Javascript Debug Toolkit是如何工作的。来看一个例子。
源代码 |
function test(a,b){ var c = a + b; return c; } |
目标调试代码 |
jsdebug("test.js",1);function test(a,b){ jsdebug("test.js",2); var c = a + b; jsdebug("test.js",3); return c; jsdebug("test.js",4);} |
这样在每一行代码执行前都会调用jsdebug函数,该函数可以向调试服务器发调试通知,可以把当前的context传给调试服务器,调试服务器与调试代码进行交互(通过ajax技术),包括单步执行,跟进,跟出等都会可以实现。
这种技术的最大好处就是跨浏览器,目前已经能够在ie,firefox,safari,chrome,opera以及一些掌机浏览器上调试javascript。相信这种技术也会很快被使用起来,会有更多跨浏览器的javascript调试工具出现。
javascript调试原理(一)中讲了javascript的调试原理,本单给出一个javascript调试的客户端模拟实现:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE> New Document </TITLE> <META NAME="Generator" CONTENT="EditPlus"> <META NAME="Author" CONTENT=""> <META NAME="Keywords" CONTENT=""> <META NAME="Description" CONTENT=""> </HEAD> <BODY> <textarea id="jsstr" readonly style="width:400px;height:200px"> function test(){ var a = test1(); var b = test2(); var c = "result is " + a + b; alert(c); } function test1(){ return "test1-->abc"; } function test2(){ return "test2-->abc"; } test(); </textarea> <div id="result"> </div> <script> var debugStr = ""; function jsdebug(resource,line,evalFunc){ var lines = debugStr.split("\n"); var jsLine = lines[line-1]; lines[line-1] = "当前行:--->" + jsLine; var arr = [ "调试代码:", lines.join("\n") ]; var result = document.getElementById("result"); result.innerHTML = result.innerHTML + "<br>执行到第" + line + "行: -->" + jsLine; alert(arr.join("\n")); } function debug(str){ var lines = str.split("\n"); debugStr = str; var codes = []; for(var i=0;i<lines.length;i++){ codes[i] = "jsdebug('test'," + (i+1) + ",function(text){return eval(text)});" + lines[i]; } eval(codes.join('\n')); } debug(document.getElementById('jsstr').value); </script> </BODY> </HTML>
只要把其中的alert换成同步的ajax请求,一可以实现每句执行时把当前的行号,context等 信息传给调试服务器。
Javascript Debug Toolkit的客户端原理是在这个基础上的,下一篇将展开讲一下这种原理会遇到的问题及解决方法。
javascript调试原理(二)中给出一个模拟客户端调试的例子,在客户端有两个问题:
1.如何获得当前的context?
2.如何做resume,stepinto,stepreturn,stepover?
本章围绕着这两个问题展开讨论
1.如何获得当前的context
我们先看一段代码:
function test(){ this.a = "a"; var b = "b"; }
那么在进入test之后,如何获得a和b的值呢?
a的值比较简单,只要把this传过去,通过for...in语句就可以获得,但是b呢?它相当于一个私有变量,在外面是不能访问的,要访问b只能通过在b所在的作用域中获得,因此我们在插入前面的每一行中要加上一个eval函数,eval函数的作用域是当前行的,所以可以获得当前行的上下文。所以在第二章中才会在每一行加上
function(text){try{return eval(text)}catch(e){}});
我们把a,b,c加上看一下效果
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE> New Document </TITLE> <META NAME="Generator" CONTENT="EditPlus"> <META NAME="Author" CONTENT=""> <META NAME="Keywords" CONTENT=""> <META NAME="Description" CONTENT=""> </HEAD> <BODY> <textarea id="jsstr" readonly style="width:400px;height:200px">function test(){ var a = test1(); var b = test2(); var c = "result is " + a + b; alert(c); } function test1(){ return "test1-->abc"; } function test2(){ return "test2-->abc"; } test();</textarea> <div id="result"> </div> <script> var debugStr = ""; function jsdebug(resource,line,evalFunc){ var lines = debugStr.split("\n"); var jsLine = lines[line-1]; lines[line-1] = "当前行:--->" + jsLine; var arr = [ "调试代码:", lines.join("\n"), "==========context===========", "a = " + evalFunc("a"), "b = " + evalFunc("b"), "c = " + evalFunc("c"), ]; var result = document.getElementById("result"); result.innerHTML = result.innerHTML + "<br>执行到第" + line + "行: -->" + jsLine; alert(arr.join("\n")); } function debug(str){ var lines = str.split("\n"); debugStr = str; var codes = []; for(var i=0;i<lines.length;i++){ codes[i] = "jsdebug('test'," + (i+1) + ",function(text){try{return eval(text)}catch(e){}});" + lines[i]; } eval(codes.join('\n')); } debug(document.getElementById('jsstr').value); </script> </BODY> </HTML>
在执行到第3,4,5行的时候可以看到a,b,c的值
这里又有一个问题:我怎么知道函数中有哪些变量?
这就要用到arguments.callee.caller了,把函数当成一个字条串解析,解析出函数的输入参数及定义的变量,就能拿到一个变量名的集合,然后再一个一个地取值。具体的算法这里就不给出了。
再说第二个问题,如何控制stepinto,stepreturn,resume
我们需要在客户端维护一个函数的调用栈,这个栈中记录着每一步的调用,在执行的时候根据栈中存的信息和当前的上下文比较,就能拿到当前语句究竟是stepover还是stepinto还是stepreturn。
那么如何做resume呢,客户端维护一份断点列表,执行每一句的时候判断是否到了断点,不在断点就继续执行,到了断点就停止。
这部分内容也不再给出详细代码,请参照Javascript Debug Toolkit的源代码
开源项目--跨浏览器的javascript调试工具Javascript Debug Toolkit(jsdt)
http://code.google.com/p/jsdt/
jsdt是一个eclipse的插件,要求在eclipse3.2,jre1.5以上运行,可以进行跨浏览器的javascript调试。
jsdt在eclipse中设置断点,在浏览器中运行,执行到断点时自行中断到调试器
jsdt可以单步调试,可以查看当前函数的context
jsdt可跨浏览器调试,目前的浏览器ie,firefox,safari,chrome都支持,其中,因为chrome的bug,css和image不能正常显示
调试的效果图如下:
设置断点
中断
stepOver
stepInto
stepReturn
jsdt包含以下几个部分
1.js引擎,基于rhino引擎,解析js,确定哪一行可以加断点,哪一行不能加断点
2.jsdt server,是一个http服务器,接管客户端的所有请求
3.jsdt client,客户端引擎
4.jsdt debug implement,实现了一套eclipse插件的接口和ui,譬如设置断点等。
jsdt的基本原理如下:
1.jsdt提供一个http代理,把要访问的文件或url的地址转换成jsdt的server地址
2.jsdt的server在送给浏览器的每一行javascript代码前加上调试的代码,该调试代码发送ajax请求到jsdt的server,将当前的上下文送给jsdt
3.jsdt根据上下文在eclipse中中断,显示上下文等。
4.jsdt根据用户的指令(resume,stepinto,stepover,stepreturn)等回答ajax请求
5.jsdt的客户端引擎根据server发的指令进行下一步操作
原文转载自:http://www.iteye.com/topic/299025