调试辅助代码段
常用于 Console 面板快速调试,或注入到页面辅助分析的工具代码。 在任意位置插入,打印当前的函数调用链,不中断执行。 包装目标函数,记录每次调用的参数和返回值。 使用 Object.defineProperty 拦截属性的读写。 快速查找对象中所有函数类型的属性。 在嵌套对象中递归查找包含特定关键字的属性名。 有些网站在 JS 中用 console.log 输出加密中间值,但因日志太多难以查找,可以过滤关键词。 有些反调试代码通过检测函数的 toString() 结果来判断是否被 Hook。 在 XHR 断点命中后,一次性打印所有请求信息。 在 C
官方文档:https://developer.chrome.com/docs/devtools/console/api/
常用于 Console 面板快速调试,或注入到页面辅助分析的工具代码。
打印调用堆栈
在任意位置插入,打印当前的函数调用链,不中断执行。
console.trace('当前调用栈');
监控函数调用
包装目标函数,记录每次调用的参数和返回值。
// 监控指定函数
function monitorFunc(obj, funcName) {
var original = obj[funcName];
obj[funcName] = function() {
console.log(`[调用] ${funcName} 参数:`, JSON.stringify(arguments));
var result = original.apply(this, arguments);
console.log(`[返回] ${funcName} 结果:`, JSON.stringify(result));
return result;
};
}
// 使用示例
monitorFunc(window, 'btoa');
monitorFunc(JSON, 'stringify');
监控对象属性变化
使用 Object.defineProperty 拦截属性的读写。
function watchProperty(obj, prop) {
var _val = obj[prop];
Object.defineProperty(obj, prop, {
get: function() {
console.log(`[GET] ${prop} =`, _val);
return _val;
},
set: function(newVal) {
console.log(`[SET] ${prop} =`, newVal);
console.trace();
_val = newVal;
},
configurable: true
});
}
// 使用示例
watchProperty(window, '_token');
遍历对象找函数
快速查找对象中所有函数类型的属性。
function findFunctions(obj, name) {
name = name || 'obj';
Object.keys(obj).forEach(function(key) {
if (typeof obj[key] === 'function') {
console.log(`${name}.${key}`);
}
});
}
findFunctions(window._utils, 'window._utils');
深度搜索对象属性
在嵌套对象中递归查找包含特定关键字的属性名。
function deepSearch(obj, keyword, path, depth) {
path = path || 'root';
depth = depth || 0;
if (depth > 5) return; // 避免循环引用死循环
try {
Object.keys(obj).forEach(function(key) {
var fullPath = path + '.' + key;
if (key.toLowerCase().includes(keyword.toLowerCase())) {
console.log(fullPath, '=', obj[key]);
}
if (obj[key] && typeof obj[key] === 'object') {
deepSearch(obj[key], keyword, fullPath, depth + 1);
}
});
} catch(e) {}
}
// 在 window 中查找含 'sign' 的属性
deepSearch(window, 'sign');
拦截 console.log
有些网站在 JS 中用 console.log 输出加密中间值,但因日志太多难以查找,可以过滤关键词。
(function() {
var _log = console.log.bind(console);
console.log = function() {
var args = Array.from(arguments);
var str = args.join(' ');
if (str.includes('sign') || str.includes('token')) {
_log.apply(console, ['[过滤命中]', ...args]);
debugger;
} else {
_log.apply(console, args);
}
};
})();
还原 toString 检测
有些反调试代码通过检测函数的 toString() 结果来判断是否被 Hook。
// 让被 Hook 的函数的 toString 返回原生代码字符串
function fakeNative(func) {
func.toString = function() {
return 'function ' + func.name + '() { [native code] }';
};
return func;
}
// 使用示例
window.XMLHttpRequest.prototype.open = fakeNative(function open(method, url) {
// Hook 逻辑
});
获取页面所有请求参数
在 XHR 断点命中后,一次性打印所有请求信息。
// 在 XHR send 断点处执行
(function() {
var xhrs = performance.getEntriesByType('resource');
xhrs.filter(e => e.initiatorType === 'xmlhttprequest').forEach(function(e) {
console.log(e.name);
});
})();
复制变量到剪贴板
在 Console 中快速复制当前作用域的变量值。
// 在断点处执行,复制变量到剪贴板
copy(JSON.stringify(signData));
// 或者
copy(btoa(JSON.stringify(encryptedResult)));
格式化输出对象
// 替代 console.log,更清晰地显示嵌套对象
console.dir(complexObject, { depth: null });
// 以表格形式展示数组
console.table(arrayOfObjects);
时间戳快速转换
// 当前时间戳(秒)
Math.floor(Date.now() / 1000)
// 当前时间戳(毫秒)
Date.now()
// 时间戳转可读时间
new Date(1700000000 * 1000).toLocaleString()
// 可读时间转时间戳
new Date('2024-01-01 00:00:00').getTime() / 1000
Base64 互转
// 编码
btoa('hello world')
btoa(unescape(encodeURIComponent('中文'))) // 支持中文
// 解码
atob('aGVsbG8gd29ybGQ=')
decodeURIComponent(escape(atob('5Lit5Zu9'))) // 解码中文
// Node.js 版本
Buffer.from('hello').toString('base64')
Buffer.from('aGVsbG8=', 'base64').toString()
URL 编解码
// 编码
encodeURIComponent('key=value&foo=bar')
// 解码
decodeURIComponent('key%3Dvalue%26foo%3Dbar')
// 解析 URL 参数
Object.fromEntries(new URLSearchParams('a=1&b=2'))
// { a: '1', b: '2' }
最佳实践
优先用条件断点替代打印调用栈:console.trace() 在调用频繁的函数里会刷屏,导致有效信息被淹没。在 DevTools Sources 面板对该函数入口设条件断点(右键行号 → Add conditional breakpoint),只在满足特定条件时暂停,效率更高。
JSON.parse(JSON.stringify(obj)) 快速深拷贝快照:在 Hook 回调里打印对象时,对象可能在后续被修改。用 JSON.parse(JSON.stringify(obj)) 先快照再打印,避免在 Console 展开时看到的是修改后的值。
const snap = JSON.parse(JSON.stringify(complexObj));
console.log(snap); // 打印的是当前时刻的副本
用 copy() 把大对象导出到剪贴板:DevTools Console 里调用 copy(data) 可以把任意对象序列化为 JSON 复制到剪贴板,省去手动复制长字符串的麻烦,方便粘到编辑器进一步分析。
用 $_ 引用上一次 Console 表达式的结果:连续操作时不需要反复把结果赋给变量,$_ 始终指向最近一次表达式求值的结果。
fetch('/api/user').then(r => r.json()) // 执行后 $_ 是 Promise
// 下一行输入
$_.then(console.log) // 打印 JSON 结果
堆栈过深时用 Error().stack 代替 console.trace():console.trace() 输出格式因浏览器而异,而 new Error().stack 返回字符串,可以 includes() 过滤或 .split('\n').slice(1, 6) 只取前几层,便于程序化处理。
常见陷阱
陷阱:Console 打印对象显示的是修改后的值
现象: console.log(obj) 展开后,看到的属性值和打印时刻不符,像是"预言"了后续修改。
原因: Chrome Console 对对象是延迟求值的:展开前对象已经被后续代码修改,展开时看到的是最新状态。
解决: 用 JSON.parse(JSON.stringify(obj)) 深拷贝后再打印,或用 console.log(JSON.stringify(obj)) 打印字符串。
陷阱:console.trace() 在高频函数里导致页面卡死
现象: 注入 console.trace() 后页面响应变慢甚至崩溃,Console 刷出数万条堆栈。
原因: 目标函数每秒调用数百次,console.trace() 每次都要收集完整调用栈并渲染 DOM,开销巨大。
解决: 改用条件断点(callCount++ > 100 && false),或在调用前加计数器,只打印前几次。
let _count = 0;
const orig = target.fn;
target.fn = function() {
if (_count++ < 3) console.trace('fn called');
return orig.apply(this, arguments);
};
陷阱:encodeURIComponent 与 btoa 混淆
现象: 调试时发现 URL 参数是 Base64 还是 URL 编码不确定,用错函数导致无法还原。
原因: URL 编码结果以 % 开头(如 %7B),Base64 结果只含 A-Za-z0-9+/=(标准)或 -_(URL safe 变体),两者形式不同但都常见于请求参数。
解决: 先观察字符集:含 % 是 URL 编码,纯字母数字且末尾可能有 = 是 Base64。用 decodeURIComponent 解 URL 编码,atob 解 Base64。