Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

添加line命令,用来观测方法内的局部变量,可以通过行号或者LineCode来指定位置 #2852

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

isadliliying
Copy link

@isadliliying isadliliying commented Jun 21, 2024

为什么要做这个呢?

  • 符合Debug的习惯,查看方法内的局部变量是理所当然的想法
  • 协助快速定位排查问题,若通过加日志观测的话自由度会更大,但retransform会比较麻烦,而且往往随着排查深入,可能要加多个地方
  • 有看到isuues里边也有一些小伙伴提到这个,需求有其合理性
  • 所在业务已经使用1年有余,虽使用频率不如watch、trace,但仍能提高问题排查效率(上次提的PR没有通过,这次完善了一些点🙏)

为什么要定义新的命令?

  • 跟观测非常契合的命令是watch,但是watch的对象是method,而我们观测local variables的时候,用来作为定位点是比较合适的(因为同一个变量会被多次重复赋值),两者的回调监听是不一致的
  • 独立一个命令也能降低对旧命令的影响,也能降低实现的复杂度

实现思路:

  • 定义新的Advice变量 varMap,类型是HashMap,key是变量名,value是变量值
  • 插桩的位置是命令参数传入的 LineNumber 或 LineCode(自定义的特殊值) 之前,并使用-1来表示方法退出前的插桩点
  • 监听器的注册需要做相应的区分处理

已经做的基本测试:

  • 基础功能实现,能观测到定义的local variables,并能在expresscondition-express中使用
  • 重复插桩处理,增加了对应的LocationFilter,避免重复插桩
  • 多人同时监听使用
  • tracewatch命令的同时使用

为什么会需要使用LineCode?

主要是kotlin编译后的字节码跟源码相差甚远,如下:
kotlin源码:

/*56*/    fun index(): JSONObject {
/*57*/        listOf(1,2,3,4,5,6).map {
/*58*/            listOf("a","b","c","d","e","f").map {
/*59*/                listOf(true,false,true,false,false).map {
/*60*/                    println(it)
/*61*/                    if (true) return JSONObject()
/*62*/                }
/*63*/            }
/*64*/        }
/*65*/        return ResponseBuilder().ok().data("Hello World!").build()
/*66*/    }

jad出来的样子:

       public final JSONObject index() {
           void var3_3;
           void $receiver$iv$iv;
           Iterable $receiver$iv;
/*57*/     Iterable iterable = $receiver$iv = (Iterable)CollectionsKt.listOf(1, 2, 3, 4, 5, 6);
           Collection destination$iv$iv = new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10));
/*83*/     for (Object item$iv$iv : $receiver$iv$iv) {
               void $receiver$iv$iv2;
               Iterable $receiver$iv2;
               int n = ((Number)item$iv$iv).intValue();
               Collection collection = destination$iv$iv;
               boolean bl = false;
/*58*/         Iterable iterable2 = $receiver$iv2 = (Iterable)CollectionsKt.listOf("a", "b", "c", "d", "e", "f");
               Collection destination$iv$iv2 = new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv2, 10));
/*86*/         for (Object item$iv$iv2 : $receiver$iv$iv2) {
                   void $receiver$iv$iv3;
                   Iterable $receiver$iv3;
                   String string = (String)item$iv$iv2;
                   Collection collection2 = destination$iv$iv2;
                   boolean bl2 = false;
/*59*/             Iterable iterable3 = $receiver$iv3 = (Iterable)CollectionsKt.listOf(true, false, true, false, false);
                   Collection destination$iv$iv3 = new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv3, 10));
                   Iterator iterator2 = $receiver$iv$iv3.iterator();
                   if (iterator2.hasNext()) {
                       void it;
                       void it2;
                       void it3;
                       Object item$iv$iv3 = iterator2.next();
                       boolean bl3 = (Boolean)item$iv$iv3;
                       Collection collection3 = destination$iv$iv3;
                       boolean bl4 = false;
/*60*/                 System.out.println((boolean)it3);
                       JSONObject jSONObject = new JSONObject();
                       return jSONObject;
                   }
/*91*/             List list = (List)destination$iv$iv3;
                   collection2.add(list);
               }
/*92*/         List list = (List)destination$iv$iv2;
               collection.add(list);
           }
/*93*/     List cfr_ignored_0 = (List)var3_3;
           return new ResponseBuilder().ok().data((Object)"Hello World!").build();
       }

问题:
反编译后,它的行号分布是乱序的,另外实践中还发现有重复行号的问题,而且反编译后与源代码大相径庭,也生成了很多的额外的零时变量,也有很多行是没有行号的,所以使用行号定位的话,是不够完善的,有些点无法进行定位插入监听!

解法:
如何能监听到所有本地变量的变化过程呢? -> 变量在何时会被改变呢? -> 赋值、作为方法参数被调用
基于此,通过筛选方法中的InsnNode,只保留 VarInsnNode 和 MethodInsnNode 作为备选插入点,然后生成类似如下的标记行并以此为变量监测的插入点.
目前格式如: 行号 + LineCode + 指令(方法调用/变量赋值 )

