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.
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.constants.any? { |c| mod.const_get(c) == val }
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
- val = val.to_sym
49
- raise ArgumentError, "No constants in #{module_name} named \"#{val}\"" unless mod.const_defined?(val)
73
+ value = constants_of(mod)[val]
74
+ raise ArgumentError, "No constants in #{module_name} named \"#{val}\"" if value.nil?
50
75
 
51
- mod.const_get(val)
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
- # read until "\x00"
64
- ret = ''
90
+ ret = +''
65
91
  loop do
66
- c = stream.read(1)
67
- return nil if c.nil? # reach EOF
68
- break if c == "\x00"
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 += c
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ELFTools
4
4
  # Current gem version
5
- VERSION = '2.0.0'
5
+ VERSION = '2.1.0'
6
6
  end
@@ -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.0.0
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.0.0/CHANGELOG.md
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