elftools 2.0.0 → 2.1.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 +4 -4
- data/README.md +82 -0
- data/lib/elftools/constants.rb +44 -0
- data/lib/elftools/dynamic/hash_table.rb +16 -0
- data/lib/elftools/dynamic/symbols.rb +45 -17
- data/lib/elftools/dynamic/versions.rb +87 -0
- data/lib/elftools/dynamic.rb +21 -4
- data/lib/elftools/elf_file.rb +1 -0
- data/lib/elftools/relative_relocations.rb +88 -0
- data/lib/elftools/relocation.rb +50 -0
- data/lib/elftools/sections/relative_relocation_section.rb +46 -0
- data/lib/elftools/sections/section.rb +31 -0
- data/lib/elftools/sections/sections.rb +8 -0
- data/lib/elftools/sections/sym_tab_section.rb +39 -7
- data/lib/elftools/sections/symbol.rb +91 -1
- data/lib/elftools/sections/version_definition_section.rb +40 -0
- data/lib/elftools/sections/version_need_section.rb +40 -0
- data/lib/elftools/sections/version_section.rb +44 -0
- data/lib/elftools/structs.rb +109 -16
- data/lib/elftools/util.rb +68 -11
- data/lib/elftools/version.rb +1 -1
- data/lib/elftools/version_tables.rb +155 -0
- metadata +9 -2
data/lib/elftools/util.rb
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
module ELFTools
|
|
4
4
|
# Define some util methods.
|
|
5
5
|
module Util
|
|
6
|
+
# How many bytes {ClassMethods#cstring} takes from a stream at a time. Long
|
|
7
|
+
# enough that a name is usually read in one, short enough that reading one
|
|
8
|
+
# never reaches far past its end.
|
|
9
|
+
CSTRING_CHUNK = 64
|
|
10
|
+
|
|
6
11
|
# Class methods.
|
|
7
12
|
module ClassMethods
|
|
8
13
|
# Round up the number to be multiple of
|
|
@@ -23,6 +28,26 @@ module ELFTools
|
|
|
23
28
|
(num + n) & ~(n - 1)
|
|
24
29
|
end
|
|
25
30
|
|
|
31
|
+
# Checks a value is one the bits recording it can hold.
|
|
32
|
+
#
|
|
33
|
+
# Several of the values a file records share a byte with others, so a
|
|
34
|
+
# value too large for its bits would be written over its neighbours
|
|
35
|
+
# instead of being rejected.
|
|
36
|
+
# @param [Integer] value The value.
|
|
37
|
+
# @param [Integer] bits How many bits record it.
|
|
38
|
+
# @param [String] name What the value is, for the error to name.
|
|
39
|
+
# @return [Integer] The value.
|
|
40
|
+
# @raise [ArgumentError] If the bits cannot hold it.
|
|
41
|
+
# @example
|
|
42
|
+
# Util.fits!(16, 4, 'Symbol binding')
|
|
43
|
+
# #=> ArgumentError: Symbol binding must be in 0..15, got 16
|
|
44
|
+
def fits!(value, bits, name)
|
|
45
|
+
highest = (1 << bits) - 1
|
|
46
|
+
return value if value.is_a?(Integer) && value.between?(0, highest)
|
|
47
|
+
|
|
48
|
+
raise ArgumentError, format('%s must be in 0..%d, got %p', name, highest, value)
|
|
49
|
+
end
|
|
50
|
+
|
|
26
51
|
# Fetch the correct value from module +mod+.
|
|
27
52
|
#
|
|
28
53
|
# See {ELFTools::ELFFile#segment_by_type} for how to
|
|
@@ -38,20 +63,22 @@ module ELFTools
|
|
|
38
63
|
module_name = mod.name.sub('ELFTools::', '')
|
|
39
64
|
# if val is an integer, check if exists in mod
|
|
40
65
|
if val.is_a?(Integer)
|
|
41
|
-
return val if mod.
|
|
66
|
+
return val if values_of(mod).key?(val)
|
|
42
67
|
|
|
43
68
|
raise ArgumentError, "No constants in #{module_name} is #{val}"
|
|
44
69
|
end
|
|
45
70
|
val = val.to_s.upcase
|
|
46
71
|
prefix = module_name.split('::')[-1]
|
|
47
72
|
val = "#{prefix}_#{val}" unless val.start_with?(prefix)
|
|
48
|
-
|
|
49
|
-
raise ArgumentError, "No constants in #{module_name} named \"#{val}\""
|
|
73
|
+
value = constants_of(mod)[val]
|
|
74
|
+
raise ArgumentError, "No constants in #{module_name} named \"#{val}\"" if value.nil?
|
|
50
75
|
|
|
51
|
-
|
|
76
|
+
value
|
|
52
77
|
end
|
|
53
78
|
|
|
54
79
|
# Read from stream until reach a null-byte.
|
|
80
|
+
#
|
|
81
|
+
# The stream is left just past the null-byte.
|
|
55
82
|
# @param [#pos=, #read] stream Streaming object.
|
|
56
83
|
# @param [Integer] offset Start from here.
|
|
57
84
|
# @return [String] Result string will never contain null byte.
|
|
@@ -60,16 +87,19 @@ module ELFTools
|
|
|
60
87
|
# #=> "\x7FELF\x02\x01\x01"
|
|
61
88
|
def cstring(stream, offset)
|
|
62
89
|
stream.pos = offset
|
|
63
|
-
|
|
64
|
-
ret = ''
|
|
90
|
+
ret = +''
|
|
65
91
|
loop do
|
|
66
|
-
|
|
67
|
-
return nil if
|
|
68
|
-
|
|
92
|
+
chunk = stream.read(CSTRING_CHUNK)
|
|
93
|
+
return nil if chunk.nil? # reach EOF
|
|
94
|
+
|
|
95
|
+
stop = chunk.index("\x00")
|
|
96
|
+
if stop
|
|
97
|
+
stream.pos = offset + ret.bytesize + stop + 1
|
|
98
|
+
return ret << chunk.byteslice(0, stop)
|
|
99
|
+
end
|
|
69
100
|
|
|
70
|
-
ret
|
|
101
|
+
ret << chunk
|
|
71
102
|
end
|
|
72
|
-
ret
|
|
73
103
|
end
|
|
74
104
|
|
|
75
105
|
# Select objects from enumerator with +.type+ property
|
|
@@ -93,6 +123,33 @@ module ELFTools
|
|
|
93
123
|
end
|
|
94
124
|
end
|
|
95
125
|
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# What a module names each of its constants, and what each is worth.
|
|
130
|
+
#
|
|
131
|
+
# Names are compared upcased, which is how a constant keeping the case
|
|
132
|
+
# the ABI wrote it in, +SHT_GNU_verneed+ for one, is found by the name it
|
|
133
|
+
# is asked for. Read once per module, because reading them is more work
|
|
134
|
+
# than the lookup it is for.
|
|
135
|
+
# @param [Module] mod The module.
|
|
136
|
+
# @return [Hash{String => Integer}] The constants, by their upcased name.
|
|
137
|
+
def constants_of(mod)
|
|
138
|
+
(@constants_of ||= {})[mod] ||= mod.constants.each_with_object({}) do |constant, table|
|
|
139
|
+
# A constant still waiting to be autoloaded is left alone, so that
|
|
140
|
+
# asking after one never loads what nothing has asked for.
|
|
141
|
+
next if mod.autoload?(constant)
|
|
142
|
+
|
|
143
|
+
table[constant.to_s.upcase] ||= mod.const_get(constant)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Which values a module names a constant for.
|
|
148
|
+
# @param [Module] mod The module.
|
|
149
|
+
# @return [Hash{Integer => Boolean}] The values, as the keys.
|
|
150
|
+
def values_of(mod)
|
|
151
|
+
(@values_of ||= {})[mod] ||= constants_of(mod).values.to_h { |value| [value, true] }
|
|
152
|
+
end
|
|
96
153
|
end
|
|
97
154
|
extend ClassMethods
|
|
98
155
|
end
|
data/lib/elftools/version.rb
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'elftools/constants'
|
|
4
|
+
require 'elftools/structs'
|
|
5
|
+
|
|
6
|
+
module ELFTools
|
|
7
|
+
# The two tables a file records its versions in.
|
|
8
|
+
#
|
|
9
|
+
# One holds the versions the file needs of the files it is loaded with, the
|
|
10
|
+
# other the versions it defines for what it exports, and a symbol names one
|
|
11
|
+
# of either by the same index. Both are chains, each entry saying how far
|
|
12
|
+
# off the next one is, and both are recorded twice over: the tags point at
|
|
13
|
+
# them, and so do sections.
|
|
14
|
+
class VersionTables
|
|
15
|
+
# The name each index of either table names.
|
|
16
|
+
# @param [Array<ELFTools::VersionTables::Requirement>] requirements The requirements.
|
|
17
|
+
# @param [Array<ELFTools::VersionTables::Definition>] definitions The definitions.
|
|
18
|
+
# @return [Hash{Integer => String}] The names.
|
|
19
|
+
def self.names(requirements, definitions)
|
|
20
|
+
(requirements.flat_map(&:versions) + definitions).to_h { |version| [version.index, version.name] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The version a symbol records, read against the names of the tables.
|
|
24
|
+
# @param [Integer, nil] recorded
|
|
25
|
+
# What the symbol records, which is an index with the highest bit marking
|
|
26
|
+
# a version asked for by name rather than the default one.
|
|
27
|
+
# @param [Hash{Integer => String}] names What {names} answered.
|
|
28
|
+
# @return [ELFTools::VersionTables::Version, nil]
|
|
29
|
+
# The version, +nil+ where the symbol names none: a symbol of the file
|
|
30
|
+
# itself, a symbol of no version at all, and a name nothing records.
|
|
31
|
+
def self.version(recorded, names)
|
|
32
|
+
return if recorded.nil?
|
|
33
|
+
|
|
34
|
+
index = recorded & ~Constants::VER_NDX_HIDDEN
|
|
35
|
+
return if index <= Constants::VER_NDX_GLOBAL
|
|
36
|
+
|
|
37
|
+
name = names[index]
|
|
38
|
+
Version.new(name, index, hidden: !(recorded & Constants::VER_NDX_HIDDEN).zero?) if name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Instantiate a {ELFTools::VersionTables} object.
|
|
42
|
+
# @param [#pos=, #read] stream Streaming object.
|
|
43
|
+
# @param [#name_at] strtab The table the names are recorded in.
|
|
44
|
+
# @param [Symbol] endian +:little+ or +:big+.
|
|
45
|
+
def initialize(stream, strtab, endian:)
|
|
46
|
+
@stream = stream
|
|
47
|
+
@strtab = strtab
|
|
48
|
+
@endian = endian
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Reads the versions a file needs of the files it is loaded with.
|
|
52
|
+
# @param [Integer] at The file offset the table starts at.
|
|
53
|
+
# @param [Integer] count How many entries it has.
|
|
54
|
+
# @return [Array<ELFTools::VersionTables::Requirement>] The requirements.
|
|
55
|
+
def requirements(at, count)
|
|
56
|
+
chain(at, count, Structs::ELF_Verneed, :vn_next) do |need, from|
|
|
57
|
+
versions = chain(from + need.vn_aux.to_i, need.vn_cnt.to_i, Structs::ELF_Vernaux, :vna_next) do |aux, _|
|
|
58
|
+
Version.new(@strtab.name_at(aux.vna_name.to_i), aux.vna_other.to_i)
|
|
59
|
+
end
|
|
60
|
+
Requirement.new(@strtab.name_at(need.vn_file.to_i), versions)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Reads the versions a file defines for what it exports.
|
|
65
|
+
# @param [Integer] at The file offset the table starts at.
|
|
66
|
+
# @param [Integer] count How many entries it has.
|
|
67
|
+
# @return [Array<ELFTools::VersionTables::Definition>] The definitions.
|
|
68
|
+
def definitions(at, count)
|
|
69
|
+
chain(at, count, Structs::ELF_Verdef, :vd_next) do |defn, from|
|
|
70
|
+
names = chain(from + defn.vd_aux.to_i, defn.vd_cnt.to_i, Structs::ELF_Verdaux, :vda_next) do |aux, _|
|
|
71
|
+
@strtab.name_at(aux.vda_name.to_i)
|
|
72
|
+
end
|
|
73
|
+
# The first name is the version's own, the rest are what it descends from.
|
|
74
|
+
Definition.new(names.first, defn.vd_ndx.to_i, names.drop(1),
|
|
75
|
+
base: !(defn.vd_flags.to_i & Constants::VER_FLG_BASE).zero?)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Reads a chain of entries, each saying how far off the one after it is.
|
|
82
|
+
# @return [Array] What the block makes of each entry.
|
|
83
|
+
def chain(at, count, klass, following)
|
|
84
|
+
Array.new(count) do
|
|
85
|
+
entry = klass.new(endian: @endian, offset: at)
|
|
86
|
+
@stream.pos = at
|
|
87
|
+
entry.read(@stream)
|
|
88
|
+
yield(entry, at).tap { at += entry.send(following).to_i }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# A version a file needs or defines.
|
|
93
|
+
class Version
|
|
94
|
+
attr_reader :name # @return [String] The name, +GLIBC_2.2.5+ for instance.
|
|
95
|
+
attr_reader :index # @return [Integer] The index the symbols name it with.
|
|
96
|
+
|
|
97
|
+
# Instantiate a {ELFTools::VersionTables::Version} object.
|
|
98
|
+
# @param [String] name The name.
|
|
99
|
+
# @param [Integer] index The index.
|
|
100
|
+
# @param [Boolean] hidden Whether a symbol binds to it as a version that is not the default.
|
|
101
|
+
def initialize(name, index, hidden: false)
|
|
102
|
+
@name = name
|
|
103
|
+
@index = index
|
|
104
|
+
@hidden = hidden
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Whether the symbol binding to this version binds to something other
|
|
108
|
+
# than the default, which is what more than one version of a name means.
|
|
109
|
+
# @return [Boolean] The answer.
|
|
110
|
+
def hidden?
|
|
111
|
+
@hidden
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# The versions a file needs of one of the files it is loaded with.
|
|
116
|
+
class Requirement
|
|
117
|
+
attr_reader :file # @return [String] The name of the file, +libc.so.6+ for instance.
|
|
118
|
+
attr_reader :versions # @return [Array<ELFTools::VersionTables::Version>] The versions needed of it.
|
|
119
|
+
|
|
120
|
+
# Instantiate a {ELFTools::VersionTables::Requirement} object.
|
|
121
|
+
# @param [String] file The name of the file.
|
|
122
|
+
# @param [Array<ELFTools::VersionTables::Version>] versions The versions.
|
|
123
|
+
def initialize(file, versions)
|
|
124
|
+
@file = file
|
|
125
|
+
@versions = versions
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# A version a file defines for what it exports.
|
|
130
|
+
class Definition
|
|
131
|
+
attr_reader :name # @return [String] The name.
|
|
132
|
+
attr_reader :index # @return [Integer] The index the symbols name it with.
|
|
133
|
+
attr_reader :parents # @return [Array<String>] The names of the versions it descends from.
|
|
134
|
+
|
|
135
|
+
# Instantiate a {ELFTools::VersionTables::Definition} object.
|
|
136
|
+
# @param [String] name The name.
|
|
137
|
+
# @param [Integer] index The index.
|
|
138
|
+
# @param [Array<String>] parents The names it descends from.
|
|
139
|
+
# @param [Boolean] base Whether it names the file rather than a version of it.
|
|
140
|
+
def initialize(name, index, parents, base: false)
|
|
141
|
+
@name = name
|
|
142
|
+
@index = index
|
|
143
|
+
@parents = parents
|
|
144
|
+
@base = base
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Whether this names the file itself rather than a version of it, which
|
|
148
|
+
# the first definition of a file does.
|
|
149
|
+
# @return [Boolean] The answer.
|
|
150
|
+
def base?
|
|
151
|
+
@base
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: elftools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- david942j
|
|
@@ -224,20 +224,26 @@ files:
|
|
|
224
224
|
- lib/elftools/dynamic/string_table.rb
|
|
225
225
|
- lib/elftools/dynamic/symbols.rb
|
|
226
226
|
- lib/elftools/dynamic/tag.rb
|
|
227
|
+
- lib/elftools/dynamic/versions.rb
|
|
227
228
|
- lib/elftools/elf_file.rb
|
|
228
229
|
- lib/elftools/exceptions.rb
|
|
229
230
|
- lib/elftools/lazy_array.rb
|
|
230
231
|
- lib/elftools/note.rb
|
|
232
|
+
- lib/elftools/relative_relocations.rb
|
|
231
233
|
- lib/elftools/relocation.rb
|
|
232
234
|
- lib/elftools/sections/dynamic_section.rb
|
|
233
235
|
- lib/elftools/sections/note_section.rb
|
|
234
236
|
- lib/elftools/sections/null_section.rb
|
|
237
|
+
- lib/elftools/sections/relative_relocation_section.rb
|
|
235
238
|
- lib/elftools/sections/relocation_section.rb
|
|
236
239
|
- lib/elftools/sections/section.rb
|
|
237
240
|
- lib/elftools/sections/sections.rb
|
|
238
241
|
- lib/elftools/sections/str_tab_section.rb
|
|
239
242
|
- lib/elftools/sections/sym_tab_section.rb
|
|
240
243
|
- lib/elftools/sections/symbol.rb
|
|
244
|
+
- lib/elftools/sections/version_definition_section.rb
|
|
245
|
+
- lib/elftools/sections/version_need_section.rb
|
|
246
|
+
- lib/elftools/sections/version_section.rb
|
|
241
247
|
- lib/elftools/segments/dynamic_segment.rb
|
|
242
248
|
- lib/elftools/segments/interp_segment.rb
|
|
243
249
|
- lib/elftools/segments/load_segment.rb
|
|
@@ -247,12 +253,13 @@ files:
|
|
|
247
253
|
- lib/elftools/structs.rb
|
|
248
254
|
- lib/elftools/util.rb
|
|
249
255
|
- lib/elftools/version.rb
|
|
256
|
+
- lib/elftools/version_tables.rb
|
|
250
257
|
homepage: https://github.com/david942j/rbelftools
|
|
251
258
|
licenses:
|
|
252
259
|
- MIT
|
|
253
260
|
metadata:
|
|
254
261
|
rubygems_mfa_required: 'true'
|
|
255
|
-
changelog_uri: https://github.com/david942j/rbelftools/blob/v2.
|
|
262
|
+
changelog_uri: https://github.com/david942j/rbelftools/blob/v2.1.0/CHANGELOG.md
|
|
256
263
|
rdoc_options: []
|
|
257
264
|
require_paths:
|
|
258
265
|
- lib
|