java 汇编_大话+图说:Java 汇编指令——只为让你懂
原標題:大話+圖說:Java 匯編指令——只為讓你懂
前言
隨著Android開發技術不斷被推到新的高度,對于Android程序員來講越來越需要具備一些對深入的基礎性的技術的理解,比如說Java匯編指令。不然,可能很難深入理解一些時下的新框架、新技術,盲目一味追新也會越來越感乏力。
本文既不求照本宣科,亦不求炫技或著文立說,僅力圖以最簡明、最形象生動的方式,結合例子與實戰,讓小白也能搞懂這門看似復雜的技術概念。
單刀直入
閑言碎語不要講,先表一表,什么是Java匯編指令?簡而言之,Java匯編指令就是Java虛擬機能夠聽得懂、可執行的指令,也可以說是Java代碼的最小執行單元。有點Java基礎的人一定都知道,javac命令會將Java源文件編譯成字節碼文件,也就是.class文件,里面就包含了大量的匯編指令。因此可以將javac命令理解為一個翻譯命令,將源文件翻譯成虛擬機可以執行的指令。那么最直觀的探究方法莫過于直接對比一下翻譯前后的內容。具體如何對比呢?就不得不用到Java為我們一直默默提供的一項利器,javap命令,它可以解析字節碼,將字節碼內部邏輯以可讀的方式呈現出來。為了緊貼實戰,我們直接在新建的Android工程里,寫這樣一個Activity類,里面包含幾個由簡單到復雜的方法和一個名為name的字段:
如圖,以上方法,復雜度由低到高依次為:getName
cd到這個路徑下,運行萬能的javap命令:javap -v -p MainActivity,就可以觀看到翻譯版的Java字節碼的胴體了!這里的-v意思是啰嗦模式,會輸出全面的字節碼信息,而-p是指涵蓋所有成員。原字節碼信息輸出內容較多,基于本文的目標,取其一方法的內容,整理如下圖:方法1,getName():
這個getName的方法應該是再簡單不過的Java代碼,翻譯成字節碼后也變成了三行,我們先來簡單推理一下:第一句,aload_0不知所云,索性略過;第二行,getfield應該可以讀懂,后面這個#2似乎是他的參數(實際上是對常量池的引用),//后面注釋的內容是javap給我們加上的,意思應該是#2的指向是"Field name:Ljava/lang/String;"這個內容。所以getfield這一行就是取出name這個字段嘍,so easy。areturn肯定就是return的意思,a的含義也先略過不表。總之就是取出name字段然后return嘍。
那么現在的問題就是aload_0是什么意思了,看似多余,但仔細思考一下,似乎之前給getfield指令傳入了“Field name:Ljava/lang/String;”這樣一個并不完整的參數,其后半部分的“Ljava/lang/String;”僅僅表示這個name字段的類型是String,也就是說,整個參數里沒有說是取的誰的name字段啊!究竟是get誰的feild呢?
由此可以想到:aload操作一定是在為getfield指令準備了一個主體。
實際上,再結合下面的局部變量表,aload_0中的0正是局部變量表里的Slot 0的含義。意思是將局部變量表里的Slot 0的東西壓入操作數棧,這個Slot 0里的東西name正是this,也就是MainActivity的實例,即getfield的主體。
大戲上演
好了,對于小白同學有些陌生的概念來了,啥是操作數棧?啥是局部變量表?其實這兩個東西理解好了,關于虛擬機指令就懂了一大半了。那么,不妨刪繁就簡,由易入難,先講一個這樣的故事,故事起名叫:
Java方法之創世紀
話說Jvm大帝是神之旨意的履行者(Jvm大帝就是虛擬機,神就是開發者,神之旨意是開發者寫好并編譯后的字節碼...),當Jvm大帝帶領Java世界運行進入了一個新的方法后,會為這個方法在棧內存大陸上創造兩個重要的領域:局部變量表和操作數棧。
要有棧。要有表。神說。
依照神之旨意,jvm大帝創造的局部變量表里一般會包含this指針(針對實例方法,靜態方法當然無此)、方法的所有傳入參數和方法中所開辟的本地變量。
那么操作數棧是干嘛用的呢?
我們再引入另外一個比喻,如果把運行Java方法理解為拍戲,那么局部變量表里的各個局部變量就是這部戲的核心主角,或者說領銜主演,而操作數棧正是這部戲的舞臺。所謂操作數棧搭臺,局部變量唱戲,是也。那么aload0就是告訴Jvm導演(大帝已淪落為導演),請0號演員this同志登臺(壓棧),演后邊的本子。當然了,這個比喻并不完全恰當,因為操作數棧并不是“舞臺”的結構,而是棧的結構。但是這個比喻可以很好地說明局部變量表和操作數棧之間的關系,以及aload0的作用。
下面我們用一張圖來演示一下getName這個小劇本橋段所導演的故事:
好吧這部劇雖然短的可憐,但已經基本把指令、操作數棧和局部變量表三者的關系演繹了出來。值得注意的是,getfield這條指令對操作數棧進行了復合操作,其流程可以示意如下圖:
后面我們將要接觸到的許多指令都如此,指令內部執行了彈出—>處理—>壓回的流程。下面我們就來分析一個相對復雜一點的方法,setName(String),如下圖:
這里我們看到,變化主要有,指令多了一行,多進行了一次aload,getfield變成了putfield,areturn變成了return,僅此而已。另外領銜主演也就是局部變量表里多了一位,也就是方法的傳入參數name字符串對象了。其情節如下:
這里,putfield只彈出棧內的操作數,而沒有向操作數棧壓回任何數據,而且執行putfield之前,棧內元素的位置也必須符合“值在上,主體在下”要求。而最后的return僅表示方法結束,而不會像areturn一樣返回棧頂元素。這也印證了setName(String)方法沒有返回參數。
融會貫通
相信有了以上的講解,大家對指令、操作數棧、局部變量表三者的運作關系有了一定認識,為了后邊能夠分析更復雜的方法,這里必須概括性地講解一下更多的Jvm匯編指令。雖然Jvm匯編指令非常多,但其實常用的不外乎幾個類別,先從這幾個常用類別入手理解,便可漸入佳境。關于Java匯編指令的分類,可以從兩個維度進行:一是指令的功能,二是指令操作的數據類型。我們先從功能說起,指令主要可以分為如下幾類:
存儲和加載類指令:主要包括load系列指令、store系列指令和ldc、push系列指令,主要用于在局部變量表、操作數棧和常量池三者之間進行數據調度;(關于常量池前面沒有特別講解,這個也很簡單,顧名思義,就是這個池子里放著各種常量,好比片場的道具庫)
對象操作指令(創建與讀寫訪問):比如我們剛剛的putfield和getfield就屬于讀寫訪問的指令,此外還有putstatic/getstatic,還有new系列指令,以及instanceof等指令。
操作數棧管理指令:如pop和dup,他們只對操作數棧進行操作。
類型轉換指令和運算指令:如add/div/l2i等系列指令,實際上這類指令一般也只對操作數棧進行操作。
控制跳轉指令:這類里包含常用的if系列指令以及goto類指令。
方法調用和返回指令:主要包括invoke系列指令和return系列指令。這類指令也意味這一個方法空間的開辟和結束,即invoke會喚醒一個新的java方法小宇宙(新的棧和局部變量表),而return則意味著這個宇宙的結束回收。
如下圖,展示了各類指令的作用:
再從另外一個維度,即指令操作的數據類型來講:指令開頭或尾部的一些字母,就往往表明了它所能操作的數據類型:
a對應對象,表示指令操作對象性數據,比如aload和astore、areturn等等。i對應整形。也就有iload,istore等i系列指令。f對應浮點型。l對應long,b對應byte,d對應double,c對應char。另外地,ia對應int array,aa對應object array,da對應double array。不在一一贅述。
了解了以上內容,我們再去看最后幾個方法,應該就會容易理解很多了。下面我們就直搗黃龍gotoBrowser這個方法:
這個過程簡單解讀如下:
new一個Intent對象(在堆內存中開辟空間),并將其引用入棧;
dup復制棧頂的剛剛放入的引用,再次壓棧,這時棧里有兩個重復的內容,深度為2;
從常量池取出“android.intent.action.View”這個字符串(對象引用)壓棧,此時棧深度為3;
彈出棧頂的兩個對象,調用彈出的第二個對象的 方法,棧深度為1;
將此時棧頂引用彈出并存儲到局部變量中(slot 2),此時棧就清空了,深度0;
后面的就相對簡單了,讀者可以嘗試自己解讀。再看這個包含if跳轉的方法staticMethod:
如上圖,圖中已經說明的比較全面了,不再贅述。值得一提的是,Java的這種基于棧結構的匯編指令,在設計上有一種非常簡潔的美感,指令與指令之間并沒有較重的依賴,每條指令僅僅與操作數棧等領域內的數據發生關系,充滿著某種平衡與秩序感。因此也必須注意,幾乎每條指令的運行都有其前提,比如在invokevirtual或invokespecial指令執行前,必須保證操作數棧內提前按順序壓入好所需的操作數,否則就會發生問題。關于最復雜的onCreate方法,就不再啰嗦解讀了,讀者可以前往我的github上的對應demo repo,進入tutorial分支,拉取源碼和教程資源,或者自己寫demo體驗這一完整過程。地址:https://github.com/BryanSharp/hiBeaverDemo
后話
關于實戰,一是可以學習使用強大開源工具ASM.jar;二是,特別對于Android同學,可以參考本人的另一篇文章:Android字節碼修改神器HiBeaver:黑掉你的SDK(https://segmentfault.com/a/1190000008491823)以及一次Android字節碼插樁實戰(https://segmentfault.com/a/1190000008658815),利用hibeaver這個助手,開發者可以非常靈活地對字節碼進行修改,插入指令,hook代碼,甚至建立一些簡單的AOP框架,對于Java字節碼學習大有裨益。hibeaver完全開源,github項目地址:https://github.com/BryanSharp/hibeaver
祝玩的愉快!本文如有不妥之處,歡迎交流指正。
另外,本文為了盡可能地簡明生動、直入核心,簡化了很多概念和細節,讀者須知實際情況的更為復雜。但相信在理解了本文以后,就可以抓住Java匯編指令的核心理念,也就算扣開虛擬機學習的大門并可以開始讀書精進了。下面盜圖一張(后有出處),可作拓展:
鏈接:http://blog.csdn.net/luanlouis/article/details/39892027返回搜狐,查看更多
責任編輯:
總結
以上是生活随笔為你收集整理的java 汇编_大话+图说:Java 汇编指令——只为让你懂的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 运城学院计算机课,主讲教师
- 下一篇: 端口号被占用:The Tomcat co