/*61 */ (4076-1)->  
                  invoke-method:java/lang/Integer#valueOf:(I)Ljava/lang/Integer;
/*61 */ (4076-2)->  
                  invoke-method:java/lang/Integer#valueOf:(I)Ljava/lang/Integer;
/*61 */ (4076-3)->  
                  invoke-method:java/lang/Integer#valueOf:(I)Ljava/lang/Integer;
/*61 */ (4076-4)->  
                  invoke-method:java/lang/Integer#valueOf:(I)Ljava/lang/Integer;
/*61 */ (4076-5)->  
                  invoke-method:java/lang/Integer#valueOf:(I)Ljava/lang/Integer;
/*61 */ (4076-6)->  
                  invoke-method:java/lang/Integer#valueOf:(I)Ljava/lang/Integer;
/*61 */ (7a12-1)->  
                  invoke-method:kotlin/collections/CollectionsKt#listOf:([Ljava/lang/Object;)Ljava/util/List;
/*84 */ (17cc-1)->  
                  invoke-method:kotlin/collections/CollectionsKt#collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
/*84 */ (ec06-1)->  
                  invoke-method:java/util/ArrayList#<init>:(I)V
/*85 */ (2795-1)->  
                  invoke-method:java/lang/Iterable#iterator:()Ljava/util/Iterator;
/*85 */ (b5c4-1)->  
                  invoke-method:java/util/Iterator#hasNext:()Z
/*85 */ (dce9-1)->  
                  invoke-method:java/util/Iterator#next:()Ljava/lang/Object;
/*86 */ (9699-1)->  
                  invoke-method:java/lang/Number#intValue:()I
/*86 */ (81f3-1)->  
                  assign-variable:it
/*62 */ (7a12-2)->  
                  invoke-method:kotlin/collections/CollectionsKt#listOf:([Ljava/lang/Object;)Ljava/util/List;
/*87 */ (17cc-2)->  
                  invoke-method:kotlin/collections/CollectionsKt#collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
/*87 */ (ec06-2)->  
                  invoke-method:java/util/ArrayList#<init>:(I)V
/*88 */ (2795-2)->  
                  invoke-method:java/lang/Iterable#iterator:()Ljava/util/Iterator;
/*88 */ (b5c4-2)->  
                  invoke-method:java/util/Iterator#hasNext:()Z
/*88 */ (dce9-2)->  
                  invoke-method:java/util/Iterator#next:()Ljava/lang/Object;
/*89 */ (81f3-2)->  
                  assign-variable:it
/*63 */ (fe63-1)->  
                  invoke-method:java/lang/Boolean#valueOf:(Z)Ljava/lang/Boolean;
/*63 */ (fe63-2)->  
                  invoke-method:java/lang/Boolean#valueOf:(Z)Ljava/lang/Boolean;
/*63 */ (fe63-3)->  
                  invoke-method:java/lang/Boolean#valueOf:(Z)Ljava/lang/Boolean;
/*63 */ (fe63-4)->  
                  invoke-method:java/lang/Boolean#valueOf:(Z)Ljava/lang/Boolean;
/*63 */ (fe63-5)->  
                  invoke-method:java/lang/Boolean#valueOf:(Z)Ljava/lang/Boolean;
/*63 */ (7a12-3)->  
                  invoke-method:kotlin/collections/CollectionsKt#listOf:([Ljava/lang/Object;)Ljava/util/List;
/*90 */ (17cc-3)->  
                  invoke-method:kotlin/collections/CollectionsKt#collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
/*90 */ (ec06-3)->  
                  invoke-method:java/util/ArrayList#<init>:(I)V
/*91 */ (2795-3)->  
                  invoke-method:java/lang/Iterable#iterator:()Ljava/util/Iterator;
/*91 */ (b5c4-3)->  
                  invoke-method:java/util/Iterator#hasNext:()Z
/*91 */ (dce9-3)->  
                  invoke-method:java/util/Iterator#next:()Ljava/lang/Object;
/*92 */ (e4e8-1)->  
                  invoke-method:java/lang/Boolean#booleanValue:()Z
/*92 */ (81f3-3)->  
                  assign-variable:it
/*64 */ (a406-1)->  
                  invoke-method:java/io/PrintStream#println:(Z)V
/*65 */ (7338-1)->  
                  invoke-method:com/alibaba/fastjson/JSONObject#<init>:()V
/*93 */ (088d-1)->  
                  invoke-method:java/util/Collection#add:(Ljava/lang/Object;)Z
/*94 */ (088d-2)->  
                  invoke-method:java/util/Collection#add:(Ljava/lang/Object;)Z
/*69 */ (f84c-1)->  
                  invoke-method:com/seewo/study/minder/common/util/ResponseBuilder#<init>:()V
