成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何優(yōu)雅地 Hack 用戶的代碼

開(kāi)發(fā) 前端
本文介紹一些一種在 JS 層面 hack 用戶代碼的方式。

前言:做基礎(chǔ)技術(shù)的時(shí)候,會(huì)經(jīng)常碰到一個(gè)問(wèn)題就是如何讓自己提供的代碼對(duì)用戶少侵入,無(wú)感。比如我提供了一個(gè) SDK 收集 Node.js 進(jìn)程的 HTTP 請(qǐng)求耗時(shí),最簡(jiǎn)單的方式就是給用戶提供一個(gè) request 方法,然后讓用戶統(tǒng)一調(diào)用,這樣我就可以在 request 里拿到這些數(shù)據(jù)。但是這種方式很多時(shí)候并不方便,這時(shí)候我們就需要去 hack Node.js 的 HTTP 模塊或者給 Node.js 提 PR。在操作系統(tǒng)層面,有提供很多技術(shù)解決這種問(wèn)題,比如 ebpf、uprobe、kprobe。但是應(yīng)用層無(wú)法使用這種技術(shù)解決我們的問(wèn)題,因?yàn)椴僮飨到y(tǒng)的這些技術(shù)針對(duì)的是底層的函數(shù),比如我想知道一個(gè) JS 函數(shù)的耗時(shí),只能在 V8 層面或者 JS 層面去解決,V8 這方面似乎也沒(méi)有提供很好能力,所以目前我們更多是考慮純 JS 或者 Node.js 內(nèi)核層面。本文介紹一些一種在 JS 層面 hack 用戶代碼的方式。

在 Node.js 中,統(tǒng)計(jì) JS 函數(shù)的耗時(shí)通常的做法是 cpu profile,但是這種方式只能拿到一段時(shí)間的耗時(shí),如果我想實(shí)時(shí)收集耗時(shí)數(shù)據(jù),cpu profile 就有點(diǎn)難搞,最直接的就是定時(shí)收集 cpu profile 數(shù)據(jù),然后我們手動(dòng)去解析 profile 數(shù)據(jù)然后上報(bào)。除了這種方式外,本文介紹另外一種方式。就是通過(guò) hack JS 代碼的方式。假如有以下一個(gè)函數(shù)。

function compute() {
// do something
}

如果我們想統(tǒng)計(jì)這種函數(shù)的執(zhí)行耗時(shí),最自然的方式就是在函數(shù)的開(kāi)始和結(jié)束的地方插入一些代碼。但是我們不希望這種事情讓用戶手動(dòng)去做,而是使用一種更優(yōu)雅的方式。那就是通過(guò)分析源碼,拿到 AST,然后重寫(xiě) AST。我們看看怎么做。

const acorn = require('acorn');
const escodegen = require('escodegen');
const b = require('ast-types').builders;
const walk = require("acorn-walk");
const fs = require('fs');

// 分析源碼,拿到 AST
const ast = acorn.parse(fs.readFileSync('./test.js', 'utf-8'), {
ecmaVersion: 'latest',
});

function inject(node) {
// 在函數(shù)前后插入代碼
const entryNode = b.variableDeclaration('const', [b.variableDeclarator(b.identifier('start'), b.callExpression(
b.identifier('(() => { return Date.now(); })'), [],
))]);
const exitNode = b.returnStatement(b.callExpression(
b.identifier('((start) => {console.log(Date.now() - start);})'), [
b.identifier('start')
],
));

if (node.body.body) {
node.body.body.unshift(entryNode);
node.body.body.push(exitNode);
}
}

// 遍歷 AST,修改 AST
walk.simple(ast, {
ArrowFunctionExpression: inject,
ArrowFunctionDeclaration: inject,
FunctionDeclaration: inject,
FunctionExpression: inject
});

// 根據(jù)修改的 AST 重新生成代碼
const newCode = escodegen.generate(ast);

fs.writeFileSync('test.js', newCode)

執(zhí)行上面的代碼后拿到如下結(jié)果。

function compute() {
const start = (() => { return Date.now(); })();
return ((start) => {console.log(Date.now() - start);})(start);
}

這樣我們就可以拿到每個(gè)函數(shù)的耗時(shí)數(shù)據(jù)了。但是這種方式是靜態(tài)分析源碼,落地起來(lái)需要用戶主動(dòng)操作,并不是那么友好。那么基于這個(gè)基礎(chǔ)我們利用 V8 調(diào)試協(xié)議中的 Debugger Domain 實(shí)現(xiàn)動(dòng)態(tài)重寫(xiě),這種方式還能重寫(xiě) Node.js 內(nèi)部的 JS 代碼。首先改一下測(cè)試代碼。

function compute() {
// do something
}

setInterval(compute, 1000)

然后再看改寫(xiě)代碼的邏輯。

const { Session } = require('inspector');
const acorn = require('acorn');
const escodegen = require('escodegen');
const b = require('ast-types').builders;
const walk = require("acorn-walk");
const session = new Session();
session.connect();

