<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
看完本章,你將會學習到用ASM的tree api進行對匿名執行緒的hook操作,同時也能夠了解到asm相關的操作和背景知識介紹!對於ASM插樁來說,可能很多人都不陌生了,但是大多數可能都停留在core api上,對於現在市面上的一些插樁庫,其實很多都用tree api進行編寫了,因為tree api的簡單與明瞭的特性,也越來越成為許多開源庫的選擇。(ASM有兩套api型別,分別是core 和 tree)
ASM其實就是一個可以編譯位元組碼的工具,比如說我們日常開發會引入很多的類庫對不對,又或者說我們的專案太大了,想修改某個點的時候,統一修改容易出錯(比如隱私合規問題等),這個時候如果能有一個工具對生成後的class檔案進行編輯的話,就非常方便我們進行後續的工作了。
本章主要介紹tree api,下文所說的ASM都是指tree api的操作哦,對於core api的介紹可以檢視筆者曾經寫過的文章Spider。
我們常說的class檔案,其實從二進位制的角度出發,無非是分成以下幾個部分:
可以看到,一個class檔案其實就是由上圖中的多個部分組成,而ASM,就是把這些結構進行了更進一步的抽象,對於class檔案,其實就是抽象成asm中的class node類
對於一個class檔案來說,通過以下就可以進行唯一性識別,分別是:version(版本),access(作用域,比如private等修飾符),name(名稱),signature(泛型簽名),superName(父類別),interfaces(實現的介面),fields(當前的屬性),methoss(當前的方法)。 所以如果想要修改一個class,我們修改對應的classNode即可
屬性,也是類非常重要的一部分,在位元組碼中,是如此定義的
對於一個屬性,ASM將其抽象為FieldNode
對於一個屬性field來說,通過以下就可以進行唯一性識別:access(作用域,跟class結構一樣,比如private修飾),name(屬性名稱),desc(簽名),signature(泛型簽名),value(當前對應的數值)
相比於屬性,我們的方法結構更為複雜
相比於屬性的單一,一個方法可能由多條指令組成而,一個方法的成功執行,也涉及到區域性變數表跟運算元棧的配合。ASM中把方法抽象成這樣一個定義 方法 = 方法頭+方法體
方法頭:即標識一個方法的基本屬性,包括:access(作用域),name(方法名),desc(方法簽名),signature(泛型簽名),exceptions(方法可以丟擲的異常)
方法體:相比於方法頭,方法體的概念其實就比較簡單了,其實方法體就是方法的各條指令的集合,主要包括instrutions(方法的指令集),tryCatchBlocks(異常的節點集),maxStack(運算元棧的最大深度),maxLocals(本地變數表的最大長度)
可以看到,方法其中的InsnList物件,是特指方法的指令集的抽象,這裡繼續講解
public class InsnList implements Iterable<AbstractInsnNode> { private int size; private AbstractInsnNode firstInsn; private AbstractInsnNode lastInsn; AbstractInsnNode[] cache; ...
可以看到,主要的物件就是firstInsn,與lastInsn,代表著方法指令集的頭指令與尾指令,每一個指令其實都被抽象成了AbstractInsnNode的子類,AbstractInsnNode定義了一條指令最基礎的資訊,我們可以看看這個類的子類
這裡我們再看看我們最常用的methodInsnNode
public class MethodInsnNode extends AbstractInsnNode { /** * The internal name of the method's owner class (see {@link * org.objectweb.asm.Type#getInternalName()}). * * <p>For methods of arrays, e.g., {@code clone()}, the array type descriptor. */ public String owner; /** The method's name. */ public String name; /** The method's descriptor (see {@link org.objectweb.asm.Type}). */ public String desc; /** Whether the method's owner class if an interface. */ public boolean itf;
這個就是一個普通方法指令最根本的定義了,owner(方法呼叫者),name(方法名稱),desc(方法簽名)等等,他們都有著相似的結構,這個也是我們接下來會實戰的重點。
嗯!我們最後介紹一下這個神奇的東西!不知道大家在看介紹的時候,有沒有一臉疑惑,這個我解釋為泛型簽名,這個跟desc(函數簽名)引數有什麼區別呢?當然,這個不僅僅在函數上有出現,在屬性,類的結構上都有出現!是不是非常神奇!
其實Signature屬性是在JDK 1.5釋出後增加到了Class檔案規範之中,它是一個可選的定長屬性, 可以出現於類、屬性表和方法表結構的屬性表中。我們想想看,jdk1.5究竟是發生什麼了!其實就是對泛型的支援,那麼1.5版本之前的sdk怎麼辦,是不是也要進行相容了!所以java標準組就想到了一個折中的方法,就是泛型擦除,泛型資訊編譯(型別變數、引數化型別)之後 都通通被擦除掉,以此來進行對前者的相容。那麼這又導致了一個問題,擦除的泛型資訊有時候正是我們所需要的,所以Signature就出現了,把這些泛型資訊儲存在這裡,以提供執行時反射等型別資訊的獲取!實際上可以看到,我們大部分的方法或者屬性這個值都為null,只有存在泛型定義的時候,泛型的資訊才會被儲存在Signature裡面
好啦!有了理論基礎,我們也該去實戰一下,才不是口水文!以我們執行緒優化為例子,在工作專案中,或者在老專案中,可能存在大多數不規範的執行緒建立操作,比如直接new Thread等等,這樣生成的執行緒名就會被賦予預設的名字,我們這裡先把這類執行緒叫做“匿名執行緒”!當然!並不是說這個執行緒沒有名字,而是執行緒名一般是“Thread -1 ”這種沒有額外資訊含量的名字,這樣對我們後期的執行緒維護會帶來很大的干擾,時間長了,可能就存在大多數這種匿名執行緒,有可能帶來執行緒建立的oom crash!所以我們的目標是,給這些執行緒賦予“名字”,即呼叫者的名字
為了達到這個目的,我們需要對thread的構造有一個瞭解,當然Thread的建構函式有很多,我們舉幾個例子
public Thread(String name) { init(null, null, name, 0); }
public Thread(ThreadGroup group, String name) { init(group, null, name, 0); }
可以看到,我們Thread的多個建構函式,最後一個引數都是name,即Thread的名稱,所以我們的hook點是,能不能在Thread的構造過程,呼叫到有name的建構函式是不是就可以實現我們的目的了!我們再看一下普通的new Thread()位元組碼
那麼我們怎麼才能把new Thread()的方式變成 new Thread(name)的方式呢?很簡單!只需要我們把init的這條指令變成有參的方式就可以了,怎麼改變呢?其實就是改變desc!方法簽名即可,因為一個方法的呼叫,就是依據方法簽名進行匹配的。我們在函數後面新增一個string的引數即可
node是methidInsnNode def desc = "${node.desc.substring(0, r)}Ljava/lang/String;${node.desc.substring(r)}" node.desc = desc
那麼這樣我們就可以完成了嗎,非也非也,我們只是給方法簽名對加了一個引數,但是這並不代表我們函數就是這麼執行的!因為方法引數的參數列中的string引數我們還沒放入運算元棧呢!那麼我們就可以構造一個string引數放入運算元棧中,這個指令就是ldc指令啦!asm為我們提供了一個類是LdcInsnNode,我們可以建立一個該類物件即可,構造引數需要傳入一個字串,那麼這個就可以把當前方法的owner(解釋如上,呼叫者名稱)放進去了,是不是就達到我們想要的目的了!好啦!東西我們又了,我們要在哪裡插入呢?
所以我們的目標很明確,就是在init指令呼叫前插入即可,asm也提供了insertBefore方法,提供在某個指令前插入的便捷操作。
method.instructions.insertBefore( node, new LdcInsnNode(klass.name) )
我們看看最後插入後的位元組碼
當然,我們插入asm程式碼一般是在android提供給我們的Transform階段進行的(agp新版有改變,但是大體工作流程一致),所以我們在transfrom中為了避免對類的過度干擾,我們還需要把不必要的階段提早剔除!比如我們只在new Thread操作,那麼就把非Opcodes.INVOKESPECIAL的操作過濾即可。還有就是非init階段(即非建構函式階段)或者owner不為Thread類就可以提前過濾,不參與更改即可。
那我們看到完整的程式碼(需要在Transform中執行的程式碼)
static void transform(ClassNode klass) { println("ThreadTransformUtils") // 這裡只處理Thread klass.methods?.forEach { methodNode -> methodNode.instructions.each { // 如果是建構函式才繼續進行 if (it.opcode == Opcodes.INVOKESPECIAL) { transformInvokeSpecial((MethodInsnNode) it, klass, methodNode) } } } } private static void transformInvokeSpecial(MethodInsnNode node, ClassNode klass, MethodNode method) { // 如果不是建構函式,就直接退出 if (node.name != "<init>" || node.owner != THREAD) { return } println("transformInvokeSpecial") transformThreadInvokeSpecial(node, klass, method) } private static void transformThreadInvokeSpecial( MethodInsnNode node, ClassNode klass, MethodNode method ) { switch (node.desc) { // Thread() case "()V": // Thread(Runnable) case "(Ljava/lang/Runnable;)V": method.instructions.insertBefore( node, new LdcInsnNode(klass.name) ) def r = node.desc.lastIndexOf(')') def desc = "${node.desc.substring(0, r)}Ljava/lang/String;${node.desc.substring(r)}" // println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}") println(" * ${node.owner}.${node.name}${node.desc} => ${node.owner}.${node.name}$desc: ${klass.name}.${method.name}${method.desc}") node.desc = desc break } }
看到這裡,應該可以瞭解到asm tree api相關用法與實戰了,希望能有所幫助!
以上就是ASM的tree api對匿名執行緒的hook操作詳解的詳細內容,更多關於ASM tree api匿名執行緒hook的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45