一、前言
在本章节中,将学习以下 Arthas 的 Class 相关命令,同时我也会附上官方文档的链接,方便大家查阅:
- sc Search Class 查看运行中的类信息
- sm Search Method 查看类中方法的信息
- jad 反编译字节码为源代码
- mc Memory Compile 将源代码编译成字节码
- redefine 将编译好的字节码文件加载到 JVM 中运行
- dump 将已加载类的 bytecode 下载到特定目录
- classloader 查看 classloader 的继承树,urls,类加载信息
二、sc
sc
是 Search-Class 的缩写,用于查看 JVM 已加载的类信息,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息。
class-pattern支持全限定名,如com.taobao.test.AAA,也支持com/taobao/test/AAA 这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把
/
替换为.
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开
options disable-sub-class true
开关。
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。 如果一个类被多个ClassLoader所加载,则会出现多次 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[f] | 输出当前类的成员变量信息(需要配合参数-d一起使用) |
[x:] | 指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出 |
[c:] |
指定class的 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[n:] |
具有详细信息的匹配类的最大数量(默认为100) |
(1)模糊搜索,demo 包下所有的类
1 | sc demo.* |
(2)打印 demo.MathGame 类的详细信息
(3)打印 demo.MathGame 类的详细信息 + 变量信息
三、sm
sm
是 Search-Method 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm
命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 展示每个方法的详细信息 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] |
指定class的 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[n:] |
具有详细信息的匹配类的最大数量(默认为100) |
(1)显示 String 类加载的方法
(2)显示 String 中的 toString 方法详细信息
三、jad
jad
命令的主要工作是反编译,将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑。
- 在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便
- 当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
[c:] |
类所属 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
3.1 编译 String 类
1 | jad <class-pattern> |
3.2 反编译时只显示源代码
默认情况下,反编译结果里会带有 ClassLoader
信息,通过 --source-only
选项,可以只打印源代码。方便和 mc/redefine 命令结合使用。
1 | jad --source-only <class-pattern> |
3.3 反编译指定的函数
1 | jad <class-pattern> <method-pattern> |
四、mc
mc
是 Memory Compiler 的缩写,编译 .java
文件生成 .class
。
(1)在内存中编译 Hello.java 为 Hello.class
1 | mc /root/Hello.java |
(2)可以通过 -d 命令指定输出目录
1 | mc -d /root/bbb /root/Hello.java |
五、redefine
加载外部的 .class
文件,redefine JVM 已加载的类。
注意, redefine 后的原来的类不能恢复,redefine 有可能失败(比如增加了新的 field),参考 JDK 本身的文档。
参数名称 | 参数说明 |
---|---|
[c:] | ClassLoader的hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[p:] | 外部的.class 文件的完整路径,支持多个 |
5.1 自身限制
- 不允许新增加 field / method
- 正在跑的函数,没有退出不能生效,比如下面新增加的
System.out.println
,只有run()
函数里的会生效
1 | public class MathGame { |
5.2 命令冲突
reset
命令对redefine
的类无效。如果想重置,需要redefine
原始的字节码。redefine
命令和jad
/watch
/trace
/monitor
/tt
等命令会冲突。执行完redefine
之后,如果再执行上面提到的命令,则会把redefine
的字节码重置。 原因是 JDK 本身 redefine 和 Retransform 是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。
5.3 实战
(1)反编译 MathGame 类
1 | jad demo.MathGame > MathGame.java |
(2)编辑该类,增加两行输出。一行在 main 方法死循环中,一行在 run() 方法首行。
(2)编译修改后的类
1 | mc -d C://Users//Jitwxs//Downloads//MathGame.java C://Users//Jitwxs//Downloads |
原谅我这边没有截图,因为我在 Windows 电脑上执行 mc 命令失败了。
先是提示我 Can not load JavaCompiler from javax.tools.ToolProvider#getSystemJavaCompiler(), please confirm the application running in JDK not JRE。
解决后又报 FileNotFoundException: C:\Users\Jitwxs\Downloads (拒绝访问) 的错。
这就告诉我们,虽然是跨平台的,但还是不要用 Windows 去做命令行开发,否则慢慢踩坑吧。。
(3)加载最新的字节码
1 | redefine C://Users//Jitwxs//Downloads//MathGame.class |
5.6 上传 .class 文件到服务器的技巧
使用 mc
命令来编译 jad
的反编译的代码有可能失败。可以在本地修改代码,编译好后再上传到服务器上。有的服务器不允许直接上传文件,可以使用 base64
命令来绕过。
-
在本地先转换
.class
文件为 base64,再保存为 result.txt1
base64 < Test.class > result.txt
-
到服务器上,新建并编辑
result.txt
,复制本地的内容,粘贴再保存 -
把服务器上的
result.txt
还原为.class
1
base64 -d < result.txt > Test.class
-
用 MD5 命令计算哈希值,校验是否一致
六、dump
dump 已加载类的 bytecode 到特定目录。
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
[c:] |
类所属 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[d:] |
设置类文件的目标目录 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
(1)把 String 类的字节码文件保存到当前目录下
1 | dump java.lang.String -d . |
(2)把 demo 包下所有的类的字节码文件保存到当前目录下
1 | dump demo.* -d . |
七、classloader
classloader
命令将 JVM 中所有的classloader的信息统计出来,并可以展示继承树,urls等。
可以让指定的 classloader 去 getResources,打印出所有查找到的 resources 的 url。对于 ResourceNotFoundException
比较有用。
参数名称 | 参数说明 |
---|---|
[l] | 按类加载实例进行统计 |
[t] | 打印所有ClassLoader的继承树 |
[a] | 列出所有ClassLoader加载的类,请谨慎使用 |
[c:] |
ClassLoader的hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[c: r:] |
用ClassLoader去查找resource |
[c: load:] |
用ClassLoader去加载指定的类 |
(1)按类加载器的类型查看统计信息
1 | classloadaer |
(2)按类加载实例查看统计信息
1 | classloadaer -l |
(3)查看 ClassLoader 的继承树
1 | classloadaer -t |
(4)通过类加载器的 hash,查看此类加载器实际所在的位置
注意hashcode是变化的,需要先查看当前的ClassLoader信息,提取对应ClassLoader的hashcode。对于只有唯一实例的 ClassLoader 可以通过 class name 指定,使用起来更加方便
1 | classloader -c 1c1582d6 |
(5)使用 ClassLoader 去查找指定资源 resource 所在的位置
1 | classloader -c 1c1582d6 -r META-INF/MANIFEST.MF |
(6)使用 ClassLoader 去加载类
1 | classloader -c 5c647e05 --load demo.MathGame |