深入理解Dalvik位元組碼指令及Smali檔案
今天來介紹有關Dalvik虛擬機器相關的知識,首先便是介紹我們最關心的Dalvik位元組碼相關知識,進而深入到Android逆向領域.之所以寫這篇文章,是因為有姑娘要學習這,再加上網上的許多資料太過零散和片面,當然,更重要的是為以前做個總結.
Dalvik暫存器
在開始之前,首先來了解暫存器相關的知識.Dalvik中用的暫存器都是32位,64位型別資料則用兩個相鄰的32位暫存器表示,也就是對於double這種64位型別的資料,需要用到兩個32位暫存器來儲存.
虛擬機器暫存器
我們知道Dalvik最多支援65536個暫存器(編號從0~65535),但是在ARM架構的cpu中只存在37個暫存器,那麼這種不對稱是怎麼解決的呢?
Dalvik中的暫存器是虛擬暫存器, 通過對映真實的暫存器來實現.我們知道每個Dalvik維護了一個呼叫棧,該呼叫棧就是用來支援虛擬暫存器和真實暫存器相互對映的.在執行具體函式時,Dalvik會根據.registers指令來確定該函式要用到的暫存器數目.具體的原理,可以自行參考Davilk的實現.
下面我們談到的暫存器都是虛擬暫存器.
暫存器的使用規則
對於一個使用m個暫存器(m=區域性變數暫存器個數l+引數暫存器個數n)的方法而言,區域性暫存器使用從v0開始的l個暫存器,而引數暫存器則使用最後的n個暫存器.舉個例子說明假設例項方法test(String a,String b)一共使用了5個暫存器:0,1,2,3,4,那麼引數暫存器是能使用2,3,4這三個暫存器,如圖:
暫存器的命名
暫存器有兩種不同的命名方法:v字命名法和p字命 名法.這兩種命名法僅僅是影響了位元組碼的可讀性.
v字命名法
以小寫字母v開頭的方式表示方法中使用的區域性變數和引數.
對於上面例項方法test(String a,String b)來說,v0,v1為區域性變數能夠使用的暫存器,v2,v3,v4為引數能夠使用的暫存器:
p字命名法
以小寫字母p開頭的方式表示引數,引數名稱從p0開始,依次增大.區域性變數能夠使用的暫存器仍然是以v開頭.
對於上面例項方法test(String a,String b)來說,v0,v1為區域性變數能夠使用的暫存器,p0,p1,v2為引數能夠使用的暫存器:
Dalvik描述符
與JVM相類似,Davilk位元組碼中同樣有一套用於描述型別,方法,欄位的方法,這些方法結合Davilk的指令便形成了完整的彙編程式碼.
位元組碼和資料型別
Davilk位元組碼只有兩種型別:基本型別和引用型別.物件和陣列都是引用型別,Davilk中對位元組碼型別的描述和JVM中的描述符規則一致:對於基本型別和無返回值的void型別都是用一個大寫字母表示,物件型別則用字母L加物件的全限定名來表示.陣列則用[來表示,具體規則如下所示:
全限定名是什麼
以String為例,其完整名稱是java.lang.String,那麼其全限定名就是java/lang/String;
,即java.lang.String的”.”用”/”代替,並在末尾新增分號”;”做結束符.
java型別 | 型別描述符 |
---|---|
boolean | Z |
byte | B |
short | S |
char | C |
int | I |
long | J |
float | F |
double | D |
void | V |
物件型別 | L |
陣列型別 | [ |
這裡我們重點解釋物件型別和陣列型別:
物件型別
L可以表示java型別中的任何類.在java程式碼中以package.name.ObjectName的方式引用,而在Davilk中其描述則是以Lpackage/name/ObjectName;
的形式表示.L即上面定義的java類型別,表示後面跟著的是累的全限定名.比如java中的java.lang.String對應的描述是Ljava/lang/String;
.
陣列型別
[型別用來表示所有基本型別的陣列,[後跟著是基本型別的描述符.每一維度使用一個前置的[.
比如java中的int[] 用匯編碼表示便是[I;
.二維陣列int[][]為[[I;
,三維陣列則用[[[I;
表示.
對於物件陣列來說,[後跟著對應類的全限定符.比如java當中的String[]對應的是[java/lang/String;
.
欄位的描述
Davilk中對欄位的描述分為兩種,對基本型別欄位的描述和對引用型別的描述,但兩者的描述格式一樣:
物件型別描述符->欄位名:型別描述符;
比如com.sbbic.Test類中存在String型別的name欄位及int型別的age欄位,那麼其描述為:
方法的描述
java中方法的簽名包括方法名,引數及返回值,在Davilk相應的描述規則為:
物件型別描述符->方法名(引數型別描述符)返回值型別描述符
下面我們通過幾個例子來說明,以java.lang.String為例:
Dalvik指令集
掌握以上的欄位和方法的描述,只能說我們懂了如何描述一個欄位和方法,而關於方法中具體的邏輯則需要了解Dalvik中的指令集.因為Dalvik是基於暫存器的架構的,因此指令集和JVM中的指令集區別較大,反而更類似x86的中的彙編指令.
資料定義指令
資料定義指令用於定義程式碼中使用的常量,類等資料,基礎指令是const
指令 | 描述 |
---|---|
const/4 vA,#+B | 將數值符號擴充套件為32後賦值給暫存器vA |
const-wide/16 vAA,#+BBBB | 將數值符號擴充套件為64位後賦值個暫存器對vAA |
const-string vAA,[email protected] | 通過字串索引高走字串賦值給暫存器vAA |
const-class vAA,[email protected] | 通過型別索引獲取一個類的引用賦值給暫存器vAA |
資料操作指令
move指令用於資料操作,其表示move destination,source,即資料資料從source暫存器(源暫存器)移動到destionation暫存器(源暫存器),可以理解java中變數間的賦值操作.根據位元組碼和型別的不同,move指令後會跟上不同的字尾.
指令 | 描述 |
---|---|
move vA,vB | 將vB暫存器的值賦值給vA暫存器,vA和vB暫存器都是4位 |
move/from16 vAA,VBBBB | 將vBBBB暫存器(16位)的值賦值給vAA暫存器(7位),from16表示源暫存器vBBBB是16位的 |
move/16 vAAAA,vBBBB | 將暫存器vBBBB的值賦值給vAAAA暫存器,16表示源暫存器vBBBB和目標暫存器vAAAA都是16位 |
move-object vA,vB | 將vB暫存器中的物件引用賦值給vA暫存器,vA暫存器和vB暫存器都是4位 |
move-result vAA | 將上一個invoke指令(方法呼叫)操作的單字(32位)非物件結果賦值給vAA暫存器 |
move-result-wide vAA | 將上一個invoke指令操作的雙字(64位)非物件結果賦值給vAA暫存器 |
mvoe-result-object vAA | 將上一個invoke指令操作的物件結果賦值給vAA暫存器 |
move-exception vAA | 儲存上一個執行時發生的異常到vAA暫存器 |
物件操作指令
與物件例項相關的操作,比如物件建立,物件檢查等.
指令 | 描述 |
---|---|
new-instance vAA,[email protected] |
構造一個指定型別的物件將器引用賦值給vAA暫存器.此處不包含陣列物件 |
instance-of vA,vB,[email protected] |
判斷vB暫存器中物件的引用是否是指定型別,如果是,將v1賦值為1,否則賦值為0 |
check-cast vAA,[email protected] |
將vAA暫存器中物件的引用轉成指定型別,成功則將結果賦值給vAA,否則丟擲ClassCastException異常. |
陣列操作指令
在例項操作指令中我們並沒有發現建立物件的指令.Davilk中設定專門的指令用於陣列操作.
指令 | 說明 |
---|---|
new-array vA,vB,[email protected] | 建立指定型別與指定大小(vB暫存器指定)的陣列,並將其賦值給vA暫存器 |
fill-array-data vAA,+BBBBBBBB | 用指定的資料填充陣列,vAA代表陣列的引用(陣列的第一個元素的地址) |
資料運算指令
資料運算主要包括兩種:算數運算和邏輯運算.
1. 算術運算指令
指令 | 說明 |
---|---|
add-type | 加法指令 |
sub-type | 減法指令 |
mul-type | 乘法指令 |
div-type | 除法指令 |
rem-type | 求 |
2. 邏輯元算指令
指令 | 說明 |
---|---|
and-type | 與運算指令 |
or-type | 或運算指令 |
xor-type | 異或元算指令 |
3. 位移指令
指令 | 說明 |
---|---|
shl-type | 有符號左移指令 |
shr-type | 有符號右移指令 |
ushr-type | 無符號右移指令 |
上面的-type表示操作的暫存器中資料的型別,可以是-int,-float,-long,-double等.
比較指令
比較指令用於比較兩個暫存器中值的大小,其基本格式格式是cmp+kind-type vAA,vBB,vCC
,type表示比較資料的型別,如-long,-float等;kind則代表操作型別,因此有cmpl,cmpg,cmp
三種比較指令.coml是compare
less的縮寫,cmpg是compare greater的縮寫,因此cmpl表示vBB小於vCC中的值這個條件是否成立,是則返回1,否則返回-1,相等返回0;cmpg表示vBB大於vCC中的值這個條件是否成立,是則返回1,否則返回-1,相等返回0.
cmp和cmpg的語意一致,即表示vBB大於vCC暫存器中的值是否成立,成立則返回1,否則返回-1,相等返回0
來具體看看Davilk中的指令:
指令 | 說明 |
---|---|
cmpl-float vAA,vBB,vCC |
比較兩個單精度的浮點數.如果vBB暫存器中的值大於vCC暫存器的值,則返回-1到vAA中,相等則返回0,小於返回1 |
cmpg-float vAA,vBB,vCC |
比較兩個單精度的浮點數,如果vBB暫存器中的值大於vCC的值,則返回1,相等返回0,小於返回-1 |
cmpl-double vAA,vBB,vCC |
比較兩個雙精度浮點數,如果vBB暫存器中的值大於vCC的值,則返回-1,相等返回0,小於則返回1 |
cmpg-double vAA,vBB,vCC |
比較雙精度浮點數,和cmpl-float的語意一致 |
cmp-double vAA,vBB,vCC |
等價與cmpg-double vAA,vBB,vCC指令 |
欄位操作指令
欄位操作指令表示對物件欄位進行設值和取值操作,就像是你在程式碼中長些的set和get方法.基本指令是iput-type,iget-type,sput-type,sget-type.type表示資料型別.
普通欄位讀寫操作
字首是i的iput-type和iget-type指令用於欄位的讀寫操作.
指令 | 說明 |
---|---|
iget-byte vX,vY,filed_id | 讀取vY暫存器中的物件中的filed_id欄位值賦值給vX暫存器 |
iput-byte vX,vY,filed_id | 設定vY暫存器中的物件中filed_id欄位的值為vX暫存器的值 |
iget-boolean vX,vY,filed_id | |
iput-boolean vX,vY,filed_id | |
iget-long vX,vY,filed_id | |
iput-long vX,vY,filed_id |
靜態欄位讀寫操作
字首是s的sput-type和sget-type指令用於靜態欄位的讀寫操作
指令 | 說明 |
---|---|
sget-byte vX,vY,filed_id | |
sput-byte vX,vY,filed_id | |
sget-boolean vX,vY,filed_id | |
sput-boolean vX,vY,filed_id | |
sget-long vX,vY,filed_id | |
sput-long vX,vY,filed_id |
方法呼叫指令
Davilk中的方法指令和JVM的中指令大部分非常類似.目前共有五條指令集:
指令 | 說明 |
---|---|
invoke-direct{parameters},methodtocall |
呼叫例項的直接方法,即private修飾的方法.此時需要注意{}中的第一個元素代表的是當前例項物件,即this,後面接下來的才是真正的引數.比如指令invoke-virtual {v3,v1,v4},Test2.method5:(II)V中,v3表示Test2當前例項物件,而v1,v4才是方法引數 |
invoke-static{parameters},methodtocall |
呼叫例項的靜態方法,此時{}中的都是方法引數 |
invoke-super{parameters},methodtocall |
呼叫父類方法 |
invoke-virtual{parameters},methodtocall |
呼叫例項的虛方法,即public和protected修飾修飾的方法 |
invoke-interface{parameters},methodtocall |
呼叫介面方法 |
這五種指令是基本指令,除此之外,你也會遇到invoke-direct/range,invoke-static/range,invoke-super/range,invoke-virtual/range,invoke-interface/range指令,該型別指令和以上指令唯一的區別就是後者可以設定方法引數可以使用的暫存器的範圍,在引數多於四個時候使用.
再此強調一遍對於非靜態方法而言{}的結構是{當前例項物件,引數1,引數2,…引數n},而對於靜態方法而言則是{引數1,引數2,…引數n}
需要注意,如果要獲取方法執行有返回值,需要通過上面說道的move-result指令獲取執行結果.
方法返回指令
在java中,很多情況下我們需要通過Return返回方法的執行結果,在Davilk中同樣提供的return指令來返回執行結果:
指令 | 說明 |
---|---|
return-void | 什麼也不返回 |
return vAA | 返回一個32位非物件型別的值 |
return-wide vAA | 返回一個64位非物件型別的值 |
return-object vAA | 反會一個物件型別的引用 |
同步指令
同步一段指令序列通常是由java中的synchronized語句塊表示,則JVM中是通過monitorenter和monitorexit的指令來支援synchronized關鍵字的語義的,而在Davilk中同樣提供了兩條類似的指令來支援synchronized語義:
指令 | 說明 |
---|---|
monitor-enter vAA | 為指定物件獲取鎖操作 |
monitor-exit vAA | 為指定物件釋放鎖操作 |
異常指令
很久以前,VM也是用過jsr和ret指令來實現異常的,但是現在的JVM中已經丟擲原先的做法,轉而採用異常表來實現異常.而Davilk仍然使用指令來實現:
指令 | 說明 |
---|---|
throw vAA | 丟擲vAA暫存器中指定型別的異常 |
跳轉指令
跳轉指令用於從當前地址條狀到指定的偏移處,在if,switch分支中使用的居多.Davilk中提供了goto,packed-switch,if-test指令用於實現跳轉操作
指令 | 操作 |
---|---|
goto +AA |
無條件跳轉到指定偏移處(AA即偏移量) |
packed-switch vAA,+BBBBBBBB |
分支跳轉指令.vAA暫存器中的值是switch分支中需要判斷的,BBBBBBBB則是偏移表(packed-switch-payload)中的索引值, |
spare-switch vAA,+BBBBBBBB |
分支跳轉指令,和packed-switch類似,只不過BBBBBBBB偏移表(spare-switch-payload)中的索引值 |
if-test vA,vB,+CCCC |
條件跳轉指令,用於比較vA和vB暫存器中的值,如果條件滿足則跳轉到指定偏移處(CCCC即偏移量),test代表比較規則,可以是eq.lt等. |
在條件比較中,if-test中的test表示比較規則.該指令用的非常多,因此我們簡單的坐下說明:
指令 | 說明 |
---|---|
if-eq vA,vB,target |
vA,vB暫存器中的相等,等價於java中的if(a==b),比如if-eq v3,v10,002c表示如果條件成立,則跳轉到current position+002c處.其餘的類似 |
if-ne vA,vB,target |
等價與java中的if(a!=b) |
if-lt vA,vB,target |
vA暫存器中的值小於vB,等價於java中的if(a< b) |
if-gt vA,vB,target |
等價於java中的if(a> b) |
if-ge vA,vB,target |
等價於java中的if(a>= b) |
if-le vA,vB,target |
等價於java中的if(a<= b) |
除了以上指令之外,Davilk還提供可一個零值條件指令,該指令用於和0比較,可以理解為將上面指令中的vB暫存器的值固定為0.
指令 | 說明 |
---|---|
if-eqz vAA,target | 等價於java中的if(a==0)或者if(!a) |
if-nez vAA,target | 等價於java中的if(a!=0)或者if(a) |
if-ltz vAA,target |
等價於java中的if(a< 0) |
if-gtz vAA,target |
等價於java中的if(a> 0) |
if-lez vAA,target |
等價於java中的if(a<= 0) |
if-gtz vAA,target |
等價於java中的if(a>= 0) |
附:
上面我們說道兩張偏移表packed-switch-payload和spare-switch-payload,兩者唯一的區別就是表中的值是否有序,後面我們會在下文中進行詳細的說明.
資料轉換指令
資料型別轉換對任何java開發者都是非常熟悉的,用於實現兩種不同資料型別的相互轉換.其基本指令格式是:unop vA,vB,表示對vB暫存器的中值進行操作,並將結果儲存在vA暫存器中.
指令 | 說明 |
---|---|
int-to-long | 整形轉為長整型 |
float-to-int | 單精度浮點型轉為整形 |
int-to-byte | 整形轉為位元組型別 |
neg-int | 求補指令,對整數求補 |
not-int | 求反指令,對整數求反 |
到現在為止,我們對Davilk中的指令做了簡單的說明.Davilk的指令在很大程度上結合了x86指令和JVM的指令結構和語意,因此總體來說Davilk中的指令還是非常容易學習.更多更詳細的指令參考請參考:Davilk指令集大全
詳解smali檔案
上面我們介紹了Dalvik的相關指令,下面我們則來認識一下smali檔案.儘管我們使用java來寫Android應用,但是Dalvik並不直接載入.class檔案,而是通過dx工具將.class檔案優化成.dex檔案,然後交由Dalvik載入.這樣說來,我們無法通過分析.class來直接分析apk檔案,而是需要藉助工具baksmali.jar反編譯dex檔案來獲得對應smali檔案,smali檔案可以認為是Davilk的位元組碼檔案,但是並兩者並不完全等同.
通過baksmali.jar反編譯出來每個.smali,都對應與java中的一個類,每個smali檔案都是Davilk指令組成的,並遵循一定的結構.smali存在很多的指令用於描述對應的java檔案,所有的指令都以”.”開頭,常用的指令如下:
關鍵詞 | 說明 |
---|---|
.filed | 定義欄位 |
.method…end method | 定義方法 |
.annotation…end annotation | 定義註解 |
.implements | 定義介面指令 |
.local | 指定了方法內區域性變數的個數 |
.registers | 指定方法內使用暫存器的總數 |
.prologue | 表示方法中程式碼的開始處 |
.line | 表示java原始檔中指定行 |
.paramter | 指定了方法的引數 |
.param |
相關推薦深入理解Dalvik位元組碼指令及Smali檔案今天來介紹有關Dalvik虛擬機器相關的知識,首先便是介紹我們最關心的Dalvik位元組碼相關知識,進而深入到Android逆向領域.之所以寫這篇文章,是因為有姑娘要學習這,再加上網上的許多資料太過零散和片面,當然,更重要的是為以前做個總結. Dalvik暫存器 在開始之前,首先來了解暫存 深入理解java位元組碼Javap 反編譯class檔案 –verbose 顯示冗餘資訊 (1)魔數:所有的class位元組碼檔案的4個位元組都是魔數,魔數固定值:0xCAFEBABE (2)版本:魔數之後4個位元組是版本資訊,前兩個位元組minor version次版本號例如0,後兩個位元組是主機板號majo 從一個class檔案深入理解Java位元組碼結構前言 我們都知道,Java程式最終是轉換成class檔案執行在虛擬機器上的,那麼class檔案是個怎樣的結構,虛擬機器又是如何處理去執行class檔案裡面的內容呢,這篇文章帶你深入理解Java位元組碼中的結構。 1.Demo原始碼 首先,編寫一個簡單的 深入理解JVM-位元組碼執行引擎前面我們不止一次的提到,Java是一種跨平臺的語言,為什麼可以跨平臺,因為我們編譯的結果是中間程式碼—位元組碼,而不是機器碼,那位元組碼在整個Java平臺扮演著什麼樣的角色的呢?JDK1.2之前對應的結構圖如下所示: 從JDK1.2開始,迫於Java執行始終比C++慢 深入理解java虛擬機器(六)位元組碼指令簡介Java虛擬機器指令是由(佔用一個位元組長度、代表某種特定操作含義的數字)操作碼Opcode,以及跟隨在其後的零至多個代表此操作所需引數的稱為運算元 Operands 構成的。由於Java虛擬機器是面向運算元棧而不是暫存器的架構,所以大多數指令都只有操作碼,而沒有運算元。 位元組碼指令集是一種具有鮮明特點、 深入理解JVM虛擬機器(五):位元組碼指令簡介Java 虛擬機器的指令由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼)以及跟隨其後的零至多個代表此操作所需引數(運算元)而構成。由於 Java 虛擬機器採用面向運算元棧而不是暫存器的架構,所以大多數的指令都不包含運算元,只有一個操作碼。 1. 位元組碼與資料型別 Dalvik 指令集練習 及 smali檔案的編寫一、實驗題目 1.輸出“Hello Xidian”字串 2.編寫smali檔案,計算(7+5)*(7-5)並輸出結果 二、smali檔案的編寫 1.HelloXidian.smali的編寫 smali 是一種寬鬆的 Jasmin/dedexer 語法,它可以通過 Java虛擬機器(四):Class檔案結構及位元組碼指令接下來的兩個位元組為this_class項,它是一個對常量池的索引。在this_class位置的常量池入口必須為CONSTANT_Class_info表。該表由兩個部分組成——標籤和name_index。標籤部分是一個具有CONSTANT_Class值的常量,在name_index位置的常量池入口為一 深入理解JVM_java代碼的執行機制01功能 存在 oot 對象實例 符號 token 類型 格式 找對象 本章學習重點: 1、Jvm: 如何將java代碼編譯為class文件。 如何裝載class文件及如何執行class文件。 jvm如何進行內存分配和回收。 jvm多線程 深入理解OkHttp源碼(一)——提交請求mat esp 屬於 idt set ref setname 失敗 class 本篇文章主要介紹OkHttp執行同步和異步請求的大體流程。主要流程如下圖: 主要分析到getResponseWidthInterceptorChain方法,該方法為具體的根據請求獲取響應 深入理解Dalvik虛擬機- 解釋器的執行機制util dlink stat counter before expose 加鎖 enter 機制 Dalvik的指令運行是解釋器+JIT的方式,解釋器就 深入理解字節碼理解invokeSuper無限循環的原因UC declare oca tcl ron try 快速定位 on() nal 來一段簡單的cglib代碼 1 public class SampleClass { 2 public void test(){ 3 System.out. JVM總括三-位元組碼、位元組碼指令、JIT編譯執行JVM總括三-位元組碼、位元組碼指令、JIT編譯執行 java檔案編譯後的class檔案,java跨平臺的中間層,JVM通過對位元組碼的解釋執行(執行模式,還有JIT編譯執行,下面講解),遮蔽對作業系統的依賴。一個位元組(8位)可以儲存256中不同的指令,這樣的指令就是位元組碼,ja 大話+圖說:Java位元組碼指令——只為讓你懂前言 隨著Java開發技術不斷被推到新的高度,對於Java程式設計師來講越來越需要具備對更深入的基礎性技術的理解,比如Java位元組碼指令。不然,可能很難深入理解一些時下的新框架、新技術,盲目一味追新也會越來越感乏力。 本文既不求照本宣科,亦不求炫技或著文立說,僅力圖以最簡明、最形象生動的方式,結合例子與 JVM 虛擬機器位元組碼指令表把JVM虛擬機器位元組指令表整理了一下,方便搜尋,偶爾複習下 純手工整理,可能存在一些問題,如果發現請及時告之我會修正 位元組碼 助記符 指令含義 0x00 nop None 0x01 【小家Java】深入理解Java列舉型別(enum)及7種常見的用法(含EnumMap和EnumSet)相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9 例項分析理解Java位元組碼Java語言最廣為人知的口號就是“一次編譯到處執行”,這裡的“編譯”指的是編譯器將Java原始碼編譯為Java位元組碼檔案(也就是.class檔案,本文中不做區分),“執行”則指的是Java虛擬機器執行位元組碼檔案。Java的跨平臺得益於不同平臺上不同的JVM的實現,只要提供規範的位元組碼檔案,無論是什麼平臺 深入理解分散式事務(XA及rocketmq事務)深入理解分散式事務(XA及rocketmq事務) 釋出時間:2018-04-16 來源:網路 上傳者:使用者 關鍵字: 事務 分散式 RocketMq 深入 理解 發表文章 摘要:本文由碼農網–吳極心原創,轉載請看清文末的轉載要求 class檔案結構與jvm位元組碼指令https://blog.csdn.net/luckydog1991/article/details/51654964這篇文章詳細的介紹了class檔案和位元組碼指令 Class檔案結構 https://blog.csdn.net/tyrone1979/article/details/9 Java位元組碼指令收集大全Java位元組碼指令大全 常量入棧指令 指令碼 操作碼(助記符) 運算元 描述(棧指運算元棧) 0x01 aconst_null |