52

一个查找字节码更好研究Kotlin的脚本 - 技术小黑屋

 5 years ago
source link: https://droidyue.com/blog/2019/07/14/search-bytecode-script-to-study-kotlin-better/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一个查找字节码更好研究Kotlin的脚本

Jul 14th, 2019

众所周知,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
2@[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 第三个参数,为结果输出文件

为了更好的表达应用正在执行,执行时会有日志输出。

https://asset.droidyue.com/image/2019_07/search_bytecode_logs.jpg
  • 正常的日志会以白色颜色输出
  • 不匹配的内容会以红颜色输出
  • 匹配的内容会以绿颜色输出
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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK