java_dissassembler 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/java_dissassembler/annotatable.rb +17 -0
- data/lib/java_dissassembler/array.rb +37 -0
- data/lib/java_dissassembler/class.rb +71 -0
- data/lib/java_dissassembler/erb_helper.rb +193 -0
- data/lib/java_dissassembler/field.rb +58 -0
- data/lib/java_dissassembler/method.rb +91 -0
- data/lib/java_dissassembler.rb +226 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 86d641d10b9d79aef55fda226790ce55102bd03d
|
4
|
+
data.tar.gz: b9c1df1dccff8a01540592bb0a556b65d855a61f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f8dd23e5f96f46ca8547f13bef5971d467c8a9e4e2dba9c96111577dbf793cdea470d5a208c393a036ab3ea492d5b147332fea0a48152685cf57b76dcf79b2cf
|
7
|
+
data.tar.gz: e851eb92bdc1ca3ffab7fce5f95554a1f65b6d45b043b2f2c78cc3085c12b6ba3a6a89ba44dbd79b664ea45af4e84c95f7af8bca5172a58626d92d70e7ca5b56
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Java
|
2
|
+
class Annotatable
|
3
|
+
attr_reader :annotations
|
4
|
+
def initialize(annotations)
|
5
|
+
@annotations = annotations
|
6
|
+
end
|
7
|
+
|
8
|
+
def has_annotation?(annotation_name)
|
9
|
+
@annotations.any? { |hash| hash.keys.any? { |key| key == annotation_name } }
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_annotation(annotation_name)
|
13
|
+
hash = @annotations.find { |a| a.keys.include?(annotation_name) }
|
14
|
+
hash[annotation_name] unless hash.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Array
|
2
|
+
def u2
|
3
|
+
self.pack("C*").unpack("s>").first
|
4
|
+
end
|
5
|
+
|
6
|
+
def u4
|
7
|
+
self.pack("C*").unpack("N").first
|
8
|
+
end
|
9
|
+
|
10
|
+
def modified_utf8_to_s
|
11
|
+
new_str = ""
|
12
|
+
bytes = self
|
13
|
+
until bytes.empty?
|
14
|
+
# Code points in the range '\u0001' to '\u007F' are represented by a single byte
|
15
|
+
if (bytes[0] >> 7) == 0
|
16
|
+
new_str << bytes[0]
|
17
|
+
bytes.shift
|
18
|
+
# The null code point ('\u0000') and code points in the range '\u0080' to '\u07FF' are represented by a pair of bytes x and y
|
19
|
+
elsif (bytes[0] >> 5) == 0b110
|
20
|
+
new_str << [((bytes[0] & 0x1f) << 6) + (bytes[1] & 0x3f)].pack("U")
|
21
|
+
bytes.shift(2)
|
22
|
+
# Code points in the range '\u0800' to '\uFFFF' are represented by 3 bytes
|
23
|
+
elsif ((bytes[0] >> 4) == 0b1110) && (bytes[0] != 0b11101101)
|
24
|
+
new_str << [((bytes[0] & 0xf) << 12) + ((bytes[1] & 0x3f) << 6) + (bytes[2] & 0x3f)].pack("U")
|
25
|
+
bytes.shift(3)
|
26
|
+
# Characters with code points above U+FFFF (so-called supplementary characters) are represented by separately encoding the
|
27
|
+
# two surrogate code units of their UTF-16 representation
|
28
|
+
elsif (bytes[0] == 0b11101101) && (bytes[3] == 0b11101101)
|
29
|
+
new_str << [0x10000 + ((bytes[1] & 0x0f) << 16) + ((bytes[2] & 0x3f) << 10) + ((bytes[4] & 0x0f) << 6) + (bytes[5] & 0x3f)].pack("U")
|
30
|
+
bytes.shift(6)
|
31
|
+
else
|
32
|
+
raise "Invalid \"Modified\" UTF-8 byte `#{bytes[0]}'"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
new_str
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Java
|
2
|
+
class Class < Annotatable
|
3
|
+
attr_reader :flags, :this_klass, :super_klass, :interfaces, :fields, :methods
|
4
|
+
|
5
|
+
ACC_PUBLIC = 0x0001 # Declared public; may be accessed from outside its package.
|
6
|
+
ACC_FINAL = 0x0010 # Declared final; no subclasses allowed.
|
7
|
+
ACC_SUPER = 0x0020 # Treat superclass methods specially when invoked by the invokespecial instruction.
|
8
|
+
ACC_INTERFACE = 0x0200 # Is an interface, not a class.
|
9
|
+
ACC_ABSTRACT = 0x0400 # Declared abstract; must not be instantiated.
|
10
|
+
ACC_SYNTHETIC = 0x1000 # Declared synthetic; not present in the source code.
|
11
|
+
ACC_ANNOTATION = 0x2000 # Declared as an annotation type.
|
12
|
+
ACC_ENUM = 0x4000 # Declared as an enum type.
|
13
|
+
|
14
|
+
def initialize(major, minor, flags, this_klass, super_klass, interfaces, fields, methods, annotations)
|
15
|
+
super(annotations)
|
16
|
+
@major = major
|
17
|
+
@minor = minor
|
18
|
+
@flags = flags
|
19
|
+
@this_klass = this_klass
|
20
|
+
@super_klass = super_klass
|
21
|
+
@interfaces = interfaces
|
22
|
+
@fields = fields
|
23
|
+
@methods = methods
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_public?
|
27
|
+
(@flags & ACC_PUBLIC) != 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_final?
|
31
|
+
(@flags & ACC_FINAL) != 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def is_super?
|
35
|
+
(@flags & ACC_SUPER) != 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_interface?
|
39
|
+
(@flags & ACC_INTERFACE) != 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_abstract?
|
43
|
+
(@flags & ACC_ABSTRACT) != 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_synthetic?
|
47
|
+
(@flags & ACC_SYNTHETIC) != 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def is_annotation?
|
51
|
+
(@flags & ACC_ANNOTATION) != 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_enum?
|
55
|
+
(@flags & ACC_ENUM) != 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def java_version
|
59
|
+
case @major
|
60
|
+
when 46 then "1.2"
|
61
|
+
when 47 then "1.3"
|
62
|
+
when 48 then "1.4"
|
63
|
+
when 49 then "5"
|
64
|
+
when 50 then "6"
|
65
|
+
when 51 then "7"
|
66
|
+
when 52 then "8"
|
67
|
+
when 53 then "9"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Java
|
2
|
+
class Method
|
3
|
+
|
4
|
+
# JNI Call Java from C/C++
|
5
|
+
# =========================================
|
6
|
+
def jni_c_parameters(prefix = "param")
|
7
|
+
cparams = Array.new
|
8
|
+
java_params.each_with_index do |param, i|
|
9
|
+
cparams << "#{java_to_c(param)} #{prefix}#{i}"
|
10
|
+
end
|
11
|
+
cparams
|
12
|
+
end
|
13
|
+
|
14
|
+
def jni_c_return_type
|
15
|
+
java_to_c(java_return_type)
|
16
|
+
end
|
17
|
+
|
18
|
+
def jni_return_type
|
19
|
+
return "" if java_return_type == "void"
|
20
|
+
java_to_jni(java_return_type)
|
21
|
+
end
|
22
|
+
|
23
|
+
def jni_convert_c_to_jni(prefix = "param")
|
24
|
+
conversions = Array.new
|
25
|
+
java_params.each_with_index do |param, i|
|
26
|
+
case param
|
27
|
+
when "java.lang.String"
|
28
|
+
conversions << "jstring jni#{prefix}#{i} = env->NewStringUTF(#{prefix}#{i})"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
conversions
|
32
|
+
end
|
33
|
+
|
34
|
+
def jni_method_call
|
35
|
+
"Call#{is_static? ? "Static" : ""}#{java_return_type.capitalize}Method"
|
36
|
+
end
|
37
|
+
|
38
|
+
def jni_method_call_parameters(prefix = "param")
|
39
|
+
java_params.enum_for(:each_with_index).map do |param, i|
|
40
|
+
case param
|
41
|
+
when "java.lang.String" then "jni#{prefix}#{i}"
|
42
|
+
else "#{prefix}#{i}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# JNI Call C from Java
|
48
|
+
# =========================================
|
49
|
+
def jni_mangled_name(klass_name, is_override)
|
50
|
+
mangled_name = mangle(name)
|
51
|
+
mangled_name << "__#{mangle(vm_signature_params)}" if is_override and not vm_signature_params.empty?
|
52
|
+
"Java_#{klass_name.gsub(".", "_")}_#{mangled_name}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def jni_native_method_parameters(prefix = "param")
|
56
|
+
native_method_parameters = ["JNIEnv* env"]
|
57
|
+
native_method_parameters << (is_static? ? "jobject caller" : "jclass klass")
|
58
|
+
native_method_parameters + java_params.enum_for(:each_with_index).map { |param,i| "#{java_to_jni(param)} #{prefix}#{i}" }
|
59
|
+
end
|
60
|
+
|
61
|
+
def jni_convert_jni_to_c(prefix = "param")
|
62
|
+
conversions = Array.new
|
63
|
+
java_params.each_with_index do |param, i|
|
64
|
+
case param
|
65
|
+
when "java.lang.String"
|
66
|
+
conversions << "const char* c#{prefix}#{i} = env->GetStringUTFChars(#{prefix}#{i}, nullptr /* iscopy */)"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
conversions
|
70
|
+
end
|
71
|
+
|
72
|
+
def jni_native_method_converted_parameters(prefix = "param")
|
73
|
+
conversions = Array.new
|
74
|
+
java_params.each_with_index do |param, i|
|
75
|
+
case param
|
76
|
+
when "java.lang.String"
|
77
|
+
conversions << "c#{prefix}#{i}"
|
78
|
+
else
|
79
|
+
conversions << "#{prefix}#{i}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
conversions
|
83
|
+
end
|
84
|
+
|
85
|
+
def jni_release_converted_to_c(prefix = "param")
|
86
|
+
releases = Array.new
|
87
|
+
java_params.each_with_index do |param, i|
|
88
|
+
case param
|
89
|
+
when "java.lang.String"
|
90
|
+
releases << "env->ReleaseStringUTFChars(#{prefix}#{i}, c#{prefix}#{i})"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
releases
|
94
|
+
end
|
95
|
+
|
96
|
+
def vm_signature_params
|
97
|
+
vm_signature.match(/^\(([^\)]*)\)/)[1]
|
98
|
+
end
|
99
|
+
|
100
|
+
def java_params
|
101
|
+
java_signature.drop(1)
|
102
|
+
end
|
103
|
+
|
104
|
+
def java_return_type
|
105
|
+
java_signature.at(0)
|
106
|
+
end
|
107
|
+
|
108
|
+
def jni_params
|
109
|
+
java_params.map { |java_type| java_to_jni(java_type) }
|
110
|
+
end
|
111
|
+
|
112
|
+
def jni_return_value
|
113
|
+
java_to_jni(java_return_type)
|
114
|
+
end
|
115
|
+
|
116
|
+
#private
|
117
|
+
|
118
|
+
def java_signature
|
119
|
+
types = vm_signature.scan(/(\[?([ZBCSIJFDV]|L[^;]*;))/).map do |match|
|
120
|
+
is_array = match[0].start_with? "["
|
121
|
+
val = vm_to_java(match[0])
|
122
|
+
if is_array
|
123
|
+
val << "[]"
|
124
|
+
val[1..-1]
|
125
|
+
else
|
126
|
+
val
|
127
|
+
end
|
128
|
+
end
|
129
|
+
back = types.pop
|
130
|
+
types.unshift(back)
|
131
|
+
end
|
132
|
+
|
133
|
+
def vm_to_java(vm_type)
|
134
|
+
case vm_type
|
135
|
+
when "Z" then "boolean"
|
136
|
+
when "B" then "byte"
|
137
|
+
when "C" then "char"
|
138
|
+
when "S" then "short"
|
139
|
+
when "I" then "int"
|
140
|
+
when "J" then "long"
|
141
|
+
when "F" then "float"
|
142
|
+
when "D" then "double"
|
143
|
+
when "V" then "void"
|
144
|
+
else
|
145
|
+
vm_type.gsub("L", "").gsub(";", "").gsub("/", ".")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def java_to_c(java_type)
|
150
|
+
case java_type
|
151
|
+
when "boolean" then "bool"
|
152
|
+
when "byte" then "byte"
|
153
|
+
when "char" then "char"
|
154
|
+
when "short" then "short"
|
155
|
+
when "int" then "int"
|
156
|
+
when "long" then "long"
|
157
|
+
when "float" then "float"
|
158
|
+
when "double" then "double"
|
159
|
+
when "java.lang.String" then "const char*"
|
160
|
+
when "void" then "void"
|
161
|
+
else "jobject" end
|
162
|
+
end
|
163
|
+
|
164
|
+
def java_to_jni(java_type)
|
165
|
+
case java_type
|
166
|
+
when "boolean" then "jboolean"
|
167
|
+
when "byte" then "jbyte"
|
168
|
+
when "char" then "jchar"
|
169
|
+
when "short" then "jshort"
|
170
|
+
when "int" then "jint"
|
171
|
+
when "long" then "jlong"
|
172
|
+
when "float" then "jfloat"
|
173
|
+
when "double" then "jdouble"
|
174
|
+
when "java.lang.String" then "jstring"
|
175
|
+
when "void" then raise "Cannot convert `void' to JNI type"
|
176
|
+
else "jobject" end
|
177
|
+
end
|
178
|
+
|
179
|
+
# See https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html Table 2-1)
|
180
|
+
def mangle(str)
|
181
|
+
mangled_name = str.gsub("_", "_1")
|
182
|
+
.gsub(";", "_2")
|
183
|
+
.gsub("[", "_3")
|
184
|
+
.gsub("/", "_")
|
185
|
+
mangled_name.codepoints.map do |codepoint|
|
186
|
+
case
|
187
|
+
when codepoint <= 0xFF then codepoint.chr
|
188
|
+
else "_0#{"%04x" % codepoint}"
|
189
|
+
end
|
190
|
+
end.join
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Java
|
2
|
+
class Field < Annotatable
|
3
|
+
attr_reader :flags, :name, :vm_type, :java_type, :annotations
|
4
|
+
|
5
|
+
ACC_PUBLIC = 0x0001 # Declared public; may be accessed from outside its package.
|
6
|
+
ACC_PRIVATE = 0x0002 # Declared private; usable only within the defining class.
|
7
|
+
ACC_PROTECTED = 0x0004 # Declared protected; may be accessed within subclasses.
|
8
|
+
ACC_STATIC = 0x0008 # Declared static.
|
9
|
+
ACC_FINAL = 0x0010 # Declared final; never directly assigned to after object construction (JLS §17.5).
|
10
|
+
ACC_VOLATILE = 0x0040 # Declared volatile; cannot be cached.
|
11
|
+
ACC_TRANSIENT = 0x0080 # Declared transient; not written or read by a persistent object manager.
|
12
|
+
ACC_SYNTHETIC = 0x1000 # Declared synthetic; not present in the source code.
|
13
|
+
ACC_ENUM = 0x4000 # Declared as an element of an enum.
|
14
|
+
|
15
|
+
def initialize(flags, name, vm_type, annotations)
|
16
|
+
super(annotations)
|
17
|
+
@flags = flags
|
18
|
+
@name = name
|
19
|
+
@vm_type = vm_type
|
20
|
+
end
|
21
|
+
|
22
|
+
def is_public?
|
23
|
+
(@flags & ACC_PUBLIC) != 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_private?
|
27
|
+
(@flags & ACC_PRIVATE) != 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_protected?
|
31
|
+
(@flags & ACC_PROTECTED) != 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def is_static?
|
35
|
+
(@flags & ACC_STATIC) != 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_final?
|
39
|
+
(@flags & ACC_FINAL) != 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_volatile?
|
43
|
+
(@flags & ACC_VOLATILE) != 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_transient?
|
47
|
+
(@flags & ACC_TRANSIENT) != 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def is_synthetic?
|
51
|
+
(@flags & ACC_SYNTHETIC) != 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_enum?
|
55
|
+
(@flags & ACC_ENUM) != 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Java
|
2
|
+
class Method < Annotatable
|
3
|
+
attr_reader :flags, :name, :vm_signature
|
4
|
+
|
5
|
+
ACC_PUBLIC = 0x0001 # Declared public; may be accessed from outside its package.
|
6
|
+
ACC_PRIVATE = 0x0002 # Declared private; accessible only within the defining class.
|
7
|
+
ACC_PROTECTED = 0x0004 # Declared protected; may be accessed within subclasses.
|
8
|
+
ACC_STATIC = 0x0008 # Declared static.
|
9
|
+
ACC_FINAL = 0x0010 # Declared final; must not be overridden (§5.4.5).
|
10
|
+
ACC_SYNCHRONIZED = 0x0020 # Declared synchronized; invocation is wrapped by a monitor use.
|
11
|
+
ACC_BRIDGE = 0x0040 # A bridge method, generated by the compiler.
|
12
|
+
ACC_VARARGS = 0x0080 # Declared with variable number of arguments.
|
13
|
+
ACC_NATIVE = 0x0100 # Declared native; implemented in a language other than Java.
|
14
|
+
ACC_ABSTRACT = 0x0400 # Declared abstract; no implementation is provided.
|
15
|
+
ACC_STRICT = 0x0800 # Declared strictfp; floating-point mode is FP-strict.
|
16
|
+
ACC_SYNTHETIC = 0x1000 # Declared synthetic; not present in the source code.
|
17
|
+
|
18
|
+
def initialize(flags, name, vm_signature, annotations)
|
19
|
+
super(annotations)
|
20
|
+
@flags = flags
|
21
|
+
@name = name
|
22
|
+
@vm_signature = vm_signature
|
23
|
+
#java_signature = vm_signature.scan(/\[?[ZBCSIJFDV]|\[?L[^\;]*;/).map { |m| vm_to_java(m) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_public?
|
27
|
+
(@flags & ACC_PUBLIC) != 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_private?
|
31
|
+
(@flags & ACC_PRIVATE) != 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def is_protected?
|
35
|
+
(@flags & ACC_PROTECTED) != 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_static?
|
39
|
+
(@flags & ACC_STATIC) != 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_final?
|
43
|
+
(@flags & ACC_FINAL) != 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_synchronized?
|
47
|
+
(@flags & ACC_SYNCHRONIZED) != 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def is_bridge?
|
51
|
+
(@flags & ACC_BRIDGE) != 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_varargs?
|
55
|
+
(@flags & ACC_VARARGS) != 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_native?
|
59
|
+
(@flags & ACC_NATIVE) != 0
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_abstract?
|
63
|
+
(@flags & ACC_ABSTRACT) != 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_strict?
|
67
|
+
(@flags & ACC_STRICT) != 0
|
68
|
+
end
|
69
|
+
|
70
|
+
def is_synthetic?
|
71
|
+
(@flags & ACC_SYNTHETIC) != 0
|
72
|
+
end
|
73
|
+
|
74
|
+
#private
|
75
|
+
# def vm_to_java(vm_type)
|
76
|
+
# suffix = vm_type =~ /^\[/ ? "[]" : ""
|
77
|
+
# case vm_type
|
78
|
+
# when "V" then "void"
|
79
|
+
# when "Z" then "boolean"
|
80
|
+
# when "B" then "byte"
|
81
|
+
# when "C" then "char"
|
82
|
+
# when "S" then "short"
|
83
|
+
# when "I" then "int"
|
84
|
+
# when "J" then "long"
|
85
|
+
# when "F" then "float"
|
86
|
+
# when "D" then "double"
|
87
|
+
# else vm_type.gsub(/[L;]/, "").gsub("/", ".")
|
88
|
+
# end + suffix
|
89
|
+
# end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'java_dissassembler/array'
|
2
|
+
require 'java_dissassembler/annotatable'
|
3
|
+
require 'java_dissassembler/class'
|
4
|
+
require 'java_dissassembler/method'
|
5
|
+
require 'java_dissassembler/field'
|
6
|
+
|
7
|
+
# See https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html for JVM Class Binary Specification
|
8
|
+
module Java
|
9
|
+
class Dissassembler
|
10
|
+
class << self
|
11
|
+
CONSTANT_UTF8 = 1
|
12
|
+
CONSTANT_INTEGER = 3
|
13
|
+
CONSTANT_FLOAT = 4
|
14
|
+
CONSTANT_LONG = 5
|
15
|
+
CONSTANT_DOUBLE = 6
|
16
|
+
CONSTANT_CLASS = 7
|
17
|
+
CONSTANT_STRING = 8
|
18
|
+
CONSTANT_FIELDREF = 9
|
19
|
+
CONSTANT_METHODREF = 10
|
20
|
+
CONSTANT_INTERFACEMETHODREF = 11
|
21
|
+
CONSTANT_NAMEANDTYPE = 12
|
22
|
+
CONSTANT_METHODHANDLE = 15
|
23
|
+
CONSTANT_METHODTYPE = 16
|
24
|
+
CONSTANT_INVOKEDYNAMIC = 18
|
25
|
+
|
26
|
+
MAGIC = "\xCA\xFE\xBA\xBE".each_byte.to_a.freeze
|
27
|
+
|
28
|
+
def dissassemble(bytes)
|
29
|
+
if bytes.is_a?(Array)
|
30
|
+
do_dissassemble(bytes)
|
31
|
+
elsif bytes.is_a?(String)
|
32
|
+
do_dissassemble(bytes.each_byte.to_a)
|
33
|
+
else
|
34
|
+
raise "Expected `bytes' to be Array of bytes or String of bytes"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def dissassemble_file(path)
|
39
|
+
File.open(path) do |file|
|
40
|
+
dissassemble(file.read)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def do_dissassemble(bytes)
|
46
|
+
raise "Missing magic number 0xCAFEBABE. Not a .class file" unless bytes[0..3] == MAGIC
|
47
|
+
|
48
|
+
minor = bytes[4..5].u2
|
49
|
+
major = bytes[6..7].u2
|
50
|
+
offset = 8
|
51
|
+
|
52
|
+
# Constant Pool
|
53
|
+
constant_pool = {}
|
54
|
+
constant_pool_size = bytes[offset..offset + 1].u2 - 1
|
55
|
+
offset += 2
|
56
|
+
i = 0
|
57
|
+
while i < constant_pool_size
|
58
|
+
consumed, val, tag = constant_pool_info_size(bytes[offset..-1])
|
59
|
+
i += 1 if !tag.nil? && ((tag == 5) || (tag == 6)) # Floats and Doubles take up two constant pool entries :/
|
60
|
+
constant_pool[i + 1] = val unless val.nil?
|
61
|
+
offset += consumed
|
62
|
+
i += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
# Class Info: 3 x u2 (access_flags, this_klass, super_klass)
|
66
|
+
access_flags = bytes[offset..offset + 1].u2
|
67
|
+
this_klass = constant_pool[constant_pool[bytes[offset + 2..offset + 3].u2]]
|
68
|
+
super_klass = constant_pool[constant_pool[bytes[offset + 4..offset + 5].u2]]
|
69
|
+
offset += 6
|
70
|
+
|
71
|
+
# Interfaces
|
72
|
+
interfaces = Array.new
|
73
|
+
interfaces_count = bytes[offset..offset + 1].u2
|
74
|
+
offset += 2
|
75
|
+
interfaces_count.times do
|
76
|
+
interface_index = bytes[offset..offset + 1].u2
|
77
|
+
interfaces << constant_pool[interface_index]
|
78
|
+
offset += 2
|
79
|
+
end
|
80
|
+
|
81
|
+
# Fields
|
82
|
+
fields = Array.new
|
83
|
+
fields_count = bytes[offset..offset + 1].u2
|
84
|
+
offset += 2
|
85
|
+
fields_count.times do
|
86
|
+
flags = bytes[offset..offset + 1].u2
|
87
|
+
name = bytes[offset + 2..offset + 3].u2
|
88
|
+
sig = bytes[offset + 4..offset + 5].u2
|
89
|
+
offset += 6 # Skip the field header
|
90
|
+
|
91
|
+
consumed, annotations = parse_attributes(bytes[offset..-1], constant_pool)
|
92
|
+
fields << Java::Field.new(flags, constant_pool[name], constant_pool[sig], annotations.compact)
|
93
|
+
offset += consumed
|
94
|
+
end
|
95
|
+
|
96
|
+
# Parse methods
|
97
|
+
methods = Array.new
|
98
|
+
methods_count = bytes[offset..offset + 1].u2
|
99
|
+
offset += 2
|
100
|
+
methods_count.times do
|
101
|
+
flags = bytes[offset..offset + 1].u2
|
102
|
+
name = bytes[offset + 2..offset + 3].u2
|
103
|
+
sig = bytes[offset + 4..offset + 5].u2
|
104
|
+
offset += 6
|
105
|
+
|
106
|
+
consumed, annotations = parse_attributes(bytes[offset..-1], constant_pool)
|
107
|
+
methods << Java::Method.new(flags, constant_pool[name], constant_pool[sig], annotations.compact)
|
108
|
+
offset += consumed
|
109
|
+
end
|
110
|
+
|
111
|
+
# Parse class attributes
|
112
|
+
_, annotations = parse_attributes(bytes[offset..-1], constant_pool)
|
113
|
+
Java::Class.new(major, minor, access_flags, this_klass, super_klass, interfaces, fields, methods, annotations)
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_double(fixnum)
|
117
|
+
case fixnum
|
118
|
+
when 0x7ff0000000000000 then Float::INFINITY
|
119
|
+
when 0xfff0000000000000 then -Float::INFINITY
|
120
|
+
#TODO: NaN
|
121
|
+
else
|
122
|
+
s = (fixnum >> 63) == 0 ? 1 : -1
|
123
|
+
e = (fixnum >> 52) & 0x7ff
|
124
|
+
m = e == 0 ? (fixnum & 0xfffffffffffff) << 1 : (fixnum & 0xfffffffffffff) | 0x10000000000000
|
125
|
+
(s * m * 2**(e - 1075)).to_f
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_float(fixnum)
|
130
|
+
case fixnum
|
131
|
+
when 0x7f800000 then Float::INFINITY
|
132
|
+
when 0xff800000 then Float::INFINITY
|
133
|
+
#TODO: NaN
|
134
|
+
else
|
135
|
+
s = ((fixnum >> 31) == 0) ? 1 : -1
|
136
|
+
e = ((fixnum >> 23) & 0xff)
|
137
|
+
m = (e == 0) ? (fixnum & 0x7fffff) << 1 : (fixnum & 0x7fffff) | 0x800000;
|
138
|
+
(s * m * 2**(e - 150)).to_f
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def constant_pool_info_size(bytes)
|
143
|
+
constant_pool_tag = bytes[0]
|
144
|
+
case constant_pool_tag
|
145
|
+
when CONSTANT_UTF8
|
146
|
+
tag_content_size = bytes[1..2].u2
|
147
|
+
val = bytes[3..tag_content_size + 2].modified_utf8_to_s
|
148
|
+
return 3 + tag_content_size, val
|
149
|
+
when CONSTANT_INTEGER
|
150
|
+
val = bytes[1..4].u4
|
151
|
+
return 5, val
|
152
|
+
when CONSTANT_FLOAT
|
153
|
+
val = to_float(bytes[1..4].u4)
|
154
|
+
return 5, val
|
155
|
+
when CONSTANT_LONG
|
156
|
+
high_bytes = bytes[1..4].u4
|
157
|
+
low_bytes = bytes[5..8].u4
|
158
|
+
return 9, (high_bytes << 32) + low_bytes, constant_pool_tag
|
159
|
+
when CONSTANT_DOUBLE
|
160
|
+
high_bytes = bytes[1..4].u4
|
161
|
+
low_bytes = bytes[5..8].u4
|
162
|
+
return 9, to_double((high_bytes << 32) + low_bytes), constant_pool_tag
|
163
|
+
when CONSTANT_STRING, CONSTANT_CLASS
|
164
|
+
val = bytes[1..2].u2
|
165
|
+
return 3, val
|
166
|
+
when CONSTANT_FIELDREF, CONSTANT_METHODREF, CONSTANT_INTERFACEMETHODREF
|
167
|
+
klass_index = bytes[1..2].u2
|
168
|
+
name_and_type_index = bytes[3..4].u2
|
169
|
+
return 5, [klass_index, name_and_type_index]
|
170
|
+
when CONSTANT_NAMEANDTYPE
|
171
|
+
name_index = bytes[1..2].u2
|
172
|
+
descriptor_index = bytes[3..4].u2
|
173
|
+
return 5, [name_index, descriptor_index]
|
174
|
+
when CONSTANT_METHODHANDLE
|
175
|
+
reference_kind = bytes[1].u1
|
176
|
+
reference_index = bytes[2..3].u2
|
177
|
+
return 4, [reference_kind, reference_index]
|
178
|
+
when CONSTANT_METHODTYPE
|
179
|
+
return 3, bytes[1..2].u2
|
180
|
+
when CONSTANT_INVOKEDYNAMIC
|
181
|
+
bootstrap_method_attr_index = bytes[1..2].u2
|
182
|
+
name_and_type_index = bytes[3..4].u2
|
183
|
+
return 5, [bootstrap_method_attr_index, name_and_type_index]
|
184
|
+
else raise "Unknown Constant Pool Tag: 0x#{constant_pool_tag.to_s(16)}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def parse_annotation(bytes, constant_pool)
|
189
|
+
annotations = {}
|
190
|
+
num_annotations = bytes[0..1].u2
|
191
|
+
offset = 2
|
192
|
+
num_annotations.times do |_annotation|
|
193
|
+
annotation_name_index = bytes[offset..offset + 1].u2
|
194
|
+
num_value_pairs = bytes[offset + 2..offset + 3].u2
|
195
|
+
offset += 4
|
196
|
+
key_values = annotations[ constant_pool[annotation_name_index] ] = []
|
197
|
+
num_value_pairs.times do |_value_pairs|
|
198
|
+
name = bytes[offset..offset + 1].u2
|
199
|
+
# TODO: Handle 4.7.16.1. The element_value structure
|
200
|
+
value = bytes[offset + 3..offset + 4].u2
|
201
|
+
key_values << constant_pool[name]
|
202
|
+
key_values << constant_pool[value]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
return nil if annotations.empty?
|
206
|
+
annotations
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_attributes(bytes, constant_pool)
|
210
|
+
annotations = []
|
211
|
+
attr_count = bytes[0..1].u2
|
212
|
+
offset = 2 # Consume attr_count
|
213
|
+
attr_count.times do |_attr|
|
214
|
+
attr_name_index = bytes[offset..offset + 1].u2
|
215
|
+
attr_size = bytes[offset + 2..offset + 5].u4
|
216
|
+
offset += 6
|
217
|
+
if (constant_pool[attr_name_index] == "RuntimeInvisibleAnnotations") && (attr_size > 0)
|
218
|
+
annotations << parse_annotation(bytes[offset..offset + attr_size], constant_pool)
|
219
|
+
end
|
220
|
+
offset += attr_size
|
221
|
+
end
|
222
|
+
[offset, annotations]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: java_dissassembler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josh Bodily
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-02-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A Java .class file dissassembler. Similar to javap, but w/ no JDK dependencies. Pure
|
14
|
+
Ruby
|
15
|
+
email: joshbodily@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/java_dissassembler.rb
|
21
|
+
- lib/java_dissassembler/annotatable.rb
|
22
|
+
- lib/java_dissassembler/array.rb
|
23
|
+
- lib/java_dissassembler/class.rb
|
24
|
+
- lib/java_dissassembler/erb_helper.rb
|
25
|
+
- lib/java_dissassembler/field.rb
|
26
|
+
- lib/java_dissassembler/method.rb
|
27
|
+
homepage: http://rubygems.org/gems/java_dissassembler
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.9.3
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.6.7
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: Java .class file dissassembler
|
51
|
+
test_files: []
|