require('./test_ast');
// 監(jiān)聽(tīng) JS 代碼解析事件,拿到所有的 JS
session.on('Debugger.scriptParsed', (message) => {
// 只處理這個(gè)文件
if (message.params.url.indexOf('test_ast') === -1) {
return;
}
// 拿到源碼
session.post('Debugger.getScriptSource', {scriptId: message.params.scriptId}, (err, ret) => {
const ast = acorn.parse(ret.scriptSource, {
ecmaVersion: 'latest',
});
function inject(node) {
const entry = b.variableDeclaration('const', [b.variableDeclarator(b.identifier('start'), b.callExpression(
b.identifier('(() => { return Date.now(); })'), [],
))]);
const exit = b.returnStatement(b.callExpression(
b.identifier('((start) => {console.log(Date.now() - start);})'), [
b.identifier('start')
],
));

if (node.body.body) {
node.body.body.unshift(entry);
node.body.body.push(exit);
}
}
walk.simple(ast, {
ArrowFunctionExpression: inject,
ArrowFunctionDeclaration: inject,
FunctionDeclaration: inject,
FunctionExpression: inject
});
const newCode = escodegen.generate(ast);
// 分析完,重寫(xiě) AST后生成新的代碼,并重寫(xiě)
session.post('Debugger.setScriptSource', {
scriptId: message.params.scriptId,
scriptSource: newCode,
dryRun: false
});
})
});

session.post('Debugger.enable', () => {});

正常來(lái)說(shuō),setInterval 執(zhí)行的函數(shù)沒(méi)有東西輸出,但是我們發(fā)現(xiàn)會(huì)不斷輸出 0,也就是耗時(shí),因?yàn)檫@里使用毫秒級(jí)的統(tǒng)計(jì),所以是 0,不過(guò)我們不需要關(guān)注這個(gè)。這樣我們就完成了 hack 用戶的代碼,而對(duì)用戶來(lái)說(shuō)是無(wú)感的,唯一需要做的事情就是引入我們提供的一個(gè) SDK。不過(guò)這種方式的難點(diǎn)在重寫(xiě)代碼的邏輯,風(fēng)險(xiǎn)也比較大,但是如果我們解決了這個(gè)問(wèn)題后,我們就可以隨便 hack 用戶的代碼,做我們想做的事情,當(dāng)然,是正事。

責(zé)任編輯:姜華 來(lái)源: 編程雜技
相關(guān)推薦

2025-06-12 09:42:08

2021-03-24 10:20:50

Fonts前端代碼

2023-06-06 08:51:06

2024-11-13 16:37:00

Java線程池

2021-01-28 14:53:19

PHP編碼開(kāi)發(fā)

2024-04-24 12:34:08

Spring事務(wù)編程

2019-04-11 18:25:29

Android Q權(quán)限位置

2020-03-26 11:04:00

Linux命令光標(biāo)

2021-01-18 13:17:04

鴻蒙HarmonyOSAPP

2021-05-12 22:07:43

并發(fā)編排任務(wù)

2022-05-13 21:20:23

組件庫(kù)樣式選擇器

2020-07-09 10:15:55

空值Bug語(yǔ)言

2020-10-22 10:15:33

優(yōu)化Windows電腦

2021-09-08 08:34:37

Go 文檔Goland

2023-02-13 14:37:13

開(kāi)發(fā)web瀏覽器

2018-08-20 10:40:09

Redis位圖操作

2020-12-08 08:08:51

Java接口數(shù)據(jù)

2020-02-24 11:12:01

Linux電腦數(shù)據(jù)

2024-11-21 09:00:00

Python字典代碼

2020-04-03 13:45:16

刪除Linux垃圾文件
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 精品视频一区二区三区在线观看 | 日日噜| 91资源在线观看 | 亚洲a在线视频 | 久久99精品久久久97夜夜嗨 | 91精品久久久久久久久中文字幕 | 亚洲国产一区在线 | 国产激情一区二区三区 | 操久久 | 嫩草视频在线免费观看 | 久久精品亚洲精品国产欧美 | аⅴ资源新版在线天堂 | 欧美激情精品久久久久久 | 国产情侣啪啪 | 亚洲一在线 | 日本免费一区二区三区 | 亚洲免费网址 | 一区二区三区四区电影 | 羞羞视频免费观看入口 | 天天操天天天 | 国产精品久久久久久一区二区三区 | 伊人网综合 | 精品久久国产 | 国产在线视频在线观看 | 中文字幕欧美日韩一区 | 一级特黄在线 | 91在线资源| 少妇一级淫片aaaaaaaaa | 日韩在线免费看 | 喷潮网站| 欧美中文字幕一区二区三区亚洲 | 国内久久精品 | 欧美成人精品一区二区三区 | 荷兰欧美一级毛片 | 精品乱人伦一区二区三区 | 欧美在线国产精品 | 国产成人综合亚洲欧美94在线 | 午夜网站视频 | 欧美一级在线免费观看 | 国产一区二区不卡 | av中文字幕在线播放 |