Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

🔥🔥饿了么开源的 字节码插桩框架 lancet的增强版本,修复了一些Bug,并基于ByteX提高编译速度。支持以下特性:1.插桩功能分组,独立开关配置 2.更多字节码修改能力

License

Notifications You must be signed in to change notification settings

Knight-ZXW/LancetX

Repository files navigation

LancetX 是一个为Android项目设计的字节码插桩框架,其使用方式类似AspectJ。

该项目核心实现原理参考了 ele开源的 lancet 字节码插桩框架,与原有的lancet的不同点在于 本项目的plugin使用字节跳动的ByteX进行 class 文件的并行化 以便加快编译速度。

另外项目还并修复了原有项目的一些BUG,增加了一部分特性,比如提供了 功能分组、单独配置开关的能力。

使用

安装

在项目根目录的 build.gradle中,引入 ByteX 及 Lancex 插件依赖

buildscript {
 repositories {
 //... 其他maven地址
 maven { setUrl("https://artifact.bytedance.com/repository/byteX/") }
 }
 
 dependencies {
 //0.3.0 或其他更高版本
 classpath "com.bytedance.android.byteX:base-plugin:0.3.0"
 classpath "io.github.knight-zxw:lancet-plugin:${lancexVersion}"
 }
}

在app目录的 build.gradle 引入 sdk 并配置插件

apply plugin: 'bytex'
ByteX {
 enable true
 enableInDebug true
}
apply plugin: 'LancetX'
LancetX{
 enable true
 enableInDebug true
}
dependencies {
 implementation 'io.github.knight-zxw:lancet-runtime:${lancexVersion}'
}

三分钟示例

LanceX要求所有的字节码织入定义在申明了 @Weaver的类中,类名可以随意定义

@Weaver
public class InsertTest{
}

在类中,通过在函数上定义使用不同的注解,如 @ReplaceInvoke @Proxy @Insert 等来定义不同的函数字节码修改行为。

ReplaceInvoke 注解

用户替换函数调用, 既可以替换 普通成员函数调用,也可以替换 静态函数的调用。 比如替换 所有 Log.i 函数 (该函数是一个静态函数)的调用,可以通过如下方式实现

 @ReplaceInvoke(isStatic = true)
 @TargetClass(value = "android.util.Log",scope = Scope.SELF)
 @TargetMethod(methodName = "i")
 public static int replaceLog(String tag,String msg){
 msg = msg + "被替换";
 return Log.e("zxw",msg);
 }

或者替换一个成员函数的调用。 比如有一个ClassA 其定义如下

public class ClassA {
 public void printMessage(String message){
 }
}

在另一处中有调用printMessage

ClassA a = new ClassA()
a.printMessage("haha!");

现在希望替换掉 printMessage的实现, 注意该函数是一个成员函数,我们可以用如下注解实现替换

@Weaver
@Group("replaceInvokeTest")
public class ReplaceInvokeTest {
 @ReplaceInvoke()
 @TargetClass(value = "com.knightboost.lancetx.ClassA",scope = Scope.SELF)
 @TargetMethod(methodName = "printMessage")
 public static void printMessage(ClassA a, String msg){
 msg = msg + "";
 Log.e("ClassA",msg);
 }
}

注意函数的第一个参数表示被替换的类,由于原函数为成员函数,默认将这个对象实例作为第一个函数参数传递过来,其他函数参数为原函数 的参数。 通过该注解,原函数的调用就被替换为

ReplaceInvokeTest.printMessage(a,"haha!");

ReplaceNewInvoke 注解

用于替换 new xx() 指令。 比如在项目中,希望将 所有 new Thread() 的调用替换为 new ProxyThread的调用可以通过可以注解实现

@ReplaceNewInvoke()
public static void replaceNewThread(Thread t, ProxyThread proxyThread){
}

Insert

@Insert 类似AspectJ的 @Around ,可以实现在原函数前后插入代码。 比如我们希望监控Activity对象 onCreate函数的耗时,则可以用以下的定义实现

@Insert(mayCreateSuper = true)
@TargetMethod(methodName = "onCreate")
@TargetClass(value = "android.app.Activity", scope = Scope.LEAF)
public void onCreate2(@Nullable Bundle savedInstanceState) {
 long begin = System.currentTimeMillis();
 Origin.callVoid();
 long end = System.currentTimeMillis();
 Activity activity = ((Activity) This.get());
 Log.e("insertTest", activity + " onCreate cost "+(end-begin)+" ms");
 }

通过 @TargetClass@TargetMethod 表明及约束了对象哪些类的哪些函数进行类修改。@Insert 的mayCreateSuper 当在目标类未找到目标函数时,是否自动创建该函数被调用父类函数,默认值为false。 其中 @TargetClass 的 scope参数,可以实现对目标类的进一步约束,scope 将在其他小节详细介绍,示例中实现效果 是目标类为 android.app.Activity 的所有最终子类。

