一個查找位元組碼更好研究Kotlin的腳本

  • 2020 年 1 月 23 日
  • 筆記

眾所周知,Kotlin通過語法糖的形式實現了很多便捷和高效的方法,因此研究Kotlin程式碼通常是需要研究位元組碼或者反編譯後的java文件。

比如這樣的程式碼

1 2 3

fun String.toConsole() { println(this) }

Kotlin的編譯器會在位元組碼中自動地增加這樣一行程式碼Intrinsics.checkParameterIsNotNull來做一些預檢查的操作。

痛點

那麼問題來了,如果我們想找出所有的關於Intrinsics相關的自動加入內容,該怎麼辦,不能一個一個文件去反編譯查看吧,因為這樣

  • 沒有目標性,無法明確預知那個文件會生成這種程式碼
  • 不具有自動化可重複性,需要依賴於人為行為

那麼,我們查看class文件類進行內容匹配是否包含Intrinsics呢,其實也不太好,因為一個class文件的內容是這樣的

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

cat StringExtKt.class ����43 StringExtKtjava/lang/Object toConsole(Ljava/lang/String;)V#Lorg/jetbrains/annotations/NotNull;$this$toConsokotlin/jvm/internal/Intrinsics checkParameterIsNotNull'(Ljava/lang/Object;Ljava/lang/String;)V java/lang/SystemoutLjava/io/PrintStream; java/io/PrintStreamprintln(Ljava/lang/Object;)V Ljava/lang/String;Lkotlin/Metadata;mvbvkd1"�� �� �� ��0*0¨d2BytecodeSample StringExt.ktCodeLineNumberTableLocalVariableTable$RuntimeInvisibleParameterAnnotations SourceFileSourceDebugExtensionRuntimeVisibleAnnotations1,>* �<�*�- . /0+1QSMAP StringExt.kt Kotlin *S Kotlin *F + 1 StringExt.kt StringExtKt *L 1#1,3:1 *E [email protected][III ![II"I#$I%&[s'([ss)s)s*

一段很錯亂的內容,這樣不利於我們更好的分析問題。因為相比較而言,我們有更加好的方法來處理。

基於上面的痛點,自己動手寫了一個簡單的ruby腳本,來解決問題。

實現思路

  • 遍歷指定路徑下的class文件
  • 將對應的class文件使用javap反編譯
  • 使用上面反編譯的結果,查看是否包含待查詢的關鍵字
  • 如果上述結果匹配到,將反編譯內容和文件路徑輸出到結果文件中

程式碼(Talk is cheap)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

#!/usr/bin/ruby require 'find' require 'colorize' require "fileutils" # extract arguements from command line dirToSearch = ARGV[0] keywordToSearch = ARGV[1].to_s.strip matchedResultFile = ARGV[2] puts "dirToSearch=#{dirToSearch};keywordToSearch=#{keywordToSearch}, matchedResultFile=#{matchedResultFile}" # Eagerly create the result file so that user could use tools like `tail -f ` to observer the result FileUtils.touch(matchedResultFile) puts "result will be outputted to #{matchedResultFile}" # Helper method to append content(each line) to the file def appendLineContentToFile(lineContent, filePath) File.open(filePath, 'a') do |file| file.puts "#{lineContent}" end end # write matched class file path along with bytecode content to the output file. def writeResultInformation(classFilePath, byteCodeContent, outputFile) appendLineContentToFile(classFilePath, outputFile) # leave blank lines appendLineContentToFile("", outputFile) appendLineContentToFile("", outputFile) appendLineContentToFile("", outputFile) appendLineContentToFile(byteCodeContent, outputFile) # leave blank lines appendLineContentToFile("", outputFile) appendLineContentToFile("", outputFile) appendLineContentToFile("", outputFile) end Find.find(dirToSearch).select { |f| f.end_with? ".class" }.each { |f| puts "checking #{f}" byteCodeContent = `javap -c #{f}` contains = byteCodeContent.include? keywordToSearch resultMessage = "" if contains resultMessage = "#{f} contains #{keywordToSearch}".green writeResultInformation(f, byteCodeContent, matchedResultFile) else resultMessage = "#{f} does NOT contains #{keywordToSearch}".red end puts resultMessage }

執行命令

1

ruby searchBytecode.rb ./ "Intrinsics" /tmp/result.txt

  • searchBytecode.rb 是上述的腳本文件名稱
  • ./ 第一個參數,為待查找的目錄
  • 「Intrinsics」 第二個參數,為查詢關鍵字
  • /tmp/result.txt 第三個參數,為結果輸出文件

執行日誌

為了更好的表達應用正在執行,執行時會有日誌輸出。

其中

  • 正常的日誌會以白色顏色輸出
  • 不匹配的內容會以紅顏色輸出
  • 匹配的內容會以綠顏色輸出

結果文件

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

cat sample_intrinsics.txt ./out/production/BytecodeSample/MainKt.class Compiled from "Main.kt" public final class MainKt { public static final void main(); Code: 0: ldc #11 // String Hello 2: invokestatic #17 // Method StringExtKt.toConsole:(Ljava/lang/String;)V 5: iconst_1 6: invokestatic #23 // Method IntExtKt.increase:(I)I 9: pop 10: new #25 // class Book 13: dup 14: invokespecial #28 // Method Book."<init>":()V 17: getfield #32 // Field Book.name:Ljava/lang/String; 20: dup 21: ifnonnull 27 24: invokestatic #37 // Method kotlin/jvm/internal/Intrinsics.throwNpe:()V 27: invokevirtual #43 // Method java/lang/String.toString:()Ljava/lang/String; 30: pop 31: return public static void main(java.lang.String[]); Code: 0: invokestatic #9 // Method main:()V 3: return } ./out/production/BytecodeSample/StringExtKt.class Compiled from "StringExt.kt" public final class StringExtKt { public static final void toConsole(java.lang.String); Code: 0: aload_0 1: ldc #9 // String $this$toConsole 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: iconst_0 7: istore_1 8: getstatic #21 // Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_0 12: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 15: return }

問題排查

1 2 3

/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file — colorize (LoadError) from /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /Users/androidyue/Documents/OneDrive/scripts//searchBytecode.rb:3:in `<main>'

需手動安裝ruby gems依賴

1 2 3 4 5 6 7

➜ gem install colorize YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0). Fetching: colorize-0.8.1.gem (100%) Successfully installed colorize-0.8.1 Parsing documentation for colorize-0.8.1 Installing ri documentation for colorize-0.8.1 1 gem installed

再次執行即可。

執行優化

  • 具體的執行時間可能會隨著工程的複雜而不同。
  • 建議篩選更加精細的目錄,避免不必要的查詢和操作
  • 可以同時使用tail -f篩選匹配結果。

腳本github地址:https://github.com/androidyue/DroidScripts/blob/master/ruby/searchBytecode.rb

以上。

相關內容