调试辅助代码段

常用于 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);
};

陷阱:encodeURIComponentbtoa 混淆

现象: 调试时发现 URL 参数是 Base64 还是 URL 编码不确定,用错函数导致无法还原。

原因: URL 编码结果以 % 开头(如 %7B),Base64 结果只含 A-Za-z0-9+/=(标准)或 -_(URL safe 变体),两者形式不同但都常见于请求参数。

解决: 先观察字符集:含 % 是 URL 编码,纯字母数字且末尾可能有 = 是 Base64。用 decodeURIComponent 解 URL 编码,atob 解 Base64。


参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog