如果一个 NodeJS 进程正在运行,有办法修改程序中的变量值么?答案是:通过 V8 的 Debugger 接口可以!本文将详细介绍实现步骤。

启动一个 HTTP Server

用简单的 Hello World 做例子吧,不过略作修改。在 global 下放一个变量 message, 然后打印出来:

// message content will be modified !
global.message = "hello world!"; var server = require('http').createServer(function (req, res) {
res.end(global.message);
}).listen(8001); console.log('pid = %d', process.pid);

用命令启动 Server,此时,通过用浏览器访问 http://localhost:8001 可以看到网页内容是 hello world!。 接下来我们将尝试在不改变代码,不重启进程的情况下把 message 换成 "hello bugs!"

使 Server 进程进入 Debug 模式

V8 引擎在实现的时候留了 Debugger 接口。 通过命令 node --debug-brk=5858 [filename] 可以启动一个脚本,并且立即进入 Debug 模式。

那么如果是已经运行着的 NodeJS 程序,可以进入 Debug 模式吗?也是可以的,在操作系统下给 NodeJS 的进程发一个 SIGUSR1 信号,可以让进程进入 Debug 模式。 进入 Debug 模式的进程会在本地启动一个 TCP Server 并且默认监听 5858 端口。

此时在另一个命令行窗口执行命令 node debug localhost:5858 就可以连接到 Debugger 调试端口, 并且可以使用很多常用的 Debug 命令,比如 c继续执行,s 步入, o步出等。

Debugger 协议

使用 node debug hostname:port 命令连接到进程进行 Debug 的方式比较简单,但是要完成一些高级的功能就会处处受限,因为它只封装了 Debugger 协议中 command 的一部分。 下面介绍一下这个简单的协议。

Client 和 Server 的通讯是通过 TCP 进行的。 DebugClient 第一次连接到 DebugServer 的时候会拿到一些 Header,比如 Node 版本, V8 版本等。后面紧跟着一个空的消息1

消息1

Type: connect\r\n
V8-Version: 3.28.71.19\r\n
Protocol-Version: 1\r\n
Embedding-Host: node v0.12.4\r\n
Content-Length: 0\r\n
\r\n

消息实体由 Header 和 Body 组成,消息1的 Body 为空,所以 Header 中对应的 Content-Length 为 0。而在下面这个例子里,Body 为一个单行的 JSON 字符串,这是由协议所规定的。

消息2

Content-Length: 46\r\n
\r\n
{"command":"version","type":"request","seq":1}

消息2的类型( type )是 request,代表这是 Client 发给 Server 的命令,其他的可能值是 responseevent 分别代表 Server 对 Client 的相应,和 Server 端发生的事件。

消息3

Content-Length: 137\r\n
\r\n
{"seq":1,"request_seq":1,"type":"response","command":"version","success":true,"body":{"V8Version":"3.28.71.19"},"refs":[],"running":true}

消息2是 Client 发送给 Server的,消息3是 Server 对 Client 的相应,那么如何判断消息3是不是消息2的结果呢?可以看到消息2中的 seq 值是1,而 消息3中的request_seq值是1。 Debugger 协议正是通过这两个值把异步返回的结果和请求一一对应起来的。

Debugger 协议就是这么的简单。

实例化一个 Debugger Client

了解了 Debugger 协议后,相信好奇心强的程序员已经跃跃欲试自己实现一个了。本着不重复发明轮子的原则开始在网上找实现,找了好久找到这个库 pDebug, 可惜这个库已经好久不更新了。后来通过阅读 node-inspector 的源码才发现,其实 NodeJS 自带了一个 Debugger 模块, 相关代码在 _debugger 模块里(源码),由于模块名是以 _ 开头的,所以网上找不到它的 API,好在代码注释写的非常详细,很快就能上手。

我们需要的正是这个模块下的 Client, 而 Client 其实是继承于 Socket 的.

var Client = require('_debugger').Client;
var client = new Client(); client.connect(5858);
client.on('ready', function () {
// 连接成功
});

通过 Debugger 接口执行命令

接下来我们来看看如何修改这个 global 的变量,代码如下

function modifyTheMessage(newMessage) {
var msg = {
'command': 'evaluate',
'arguments': {
'expression': 'global.message="' + newMessage + '"',
'global': true
}
};
client.req(msg, function (err, body, res) {
console.log('modified to %s', newMessage);
});
}

client.req 方法封装了 type=request 消息类型 和 seq 自增的逻辑,因此在构造 msg JSON对象的时候不需要指明这两个属性。 我们要修改 message 其实就是在 JavaScript 调用的顶层执行 global.message=newMessage

总结

此时,再访问 http://localhost:8001 可以看到网页上显示的内容已经由 'hello world!' 变成了 'hello bugs!',是不是很神奇。

这种方式也带来了很多可能性:

  • 动态修改配置

    线上的服务器不用重启就可以应用新的配置

  • 模块注入

    通过其他任意语言编写的应用程序为已经运行的 NodeJS 进程注入新的模块

  • 性能监控

    可以剥离用户线上代码对第三方性能监控模块的直接依赖

  • 错误监控

    发生异常时,通过 Debugger 可以抓到发生错误的函数和行号,并且抓取各个调用栈中的每一个变量,即使是在闭包里

  • Chrome 调试

    由于 Chrome 也是基于 V8 的,上述方法也可以用于 Chrome 相关的功能集成

