JS输出函数调用栈

JS输出函数调用栈最近在编写JS逆向hook类插件,然后需要获取当前代码执行时所在的位置,方便代码定位,于是就总结下 JavaScript 如何输出函数调用栈。

最近在编写JS逆向hook类插件,然后需要获取当前代码执行时所在的位置,方便代码定位,于是就总结下 JavaScript 如何输出函数调用栈。

演示代码

 function main() {
   let a = fun('hello world')
   console.log(a)
 }
 ​
 function fun(a) {
   return a
 }
 ​
 main()

方法

console.trace()

使用如下

 function main() {
   let a = fun('hello world')
   console.log(a)
 }
 ​
 function fun(a) {
   console.trace('fun')
   return a
 }
 ​
 main()

输出结果为

 Trace: fun
     at fun (c:\Users\zeyu\Desktop\demo\main.js:7:11)
     at main (c:\Users\zeyu\Desktop\demo\main.js:2:11)
     at Object.<anonymous> (c:\Users\zeyu\Desktop\demo\main.js:11:1)    at Module._compile (node:internal/modules/cjs/loader:1095:14)  
     at Object.Module._extensions..js (node:internal/modules/cjs/loader:1124:10)
     at Module.load (node:internal/modules/cjs/loader:975:32)       
     at Function.Module._load (node:internal/modules/cjs/loader:816:12)
     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
     at node:internal/main/run_main_module:17:47
 hello world

其中console.trace()可以传入参数,最终都将直接输出在Trace后面,如这里的fun,但只能在控制台中输出

不过IE6并不支持,不过应该也没人用了吧

arguments.callee.caller

非严格模式下,可以直接输出arguments,便会打印出所调用的参数,以及调用的函数,使用如下

 function main() {
   let a = fun('hello world')
   console.log(a)
 }
 ​
 function fun(a) {
   console.log(fun.caller.toString())
   console.log(arguments)
   console.log(arguments.callee.toString())
   console.log(arguments.callee.caller.toString())
 ​
   return a
 }
 ​
 main()

输出结果为

 function main() {
   let a = fun('hello world')
   console.log(a)
 }
 [Arguments] { '0': 'hello world' }
 function fun(a) {
   console.log(fun.caller.toString())
   console.log(arguments)
   console.log(arguments.callee.toString())
   console.log(arguments.callee.caller.toString())
 ​
   return a
 }
 function main() {
   let a = fun('hello world')
   console.log(a)
 }
 hello world

成功的将我们当前运行的函数给打印了出来(这里使用toString 方便将函数打印出来),而上级的函数的话通过fun.callerarguments.callee.caller都能得到。

image-20211015094231693

caller便是调用的上层函数,也就是这里的main函数,不难发现每个caller对象下都有一个caller属性,也就是caller的上层函数,由于我这里是node环境,所以这里的caller的caller我也不知道是个什么玩意。。。反正这不是所要关注的重点,重点是**fun.caller`arguments.callee.caller便可以打印出上层函数**,直到caller为空

另外圈的[[FunctionLocation]]便是函数所在位置,不过可惜是,这个并不是caller的属性,仅供js引擎使用的,所以无法输出。

总结下来:

fun.caller == arguments.callee.caller 代表fun的执行环境 (上层函数)

arguments.callee代表的是正在执行的fun

前提: 非严格模式下

new Error().stack

众所周知,程序一旦出错,便会直接停止运行,同时输出报错信息,而这里的报错信息就包括调用的函数以及具体位置,相对于上面的方法而言,这个能直接在执行环境中输出,而不是单纯的在控制台显示。

同样还是上面的代码

 function main() {
   let a = fun('hello world')
   console.log(a)
 }
 ​
 function fun(a) {
   printStack()
   return a
 }
 ​
 function printStack() {
   let stack = (new Error()).stack
   console.log(stack)
 }
 ​
 main()

输出的结果为一串字符串,如下

 Error
     at printStack (c:\Users\zeyu\Desktop\demo\main.js:12:16)       
     at fun (c:\Users\zeyu\Desktop\demo\main.js:7:3)
     at main (c:\Users\zeyu\Desktop\demo\main.js:2:11)
     at Object.<anonymous> (c:\Users\zeyu\Desktop\demo\main.js:16:1)    at Module._compile (node:internal/modules/cjs/loader:1095:14)  
     at Object.Module._extensions..js (node:internal/modules/cjs/loader:1124:10)
     at Module.load (node:internal/modules/cjs/loader:975:32)       
     at Function.Module._load (node:internal/modules/cjs/loader:816:12)
     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
     at node:internal/main/run_main_module:17:47
 hello world

由于结果是个字符串,所以通过split分割一下,便能得到调用的函数(fun)以及调用位置(c:\Users\zeyu\Desktop\demo\main.js:7:3),稍加处理一下,如下

 function main() {
   let a = fun('hello world')
   console.log(a)
 }
 ​
 function fun(a) {
   printStack()
   return a
 }
 ​
 main()
 ​
 function printStack() {
   const callstack = new Error().stack.split("\n");
   callstack.forEach((s) => {
     let matchArray = s.match(/at (.+?) ((.+?))/)
     if (!matchArray) return
 ​
     let name = matchArray[1]
     let location = matchArray[2]
     console.log(name, location)
   })
 }

输出结果如下(由于是Node环境,所以会输出一些有关模块modules的东西)

printStack c:\Users\zeyu\Desktop\demo\main.js:14:21
fun c:\Users\zeyu\Desktop\demo\main.js:7:3
main c:\Users\zeyu\Desktop\demo\main.js:2:11
Object.<anonymous> c:\Users\zeyu\Desktop\demo\main.js:11:1
Module._compile node:internal/modules/cjs/loader:1095:14
Object.Module._extensions..js node:internal/modules/cjs/loader:1124:10
Module.load node:internal/modules/cjs/loader:975:32
Function.Module._load node:internal/modules/cjs/loader:816:12      
Function.executeUserEntryPoint [as runMain] node:internal/modules/run_main:79:12
hello world

总结

如果是作为调试阶段,想输出调用栈的话,那么console.trace()肯定是个最好的选择,不过只能在控制台显示,无法在运行环境中使用

arguments.callee.caller使用的前提是非严格模式下,所以要使用的话,则需要删除"use strict";代码, 但能直接打印出完整的函数,以及调用所传入的参数。

new Error().stack 相当于主动报错,由于报错会自动打印报错所在的调用信息,所以能精确的定位到代码的函数名和代码行与列,对于后续要定位代码位置而言优先选择。

今天的文章JS输出函数调用栈分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20875.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注