fisk 2.3.0 → 2.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69f19e819ce868069b32edffc8cadcbd3d37a4253fb0686e2533a8cbea7010d5
4
- data.tar.gz: 1ff93558aa1c0d28c14a312e395ae0b8b675a8d0b11c650cc7cf6ee57bc52257
3
+ metadata.gz: f39cf1256e6c2030ee2f0dee50d91995cb2efff76d457a5f4c9eb25ca543059b
4
+ data.tar.gz: c4b92832e08b09b3660efeea2f1c3a629053cc4651ce7f224404e3065a7c8640
5
5
  SHA512:
6
- metadata.gz: b5396abb6b83733b3bb6022f615ed51f6fe8120d557b0162e913dd7e958d01b5864db51f7ac41145e362a711eee25d38915882059dd206e161ca4e3590cf6420
7
- data.tar.gz: d51e976bbc92c2682fe5287fc632618bb031805b0b1ace07f1a71481cc2cd2ae5f69c45fcab83821799cc0939f97b11761d237cfca7c3f6b360df82e1e14aa8f
6
+ metadata.gz: a8d6c2be54f415b76997a3ffd76ee1f690a7088c17df3a83efb9b6b2830804e6621d89c8b1aeb2b935f954d6efade6d8cc5480bf203078663d1e5a17a75f311c
7
+ data.tar.gz: e1368e7bab672530a501aea3e201cdd2f72f5083708102d631165b4468df56a842a9a36d8399a901d4c46d750a4c16f195059b2a7868fbc97a216f222f8bada9
@@ -29,7 +29,6 @@ jobs:
29
29
  uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
30
30
  with:
31
31
  ruby-version: 3.0
32
- - name: Install dependencies
33
- run: bundle install
32
+ bundler-cache: true # 'bundle install' and cache
34
33
  - name: Run tests
35
34
  run: bundle exec rake
data/README.md CHANGED
@@ -171,3 +171,8 @@ Target 0: (ruby) stopped.
171
171
  frame #32: 0x00007fff20530621 libdyld.dylib`start + 1
172
172
  frame #33: 0x00007fff20530621 libdyld.dylib`start + 1
