elf_utils 0.3.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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +3 -0
  4. data/CODE_OF_CONDUCT.md +132 -0
  5. data/CONTRIBUTING.md +55 -0
  6. data/Gemfile +23 -0
  7. data/LICENSE.txt +21 -0
  8. data/MAINTAINERS.md +3 -0
  9. data/README.md +126 -0
  10. data/Rakefile +76 -0
  11. data/SECURITY.md +57 -0
  12. data/elf_utils.gemspec +41 -0
  13. data/ext/elf_utils/elf_utils.c +53 -0
  14. data/ext/elf_utils/extconf.rb +3 -0
  15. data/lib/elf_utils/elf_file.rb +312 -0
  16. data/lib/elf_utils/section/base.rb +77 -0
  17. data/lib/elf_utils/section/debug_abbrev/abbreviation.rb +171 -0
  18. data/lib/elf_utils/section/debug_abbrev/abbreviation_table.rb +27 -0
  19. data/lib/elf_utils/section/debug_abbrev.rb +15 -0
  20. data/lib/elf_utils/section/debug_addr.rb +9 -0
  21. data/lib/elf_utils/section/debug_arange.rb +54 -0
  22. data/lib/elf_utils/section/debug_info/compilation_unit.rb +189 -0
  23. data/lib/elf_utils/section/debug_info/debug_str_offsets_ref.rb +15 -0
  24. data/lib/elf_utils/section/debug_info/debug_str_ref.rb +17 -0
  25. data/lib/elf_utils/section/debug_info/die/base.rb +130 -0
  26. data/lib/elf_utils/section/debug_info/die.rb +470 -0
  27. data/lib/elf_utils/section/debug_info/die_ref.rb +22 -0
  28. data/lib/elf_utils/section/debug_info/header.rb +26 -0
  29. data/lib/elf_utils/section/debug_info.rb +93 -0
  30. data/lib/elf_utils/section/debug_line/line_number_program/header.rb +48 -0
  31. data/lib/elf_utils/section/debug_line/line_number_program/state_machine.rb +206 -0
  32. data/lib/elf_utils/section/debug_line/line_number_program.rb +134 -0
  33. data/lib/elf_utils/section/debug_line.rb +35 -0
  34. data/lib/elf_utils/section/debug_ranges.rb +22 -0
  35. data/lib/elf_utils/section/debug_str_offsets.rb +16 -0
  36. data/lib/elf_utils/section/dynsym.rb +14 -0
  37. data/lib/elf_utils/section/strtab.rb +9 -0
  38. data/lib/elf_utils/section/symtab.rb +11 -0
  39. data/lib/elf_utils/section.rb +50 -0
  40. data/lib/elf_utils/segment/base.rb +72 -0
  41. data/lib/elf_utils/segment.rb +9 -0
  42. data/lib/elf_utils/string_pread.rb +18 -0
  43. data/lib/elf_utils/symbol.rb +144 -0
  44. data/lib/elf_utils/types/dwarf/expression.rb +34 -0
  45. data/lib/elf_utils/types/dwarf.rb +639 -0
  46. data/lib/elf_utils/types/dwarf32/v2.rb +44 -0
  47. data/lib/elf_utils/types/dwarf32/v3.rb +40 -0
  48. data/lib/elf_utils/types/dwarf32/v4.rb +41 -0
  49. data/lib/elf_utils/types/dwarf32/v5.rb +44 -0
  50. data/lib/elf_utils/types/dwarf32.rb +12 -0
  51. data/lib/elf_utils/types/dwarf64/v3.rb +42 -0
  52. data/lib/elf_utils/types/dwarf64/v4.rb +43 -0
  53. data/lib/elf_utils/types/dwarf64/v5.rb +46 -0
  54. data/lib/elf_utils/types/dwarf64.rb +8 -0
  55. data/lib/elf_utils/types/sleb128.rb +66 -0
  56. data/lib/elf_utils/types/uleb128.rb +56 -0
  57. data/lib/elf_utils/types/unit_length.rb +51 -0
  58. data/lib/elf_utils/types.rb +328 -0
  59. data/lib/elf_utils/version.rb +5 -0
  60. data/lib/elf_utils.rb +83 -0
  61. data/sig/elf_utils.rbs +4 -0
  62. metadata +120 -0
@@ -0,0 +1,312 @@
1
+ require "forwardable"
2
+ require_relative "string_pread"
3
+
4
+ module ElfUtils
5
+ # Read ELF and DWARF data from any class that provides a #pread method.
6
+ #
7
+ # @example dump the symbols found in an ELF file
8
+ # ElfUtils::ElfFile.open("spec/data/complex_64be-dwarf64-v5") do |elf_file|
9
+ # pp elf_file.symbols
10
+ # end
11
+ #
12
+ # @example read complex structure from running process
13
+ # # open an ELF file, in this case an executable
14
+ # elf_file = ElfUtils.open("a.out")
15
+ #
16
+ # # get the Symbol instance for a global variable
17
+ # symbol = elf_file.symbol(:some_global_var) # => #<ElfUtils::Symbol ...>
18
+ #
19
+ # # get the address of the symbol
20
+ # addr = symbol.addr # => 0x40001000
21
+ #
22
+ # # relocate the load segments to account for ASLR. Addresses for load segments
23
+ # # gleaned manually from /proc/pid/map.
24
+ # elf_file.load_segments[0].relocate(text_addr)
25
+ # elf_file.load_segments[1].relocate(data_addr)
26
+ #
27
+ # # get the address of the symbol after relocation
28
+ # addr = symbol.addr # => 0x90301000
29
+ #
30
+ # # get the data type for the symbol; requires DWARF debug information
31
+ # type = symbol.ctype # => #<CTypes::Struct ...>
32
+ #
33
+ # # open the memory for a process running this executable, and read the value of
34
+ # # the global variable.
35
+ # value = File.open(File.join("/proc", pid, "mem")) do |mem|
36
+ # # read the raw bytes for the variable
37
+ # bytes = mem.pread(type.size, addr) # => "\xef\x99\xde... "
38
+ #
39
+ # # unpack the bytes
40
+ # type.unpack_one(bytes) # => { field: val, ... }
41
+ # end
42
+ class ElfFile
43
+ extend Forwardable
44
+ using StringPread
45
+
46
+ # open a file path, and return an ElfFile instance
47
+ # @overload open(path)
48
+ # @param path [String] file path
49
+ # @return [ElfFile]
50
+ #
51
+ # @overload open(path)
52
+ # @param path [String] file path
53
+ # @yield [ElfFile] invokes block with opened file, will close when block
54
+ # returns
55
+ #
56
+ # @example
57
+ # elf_file = ElfFile.open("spec/data/complex_64be-dwarf64-v5")
58
+ # pp(elf_file.symbols)
59
+ # elf_file.close
60
+ #
61
+ # @example
62
+ # ElfFile.open("spec/data/complex_64be-dwarf64-v5") do |elf_file|
63
+ # pp(elf_file.symbols)
64
+ # end
65
+ #
66
+ def self.open(path)
67
+ if block_given?
68
+ File.open(path) do |f|
69
+ yield new(f)
70
+ end
71
+ else
72
+ new(File.open(path))
73
+ end
74
+ end
75
+
76
+ # create an instance of ElfFile
77
+ # @param io [IO, String, #pread] Anything that provides
78
+ # the #pread(max_len, offset) method
79
+ def initialize(io)
80
+ io.force_encoding("ASCII-8BIT") if io.is_a?(String)
81
+ @io = io
82
+ @header = load_header(io)
83
+ @type_prefix = (elf_class == :elf32) ? "Elf32" : "Elf64"
84
+ end
85
+
86
+ # @api private
87
+ attr_reader :header
88
+
89
+ # return the path of the ElfFile
90
+ # @return [String, nil] path of the ElfFile
91
+ def path
92
+ @io.path if @io.respond_to?(:path)
93
+ end
94
+
95
+ # Return the ELF class according to the ident in the header
96
+ # @return [Symbol] :elf32 or :elf64
97
+ def elf_class
98
+ case @header.e_ident.ei_class
99
+ when Types::ELFCLASS32
100
+ :elf32
101
+ when Types::ELFCLASS64
102
+ :elf64
103
+ else
104
+ raise InvalidFormat, "Unsupported ELF ident.ei_class: %p", @header
105
+ end
106
+ end
107
+
108
+ # get the endian according to the ELF header
109
+ # @return [Symbol] :big or :little
110
+ def endian
111
+ case @header.e_ident.ei_data
112
+ when :lsb
113
+ :little
114
+ when :msb
115
+ :big
116
+ else
117
+ raise "Invalid EI_DATA value in ELF header ident: %p", @header
118
+ end
119
+ end
120
+
121
+ # check if this is a relocatable ELF file
122
+ # @return [Boolean] true if the file is relocatable
123
+ def relocatable?
124
+ @header.e_type == :rel
125
+ end
126
+
127
+ # Get the segments
128
+ # @return [Array<Segment::Base>] segments present in ELF file
129
+ def segments
130
+ @segments ||= pread([:Phdr, @header.e_phnum], @header.e_phoff).map do |hdr|
131
+ Segment.from_header(self, hdr)
132
+ end
133
+ end
134
+
135
+ # Return the list of load segments
136
+ # @return [Array<Segment::Base>] load segments present in ELF file
137
+ def load_segments
138
+ segments.select { |s| s.type == :load }
139
+ end
140
+
141
+ # Return the .shstrtab section
142
+ # @return [Section]
143
+ def shstrtab
144
+ # XXX we end up with a duplicate copy of this section, but we need to
145
+ # get the shstrtab while building the full sections list
146
+ @shstrtab ||= begin
147
+ hdr_class = elf_type(:Shdr)
148
+ offset = hdr_class.size * @header.e_shstrndx
149
+ hdr = pread(hdr_class, @header.e_shoff + offset)
150
+ Section.from_header(self, hdr)
151
+ end
152
+ end
153
+
154
+ # Return the .strtab section
155
+ # @return [Section::Strtab]
156
+ def strtab
157
+ section(".strtab")
158
+ end
159
+
160
+ # return the .symtab section
161
+ # @return [Section::Symtab]
162
+ def symtab
163
+ @symtab ||= section(".symtab") || section(".dynsym")
164
+ end
165
+
166
+ # return the .symtab section
167
+ # @return [Section::DebugInfo]
168
+ def debug_info
169
+ section(".debug_info")
170
+ end
171
+
172
+ # get the sections present in the ELF file
173
+ # @return [Array<Section::Base>]
174
+ def sections
175
+ @sections ||= pread([:Shdr, @header.e_shnum], @header.e_shoff)
176
+ .map do |hdr|
177
+ Section.from_header(self, hdr)
178
+ end
179
+ end
180
+
181
+ # lookup a section by name
182
+ # @return [Section::Base, nil] section instance if found, nil otherwise
183
+ def section(name)
184
+ @sections_by_name ||= sections.each_with_object({}) do |section, cache|
185
+ cache[section.name] = section
186
+ end
187
+ @sections_by_name[name]
188
+ end
189
+
190
+ # return the list of symbols found in the ELF file
191
+ # @return [Array<Symbol>]
192
+ def symbols
193
+ symtab&.symbols || []
194
+ end
195
+
196
+ # lookup a symbol by name
197
+ # @param [String, Symbol] symbol name
198
+ # @return [Symbol, name]
199
+ def symbol(name)
200
+ name = name.to_s
201
+ symbols.find { |s| s.name == name }
202
+ end
203
+
204
+ # lookup a symbol by memory address
205
+ # @param [Integer] in-memory address
206
+ # @return [Symbol, name]
207
+ def symbol_at_addr(addr)
208
+ symbols.find do |sym|
209
+ next unless sym.section&.alloc?
210
+ sym.to_range.include?(addr)
211
+ end
212
+ end
213
+
214
+ # Get the ctypes datatype for a type defined in the .debug_info section
215
+ # @param name [String] name of type
216
+ #
217
+ # @example lookup a structure type by name from .debug_info section
218
+ # ElfFile.open("spec/data/complex_64be-dwarf64-v5") do |elf_file|
219
+ # elf_file.type("struct tlv")
220
+ # end # => #<CTypes::Struct ... >
221
+ def type(name)
222
+ raise Error, "File does not contain .debug_info section: #{path}" unless
223
+ (debug_info = section(".debug_info"))
224
+ debug_info.type(name)
225
+ end
226
+ alias_method :ctype, :type
227
+
228
+ # pread wrapper providing support for type lookup by name
229
+ # @api private
230
+ def pread(type_or_size, offset)
231
+ type, size = parse_type(type_or_size)
232
+ buf = @io.pread(size, offset)
233
+ return buf unless type
234
+ type.unpack(buf)
235
+ end
236
+
237
+ # lookup the ctypes datatype for this file's bitsize (ELF32 vs ELF64)
238
+ # @param name [Symbol, String] name of type
239
+ # @see Types
240
+ # @api private
241
+ def elf_type(name)
242
+ Types.const_get("#{@type_prefix}_#{name}").with_endian(endian)
243
+ end
244
+
245
+ # get the ctype that represents an address for this ELF file
246
+ # @api private
247
+ def addr_type
248
+ elf_type(:Addr)
249
+ end
250
+
251
+ private
252
+
253
+ # load the correct header type from the file
254
+ # @api private
255
+ def load_header(io)
256
+ ident = pread(Types::Elf_Ident, 0)
257
+ raise InvalidFormat, "Invalid ELF ident: %p" % ident unless
258
+ ident.ei_magic == Types::ELFMAGIC
259
+ endian = case ident.ei_data
260
+ when :lsb
261
+ :little
262
+ when :msb
263
+ :big
264
+ else
265
+ raise "Invalid EI_DATA value in ELF header ident: %p", @header
266
+ end
267
+
268
+ case ident.ei_class
269
+ when Types::ELFCLASS32
270
+ pread(Types::Elf32_Ehdr.with_endian(endian), 0)
271
+ when Types::ELFCLASS64
272
+ pread(Types::Elf64_Ehdr.with_endian(endian), 0)
273
+ else
274
+ raise InvalidFormat, "Unsupported ELF ident.ei_class: %p",
275
+ ident
276
+ end
277
+ rescue Dry::Types::ConstraintError
278
+ raise InvalidFormat, "Invalid ELF header"
279
+ end
280
+
281
+ # Return a type & size given a type or size argument; used by pread()
282
+ # @api private
283
+ def parse_type(type)
284
+ case type
285
+ when Integer
286
+ [nil, type] # just a plain old size
287
+ when CTypes::Type
288
+ [type, type.size]
289
+ when ::Symbol
290
+ # This is an elf type we'll need to conver to the correct size
291
+ type = elf_type(type)
292
+ [type, type.size]
293
+ when Array
294
+ if type.size != 2
295
+ raise Error, "array type must be of the form `[type, count]`; got %p" %
296
+ [type]
297
+ end
298
+
299
+ type, count = type
300
+ inner, _ = parse_type(type)
301
+ type = CTypes::Helpers
302
+ .array(inner, count).with_endian(inner.default_endian)
303
+ [type, type.size]
304
+ else
305
+ raise Error, "unsupported type: %p" % [type]
306
+ end
307
+ end
308
+ end
309
+ end
310
+
311
+ require_relative "section"
312
+ require_relative "segment"
@@ -0,0 +1,77 @@
1
+ module ElfUtils
2
+ class Section::Base
3
+ def initialize(file, header)
4
+ @file = file
5
+ @header = header
6
+ @offset = 0
7
+ end
8
+ attr_reader :header
9
+
10
+ def name
11
+ @file.shstrtab[@header.sh_name]
12
+ end
13
+
14
+ def inspect
15
+ "<%p %s %p 0x%08x %p>" % [self.class, name, @header.sh_type, addr, flags]
16
+ end
17
+
18
+ def bytes
19
+ @file.pread(@header.sh_size, @header.sh_offset)
20
+ end
21
+
22
+ def addr
23
+ @header.sh_addr + @offset
24
+ end
25
+
26
+ def size
27
+ @header.sh_size
28
+ end
29
+
30
+ def flags
31
+ @header.sh_flags
32
+ end
33
+
34
+ def offset
35
+ @header.sh_offset
36
+ end
37
+
38
+ def alloc?
39
+ flags.include?(:alloc)
40
+ end
41
+
42
+ def to_range
43
+ (addr...addr + size)
44
+ end
45
+
46
+ def symbols
47
+ @file.symtab.symbols.select { |s| s.section == self }
48
+ end
49
+
50
+ def symbol(name)
51
+ @file.symtab.symbols.find { |s| s.section == self && s.name == name }
52
+ end
53
+
54
+ # relocate this section
55
+ #
56
+ # @param addr address for section; `nil` clears relocation
57
+ def relocate(addr, relative: false)
58
+ @offset = if addr.nil?
59
+ 0
60
+ elsif relative
61
+ addr
62
+ else
63
+ addr - @header.sh_addr
64
+ end
65
+ end
66
+
67
+ # return the relocation offset for this section
68
+ def relocation_offset
69
+ @offset
70
+ end
71
+
72
+ # get the load_segment this section belongs to
73
+ def load_segment
74
+ @file.load_segments.find { |s| s.sections.include?(self) }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,171 @@
1
+ require "forwardable"
2
+
3
+ module ElfUtils
4
+ class Section::DebugAbbrev::Abbreviation
5
+ extend Forwardable
6
+
7
+ class UnsupportedDwarfAttributeFormError < ElfUtils::Error; end
8
+
9
+ def initialize(table, decl)
10
+ @table = table
11
+ @decl = decl
12
+ end
13
+ attr_reader :decl
14
+ def_delegators :@decl, :code, :tag
15
+
16
+ def pretty_print(q)
17
+ q.group(4,
18
+ "<%p [%d] DW_TAG_%s DW_CHILDREN_%s" %
19
+ [self.class, code, tag, children? ? "yes" : "no"],
20
+ ">") do
21
+ q.text("\n" + " " * q.indent)
22
+ q.seplist(@decl.attribute_specifications) do |attr|
23
+ q.text("DW_AT_%-15s DW_FORM_%s" % [attr.name, attr.form])
24
+ end
25
+ end
26
+ end
27
+ alias_method :inspect, :pretty_inspect
28
+
29
+ def children?
30
+ @decl.children == 1
31
+ end
32
+
33
+ def attrs
34
+ @attrs ||= begin
35
+ attrs = {}
36
+ @decl.attribute_specifications.each do |attr|
37
+ attrs[attr.name] = attr.form
38
+ end
39
+ attrs.freeze
40
+ end
41
+ end
42
+
43
+ def die_size(addr_size:, offset_size:)
44
+ return nil if @variable_size
45
+
46
+ size = 0
47
+ @decl.attribute_specifications.each do |attr|
48
+ if (s = form_size(form: attr.form, addr_size:, offset_size:))
49
+ size += s
50
+ else
51
+ @variable_size = true
52
+ return nil
53
+ end
54
+ end
55
+ size
56
+ end
57
+
58
+ def variable_sized_attrs(addr_size:, offset_size:)
59
+ out = []
60
+ @decl.attribute_specifications.each do |attr|
61
+ next if form_size(form: attr.form, addr_size:, offset_size:)
62
+ out << attr
63
+ end
64
+ out
65
+ end
66
+
67
+ def unpack_die(buf:, endian:, offset_type:, addr_type:)
68
+ out = []
69
+ @decl.attribute_specifications.each do |attr|
70
+ value, buf = case attr.form
71
+ when :addr
72
+ addr_type.unpack_one(buf, endian: endian)
73
+ when :strp, :sec_offset
74
+ offset_type.unpack_one(buf, endian: endian)
75
+ when :data1, :flag, :ref1, :strx1
76
+ CTypes::UInt8.unpack_one(buf, endian: endian)
77
+ when :data2, :ref2
78
+ CTypes::UInt16.unpack_one(buf, endian: endian)
79
+ when :data4, :ref4
80
+ CTypes::UInt32.unpack_one(buf, endian: endian)
81
+ when :data8, :ref8
82
+ CTypes::UInt64.unpack_one(buf, endian: endian)
83
+ when :udata, :addrx
84
+ Types::ULEB128.unpack_one(buf)
85
+ when :sdata
86
+ Types::SLEB128.unpack_one(buf)
87
+ when :string
88
+ CTypes::String.terminated.unpack_one(buf, endian: endian)
89
+ when :block1
90
+ len, buf = CTypes::UInt8.unpack_one(buf, endian: endian)
91
+ [buf.byteslice(0, len), buf.byteslice(len..)]
92
+ when :block2
93
+ len, buf = CTypes::UInt16.unpack_one(buf, endian: endian)
94
+ [buf.byteslice(0, len), buf.byteslice(len..)]
95
+ when :exprloc
96
+ len, buf = Types::ULEB128.unpack_one(buf)
97
+ [buf.byteslice(0, len), buf.byteslice(len..)]
98
+ when :loclistx
99
+ Types::ULEB128.unpack_one(buf)
100
+ when :flag_present
101
+ [1, buf]
102
+ else
103
+ raise UnsupportedDwarfAttributeFormError,
104
+ "unsupported DWARF form: %p" % [attr.form]
105
+ end
106
+
107
+ out << [attr.name, attr.form, value]
108
+ end
109
+ [out, buf]
110
+ end
111
+
112
+ private
113
+
114
+ # return the size of a given dwarf attribute form
115
+ def form_size(form:, addr_size:, offset_size:)
116
+ case form
117
+ when :addr
118
+ addr_size
119
+ when :data1, :ref1
120
+ 1
121
+ when :data2, :ref2
122
+ 2
123
+ when :data4, :ref4
124
+ 4
125
+ when :data8, :ref8
126
+ 8
127
+ when :flag
128
+ 1
129
+ when :strp
130
+ offset_size
131
+ when :ref_addr
132
+ addr_size
133
+ when :sec_offset
134
+ offset_size
135
+ when :flag_present
136
+ 0
137
+ when :ref_sup4
138
+ 4
139
+ when :strp_sup
140
+ offset_size
141
+ when :data16
142
+ 16
143
+ when :line_strp
144
+ offset_size
145
+ when :ref_sig8
146
+ 8
147
+ when :ref_sup8
148
+ 8
149
+ when :strx1
150
+ 1
151
+ when :strx2
152
+ 2
153
+ when :strx3
154
+ 3
155
+ when :strx4
156
+ 4
157
+ when :addrx1
158
+ 1
159
+ when :addrx2
160
+ 2
161
+ when :addrx3
162
+ 3
163
+ when :addrx4
164
+ 4
165
+ else
166
+ # variable sized
167
+ nil
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,27 @@
1
+ class ElfUtils::Section::DebugAbbrev
2
+ class AbbreviationTable
3
+ def initialize(buf)
4
+ @declarations = {}
5
+ @abbrevs = {}
6
+
7
+ # XXX CTypes needs a terminated array where we check the remaining bytes
8
+ # before decoding.
9
+ until buf[0].ord == 0
10
+ decl, buf = ElfUtils::Types::Dwarf::AbbreviationDeclaration.unpack_one(buf)
11
+ @declarations[decl.code] = decl
12
+ @abbrevs[decl.code] = Abbreviation.new(self, decl)
13
+ end
14
+ end
15
+ attr_reader :declarations
16
+
17
+ def [](code)
18
+ @abbrevs[code]
19
+ end
20
+
21
+ def entries
22
+ @abbrevs.values
23
+ end
24
+ end
25
+ end
26
+
27
+ require_relative "abbreviation"
@@ -0,0 +1,15 @@
1
+ module ElfUtils
2
+ class Section::DebugAbbrev < Section::Base
3
+ def initialize(*args)
4
+ super
5
+ @abbreviation_tables = {}
6
+ end
7
+
8
+ def abbreviation_table(offset)
9
+ @abbreviation_tables[offset] ||= AbbreviationTable.new(bytes[offset..])
10
+ end
11
+ end
12
+ end
13
+
14
+ require_relative "debug_abbrev/abbreviation_table"
15
+ require_relative "debug_abbrev/abbreviation"
@@ -0,0 +1,9 @@
1
+ module ElfUtils
2
+ class Section::DebugAddr < Section::Base
3
+ def get(base:, index:)
4
+ @addr_type ||= @file.elf_type(:Addr)
5
+ offset = base + index * @addr_type.size
6
+ @addr_type.unpack(bytes[offset..])
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,54 @@
1
+ module ElfUtils
2
+ class Section::DebugArange < Section::Base
3
+ extend CTypes::Helpers
4
+
5
+ Tuple32 = struct(addr: uint32, size: uint32)
6
+ Tuple64 = struct(addr: uint64, size: uint64)
7
+
8
+ class Entry < CTypes::Struct
9
+ layout do
10
+ attribute :length, uint32
11
+ attribute :version, uint16
12
+ attribute :debug_info_offset, uint32
13
+ attribute :addr_size, uint8
14
+ attribute :seg_desc_size, uint8
15
+ pad 4 # XXX not needed for dwarf64
16
+ attribute :tuple_bytes, string(trim: false)
17
+ size { |hdr| offsetof(:version) + hdr[:length] }
18
+ end
19
+
20
+ def tuples
21
+ @tuples ||= begin
22
+ type = (addr_size == 4) ? Tuple32 : Tuple64
23
+ type.unpack_all(self[:tuple_data], endian: @file.endian)
24
+ end
25
+ end
26
+ end
27
+
28
+ def entries
29
+ @entries ||= Entry.unpack_all(bytes, endian: @file.endian)
30
+ end
31
+
32
+ def ranges
33
+ @ranges ||= begin
34
+ ranges = []
35
+ entries.each do |entry|
36
+ tuple_type = (entry.addr_size == 4) ? Tuple32 : Tuple64
37
+ tuple_type
38
+ .unpack_all(entry.tuple_bytes, endian: @file.endian)
39
+ .each do |tuple|
40
+ next if tuple.addr == 0 && tuple.size == 0
41
+ range = tuple.addr...(tuple.addr + tuple.size)
42
+ ranges << [range, entry.debug_info_offset]
43
+ end
44
+ end
45
+ ranges
46
+ end
47
+ end
48
+
49
+ def cu_offset(addr)
50
+ ranges.each { |range, offset| return offset if range.include?(addr) }
51
+ nil
52
+ end
53
+ end
54
+ end