最近看 node 源码,记录一下怎么调试
Debugger 是 Node.js (当前版本 v12)自带的独立于运行进程之外的一个监听客户端,基于 v8 inspect 实现。
debugger 一个 node inspect myScript.js
➜ Node.js node inspect myScript.js
< Debugger listening on ws://127.0.0.1:9229/1645d9a3-b0c1-44f9-aad2-0e2a6c751508
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in myScript.js:2
1 // myscript.js
> 2 global.x = 5;
3 setTimeout(() => {
4 debugger;
debug>
可以看到 debugger 会起一个监听进程,默认情况下侦听 127.0.0.1:9229,并且有一个 uuid 标识。
debugger 相关命令:
cont
, c
: Continue executionnext
, n
: Step nextstep
, s
: Step inout
, o
: Step outpause
: Pause running code (like pause button in Developer Tools)如果代码执行完了可以用 debug>restart
重启脚本
run
: Run script (automatically runs on debugger’s start)restart
: Restart scriptkill
: Kill script1、通过 debug>
提示符使用 setBreakpoint
( sb
) https://nodejs.org/api/debugger.html#debugger_breakpoints 函数:setBreakpoint(module_file_name, line_number)
。
debug> sb(5)
1 // myscript.js
2 global.x = 5;
3 setTimeout(() => {
4 debugger;
> 5 console.log('world');
6 }, 1000);
7 console.log('hello');
debug>
2、代码中加 debugger
获取所有断点
debug> breakpoints
#0 myScript.js:5
清除断点
debug> cb(5)
Could not find breakpoint at 5:undefined
debug> cb('myScript', 5)
debug>
查看变量值
debug> repl
Press Ctrl + C to leave debug repl
> global.x
undefined
debug> n
break in myScript.js:3
1 // myscript.js
2 global.x = 5;
> 3 setTimeout(() => {
4 debugger;
* 5 console.log('world');
debug> repl
Press Ctrl + C to leave debug repl
> global.x
5
>
watch 变量 watch('expr')
, expr 是变量名,watch 接收参数 字符串
➜ Node.js node inspect myScript.js
< Debugger listening on ws://127.0.0.1:9229/14c83ac5-c5f5-4a19-a30c-2c573222f2f2
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in myScript.js:2
1 // myscript.js
> 2 global.x = 5;
3 setTimeout(() => {
4 debugger;
debug> watch('global')
debug> watch('global.x')
debug> watch('window')
debug> n
break in myScript.js:3
Watchers:
0: global =
{ global: global,
clearInterval: ,
clearTimeout: ,
setInterval: ,
setTimeout: ,
... }
1: global.x = 5
2: window =
ReferenceError: window is not defined
at eval (eval at <anonymous> (/Users/albertaz/Documents/learn/Node.js/myScript.js:3:1), <anonymous>:1:1)
at Object.<anonymous> (/Users/albertaz/Documents/learn/Node.js/myScript.js:3:1)
at Module._compile (internal/modules/cjs/loader.js:1154:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1177:10)
at Module.load (internal/modules/cjs/loader.js:1001:32)
at Function.Module._load (internal/modules/cjs/loader.js:900:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
at internal/main/run_main_module.js:18:47
1 // myscript.js
2 global.x = 5;
> 3 setTimeout(() => {
4 debugger;
5 console.log('world');
debug>
修改变量值
➜ Node.js node inspect myScript.js
< Debugger listening on ws://127.0.0.1:9229/ced21af3-6f9c-4bee-8757-769ebb4ff3ea
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in myScript.js:2
1 // myscript.js
> 2 global.x = 5;
3 setTimeout(() => {
4 debugger;
debug> watch('global.x')
debug> n
break in myScript.js:3
Watchers:
0: global.x = 5
1 // myscript.js
2 global.x = 5;
> 3 setTimeout(() => {
4 debugger;
5 console.log('world');
debug> repl
Press Ctrl + C to leave debug repl
> global.x = 7
7
debug> n
break in myScript.js:7
Watchers:
0: global.x = 7
5 console.log('world');
6 }, 1000);
> 7 console.log('hello');
debug>
原理还是先运行node进程,再使用 VS Code 去连接 Node 进程,具体做法可以通过在 vscode中添加一个 launch.json 文件
https://code.visualstudio.com/docs/editor/debugging
https://code.visualstudio.com/docs/nodejs/nodejs-debugging
node --inspect myScript.js
注意是 **--inspect
**
➜ Node.js node --inspect myScript.js
Debugger listening on ws://127.0.0.1:9229/c4018064-1abd-4e6f-b0de-bf2dc7c3170d
For help, see: https://nodejs.org/en/docs/inspector
打开 Chrome 并在地址栏中输入 about:inspect
chrome devtool 没法调 worker threads。调 worker threads 可以用 ndb
speedscope
https://github.com/jlfwong/speedscope/wiki/Importing-from-Node.js
首先看 build.md
,编译node debug版本的执行文件,因为发行版本的 node 是不支持调试的,所以我们需要自己通过源码构建一份可调试的 node
支持的平台(有多个级别)
重点关注下:
gcc
and g++
>= 6.3 or newer, orCentOS 下配好依赖
sudo yum install python gcc-c++ make
项目构建通过 make
进行管理,源码中已经有了 configure
文件
$ ./configure # 确认 xcode clt 和 python
$ ./configure --debug # 默认的编译配置是没有启用调试模式的,因此,我们需要在执行 ./configure 时加上 --debug 就可以生成可调试的编译配置项
$ make -j4 # 让 make 运行四个并行编译job,加速编译
#测试覆盖率编译
$ ./configure --coverage
$ make coverage
./configure --debug
之后会生成两份二进制文件 一分在 out/Release/node
一份在 out/Debug/node
为了方便构建,建了一个 debug-build.sh
#!/bin/bash
./configure --debug
make -C out BUILDTYPE=Debug -j4
echo "let's go"
并给他可执行的权限再执行:
chmod +x debug-build.sh
./debug-build.sh
mac 需要留下足够空间,编译构建比较耗资源,如果只执行 ./configure
(只生成 out/Release/node
)需要10g,./configure --debug
会干掉 20g (我的剩余空间从 39g 到 19g)
同时也比较耗时间。
build.md
说构建后给out目录下的 node 可执行文件设置 firewall rules,避免测试时不断弹出连接网络的提示,并可以在项目根目录下 创建 node 符号执行 。
sudo ./tools/macos-firewall.sh
但我还没遇到。
创建一个用于调试的项目,随便写点,作为入口的:
console.log('hello world');
用编译好的 node来执行:
./out/Debug/Node --inspect-brk=9229 demo/helloWorld.js #inspect-brk让他在程序加载前就进断点
会看到
➜ node git:(master) ✗ ./out/Debug/Node --inspect-brk=9229 demo/helloWorld.js
Debugger listening on ws://127.0.0.1:9229/33cc9b77-fa44-4b4c-adae-7b5126c088c6
For help, see: https://nodejs.org/en/docs/inspector
然后打开 vscode
vscode 启动 debug 需要配置 launch.json, 其中 request 支持两种:
vscode 需要先装好 c++ 插件
重新创建或添加 launch.json,type 选 cppdbg
通过以上操作基本可以感觉到,无论哪种防范,调试思路都是一致的:
在 nodejs 侧启动一个 websocket 服务提供运行时信息,在用户调试侧再启动一个 websocket 服务客户端触发各种调试操作。两个 websocket 之间通过调试协议通信。
用 debugger 调试时,前面提到他是基于 v8-inspect 实现的,v8-inspect 的主要作用就是实现 inspect protocal 调试协议。
对源码调试时,通过 --inspect-brk
也是同样触发 v8-inspect, 实现调试协议。
而由于 v8 inspect 本身是从 chrome 中提取出来的,支持 chrome devtools protocal,所以可以通过 --inspect
触发 v8-inspect 在 chrome 上调试。