173
173
  ```
174
+
175
+ Note that in order to produce a stack trace like the above, the Ruby binary must include the debugging symbols (otherwise, the `ruby` frames will not be displayed); this is accomplished by specifying the `--ggdb3` compiler flag:
176
+
177
+ - if compiling Ruby from source, use `./configure optflags=-gddb3` in the build process
178
+ - if using a version manager, refer to the help; example for RVM: `optflags="-ggdb3" rvm install 3.0.2 --disable-binary`
@@ -22,7 +22,16 @@ class Fisk
22
22
  end
23
23
 
24
24
  Form = Struct.new(:operands, :encodings)
25
- Instruction = Struct.new(:name, :forms)
25
+ Instruction = Struct.new(:name, :forms) do
26
+ def check_performance(operands)
27
+ case name
28
+ when Instructions::MOV.name
29
+ if operands[0].register? && operands[1].register? && operands[0].name == operands[1].name
30
+ return "MOV with same register (#{operands[0].name})"
31
+ end
32
+ end
33
+ end
34
+ end
26
35
 
27
36
  OPERAND_TYPES = [
28
37
  <%-
data/lib/fisk/errors.rb CHANGED
@@ -3,6 +3,10 @@ class Fisk
3
3
  class Error < StandardError
4
4
  end
5
5
 
6
+ # Raised when an instruction isn't found or can't be generated
7
+ class InvalidInstructionError < Error
8
+ end
9
+
6
10
  # Raised when a temporary register is released more than once
7
11
  class AlreadyReleasedError < Error
8
12
  end
@@ -31,5 +35,16 @@ class Fisk
31
35
  # `put_label` with the same name twice
32
36
  class LabelAlreadyDefined < Error
33
37
  end
38
+
39
+ # Raised when the performance check is enabled, and suboptimal instructions
40
+ # are found.
41
+ class SuboptimalPerformance < Error
42
+ attr_reader :warnings
43
+
44
+ def initialize(warnings)
45
+ super "Suboptimal instructions found!"
46
+ @warnings = warnings
47
+ end
48
+ end
34
49
  end
35
50
  end
@@ -36,7 +36,16 @@ class Fisk
36
36
  end
37
37
 
38
38
  Form = Struct.new(:operands, :encodings)
39
- Instruction = Struct.new(:name, :forms)
39
+ Instruction = Struct.new(:name, :forms) do
40
+ def check_performance(operands)
41
+ case name
42
+ when Instructions::MOV.name
43
+ if operands[0].register? && operands[1].register? && operands[0].name == operands[1].name
44
+ return "MOV with same register (#{operands[0].name})"
45
+ end
46
+ end
47
+ end
48
+ end
40
49
 
41
50
  OPERAND_TYPES = [
42
51
  Operand.new("al", true, true),
data/lib/fisk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Fisk
2
- VERSION = "2.3.0"
2
+ VERSION = "2.3.1"
3
3
  end
data/lib/fisk.rb CHANGED
@@ -20,6 +20,22 @@ class Fisk
20
20
  def absolute_location?; false; end
21
21
  end
22
22
 
23
+ module OperandSize
24
+ def compute_size type
25
+ return 128 if type.start_with?('xmm')
26
+
27
+ bits = type[/^r(\d+)$/, 1]&.to_i
28
+
29
+ if bits.nil?
30
+ raise ArgumentError, "Unexpected register type (#{type}); 'r<bits>' expected"
31
+ elsif bits % 8 != 0
32
+ raise ArgumentError, "Unexpected register size (#{bits}); multiple of 8 expected"
33
+ else
34
+ bits
35
+ end
36
+ end
37
+ end
38
+
23
39
  class Operand
24
40
  include OperandPredicates
25
41
 
@@ -47,12 +63,15 @@ class Fisk
47
63
 
48
64
  module Registers
49
65
  class Register < Operand
50
- attr_reader :name, :type, :value
66
+ include OperandSize
67
+
68
+ attr_reader :name, :type, :value, :size
51
69
 
52
70
  def initialize name, type, value
53
71
  @name = name
54
72
  @type = type
55
73
  @value = value
74
+ @size = compute_size(type)
56
75
  end
57
76
 
58
77
  def works? op
@@ -104,7 +123,9 @@ class Fisk
104
123
  end
105
124
 
106
125
  class Temp < Operand
107
- attr_reader :name, :type
126
+ include OperandSize
127
+
128
+ attr_reader :name, :type, :size
108
129
  attr_accessor :register, :start_point, :end_point
109
130
 
110
131
  def initialize name, type
@@ -112,6 +133,7 @@ class Fisk
112
133
  @type = type
113
134
  @start_point = nil
114
135
  @end_point = nil
136
+ @size = compute_size(type)
115
137
  end
116
138
 
117
139
  def temp_register?; true; end
@@ -508,9 +530,15 @@ class Fisk
508
530
  end
509
531
  end
510
532
 
511
- def initialize
533
+ # params:
534
+ # :check_performance: Raise a SuboptimalPerformance error on write if suboptimal
535
+ # instructions are detected.
536
+ #
537
+ def initialize check_performance: false
512
538
  @instructions = []
513
539
  @labels = {}
540
+ @check_performance = check_performance
541
+ @performance_warnings = []
514
542
  # A set of temp registers recorded as we see them (not at allocation time)
515
543
  @temp_registers = Set.new
516
544
  yield self if block_given?
@@ -763,6 +791,8 @@ class Fisk
763
791
 
764
792
  # Encode all instructions and write them to +buffer+. +buffer+ should be an
765
793
  # IO object.
794
+ # If the performance check is enabled, a runtime error is raised if suboptimal
795
+ # instructions are found.
766
796
  def write_to buffer, metadata: {}
767
797
  labels = {}
768
798
  comments = {}
@@ -777,7 +807,7 @@ class Fisk
777
807
  elsif insn.comment?
778
808
  comments.update({buffer.pos => insn.message}) { |_, *lines| lines.join($/) }
779
809
  elsif insn.lazy?
780
- insn.encode buffer, labels
810
+ write_instruction insn, buffer, labels
781
811
  instructions.unshift(*@instructions)
782
812
  @instructions.clear
783
813
  else
@@ -793,19 +823,24 @@ class Fisk
793
823
  metadata[:comments] = comments
794
824
  @instructions = backup
795
825
 
796
- return if unresolved.empty?
826
+ if !unresolved.empty?
827
+ pos = buffer.pos
828
+ unresolved.each do |req|
829
+ insn = req.insn
830
+ buffer.seek req.io_seek_pos, IO::SEEK_SET
831
+ write_instruction insn, buffer, labels
832
+ end
833
+ buffer.seek pos, IO::SEEK_SET
834
+ end
797
835
 
798
- pos = buffer.pos
799
- unresolved.each do |req|
800
- insn = req.insn
801
- buffer.seek req.io_seek_pos, IO::SEEK_SET
802
- insn.encode buffer, labels
836
+ if !@performance_warnings.empty?
837
+ raise Errors::SuboptimalPerformance.new(@performance_warnings)
803
838
  end
804
- buffer.seek pos, IO::SEEK_SET
805
839
 
806
840
  buffer
807
841
  end
808
842
 
843
+ # If the performance check is enabled, warnings are added to @performance_warnings.
809
844
  def gen_with_insn insns, params
810
845
  forms = insns.forms.find_all do |insn|
811
846
  if insn.operands.length == params.length
@@ -826,7 +861,7 @@ class Fisk
826
861
  Valid forms:
827
862
  #{valid_forms}
828
863
  eostr
829
- raise NotImplementedError, msg
864
+ raise Errors::InvalidInstructionError, msg
830
865
  end
831
866
 
832
867
  form = forms.first
@@ -857,7 +892,14 @@ class Fisk
857
892
  end
858
893
  end
859
894
 
860
- insn ||= Instruction.new(insns, form, params)
895
+ if insn.nil?
896
+ insn = Instruction.new(insns, form, params)
897
+
898
+ if @check_performance
899
+ warning = insns.check_performance(params)
900
+ @performance_warnings << warning if warning
901
+ end
902
+ end
861
903
 
862
904
  @instructions << insn
863
905
 
data/test/helper.rb CHANGED
@@ -4,6 +4,18 @@ require "crabstone"
4
4
  ENV["MT_NO_PLUGINS"] = "1"
5
5
  require "minitest/autorun"
6
6
 
7
+ # https://github.com/bnagy/crabstone/issues/10
8
+ class Crabstone::Binding::Instruction
9
+ class << self
10
+ alias :old_release :release
11
+ end
12
+
13
+ # Squelch error in crabstone
14
+ def self.release obj
15
+ nil
16
+ end
17
+ end
18
+
7
19
  class Fisk::Test < Minitest::Test
8
20
  def print_disasm binary
9
21
  cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64)
data/test/test_fisk.rb CHANGED
@@ -163,6 +163,12 @@ class FiskTest < Fisk::Test
163
163
  assert_equal sprintf("%#02x", expected_pos), i.op_str.to_s
164
164
  end
165
165
 
166
+ def test_invalid_instruction
167
+ assert_raises(Fisk::Errors::InvalidInstructionError) do
168
+ fisk.mov(fisk.imm(5), fisk.imm(5))
169
+ end
170
+ end
171
+
166
172
  %w{ rax rcx rdx rbx rsp rbp rsi rdi r8 r9 r10 }.each do |reg|
167
173
  define_method "test_#{reg}_to_offset" do
168
174
  fisk.lea(fisk.send(reg), fisk.rip(15))
@@ -9,7 +9,12 @@ class Fisk
9
9
  def write_instruction insn, buffer, labels
10
10
  pos = buffer.pos
11
11
  x = insn.encode buffer, labels
12
- raise unless x == buffer.pos - pos
12
+
13
+ if insn.lazy?
14
+ raise unless buffer.pos = pos
15
+ else
16
+ raise unless x == buffer.pos - pos
17
+ end
13
18
  end
14
19
  end
15
20
 
@@ -0,0 +1,32 @@
1
+ require "helper"
2
+
3
+ class Fisk
4
+ class PerformanceCheckTest < Fisk::Test
5
+ include Registers
6
+
7
+ attr_reader :fisk
8
+
9
+ def setup
10
+ super
11
+ @fisk = Fisk.new check_performance: true
12
+ end
13
+
14
+ def test_mov_same_register
15
+ buf = StringIO.new(''.b)
16
+
17
+ fisk.mov R9, R9
18
+ fisk.mov RAX, RAX
19
+
20
+ error = assert_raises Errors::SuboptimalPerformance do
21
+ fisk.write_to buf
22
+ end
23
+
24
+ expected_warnings = [
25
+ "MOV with same register (r9)",
26
+ "MOV with same register (rax)",
27
+ ]
28
+
29
+ assert_equal error.warnings, expected_warnings
30
+ end
31
+ end
32
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fisk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Patterson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-25 00:00:00.000000000 Z
11
+ date: 2021-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -1321,6 +1321,7 @@ files:
1321
1321
  - test/test_cfg.rb
1322
1322
  - test/test_fisk.rb
1323
1323
  - test/test_fisk_written_size.rb
1324
+ - test/test_performance_check.rb
1324
1325
  - test/test_register_allocation.rb
1325
1326
  - test/test_run_fisk.rb
1326
1327
  homepage: https://github.com/tenderlove/fisk
@@ -1351,5 +1352,6 @@ test_files:
1351
1352
  - test/test_cfg.rb
1352
1353
  - test/test_fisk.rb
1353
1354
  - test/test_fisk_written_size.rb
1355
+ - test/test_performance_check.rb
1354
1356
  - test/test_register_allocation.rb
1355
1357
  - test/test_run_fisk.rb