示例中的Origin 及 This 是 钩子类,Origin的相关API可以实现对原函数的调用, 而This来说, 你可以把它当成 java中的 this关键字对待,其表示了被编织类运行时的对象,通过getFiled()可以获取当前对象的成员变量,通过 putField 可以修改成员变量的值。

Proxy

@Insert在底层的实现是查找 目标类中符合的目标函数实现的,但是对于系统的类,比如 android.util .Log , 并未参与编译流程,这些类最终也不会打包对APK中,因此通过 @Insert 的方式无法进行修改。 虽然我们无法修改Log类及对应的函数实现,但我们可以修改自身代码(非JDK、androd SDK )中对这些系统代码的调用。 比如 我们的函数中本来调用了 Log.i()函数,可以修改为我们定义的 LogProxy.i() 函数,在LogProxy.i()中对原来的函数调用进行切面操作。

@Weaver
public class LogProxy {
 @Proxy()
 @TargetClass(value = "android.util.Log",scope = Scope.SELF)
 @TargetMethod(methodName = "i")
 public static int replaceLogI(String tag,String msg){
 msg = msg + "lancet";
 return (int) Origin.call();
 }
}

API详解

Insert注解

类似AspectJ的Around功能,可以实现对原函数实现切面编程,支持在原函数前后插入新的代码,控制原函数的调用(通过Origin钩子)。

Proxy注解

使用新的函数 替换原有函数的调用, 对于 (JDK/Android SDK)的函数,只能通过proxy的方式修改。

TargetClass注解

表示修改的目标类

Scope

以类的继承体系角度,配置或限定 Insert、Proxy 修改的范围.

  • Scope.SELF 代表仅匹配 value 指定的目标类
  • Scope.DIRECT 代表匹配 value 指定类的直接子类(直接继承于目标类的)
  • Scope.All 代表匹配 value 指定类及其所有子类
  • Scope.ALL_CHILDREN 代表匹配 value 指定类的所有子类
  • Scope.LEAF 代表匹配 value 指定类的最终子类 (即没有任何其他类再继承这个类)

TargetMethod注解

表示修改的目标函数名称

ClassOf注解

ClassOf 用于函数参数中, 实现对无法import类(私有、包级的)的引用 ClassOf 的 value 一定要按照 **(package_name.)(outer_class_name$)inner_class_name([]...)**的模板. 比如:

  • java.lang.Object
  • java.lang.Integer[][]
  • A[]
  • A$B

Origin

Origin 用来调用原目标方法. 可以被多次调用. Origin.call() 用来调用有返回值的方法. Origin.callVoid() 用来调用没有返回值的方法. 另外,如果你有捕捉异常的需求.可以使用 Origin.call/callThrowOne/callThrowTwo/callThrowThree() Origin.callVoid/callVoidThrowOne/callVoidThrowTwo/callVoidThrowThree() 比如 代理 InputStream的 read函数

@TargetClass("java.io.InputStream")
@TargetMethod(methodName="read")
@Proxy()
public int read(byte[] bytes) throws IOException {
 try {
 return (int) Origin.<IOException>callThrowOne();
 } catch (IOException e) {
 e.printStackTrace();
 throw e;
 }
}

This

仅用于Insert 方式的非静态方法的Hook中. get() 返回目标方法被调用的实例化对象. 相当于java 的this,只不过指向的对象是运行时被修改的那个类的实例对象 **putField & getField** 你可以直接存取目标类的所有属性,无论是 protected or private. 另外,如果这个属性不存在,我们还会自动创建这个属性. Exciting! 自动装箱拆箱肯定也支持了. 一些已知的缺陷:

  • Proxy 不能使用 This
  • 你不能存取你父类的属性. 当你尝试存取父类属性时,我们还是会创建新的属性.

例如:

package com.knightboost.weaver;
public class Main {
 private int a = 1;
 public void nothing(){
 }
 public int getA(){
 return a;
 }
}
@TargetClass("com.knightboost.weaver.Main")
@TargetMethod(methodName="nothing")
@Insert()
public void testThis() {
 Log.e("debug", This.get().getClass().getName());
 This.putField(3, "a");
 Origin.callVoid();
}

一些限制

  1. ReplaceXX 的实现在函数体中 This、Orignal类及其函数.

功能分组能力

你可能会有对不同的插桩功能进行独立开关控制,而不是全局控制,通过 @Group 注解,你可以为某个Weaver类的插桩功能进行分组命名, 在分组之后你可以在gradle 配置中对这组插桩功能进行单独的开关控制。 动态配置

@Weaver
@Group("insertTest")
public class InsertTest {
}
apply plugin: 'LancetX'
LancetX{
 enable true //插件开关
 enableInDebug //debug包编译时的插件开关
 weaveGroup{
 //insertTest group所属的字节码修改功能开关
 insertTest {
 enable true
 }
 }
}

底层实现说明

todo

About

🔥🔥饿了么开源的 字节码插桩框架 lancet的增强版本,修复了一些Bug,并基于ByteX提高编译速度。支持以下特性:1.插桩功能分组,独立开关配置 2.更多字节码修改能力

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

AltStyle によって変換されたページ (->オリジナル) /