关于

  1. 本文相关的源码在: https://github.com/wyvernnot/interference_demo;

  2. 如果你也对 Debugger 协议感兴趣,可以安装 oneapm-debugger 这个工具,它可以帮助你查看 Debug 过程中所有实际发送的数据。 oneapm-debugger


本文系OneAPM工程师王龑原创。想阅读更多技术文章,请访问OneAPM官方技术博客

动态修改 NodeJS 程序中的变量值的更多相关文章

  1. js获取jsp中的变量值

    js获取jsp中的变量值,有两种方式: 1.jsp标签获取属性 var message = '<%=request.getAttribute("message")%>' ...

  2. JS动态修改微信浏览器中的title

    JS动态修改微信浏览器中的title我们的原理是设置一个ifame然后我们再加载一下就可以实现了,具体的例子如下所示. 平时使用JS修改title,直接document.title=新标题就好了 这样 ...

  3. 用配置文件里面的参数值替换yaml模板中的变量值【python】

    用配置文件里面的参数值替换yaml模板中的变量值[python] #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019/9/20 1 ...

  4. Ida动态修改android程序的内存数据和寄存器数值,绕过so文件的判断语句

    我们继续分析自毁程序密码这个app,我们发现该程序会用fopen ()打开/proc/[pid]/status这个文件,随后会用fgets()和strstr()来获取,于是我们在strstr()处下个 ...

  5. VS动态修改App.config中遇到的坑(宿主进程问题)

    昨天遇到了很奇怪的一个bug,具体描述如下: 这个系统是c/s架构的针对多个工厂做的资材管理系统,由于有很多个工厂,每个工厂都有自己的服务器.所以需要动态的改变连接字符串去链接不同的服务器. 由于这个 ...

  6. 【Visual Studio】如何在VS 2012中打印变量值到输出窗口

    1.在调试程序时,想要输出某个变量的值到vs的输出窗口,而不是通过添加断点,每次调试时,一步一步的看变量的值,很麻烦,用console.writeline(str);是不行的,这个命令只能用在控制台应 ...

  7. 获取oracle sql语句中绑定变量值的方法

    在诊断 sql的性能问题时,我们有时候须要获取其绑定变量的实际值,然后将此实际值带入到sql语句其中,用原来的sql构成select语句(带where条件),实际的运行一下,看一下选择性怎样. 本文就 ...

  8. maven 根据P参数值打包动态修改properties文件中值或一定properties

    需求:由于最近开发clover项目 ,没有使用spring,更没有使用任何框架,而使用J2EE的web工程,所以连接ZK和MongoDB.Redis等服务器需用指定properties文件, 而目前公 ...

  9. ASP.NET程序中动态修改web.config中的设置项目(后台CS代码)

    using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Dra ...

随机推荐

  1. django笔记

    apt-get install libmysqlclient-devpip install mysqlclientsudo apt-get install libxml2-dev libxslt1-d ...

  2. openSUSE13.1无法打开Yast的安装/移除软件管理软件的解决办法·(未解决,临时方法) 收获:有问题,读日志

    看了日志发现错误在于Ruby,新的YaST是基于Ruby的,而我用rvm安了新版本Ruby,日志里这么出错: 2014-08-22 20:20:57 <3> linux-vfpp.site ...

  3. Secondary IP Addressing

    Secondary IP Addressing secondary IP addressing. Secondary addressing uses multiple networks or subn ...

  4. Knockout应用开发指南 第一章:入门

    2011-11-21 14:20 by 汤姆大叔, 20165 阅读, 17 评论, 收藏,  编辑 1    Knockout简介 (Introduction) Knockout是一个轻量级的UI类 ...

  5. Spark 3000门徒第二课scala面向对象总结

    昨晚听了王家林老师3000门徒spark系列课程的第二课,讲述了scala面向对象知识,并且带着过了一遍Spark核心类:SparkContent,RDD的代码,下面写一下心得: RDD是抽象类,实现 ...

  6. [转]Asp.net三种事务处理

    事务处理是在数据处理时经常遇到的问题,经常用到的方法有以下三种总结整理如下:方法1:直接写入到sql 中在存储过程中使用 BEGIN TRANS, COMMIT TRANS, ROLLBACK TRA ...

  7. Lisp使用Lambda语法

    lamdba 其实就是一个匿名函数. 定义Lisp的lambda语法非常的简单,如下: (lambda ([parameter]) [experssion]) 调用lambda的语法有三种方法,如下: ...

  8. [LAMP]——mod_security和mod_evasive模块的安装

    系统版本:Red Hat 6 httpd版本:httpd-2.4.20 tar包:modsecurity-apache_2.5.9.tar.gz   mod_evasive_1.10.1.tar.gz ...

  9. python三级菜单

    #-*- coding:utf-8 -*-#Author:gxli #一级菜单项def menu(): #遍历字典dic一级菜单 print('-----------一级菜单------------- ...

  10. cocos2dx 3.x中的渲染机制

    1.由2.x的渲染节点,变成添加渲染命令,可以避免重复渲染相同的节点,提高了渲染效率 2.单机游戏通常要求apk包在30M以内,没压缩1M会有1%的转换率(下载转换率),即收入会提高 3.2.x中首先 ...