ASM學(xué)習(xí)思路
ASM圖片監(jiān)控hook glide
統(tǒng)一項(xiàng)目中的線程池,Executors封裝的幾個(gè)線程池比較好操作,就以此為切入點(diǎn)了。閑話不多說,開始擼。
幾個(gè)靜態(tài)方法ThreadUtil
public class ThreadUtil {
private static final int coreSize = Runtime.getRuntime().availableProcessors() + 1;
private static final ExecutorService fix = Executors.newFixedThreadPool(coreSize);
private static final ExecutorService single = Executors.newSingleThreadExecutor();
private static final ExecutorService cache = Executors.newCachedThreadPool();
private static final ExecutorService scheduled = Executors.newScheduledThreadPool(coreSize);
public static ExecutorService threadPool() {
return cache;
}
}
準(zhǔn)備將項(xiàng)目中所有的線程池替換成上面的cache,寫個(gè)threadPool()靜態(tài)方法供asm替換。
老樣子ThreadClassVisitor
class ThreadClassVisitor(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM9, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
return ThreadMethodVisitor(mv, access, name, descriptor)
}
}
ThreadMethodVisitor重寫visitMethodInsn()方法。
object ThreadMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
}
visitMethodInsn()中根據(jù)參數(shù)判斷是否是Executors.newCachedThreadPool()等創(chuàng)建線程池的方法。這里多說一嘴,站在字節(jié)碼的角度看,owner、name、descriptor就可以確定是哪個(gè)類的方法。以Executors此為例,看看字節(jié)碼。圖方便,直接對(duì)上述ThreadUtil.class文件使用ASM Bytecode Viewer插件。

先看字節(jié)碼
Bytecode
GETSTATIC com/chenxuan/hook/ThreadUtil.coreSize : I
INVOKESTATIC java/util/concurrent/Executors.newFixedThreadPool (I)Ljava/util/concurrent/ExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.fix : Ljava/util/concurrent/ExecutorService;
INVOKESTATIC java/util/concurrent/Executors.newSingleThreadExecutor ()Ljava/util/concurrent/ExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.singe : Ljava/util/concurrent/ExecutorService;
INVOKESTATIC java/util/concurrent/Executors.newCachedThreadPool ()Ljava/util/concurrent/ExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.cache : Ljava/util/concurrent/ExecutorService;
GETSTATIC com/chenxuan/hook/ThreadUtil.coreSize : I
INVOKESTATIC java/util/concurrent/Executors.newScheduledThreadPool (I)Ljava/util/concurrent/ScheduledExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.scheduled : Ljava/util/concurrent/ExecutorService;
對(duì)應(yīng)看asm api ASMmified
methodVisitor.visitFieldInsn(GETSTATIC, "com/chenxuan/hook/ThreadUtil", "coreSize", "I");
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newFixedThreadPool", "(I)Ljava/util/concurrent/ExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "fix", "Ljava/util/concurrent/ExecutorService;")
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newSingleThreadExecutor", "()Ljava/util/concurrent/ExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "singe", "Ljava/util/concurrent/ExecutorService;");
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newCachedThreadPool", "()Ljava/util/concurrent/ExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "cache", "Ljava/util/concurrent/ExecutorService;");
methodVisitor.visitFieldInsn(GETSTATIC, "com/chenxuan/hook/ThreadUtil", "coreSize", "I");
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newScheduledThreadPool", "(I)Ljava/util/concurrent/ScheduledExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "scheduled", "Ljava/util/concurrent/ExecutorService;");
很清晰了:
- opcode->INVOKESTATIC 靜態(tài)方法
- owner->java/util/concurrent/Executors 所屬類
- name->newCachedThreadPool 方法名
- descriptor->()Ljava/util/concurrent/ExecutorService 方法參數(shù)和返回值
- isInterface->false 是否接口方法
為了方便匹配這幾個(gè)方法和做替換,建一個(gè)實(shí)體類ThreadMethod描述方法模型。
data class ThreadMethod(
var opcode: Int = Opcodes.INVOKESTATIC,
var owner: String?,
var name: String?,
var descriptor: String?,
var isInterface: Boolean = false
) {
fun equalThreadMethod(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?
) =
this.opcode == opcode && this.owner == owner && this.name == name && this.descriptor == descriptor
}
寫個(gè)equalThreadMethod()方法匹配visitMethodInsn傳過來的參數(shù),還需要一個(gè)集合threadMethods保存Executors的幾個(gè)靜態(tài)方法。
internal val threadMethods = mutableListOf<ThreadMethod>().apply {
add(
ThreadMethod(
owner = "java/util/concurrent/Executors",
name = "newCachedThreadPool",
descriptor = "()Ljava/util/concurrent/ExecutorService;"
)
)
}
偷個(gè)懶,先匹配Executors.newCachedThreadPool(),接下來在visitMethodInsn中判斷并替換為自定義的線程池。
object ThreadMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
if (containsThread(opcode, owner, name, descriptor)) {
//替換
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
}
private fun containsThread(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?
): Boolean {
threadMethods.forEach {
if (it.equalThreadMethod(opcode, owner, name, descriptor)) {
return true
}
}
return false
}
}
替換的方法直接在這里寫死也不太好,用ThreadMethod包裝一下
internal val realThreadMethod = ThreadMethod(
owner = "com/chenxuan/hook/ThreadUtil",
name = "threadPool",
descriptor = "()Ljava/util/concurrent/ExecutorService;"
)
修改visitMethodInsn()替換處
object ThreadMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
if (containsThread(opcode, owner, name, descriptor)) {
super.visitMethodInsn(
realThreadMethod.opcode,
realThreadMethod.owner,
realThreadMethod.name,
realThreadMethod.descriptor,
realThreadMethod.isInterface
)
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
}
private fun containsThread(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?
): Boolean {
threadMethods.forEach {
if (it.equalThreadMethod(opcode, owner, name, descriptor)) {
return true
}
}
return false
}
}
跑個(gè)測(cè)試用例MainActivity,build。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
trackMethod()
loadPic()
hookThread()
}
private fun hookThread() {
val cache = Executors.newCachedThreadPool()
}
private fun loadPic() {
Glide
.with(this)
.load("https://pic.3gbizhi.com/2014/0430/20140430043839656.jpg")
.into(findViewById(R.id.ivAvatar))
}
@Track
private fun trackMethod() {
val data = mutableListOf<String>()
}
}
查看transform下處理過的MainActivity并反編譯成Java,關(guān)注hookThread()就好。

成功替換為ThreadUtil.threadPool()。后續(xù)補(bǔ)充threadMethods將其它幾個(gè)靜態(tài)方法添加進(jìn)去,當(dāng)然還有ThreadPoolExecutor構(gòu)造方法,收斂所有創(chuàng)建線程池的方法,然后還有new Thread之類的寫法處理到cacheThreadPool中基本就ok了。還可以增加白名單,并非所有線程池都需要替換。感覺吧,asm確實(shí)是可以為所欲為啊。