fisk 2.3.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
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