odinflex 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.
- checksums.yaml +7 -0
- data/Gemfile +5 -0
- data/LICENSE +201 -0
- data/README.md +59 -0
- data/Rakefile +8 -0
- data/lib/odinflex/ar.rb +56 -0
- data/lib/odinflex/mach-o.rb +560 -0
- data/odinflex.gemspec +12 -0
- data/test/fixtures/a.out +0 -0
- data/test/fixtures/a.out.dSYM/Contents/Info.plist +20 -0
- data/test/fixtures/a.out.dSYM/Contents/Resources/DWARF/a.out +0 -0
- data/test/fixtures/out.dSYM/Contents/Info.plist +20 -0
- data/test/fixtures/out.dSYM/Contents/Resources/DWARF/out +0 -0
- data/test/fixtures/slop.c +17 -0
- data/test/fixtures/thing.c +6 -0
- data/test/helper.rb +58 -0
- data/test/mach_o_dwarf_integration_test.rb +47 -0
- data/test/ruby_test.rb +238 -0
- metadata +70 -0
data/odinflex.gemspec
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "odinflex"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.summary = "Parse AR or Mach-O files"
|
5
|
+
s.description = "Do you need to parse an AR file or a Mach-O file? If so, then this is the library for you!"
|
6
|
+
s.authors = ["Aaron Patterson"]
|
7
|
+
s.email = "tenderlove@ruby-lang.org"
|
8
|
+
s.files = `git ls-files -z`.split("\x0")
|
9
|
+
s.test_files = s.files.grep(%r{^test/})
|
10
|
+
s.homepage = "https://github.com/tenderlove/odinflex"
|
11
|
+
s.license = "Apache-2.0"
|
12
|
+
end
|
data/test/fixtures/a.out
ADDED
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>
|
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.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>
|
Binary file
|
@@ -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
|
+
}
|
data/test/helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
ENV["MT_NO_PLUGINS"] = "1"
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "odinflex/mach-o"
|
5
|
+
require "odinflex/ar"
|
6
|
+
require "rbconfig"
|
7
|
+
require "fiddle"
|
8
|
+
|
9
|
+
module OdinFlex
|
10
|
+
class Test < Minitest::Test
|
11
|
+
include Fiddle
|
12
|
+
|
13
|
+
module Hacks
|
14
|
+
include Fiddle
|
15
|
+
|
16
|
+
class Fiddle::Function
|
17
|
+
def to_proc
|
18
|
+
this = self
|
19
|
+
lambda { |*args| this.call(*args) }
|
20
|
+
end
|
21
|
+
end unless Function.method_defined?(:to_proc)
|
22
|
+
|
23
|
+
def self.make_function name, args, ret
|
24
|
+
ptr = Handle::DEFAULT[name]
|
25
|
+
func = Function.new ptr, args, ret, name: name
|
26
|
+
define_singleton_method name, &func.to_proc
|
27
|
+
end
|
28
|
+
|
29
|
+
make_function "strerror", [TYPE_INT], TYPE_CONST_STRING
|
30
|
+
#make_function "mprotect", [TYPE_VOIDP, TYPE_SIZE_T, TYPE_INT], TYPE_INT
|
31
|
+
make_function "_dyld_image_count", [], TYPE_INT32_T
|
32
|
+
make_function "_dyld_get_image_name", [TYPE_INT32_T], TYPE_CONST_STRING
|
33
|
+
make_function "_dyld_get_image_vmaddr_slide", [TYPE_INT32_T], TYPE_INTPTR_T
|
34
|
+
make_function "mach_task_self", [], TYPE_VOIDP
|
35
|
+
make_function "vm_protect", [TYPE_VOIDP, -TYPE_INT64_T, TYPE_SIZE_T, TYPE_CHAR, TYPE_INT], TYPE_INT
|
36
|
+
make_function "rb_intern", [TYPE_CONST_STRING], TYPE_INT
|
37
|
+
|
38
|
+
def self.mprotect addr, len, prot
|
39
|
+
vm_protect mach_task_self, addr, len, 0, prot | PROT_COPY
|
40
|
+
end
|
41
|
+
|
42
|
+
PROT_READ = 0x01
|
43
|
+
PROT_WRITE = 0x02
|
44
|
+
PROT_EXEC = 0x04
|
45
|
+
PROT_COPY = 0x10
|
46
|
+
|
47
|
+
def self.slide
|
48
|
+
executable = RbConfig.ruby
|
49
|
+
Hacks._dyld_image_count.times do |i|
|
50
|
+
name = Hacks._dyld_get_image_name(i)
|
51
|
+
if executable == name
|
52
|
+
return Hacks._dyld_get_image_vmaddr_slide(i)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "worf"
|
3
|
+
|
4
|
+
module OdinFlex
|
5
|
+
class MachODWARFIntegrationTest < Test
|
6
|
+
def test_find_symbol_and_make_struct
|
7
|
+
archive = nil
|
8
|
+
|
9
|
+
File.open(RbConfig.ruby) do |f|
|
10
|
+
my_macho = MachO.new f
|
11
|
+
my_macho.each do |section|
|
12
|
+
if section.symtab?
|
13
|
+
archive = section.nlist.find_all(&:archive?).map(&:archive).uniq.first
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
assert archive
|
19
|
+
|
20
|
+
found_object = nil
|
21
|
+
|
22
|
+
File.open(archive) do |f|
|
23
|
+
ar = AR.new f
|
24
|
+
ar.each do |object_file|
|
25
|
+
next unless object_file.identifier.end_with?(".o")
|
26
|
+
next unless object_file.identifier == "version.o"
|
27
|
+
|
28
|
+
f.seek object_file.pos, IO::SEEK_SET
|
29
|
+
macho = MachO.new f
|
30
|
+
debug_info = macho.find_section("__debug_info")&.as_dwarf || next
|
31
|
+
debug_strs = macho.find_section("__debug_str").as_dwarf
|
32
|
+
debug_abbrev = macho.find_section("__debug_abbrev").as_dwarf
|
33
|
+
|
34
|
+
debug_info.compile_units(debug_abbrev.tags).each do |unit|
|
35
|
+
unit.die.children.each do |die|
|
36
|
+
if die.name(debug_strs) == "ruby_api_version"
|
37
|
+
found_object = object_file
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
assert found_object
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/test/ruby_test.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module OdinFlex
|
4
|
+
class RubyTest < OdinFlex::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
|
+
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 = 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 = AR.new f
|
36
|
+
gc = ar.find { |file| file.identifier == "gc.o" }
|
37
|
+
|
38
|
+
f.seek gc.pos, IO::SEEK_SET
|
39
|
+
macho = 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 = AR.new f
|
48
|
+
gc = ar.find { |file| file.identifier == "gc.o" }
|
49
|
+
|
50
|
+
f.seek gc.pos, IO::SEEK_SET
|
51
|
+
macho = 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 = AR.new f
|
71
|
+
gc = ar.find { |file| file.identifier == "gc.o" }
|
72
|
+
|
73
|
+
f.seek gc.pos, IO::SEEK_SET
|
74
|
+
macho = 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 = AR.new f
|
108
|
+
gc = ar.find { |file| file.identifier == "gc.o" }
|
109
|
+
|
110
|
+
f.seek gc.pos, IO::SEEK_SET
|
111
|
+
macho = 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?, MachO::Section],
|
160
|
+
[:symtab?, MachO::LC_SYMTAB],
|
161
|
+
[:segment?, MachO::LC_SEGMENT_64],
|
162
|
+
[:dysymtab?, MachO::LC_DYSYMTAB],
|
163
|
+
[:command?, MachO::Command],
|
164
|
+
].each do |predicate, klass|
|
165
|
+
define_method :"test_find_#{predicate}" do
|
166
|
+
File.open(RbConfig.ruby) do |f|
|
167
|
+
my_macho = 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 = 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 = 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 = 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
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: odinflex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Patterson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-07-07 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Do you need to parse an AR file or a Mach-O file? If so, then this is
|
14
|
+
the library for you!
|
15
|
+
email: tenderlove@ruby-lang.org
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- Gemfile
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- Rakefile
|
24
|
+
- lib/odinflex/ar.rb
|
25
|
+
- lib/odinflex/mach-o.rb
|
26
|
+
- odinflex.gemspec
|
27
|
+
- test/fixtures/a.out
|
28
|
+
- test/fixtures/a.out.dSYM/Contents/Info.plist
|
29
|
+
- test/fixtures/a.out.dSYM/Contents/Resources/DWARF/a.out
|
30
|
+
- test/fixtures/out.dSYM/Contents/Info.plist
|
31
|
+
- test/fixtures/out.dSYM/Contents/Resources/DWARF/out
|
32
|
+
- test/fixtures/slop.c
|
33
|
+
- test/fixtures/thing.c
|
34
|
+
- test/helper.rb
|
35
|
+
- test/mach_o_dwarf_integration_test.rb
|
36
|
+
- test/ruby_test.rb
|
37
|
+
homepage: https://github.com/tenderlove/odinflex
|
38
|
+
licenses:
|
39
|
+
- Apache-2.0
|
40
|
+
metadata: {}
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubygems_version: 3.2.15
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: Parse AR or Mach-O files
|
60
|
+
test_files:
|
61
|
+
- test/fixtures/a.out
|
62
|
+
- test/fixtures/a.out.dSYM/Contents/Info.plist
|
63
|
+
- test/fixtures/a.out.dSYM/Contents/Resources/DWARF/a.out
|
64
|
+
- test/fixtures/out.dSYM/Contents/Info.plist
|
65
|
+
- test/fixtures/out.dSYM/Contents/Resources/DWARF/out
|
66
|
+
- test/fixtures/slop.c
|
67
|
+
- test/fixtures/thing.c
|
68
|
+
- test/helper.rb
|
69
|
+
- test/mach_o_dwarf_integration_test.rb
|
70
|
+
- test/ruby_test.rb
|