前言
相信对node源码感兴趣的同学一定知道,./lib库中的js通过node.gyp自动转换成了node_javascript.cc中的ASCII码,如果读过我之前写的一篇文章,应该也是对此有点了解的,而./lib库中的bootstrap_node.js又是js的入口文件,所有./lib下的文件几乎都需要它来挂载。那么这部分代码又该如何调试修改呢?
./lib中的js如何编译
由于./lib库中的js都是存储在node_javascript.cc中,所以我们如果想要调试./lib中的js需要多一个编译的步骤,也就是把你修改的代码编译到node_javascript.cc中。如果大家读过node.gyp以及makefile,那么整个编译流程就会很清晰,在这篇文章中不做过多解读,如果有兴趣了解的话请移步这里。我们在修改完代码后需要执行一下make来进行编译。make会根据lib库中的js重新生成node_javascript.cc文件。这样就可以保证./lib库中的代码在调用的时候都是最新的了。
调试
相信大家都了解,node的入口文件是bootstrap_node.js,那么我们就从这个文件入手,聊一聊该如何调试。二话不说,先写一个console.log然后make一下看看更新了么。
// ./lib/internal/bootstrap_node.js
'use strict';
(function(process) {
console.log(1111)
let internalBinding;
const exceptionHandlerState = { captureFn: null };
//...此处省略n多代码
然后我们make一下(手动滑稽.jpg):
nice,没有报错,release和debug都完美生成了。接下来,我们开始跑了:
What?!代码没更新?console没出来?还没报错?这是什么情况?(黑人问号脸.jpg)
吓得我赶紧去翻一下node_javascript.cc,然后看到了:
上面的红框表示是文件internal/bootstrap_node.js,下面的红框如果大家有兴趣可以查一下ASCII,表示的是console.log(1111)
。这里可以说明,编译是绝对没问题的,那么问题出在哪里呢?
js node运行环境的变化
顺着bootstrap_node.js往下看会看到一段代码:
const browserGlobals = !process._noBrowserGlobals;
if (browserGlobals) {
setupGlobalTimeouts();
setupGlobalConsole();
}
之后的调用栈就不深扒了,通过命名可以看出来,这是装载全局console的代码。哦,原来是在这里装载的,那我在这之后打个console试试? 居然打出来了?!那么另外一个问题就来了,为什么之前那个console没打出来亦没有报错呢? 然后我们可以做一个小测试,分别在这两个地方设置断点,然后在前面的断点记录下来console.log,在后面的断点对比两者是否相等,代码如下
(function(process) {
let internalBinding;
const exceptionHandlerState = { captureFn: null };
let testConsole = console.log;//添加的testConsole记录下来此时的console.log
debugger;//添加断点
//....此处省略n多代码
if (browserGlobals) {
setupGlobalTimeouts();
setupGlobalConsole();
console.log(testConsole === console.log);//添加console验证一下
}
debugger;//添加断点
//...此处省略n多代码
接下来我们跑一遍验证一下,在跑之前记得修改一下调用参数哦:
这个地方应该都没有疑问吧,肯定是true,接下来我们看下面的debugger:
通过这个结果,能清晰的看出来node已然把console重做了。那么为什么要重做呢?
于是乎,在这里引出来一个概念:v8是js的翻译(编译)器,而node在c++中会为js全局添加global而且会把process作为bootstrap_node.js的参数传入;而在bootstrap_node.js中,则会添加和重写一部分方法,使之符合node的相关需求。
我们还是用console这个例子说明一下。在node中,console的输出是针对于ttys的,所以node在处理console的时候,其实是引入了process.stdout.write的概念来实现的console,具体代码可以参考这里,对于这部分代码不在本次主要的说明范围内,所以不做过多解释,有兴趣的可以结合上下文看一下。这就是一个非常典型的node runtime interface
。
node runtime interface
如果大家看过./lib下面的js文件,那么会发现一个非常有意思的文件: ./lib/repl.js,这个文件可以让你的代码获得runtime interface。
总结
- 通过上面的流程大家应该也看出来了,./lib中的js使用debugger来调试较为方便。
- 如果大家想知道bootstrap_node.js调用栈的先后顺序,那么在调用到你的代码之后,直接在debugger中打bt,那么一切都清晰可见,😆:
by 小菜
如果有什么疑问或者我的表述有不正确的地方欢迎回复和指正。
原文地址:https://github.com/xtx1130/blog/issues/14 欢迎watch和star。