快捷搜索:  汽车  科技

如何在脑子里为所欲为(如何在线上为所欲为)

如何在脑子里为所欲为(如何在线上为所欲为)然后 java -jar arthas-boot.jar 选择我们的 SumDemo 进行 attach更多下载方式见 arthas.aliyun.com/doc/install…import java.util.Arrays; import java.util.Random; import java.util.concurrent.TimeUnit; /** * 求和测试类 * * @author xisha * @date 2022/5/9 7:33 下午 */ public class SumDemo { public static void main(String[] args) throws Exception { SumDemo demo = new SumDemo(); while (true) {

概述

如果你看到这个标题想到的是 Arthas ,那么恭喜你,你想的跟我一样~

如果你看到这个标题一点想法的没有,那你可能真的需要系统性学习一下 arthas 了。arthas 是一款线上监控诊断工具,可以在不修改应用的情况下,对业务问题进行诊断。现在不用上几手 arthas ,都不好意思出门跟人调 bug了。

如果你想到的是 bpf,bcc 之类的,那你是真个大佬,大哥带带弟弟.jpg

准备工作

首先写一段简单的 java 代码 用来测试

import java.util.Arrays; import java.util.Random; import java.util.concurrent.TimeUnit; /** * 求和测试类 * * @author xisha * @date 2022/5/9 7:33 下午 */ public class SumDemo { public static void main(String[] args) throws Exception { SumDemo demo = new SumDemo(); while (true) { demo.test(); TimeUnit.SECONDS.sleep(3); } } public void test() { random random = new Random(System.currentTimeMillis()); int numberSize = random.nextInt(5); int[] numbers = new int[numberSize]; for (int i = 0; i < numberSize; i ) { numbers[i] = random.nextInt(10); } System.out.println(sum(numbers)); } public Integer sum(int... numbers) { return Arrays.stream(numbers).sum(); } }

代码很简单,就是一直生成几个随机数,然后求和,跑起来的效果大概如下



如何在脑子里为所欲为(如何在线上为所欲为)(1)

然后下载 arthas curl -O arthas.aliyun.com/arthas-boot…

更多下载方式见 arthas.aliyun.com/doc/install…

然后 java -jar arthas-boot.jar 选择我们的 SumDemo 进行 attach

成功之后的界面大概如下图



如何在脑子里为所欲为(如何在线上为所欲为)(2)

常用功能
  1. 查看 sum 方法每次传入的参数都是啥。
  2. 修改sum方法的返回,使其返回一个固定的值。
  3. 查看 SumDemo 类的代码。

如果大佬对上面三个功能了如指掌,那么直接跳到下一章节即可。

watch 参数

watch SumDemo sum '{params returnObj throwExp}' -n 5 -x 3



如何在脑子里为所欲为(如何在线上为所欲为)(3)

反编译

有时候我们想看看线上的代码是不是我们想要的,我们可以通过 jad 命令进行在线反编译。 jad SumDemo

效果大概如下图所示,我们可以可以方便的看到这个类的代码,类加载器,类文件的路径(这个在确定线上包的版本的时候十分有用)



如何在脑子里为所欲为(如何在线上为所欲为)(4)

更多 jad 的用法可以查看 arthas.aliyun.com/doc/jad.htm…

热更新一条龙

现在让我们尝试修改 sum 函数的返回值,使其返回一个固定的值 public Integer sum(int... numbers) { return 49; //return Arrays.stream(numbers).sum(); } 改完之后通过 arthas 编译一个 class

首先我们需要找到 SumDemo 的类加载器的hash值 输入 sc -d *SumDemo 对应的 classLoaderHash 就是我们需要的hash值,如图所示,我这边的 hash 值是 277050dc



如何在脑子里为所欲为(如何在线上为所欲为)(5)

然后用这个类加载编译一下刚才改好的 java 文件 mc -c 277050dc ./SumDemo.java



如何在脑子里为所欲为(如何在线上为所欲为)(6)

如上图所示,编译就算是成功了,如果不成功的话,可能是有些特殊情况,比如用了lombok,泛型对不上等等,要是失败了…… 那就算了,你跟这个命令莫得缘分。

然后我们使用 redefine 命令,

redefine classpath ,就可以热更新这个类了。



如何在脑子里为所欲为(如何在线上为所欲为)(7)



如何在脑子里为所欲为(如何在线上为所欲为)(8)

可以看到,热更新之后,我们的代码就只返回 49了。

注意,这里为什么先讲 jad 再讲 热更新呢? 因为 jad 命令跟 redefine 命令是冲突的,jad 会还原 redefine 产生的字节码,详见文档arthas.aliyun.com/doc/redefin…

为所欲为

上面讲了这么多,都是一些问题调试的功能,怎么就为所欲为了呢?

使用spring context 为所欲为

在 spring 的项目中,如果你有【保存 spring 上下文,以便自己下次使用】的习惯(如下代码),

import org.springframework.beans.beansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringBeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringBeanUtil.applicationContext = applicationContext; } public static <T> T getBean(Class<T> clazz) { return (T) applicationContext.getBean(clazz); } public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } }

那么你可以用 arthas 获取这个上下文,然后再获取相应的bean,然后就可以为所欲为了。对应的 arthas 代码如下

ognl '#context=@SpringBeanUtil@applicationContext #context.getBean("yourbeanName").doSomething()'

注意:加载对应的类可能需要对应的类加载器Hash,可以使用 sc -d *SpringBeanUtil 命令来获取类加载器 Hash,然后再使用 ognl 的时候加上 -c 选项即可。

使用 tt 为所欲为

如果没有【保存 spring 上下文】也不打紧,一般的 spring项目 都会有 http 接口(甚至健康检查的接口就是 http 接口),这个时候我们可以使用 tt 命令为所欲为。原理是找一个调用链路里有 spring 上下文的类,然后基于这个类记录这个请求,然后通过重放这个请求,达到执行任意 bean 代码的目的。比如 RequestMappingHandlerAdapter#invokeHandlerMethod 这个就很合适。具体过程如下:

我们先使用 tt 命令记录这个请求

tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod

然后等一个请求



如何在脑子里为所欲为(如何在线上为所欲为)(9)

大概像这样,然后再用 tt 命令,-i 可以指定重放的请求,-w 可以执行ognl,然后为所欲为的代码大概如下

tt -i 1000 -w 'target.getApplicationContext().getBean('yourbeanName').doSomething()' 基于 dubbo 为所欲为

有人说我莫得 http 接口,全是 dubbo 的调用,那这个咋整?不慌的,dubbo 某个拓展里自动为我们保存了 spring 上下文,直接为所欲为即可。代码如下:

ognl '#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next #context.getBean("yourBeanName").doSomeThing()' vmtool 获取实例为所欲为

如果我的应用啥也没有,没有dubbo,甚至都不是 spring 的,那我要如何为所欲为呢? 这个时候我们可以使用 vmtool,直接获取对象进行为所欲为。当我看到这个工具的时候真是直呼好家伙!太强了简直! 以我们的SumDemo 为例,我们可以直接获取这个类进行函数调用,示例代码如下 vmtool --action getInstances --className SumDemo --express 'instances[0].test()'

猜您喜欢: