From 4c9870c1281ded09e1ad7f79b18b8a792dec2d9f Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Wed, 5 May 2021 21:55:25 +0200 Subject: [PATCH 1/9] POC readable enums --- lib/elftools/enums.rb | 71 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/elftools/enums.rb diff --git a/lib/elftools/enums.rb b/lib/elftools/enums.rb new file mode 100644 index 0000000..431d9a9 --- /dev/null +++ b/lib/elftools/enums.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# copyright https://stackoverflow.com/questions/75759/how-to-implement-enums-in-ruby +# +class Enum + def self.enum_attr(name, num) + name = name.to_s + + define_method("#{name}?") do + if self.class.exclusive? + @attrs == num + else + @attrs & num != 0 + end + end + + define_method("#{name}=") do |set| + if set + @attrs |= num + else + @attrs &= ~num + end + end + + define_singleton_method(name.upcase.to_sym) do + new(num) + end + + @values ||= {} + @values[num] = name + end + + def self.exclusive(enabled) + @exclusive = enabled + end + + def self.exclusive? + @exclusive + end + + class << self + attr_reader :values + end + + def initialize(attrs = 0) + @attrs = + if self.class.values.keys.include?(attrs) + attrs + else + self.class.values.key(attrs.to_s.downcase) + end + throw ArgumentError.new("Uknown enum #{attrs}") unless @attrs + end + + def to_i + @attrs + end + + def to_s + self.class.values[@attrs] || @attrs.to_s + end + + def inspect + v = self.class.values[@attrs] + v ? "#{self.class.name}.#{v.upcase}" : @attrs + end + + def ==(other) + to_i == other.to_i + end +end From a5785ac782bbe15fcb51854952334d94ec873194 Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Wed, 5 May 2021 21:55:51 +0200 Subject: [PATCH 2/9] Use enums with symbols --- lib/elftools/sections/sym_tab_section.rb | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/lib/elftools/sections/sym_tab_section.rb b/lib/elftools/sections/sym_tab_section.rb index 9c5fea0..bcccc88 100644 --- a/lib/elftools/sections/sym_tab_section.rb +++ b/lib/elftools/sections/sym_tab_section.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'elftools/sections/section' +require 'elftools/enums' module ELFTools module Sections @@ -103,6 +104,45 @@ class Symbol attr_reader :header # @return [ELFTools::Structs::ELF32_sym, ELFTools::Structs::ELF64_sym] Section header. attr_reader :stream # @return [#pos=, #read] Streaming object. + # based on https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-79797.html + class Bind < Enum + exclusive true + enum_attr :local, 0 + enum_attr :global, 1 + enum_attr :weak, 2 + enum_attr :loos, 10 + enum_attr :hios, 12 + enum_attr :loproc, 13 + enum_attr :hiproc, 15 + end + + class Type < Enum + exclusive true + enum_attr :notype, 0 + enum_attr :object, 1 + enum_attr :func, 2 + enum_attr :section, 3 + enum_attr :file, 4 + enum_attr :common, 5 + enum_attr :tls, 6 + enum_attr :loos, 10 + enum_attr :hios, 12 + enum_attr :sparc_register, 13 + enum_attr :loproc, 13 + enum_attr :hiproc, 15 + end + + class Visibility < Enum + exclusive true + enum_attr :default, 0 + enum_attr :internal, 1 + enum_attr :hidden, 2 + enum_attr :protected, 3 + enum_attr :exported, 4 + enum_attr :singleton, 5 + enum_attr :eliminate, 6 + end + # Instantiate a {ELFTools::Sections::Symbol} object. # @param [ELFTools::Structs::ELF32_sym, ELFTools::Structs::ELF64_sym] header # The symbol header. @@ -122,6 +162,42 @@ def initialize(header, stream, symstr: nil) def name @name ||= @symstr.call.name_at(header.st_name) end + + # Return the symbol bind property. + # @return [Symbol::Bind] Bind property. + def st_bind + Bind.new(header.st_info >> 4) + end + + # Updates the symbol bind property. Stored in header's st_info high bits. + # @param [Symbol::Bind, Integer] bind Bind property. + def st_bind=(bind) + header.st_info = (header.st_info & 0xf) | (bind.to_i << 4) + end + + # Return the symbol type property. + # @return [Symbol::Type] type Type property. + def st_type + Type.new(header.st_info & 0xf) + end + + # Updates the symbol type property. Stored in header's st_info low bits. + # @param [Symbol::Type, Integer] type Type property. + def st_type=(type) + header.st_info = (header.st_info & (~0xf)) | (type.to_i & 0xf) + end + + # Return the symbol visibility property. + # @return [Symbol::Visibility] vis Visibility property. + def st_vis + Visibility.new(header.st_other & 0x7) + end + + # Updates the symbol visibility property. Stored in header's st_other. + # @param [Symbol::Visibility, Integer] vis Visibility property. + def st_vis=(vis) + header.st_other = vis.to_i & 0x7 + end end end end From 2c41b4a67874f79c33127b43f80597f8984f7457 Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Wed, 5 May 2021 22:01:30 +0200 Subject: [PATCH 3/9] Use enums with relocations --- lib/elftools/sections/relocation_section.rb | 76 ++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/elftools/sections/relocation_section.rb b/lib/elftools/sections/relocation_section.rb index a87780b..8f56573 100644 --- a/lib/elftools/sections/relocation_section.rb +++ b/lib/elftools/sections/relocation_section.rb @@ -3,6 +3,7 @@ require 'elftools/constants' require 'elftools/sections/section' require 'elftools/structs' +require 'elftools/enums' module ELFTools module Sections @@ -78,6 +79,58 @@ class Relocation attr_reader :header # @return [ELFTools::Structs::ELF_Rel, ELFTools::Structs::ELF_Rela] Rel(a) header. attr_reader :stream # @return [#pos=, #read] Streaming object. + class Relocation32 < Enum + exclusive true + enum_attr :none, 0 + enum_attr :"32", 1 + enum_attr :pc32, 2 + enum_attr :got32, 3 + enum_attr :plt32, 4 + enum_attr :copy, 5 + enum_attr :glob_dat, 6 + enum_attr :jmp_slot, 7 + enum_attr :relative, 8 + enum_attr :gotoff, 9 + enum_attr :gotpc, 10 + enum_attr :"32plt", 11 + enum_attr :"16", 20 + enum_attr :pc16, 21 + enum_attr :"8", 22 + enum_attr :pc8, 23 + enum_attr :size32, 38 + end + + class Relocation64 < Enum + exclusive true + enum_attr :none, 0 + enum_attr :"64", 1 + enum_attr :pc32, 2 + enum_attr :got32, 3 + enum_attr :plt32, 4 + enum_attr :copy, 5 + enum_attr :glob_dat, 6 + enum_attr :jump_slot, 7 + enum_attr :relative, 8 + enum_attr :gotpcrel, 9 + enum_attr :"32", 10 + enum_attr :"32s", 11 + enum_attr :"16", 12 + enum_attr :pc16, 13 + enum_attr :"8", 14 + enum_attr :pc8, 15 + enum_attr :pc64, 24 + enum_attr :gotoff64, 25 + enum_attr :gotpc32, 26 + enum_attr :size32, 32 + enum_attr :size64, 33 + end + + # Hash containing x86 relocation types class depending on elf_class + RELOCATION_ARCH = { + 32 => Relocation32, + 64 => Relocation64 + }.freeze + # Instantiate a {Relocation} object. def initialize(header, stream) @header = header @@ -100,8 +153,29 @@ def r_info_type end alias type r_info_type - private + # Convenience method returning relocation type wrapped in an Relocation Enum type. + # @param [Integer] bits Use {bits} x86 arch instead of elf_class to parse type enum. + # @return [Relocation32, Relocation64] relocation type enum + def type_enum(bits = header.elf_class) + RELOCATION_ARCH[bits].new(type) + end + + # Update relocation type. + # @param [String, Relocation64, RElocation32, Integer] type Relocation type + def type=(type) + type = RELOCATION_ARCH[header.elf_class].new(type) if type.is_a? String + mask = (1 << mask_bit) - 1 + header.r_info = (header.r_info & (~mask)) | (type.to_i & mask) + end + # Update relocation symbol index. + # @param [Integer] ind symbol index. + def symbol_index=(ind) + mask = (1 << mask_bit) - 1 + header.r_info = (ind << mask_bit) | (header.r_info & mask) + end + + private def mask_bit header.elf_class == 32 ? 8 : 32 end From e7b5099109f2f5fc317073acc2a363e383320372 Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Wed, 5 May 2021 22:09:45 +0200 Subject: [PATCH 4/9] Fix rubocop --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 6dfd3fa..142621e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -41,3 +41,6 @@ Style/AccessorGrouping: Style/FormatStringToken: Enabled: false + +Style/TrivialAccessors: + AllowDSLWriters: true From ab10af09d63654adc49e67c43b224a44230877ba Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Fri, 7 May 2021 20:01:45 +0200 Subject: [PATCH 5/9] Fix style --- lib/elftools/enums.rb | 32 ++++++++++++--------- lib/elftools/sections/relocation_section.rb | 1 + 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/elftools/enums.rb b/lib/elftools/enums.rb index 431d9a9..f9af836 100644 --- a/lib/elftools/enums.rb +++ b/lib/elftools/enums.rb @@ -30,26 +30,32 @@ def self.enum_attr(name, num) @values[num] = name end - def self.exclusive(enabled) - @exclusive = enabled - end - - def self.exclusive? - @exclusive - end - class << self attr_reader :values + + def exclusive? + @exclusive + end + + private + + def exclusive(enabled) + @exclusive = enabled + end end - def initialize(attrs = 0) + # Initialize enum with value or name + # @param [Integer, Symbol] n Value or name. + # @return [Enum] + # Throws ArgumentError if enum name or value is invalid. + def initialize(value = 0) @attrs = - if self.class.values.keys.include?(attrs) - attrs + if self.class.values.keys.include?(value) + value else - self.class.values.key(attrs.to_s.downcase) + self.class.values.key(value.to_s.downcase) end - throw ArgumentError.new("Uknown enum #{attrs}") unless @attrs + throw ArgumentError.new("Uknown enum #{value}") unless @attrs end def to_i diff --git a/lib/elftools/sections/relocation_section.rb b/lib/elftools/sections/relocation_section.rb index 8f56573..3413cf0 100644 --- a/lib/elftools/sections/relocation_section.rb +++ b/lib/elftools/sections/relocation_section.rb @@ -176,6 +176,7 @@ def symbol_index=(ind) end private + def mask_bit header.elf_class == 32 ? 8 : 32 end From 473936a0b5f58dcc96cfd986fd8226f41e5116ec Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Fri, 7 May 2021 20:17:02 +0200 Subject: [PATCH 6/9] Remove exclusive --- lib/elftools/enums.rb | 24 +-------------------- lib/elftools/sections/relocation_section.rb | 2 -- lib/elftools/sections/sym_tab_section.rb | 3 --- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/lib/elftools/enums.rb b/lib/elftools/enums.rb index f9af836..419b974 100644 --- a/lib/elftools/enums.rb +++ b/lib/elftools/enums.rb @@ -7,19 +7,7 @@ def self.enum_attr(name, num) name = name.to_s define_method("#{name}?") do - if self.class.exclusive? - @attrs == num - else - @attrs & num != 0 - end - end - - define_method("#{name}=") do |set| - if set - @attrs |= num - else - @attrs &= ~num - end + @attrs == num end define_singleton_method(name.upcase.to_sym) do @@ -32,16 +20,6 @@ def self.enum_attr(name, num) class << self attr_reader :values - - def exclusive? - @exclusive - end - - private - - def exclusive(enabled) - @exclusive = enabled - end end # Initialize enum with value or name diff --git a/lib/elftools/sections/relocation_section.rb b/lib/elftools/sections/relocation_section.rb index 3413cf0..a41aacf 100644 --- a/lib/elftools/sections/relocation_section.rb +++ b/lib/elftools/sections/relocation_section.rb @@ -80,7 +80,6 @@ class Relocation attr_reader :stream # @return [#pos=, #read] Streaming object. class Relocation32 < Enum - exclusive true enum_attr :none, 0 enum_attr :"32", 1 enum_attr :pc32, 2 @@ -101,7 +100,6 @@ class Relocation32 < Enum end class Relocation64 < Enum - exclusive true enum_attr :none, 0 enum_attr :"64", 1 enum_attr :pc32, 2 diff --git a/lib/elftools/sections/sym_tab_section.rb b/lib/elftools/sections/sym_tab_section.rb index bcccc88..6824462 100644 --- a/lib/elftools/sections/sym_tab_section.rb +++ b/lib/elftools/sections/sym_tab_section.rb @@ -106,7 +106,6 @@ class Symbol # based on https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-79797.html class Bind < Enum - exclusive true enum_attr :local, 0 enum_attr :global, 1 enum_attr :weak, 2 @@ -117,7 +116,6 @@ class Bind < Enum end class Type < Enum - exclusive true enum_attr :notype, 0 enum_attr :object, 1 enum_attr :func, 2 @@ -133,7 +131,6 @@ class Type < Enum end class Visibility < Enum - exclusive true enum_attr :default, 0 enum_attr :internal, 1 enum_attr :hidden, 2 From 92e143a042f26c263615f34b42bc67c2f1f8d8dd Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Fri, 7 May 2021 20:31:10 +0200 Subject: [PATCH 7/9] Fix style --- lib/elftools/enums.rb | 14 +++++++------- lib/elftools/sections/relocation_section.rb | 7 ++++--- lib/elftools/sections/sym_tab_section.rb | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/elftools/enums.rb b/lib/elftools/enums.rb index 419b974..e1d68c0 100644 --- a/lib/elftools/enums.rb +++ b/lib/elftools/enums.rb @@ -7,7 +7,7 @@ def self.enum_attr(name, num) name = name.to_s define_method("#{name}?") do - @attrs == num + @value == num end define_singleton_method(name.upcase.to_sym) do @@ -27,26 +27,26 @@ class << self # @return [Enum] # Throws ArgumentError if enum name or value is invalid. def initialize(value = 0) - @attrs = + @value = if self.class.values.keys.include?(value) value else self.class.values.key(value.to_s.downcase) end - throw ArgumentError.new("Uknown enum #{value}") unless @attrs + raise ArgumentError.new("Unknown enum #{value}") unless @value end def to_i - @attrs + @value end def to_s - self.class.values[@attrs] || @attrs.to_s + self.class.values[@value] || @value.to_s end def inspect - v = self.class.values[@attrs] - v ? "#{self.class.name}.#{v.upcase}" : @attrs + v = self.class.values[@value] + v ? "#{self.class.name}.#{v.upcase}" : @value end def ==(other) diff --git a/lib/elftools/sections/relocation_section.rb b/lib/elftools/sections/relocation_section.rb index a41aacf..ea84ff7 100644 --- a/lib/elftools/sections/relocation_section.rb +++ b/lib/elftools/sections/relocation_section.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require 'elftools/constants' +require 'elftools/enums' require 'elftools/sections/section' require 'elftools/structs' -require 'elftools/enums' module ELFTools module Sections @@ -159,12 +159,13 @@ def type_enum(bits = header.elf_class) end # Update relocation type. - # @param [String, Relocation64, RElocation32, Integer] type Relocation type - def type=(type) + # @param [String, Relocation64, Relocation32, Integer] type Relocation type + def r_info_type=(type) type = RELOCATION_ARCH[header.elf_class].new(type) if type.is_a? String mask = (1 << mask_bit) - 1 header.r_info = (header.r_info & (~mask)) | (type.to_i & mask) end + alias type= r_info_type= # Update relocation symbol index. # @param [Integer] ind symbol index. diff --git a/lib/elftools/sections/sym_tab_section.rb b/lib/elftools/sections/sym_tab_section.rb index 6824462..bc3651f 100644 --- a/lib/elftools/sections/sym_tab_section.rb +++ b/lib/elftools/sections/sym_tab_section.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'elftools/sections/section' require 'elftools/enums' +require 'elftools/sections/section' module ELFTools module Sections From fe52ad3484ac5add4c4e804a954867f26e122435 Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Fri, 7 May 2021 21:22:09 +0200 Subject: [PATCH 8/9] Enums refactor Allow usages: ``` Bind::LOCAL Bind.LOCAL Bind[:local] ``` Allow comparisons: ``` b == 0 b == 'local' b == :local b == Bind::LOCAL ``` --- lib/elftools/enums.rb | 30 ++++++++++++++++++++++-------- spec/enums_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 spec/enums_spec.rb diff --git a/lib/elftools/enums.rb b/lib/elftools/enums.rb index e1d68c0..e8b6148 100644 --- a/lib/elftools/enums.rb +++ b/lib/elftools/enums.rb @@ -6,20 +6,32 @@ class Enum def self.enum_attr(name, num) name = name.to_s + @values ||= {} + @values[num] = name + define_method("#{name}?") do @value == num end - define_singleton_method(name.upcase.to_sym) do - new(num) - end + instance = new(num) - @values ||= {} - @values[num] = name + @instances ||= {} + @instances[num] = instance + @instances[name] = instance + @instances[name.to_s.upcase] = instance + + define_singleton_method(name.upcase.to_sym) { instance } + const_set(name.upcase.to_sym, instance) if name.match?(/^[[:alpha:]]/) end class << self attr_reader :values + + def [](value) + value = value.to_i if value.is_a? self.class + key = value.is_a?(Numeric) ? value : value.to_s.upcase + @instances[key] + end end # Initialize enum with value or name @@ -28,12 +40,14 @@ class << self # Throws ArgumentError if enum name or value is invalid. def initialize(value = 0) @value = - if self.class.values.keys.include?(value) + if value.is_a? self.class + value.to_i + elsif self.class.values.keys.include?(value) value else self.class.values.key(value.to_s.downcase) end - raise ArgumentError.new("Unknown enum #{value}") unless @value + raise ArgumentError, "Unknown enum #{value}" unless @value end def to_i @@ -50,6 +64,6 @@ def inspect end def ==(other) - to_i == other.to_i + to_i == self.class[other].to_i end end diff --git a/spec/enums_spec.rb b/spec/enums_spec.rb new file mode 100644 index 0000000..934f3da --- /dev/null +++ b/spec/enums_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'elftools/enums' + +class Numbers < Enum + enum_attr :first, 1 + enum_attr :second, 2 + enum_attr :third, 3 +end + +describe Enum do + it 'constructs' do + n = Numbers + expect(n::FIRST).to eq 1 + expect(n.SECOND).to eq 2 + expect(n[:third]).to eq 3 + expect(n.new('third')).to eq 3 + end + + it 'compares' do + n = Numbers + expect(n::FIRST).to eq n::FIRST + expect(n.SECOND).to eq 2 + expect(n[:third]).to eq 'third' + expect(n.new('third')).to eq :third + end +end From ad9f8e9f06a6946af1e6b55cbd8846a3eb599052 Mon Sep 17 00:00:00 2001 From: zygzagZ Date: Fri, 7 May 2021 21:53:55 +0200 Subject: [PATCH 9/9] Refactor enums to instantiate less objects --- lib/elftools/enums.rb | 31 ++++++++++++++++--------------- spec/enums_spec.rb | 25 +++++++++++++++---------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/elftools/enums.rb b/lib/elftools/enums.rb index e8b6148..af9dd78 100644 --- a/lib/elftools/enums.rb +++ b/lib/elftools/enums.rb @@ -17,8 +17,6 @@ def self.enum_attr(name, num) @instances ||= {} @instances[num] = instance - @instances[name] = instance - @instances[name.to_s.upcase] = instance define_singleton_method(name.upcase.to_sym) { instance } const_set(name.upcase.to_sym, instance) if name.match?(/^[[:alpha:]]/) @@ -27,10 +25,21 @@ def self.enum_attr(name, num) class << self attr_reader :values + def val(value) + key = if value.is_a? self.class + value.to_i + elsif values.keys.include?(value) + value + else + values.key(value.to_s) + end + raise ArgumentError, "Unknown enum #{value}" unless key + + key + end + def [](value) - value = value.to_i if value.is_a? self.class - key = value.is_a?(Numeric) ? value : value.to_s.upcase - @instances[key] + @instances[val(value)] end end @@ -39,15 +48,7 @@ def [](value) # @return [Enum] # Throws ArgumentError if enum name or value is invalid. def initialize(value = 0) - @value = - if value.is_a? self.class - value.to_i - elsif self.class.values.keys.include?(value) - value - else - self.class.values.key(value.to_s.downcase) - end - raise ArgumentError, "Unknown enum #{value}" unless @value + @value = self.class.val(value) end def to_i @@ -64,6 +65,6 @@ def inspect end def ==(other) - to_i == self.class[other].to_i + @value == self.class.val(other) end end diff --git a/spec/enums_spec.rb b/spec/enums_spec.rb index 934f3da..2e38b8f 100644 --- a/spec/enums_spec.rb +++ b/spec/enums_spec.rb @@ -10,18 +10,23 @@ class Numbers < Enum describe Enum do it 'constructs' do - n = Numbers - expect(n::FIRST).to eq 1 - expect(n.SECOND).to eq 2 - expect(n[:third]).to eq 3 - expect(n.new('third')).to eq 3 + expect(Numbers::FIRST).to eq 1 + expect(Numbers[1]).to eq 1 + expect(Numbers.SECOND).to eq 2 + expect(Numbers[:third]).to eq 3 + expect(Numbers.new('third')).to eq 3 + expect { Numbers[:fourth] }.to raise_error(ArgumentError) + expect { Numbers.new('fifth') }.to raise_error(ArgumentError) + expect { Numbers.new(6) }.to raise_error(ArgumentError) + expect { Numbers[7] }.to raise_error(ArgumentError) end it 'compares' do - n = Numbers - expect(n::FIRST).to eq n::FIRST - expect(n.SECOND).to eq 2 - expect(n[:third]).to eq 'third' - expect(n.new('third')).to eq :third + expect(Numbers::FIRST).to eq Numbers::FIRST + expect(Numbers::FIRST).not_to eq 2 + expect(Numbers.SECOND).to eq 2 + expect(Numbers[:third]).to eq 'third' + expect(Numbers[:third]).not_to eq 'first' + expect(Numbers.new('third')).to eq :third end end