/*69 */ (3252-1)->  
                  invoke-method:com/seewo/study/minder/common/util/ResponseBuilder#ok:()Lcom/seewo/study/minder/common/util/ResponseBuilder;
/*69 */ (06c3-1)->  
                  invoke-method:com/seewo/study/minder/common/util/ResponseBuilder#data:(Ljava/lang/Object;)Lcom/seewo/study/minder/common/util/ResponseBuilder;
/*69 */ (a260-1)->  
                  invoke-method:com/seewo/study/minder/common/util/ResponseBuilder#build:()Lcom/alibaba/fastjson/JSONObject;

使用示例:

源码(部分):

...
/*8*/   public class MathGame {
...
/*21*/      public void run() throws InterruptedException {
/*22*/          try {
/*23*/              int number = random.nextInt() / 10000;
/*24*/              List<Integer> primeFactors = primeFactors(number);
/*25*/              print(number, primeFactors);
/*26*/  
/*27*/          } catch (Exception e) {
/*28*/              System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
/*29*/          }
/*30*/      }
...
/*63*/  }

使用行号:

$ line demo.MathGame run 25 -x 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 17 ms, listenerId: 2
method=demo.MathGame.run line=25
ts=2024-06-21 09:57:34.452; result=@HashMap[
    @String[primeFactors]:@ArrayList[
        @Integer[2],
        @Integer[7],
        @Integer[7],
        @Integer[991],
    ],
    @String[number]:@Integer[97118],
]

查看LineCode:

$ jad --lineCode demo.MathGame run
........
------------------------- lineCode location -------------------------
format: /*LineNumber*/ (LineCode)-> Instruction
/*23 */ (aacd-1)->  
                  invoke-method:java/util/Random#nextInt:()I
/*23 */ (5918-1)->  
                  assign-variable:e
/*24 */ (653f-1)->  
                  invoke-method:demo/MathGame#primeFactors:(I)Ljava/util/List;
/*24 */ (d961-1)->  
                  assign-variable:primeFactors
/*25 */ (416e-1)->  
                  invoke-method:demo/MathGame#print:(ILjava/util/List;)V
/*27 */ (5918-2)->  
                  assign-variable:e
/*28 */ (2455-1)->  
                  invoke-method:java/lang/StringBuilder#<init>:()V
/*28 */ (4076-1)->  
                  invoke-method:java/lang/Integer#valueOf:(I)Ljava/lang/Integer;
/*28 */ (b6e4-1)->  
                  invoke-method:java/lang/String#format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
/*28 */ (850c-1)->  
                  invoke-method:java/lang/StringBuilder#append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
/*28 */ (a53d-1)->  
                  invoke-method:java/lang/Exception#getMessage:()Ljava/lang/String;
/*28 */ (850c-2)->  
                  invoke-method:java/lang/StringBuilder#append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
/*28 */ (f7bb-1)->  
                  invoke-method:java/lang/StringBuilder#toString:()Ljava/lang/String;
/*28 */ (2f1b-1)->  
                  invoke-method:java/io/PrintStream#println:(Ljava/lang/String;)V
Affect(row-cnt:1) cost in 103 ms.

使用LineCode:

$ line demo.MathGame run 416e-1 -x 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 17 ms, listenerId: 2
method=demo.MathGame.run line=25
ts=2024-06-21 09:57:34.452; result=@HashMap[
    @String[primeFactors]:@ArrayList[
        @Integer[2],
        @Integer[7],
        @Integer[7],
        @Integer[991],
    ],
    @String[number]:@Integer[97118],
]

其它:

@isadliliying
Copy link
Author

@hengyunabc 大佬麻烦看看

@PGMT12138
Copy link

line -h可以正常输出,为什么观测变量时提示不支持命令呢,是不支持java8吗
image

@lxyyouxiang123
Copy link

@isadliliying 有时候 我们观察局部变量是观察一个变化过程,比如 方法methodX内部 局部变量 a,b, 从35到55行 a,b变量发生了哪些变化,也就是方法methodX 执行一次就打印20行的(55-35)变化结果类似于表格记录吧

@xiaoabc4
Copy link

image
java8不行吗?

@zhangpeilin
Copy link

zhangpeilin commented Oct 23, 2024

我编译你这个分支好像不通过
Screenshot of IntelliJ IDEA (2024-10-23, 14-59-51)

@isadliliying
Copy link
Author

line -h可以正常输出,为什么观测变量时提示不支持命令呢,是不支持java8吗 image

这个应该是你先启动了旧版本,然后再启动这个带line命令的版本,就会出现这个提示哈

@isadliliying
Copy link
Author

image java8不行吗?

你启动的是这个版本嘛?这个命令没有合并到主分支,正式版本里边是没有这个命令的

@isadliliying
Copy link
Author

我编译你这个分支好像不通过 Screenshot of IntelliJ IDEA (2024-10-23, 14-59-51)

excludePattern 是有个新版本的依赖库,可以检查下你的 bytekit-core 版本

com.alibaba
bytekit-core
0.0.9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants