worf 1.0.0

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.
@@ -0,0 +1,128 @@
1
+ ENV["MT_NO_PLUGINS"] = "1"
2
+
3
+ require "helper"
4
+
5
+ module WORF
6
+ class Test < Minitest::Test
7
+ debug_file = "fixtures/out.dSYM/Contents/Resources/DWARF/out"
8
+ DEBUG_FILE = File.expand_path(File.join(__dir__, debug_file))
9
+
10
+ debug_file = "fixtures/a.out.dSYM/Contents/Resources/DWARF/a.out"
11
+ SLOP = File.expand_path(File.join(__dir__, debug_file))
12
+
13
+ def test_uleb128
14
+ assert_equal 624485, WORF.unpackULEB128(StringIO.new("\xE5\x8E\x26".b))
15
+ end
16
+
17
+ def test_sleb128
18
+ assert_equal(-123456, WORF.unpackSLEB128(StringIO.new("\xC0\xBB\x78".b)))
19
+ end
20
+
21
+ def test_debug_abbrev
22
+ File.open DEBUG_FILE do |io|
23
+ mach_o = OdinFlex::MachO.new(io)
24
+
25
+ section = mach_o.find do |thing|
26
+ thing.section? && thing.sectname == "__debug_abbrev"
27
+ end
28
+
29
+ debug_abbrev = WORF::DebugAbbrev.new io, section, mach_o.start_pos
30
+ tags = debug_abbrev.tags
31
+
32
+ assert_equal 5, tags.length
33
+ end
34
+ end
35
+
36
+ def test_debug_info
37
+ File.open DEBUG_FILE do |io|
38
+ mach_o = OdinFlex::MachO.new(io)
39
+
40
+ abbrev = mach_o.find do |thing|
41
+ thing.section? && thing.sectname == "__debug_abbrev"
42
+ end
43
+
44
+ debug_abbrev = WORF::DebugAbbrev.new io, abbrev, mach_o.start_pos
45
+
46
+ info = mach_o.find do |thing|
47
+ thing.section? && thing.sectname == "__debug_info"
48
+ end
49
+
50
+ debug_info = WORF::DebugInfo.new io, info, mach_o.start_pos
51
+ units = debug_info.compile_units(debug_abbrev.tags)
52
+
53
+ assert_equal 1, units.length
54
+ assert_equal Constants::DW_TAG_compile_unit, units.first.die.tag.type
55
+ assert_equal 0xb, units.first.die.offset
56
+ assert_equal 5, units.first.die.children.length
57
+ assert_equal Constants::DW_TAG_subprogram, units.first.die.children.first.tag.type
58
+ assert_equal 0x2a, units.first.die.children.first.offset
59
+ end
60
+ end
61
+
62
+ class StructInfo
63
+ def initialize die, names, ranges
64
+ @die = die
65
+ @ranges = ranges
66
+ end
67
+
68
+ def byte_size; @die.byte_size; end
69
+
70
+ def used_size
71
+ @ranges.map(&:size).inject(:+)
72
+ end
73
+ end
74
+
75
+ def show unit, die, strings, dies
76
+ names = []
77
+ ranges = die.children.map do |child|
78
+ if child.tag.member?
79
+ type_die = dies.find { |d| d.offset == child.type }
80
+ size = if type_die.tag.pointer_type?
81
+ unit.address_size
82
+ else
83
+ type_die.byte_size
84
+ end
85
+ start = child.data_member_location
86
+ name = strings.string_at(child.name_offset)
87
+ names << name
88
+ Range.new(start, start + size, exclude_end: true)
89
+ else
90
+ raise NotImplementedError
91
+ end
92
+ end
93
+
94
+ StructInfo.new die, names, ranges
95
+ end
96
+
97
+ def test_read_struct
98
+ struct_info = nil
99
+
100
+ File.open SLOP do |io|
101
+ mach_o = OdinFlex::MachO.new(io)
102
+
103
+ abbrev = mach_o.find_section "__debug_abbrev"
104
+ debug_abbrev = WORF::DebugAbbrev.new io, abbrev, mach_o.start_pos
105
+
106
+ section_info = mach_o.find_section "__debug_str"
107
+ strings = WORF::DebugStrings.new io, section_info, mach_o.start_pos
108
+
109
+ info = mach_o.find_section "__debug_info"
110
+ debug_info = WORF::DebugInfo.new io, info, mach_o.start_pos
111
+
112
+ units = debug_info.compile_units(debug_abbrev.tags)
113
+
114
+ units.each do |unit|
115
+ unit.die.children.each do |die|
116
+ if die.tag.structure_type?
117
+ struct_info = show unit, die, strings, unit.die.to_a
118
+ break
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ assert_equal 24, struct_info.byte_size
125
+ assert_equal 17, struct_info.used_size
126
+ end
127
+ end
128
+ end
Binary file
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>English</string>
7
+ <key>CFBundleIdentifier</key>
8
+ <string>com.apple.xcode.dsym.a.out</string>
9
+ <key>CFBundleInfoDictionaryVersion</key>
10
+ <string>6.0</string>
11
+ <key>CFBundlePackageType</key>
12
+ <string>dSYM</string>
13
+ <key>CFBundleSignature</key>
14
+ <string>????</string>
15
+ <key>CFBundleShortVersionString</key>
16
+ <string>1.0</string>
17
+ <key>CFBundleVersion</key>
18
+ <string>1</string>
19
+ </dict>
20
+ </plist>
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>English</string>
7
+ <key>CFBundleIdentifier</key>
8
+ <string>com.apple.xcode.dsym.out</string>
9
+ <key>CFBundleInfoDictionaryVersion</key>
10
+ <string>6.0</string>
11
+ <key>CFBundlePackageType</key>
12
+ <string>dSYM</string>
13
+ <key>CFBundleSignature</key>
14
+ <string>????</string>
15
+ <key>CFBundleShortVersionString</key>
16
+ <string>1.0</string>
17
+ <key>CFBundleVersion</key>
18
+ <string>1</string>
19
+ </dict>
20
+ </plist>
@@ -0,0 +1,17 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+
4
+ struct hello {
5
+ char *p;
6
+ char c;
7
+ long x;
8
+ };
9
+
10
+ int main(int argc, char **argv) {
11
+ struct hello * m;
12
+ m = malloc(sizeof(struct hello));
13
+ m->c = 123;
14
+ m->x = 123;
15
+ printf("Hello World %lu\n", m->x);
16
+ return 0;
17
+ }
@@ -0,0 +1,6 @@
1
+ #include <stdio.h>
2
+
3
+ int main(int argc, char **argv) {
4
+ printf("Hello World\n");
5
+ return 0;
6
+ }
data/test/helper.rb ADDED
@@ -0,0 +1,59 @@
1
+ ENV["MT_NO_PLUGINS"] = "1"
2
+
3
+ require "minitest/autorun"
4
+ require "worf"
5
+ require "odinflex/mach-o"
6
+ require "odinflex/ar"
7
+ require "rbconfig"
8
+ require "fiddle"
9
+
10
+ module WORF
11
+ class Test < Minitest::Test
12
+ include Fiddle
13
+
14
+ module Hacks
15
+ include Fiddle
16
+
17
+ class Fiddle::Function
18
+ def to_proc
19
+ this = self
20
+ lambda { |*args| this.call(*args) }
21
+ end
22
+ end unless Function.method_defined?(:to_proc)
23
+
24
+ def self.make_function name, args, ret
25
+ ptr = Handle::DEFAULT[name]
26
+ func = Function.new ptr, args, ret, name: name
27
+ define_singleton_method name, &func.to_proc
28
+ end
29
+
30
+ make_function "strerror", [TYPE_INT], TYPE_CONST_STRING
31
+ #make_function "mprotect", [TYPE_VOIDP, TYPE_SIZE_T, TYPE_INT], TYPE_INT
32
+ make_function "_dyld_image_count", [], TYPE_INT32_T
33
+ make_function "_dyld_get_image_name", [TYPE_INT32_T], TYPE_CONST_STRING
34
+ make_function "_dyld_get_image_vmaddr_slide", [TYPE_INT32_T], TYPE_INTPTR_T
35
+ make_function "mach_task_self", [], TYPE_VOIDP
36
+ make_function "vm_protect", [TYPE_VOIDP, -TYPE_INT64_T, TYPE_SIZE_T, TYPE_CHAR, TYPE_INT], TYPE_INT
37
+ make_function "rb_intern", [TYPE_CONST_STRING], TYPE_INT
38
+
39
+ def self.mprotect addr, len, prot
40
+ vm_protect mach_task_self, addr, len, 0, prot | PROT_COPY
41
+ end
42
+
43
+ PROT_READ = 0x01
44
+ PROT_WRITE = 0x02
45
+ PROT_EXEC = 0x04
46
+ PROT_COPY = 0x10
47
+
48
+ def self.slide
49
+ executable = RbConfig.ruby
50
+ Hacks._dyld_image_count.times do |i|
51
+ name = Hacks._dyld_get_image_name(i)
52
+ if executable == name
53
+ return Hacks._dyld_get_image_vmaddr_slide(i)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ require "helper"
2
+
3
+ module WORF
4
+ class MachODWARFIntegrationTest < Test
5
+ def test_find_symbol_and_make_struct
6
+ addr = nil
7
+ archive = nil
8
+
9
+ File.open(RbConfig.ruby) do |f|
10
+ my_macho = OdinFlex::MachO.new f
11
+ my_macho.each do |section|
12
+ if section.symtab?
13
+ addr = section.nlist.find { |symbol|
14
+ symbol.name == "_ruby_api_version" && symbol.value > 0
15
+ }.value + Hacks.slide
16
+
17
+ archive = section.nlist.find_all(&:archive?).map(&:archive).uniq.first
18
+ end
19
+ end
20
+ end
21
+
22
+ assert addr
23
+ assert archive
24
+
25
+ found_object = nil
26
+
27
+ File.open(archive) do |f|
28
+ ar = OdinFlex::AR.new f
29
+ ar.each do |object_file|
30
+ next unless object_file.identifier.end_with?(".o")
31
+ next unless object_file.identifier == "version.o"
32
+
33
+ f.seek object_file.pos, IO::SEEK_SET
34
+ macho = OdinFlex::MachO.new f
35
+ debug_info = macho.find_section("__debug_info")&.as_dwarf || next
36
+ debug_strs = macho.find_section("__debug_str").as_dwarf
37
+ debug_abbrev = macho.find_section("__debug_abbrev").as_dwarf
38
+
39
+ debug_info.compile_units(debug_abbrev.tags).each do |unit|
40
+ unit.die.children.each do |die|
41
+ if die.name(debug_strs) == "ruby_api_version"
42
+ found_object = object_file
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ assert found_object
50
+ end
51
+ end
52
+ end
data/test/ruby_test.rb ADDED
@@ -0,0 +1,238 @@
1
+ require "helper"
2
+
3
+ module WORF
4
+ class RubyTest < WORF::Test
5
+ def ruby_archive
6
+ File.join RbConfig::CONFIG["prefix"], "lib", RbConfig::CONFIG["LIBRUBY"]
7
+ end
8
+
9
+ def test_ruby_archive
10
+ assert File.file?(ruby_archive)
11
+ end
12
+
13
+ def test_read_archive
14
+ files = []
15
+ File.open(ruby_archive) do |f|
16
+ OdinFlex::AR.new(f).each { |file| files << file.identifier }
17
+ end
18
+ assert_includes files, "gc.o"
19
+ end
20
+
21
+ def test_read_archive_twice
22
+ files = []
23
+ File.open(ruby_archive) do |f|
24
+ ar = OdinFlex::AR.new(f)
25
+ ar.each { |file| files << file.identifier }
26
+ assert_includes files, "gc.o"
27
+ files.clear
28
+ ar.each { |file| files << file.identifier }
29
+ assert_includes files, "gc.o"
30
+ end
31
+ end
32
+
33
+ def test_macho_in_archive
34
+ File.open(ruby_archive) do |f|
35
+ ar = OdinFlex::AR.new f
36
+ gc = ar.find { |file| file.identifier == "gc.o" }
37
+
38
+ f.seek gc.pos, IO::SEEK_SET
39
+ macho = OdinFlex::MachO.new f
40
+ section = macho.find_section("__debug_str")
41
+ assert_equal "__debug_str", section.sectname
42
+ end
43
+ end
44
+
45
+ def test_macho_to_dwarf
46
+ File.open(ruby_archive) do |f|
47
+ ar = OdinFlex::AR.new f
48
+ gc = ar.find { |file| file.identifier == "gc.o" }
49
+
50
+ f.seek gc.pos, IO::SEEK_SET
51
+ macho = OdinFlex::MachO.new f
52
+ debug_strs = macho.find_section("__debug_str").as_dwarf
53
+ debug_abbrev = macho.find_section("__debug_abbrev").as_dwarf
54
+ debug_info = macho.find_section("__debug_info").as_dwarf
55
+
56
+ names = []
57
+
58
+ debug_info.compile_units(debug_abbrev.tags).each do |unit|
59
+ unit.die.children.each do |die|
60
+ names << die.name(debug_strs)
61
+ end
62
+ end
63
+
64
+ assert_includes names, "RBasic"
65
+ end
66
+ end
67
+
68
+ def test_rbasic_layout
69
+ File.open(ruby_archive) do |f|
70
+ ar = OdinFlex::AR.new f
71
+ gc = ar.find { |file| file.identifier == "gc.o" }
72
+
73
+ f.seek gc.pos, IO::SEEK_SET
74
+ macho = OdinFlex::MachO.new f
75
+ debug_strs = macho.find_section("__debug_str").as_dwarf
76
+ debug_abbrev = macho.find_section("__debug_abbrev").as_dwarf
77
+ debug_info = macho.find_section("__debug_info").as_dwarf
78
+
79
+ rbasic_layout = []
80
+
81
+ debug_info.compile_units(debug_abbrev.tags).each do |unit|
82
+ unit.die.children.each do |die|
83
+ if die.name(debug_strs) == "RBasic"
84
+ assert_predicate die.tag, :structure_type?
85
+
86
+ die.children.each do |child|
87
+ field_name = child.name(debug_strs)
88
+ field_type = nil
89
+ while child
90
+ field_type = child.name(debug_strs)
91
+ break unless child.type
92
+ child = unit.die.find_type(child)
93
+ end
94
+ rbasic_layout << [field_name, field_type]
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ assert_equal([["flags", "long unsigned int"], ["klass", "long unsigned int"]],
101
+ rbasic_layout)
102
+ end
103
+ end
104
+
105
+ def test_rclass_layout
106
+ File.open(ruby_archive) do |f|
107
+ ar = OdinFlex::AR.new f
108
+ gc = ar.find { |file| file.identifier == "gc.o" }
109
+
110
+ f.seek gc.pos, IO::SEEK_SET
111
+ macho = OdinFlex::MachO.new f
112
+ debug_strs = macho.find_section("__debug_str").as_dwarf
113
+ debug_abbrev = macho.find_section("__debug_abbrev").as_dwarf
114
+ debug_info = macho.find_section("__debug_info").as_dwarf
115
+
116
+ layout = []
117
+
118
+ debug_info.compile_units(debug_abbrev.tags).each do |unit|
119
+ unit.die.children.each do |die|
120
+ if die.name(debug_strs) == "RClass"
121
+ assert_predicate die.tag, :structure_type?
122
+
123
+ die.children.each do |child|
124
+ field_name = child.name(debug_strs)
125
+ type = unit.die.find_type(child)
126
+
127
+ if type.tag.typedef?
128
+ type = unit.die.find_type(type)
129
+ end
130
+
131
+ type_name = if type.tag.pointer_type?
132
+ c = unit.die.find_type(type)
133
+ "#{c.name(debug_strs)} *"
134
+ else
135
+ type.name(debug_strs)
136
+ end
137
+
138
+ type_size = if type.tag.pointer_type?
139
+ unit.address_size
140
+ else
141
+ type.byte_size
142
+ end
143
+
144
+ layout << [field_name, type_name, type_size]
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ assert_equal([["basic", "RBasic", 16],
151
+ ["super", "long unsigned int", 8],
152
+ ["ptr", "rb_classext_struct *", 8],
153
+ ["class_serial", "long long unsigned int", 8]],
154
+ layout)
155
+ end
156
+ end
157
+
158
+ [
159
+ [:section?, OdinFlex::MachO::Section],
160
+ [:symtab?, OdinFlex::MachO::LC_SYMTAB],
161
+ [:segment?, OdinFlex::MachO::LC_SEGMENT_64],
162
+ [:dysymtab?, OdinFlex::MachO::LC_DYSYMTAB],
163
+ [:command?, OdinFlex::MachO::Command],
164
+ ].each do |predicate, klass|
165
+ define_method :"test_find_#{predicate}" do
166
+ File.open(RbConfig.ruby) do |f|
167
+ my_macho = OdinFlex::MachO.new f
168
+ list = my_macho.find_all(&predicate)
169
+ refute_predicate list, :empty?
170
+ assert list.all? { |x| x.is_a?(klass) }
171
+ end
172
+ end
173
+ end
174
+
175
+ def test_rb_vm_get_insns_address_table
176
+ sym = nil
177
+
178
+ File.open(RbConfig.ruby) do |f|
179
+ my_macho = OdinFlex::MachO.new f
180
+
181
+ my_macho.each do |section|
182
+ if section.symtab?
183
+ sym = section.nlist.find do |symbol|
184
+ symbol.name == "_rb_vm_get_insns_address_table" && symbol.value
185
+ end
186
+ break if sym
187
+ end
188
+ end
189
+ end
190
+
191
+ addr = sym.value + Hacks.slide
192
+ ptr = Fiddle::Function.new(addr, [], TYPE_VOIDP).call
193
+ len = RubyVM::INSTRUCTION_NAMES.length
194
+ assert ptr[0, len * Fiddle::SIZEOF_VOIDP].unpack("Q#{len}")
195
+ end
196
+
197
+ def test_guess_slide
198
+ File.open(RbConfig.ruby) do |f|
199
+ my_macho = OdinFlex::MachO.new f
200
+
201
+ my_macho.each do |section|
202
+ if section.symtab?
203
+ section.nlist.each do |symbol|
204
+ if symbol.name == "_rb_st_insert"
205
+ guess_slide = Fiddle::Handle::DEFAULT["rb_st_insert"] - symbol.value
206
+ assert_equal Hacks.slide, guess_slide
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ def test_find_global
215
+ File.open(RbConfig.ruby) do |f|
216
+ my_macho = OdinFlex::MachO.new f
217
+
218
+ my_macho.each do |section|
219
+ if section.symtab?
220
+ section.nlist.each do |symbol|
221
+ if symbol.name == "_ruby_api_version"
222
+ if symbol.value > 0
223
+ addr = symbol.value + Hacks.slide
224
+ pointer = Fiddle::Pointer.new(addr, Fiddle::SIZEOF_INT * 3)
225
+ assert_equal RbConfig::CONFIG["ruby_version"].split(".").map(&:to_i),
226
+ pointer[0, Fiddle::SIZEOF_INT * 3].unpack("LLL")
227
+ else
228
+ assert_predicate symbol, :stab?
229
+ assert_predicate symbol, :gsym?
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end