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 +4 -4
- data/.github/workflows/ci.yml +1 -2
- data/README.md +5 -0
- data/bin/instructions.rb.erb +10 -1
- data/lib/fisk/errors.rb +15 -0
- data/lib/fisk/instructions.rb +10 -1
- data/lib/fisk/version.rb +1 -1
- data/lib/fisk.rb +55 -13
- data/test/helper.rb +12 -0
- data/test/test_fisk.rb +6 -0
- data/test/test_fisk_written_size.rb +6 -1
- data/test/test_performance_check.rb +32 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f39cf1256e6c2030ee2f0dee50d91995cb2efff76d457a5f4c9eb25ca543059b
|
4
|
+
data.tar.gz: c4b92832e08b09b3660efeea2f1c3a629053cc4651ce7f224404e3065a7c8640
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8d6c2be54f415b76997a3ffd76ee1f690a7088c17df3a83efb9b6b2830804e6621d89c8b1aeb2b935f954d6efade6d8cc5480bf203078663d1e5a17a75f311c
|
7
|
+
data.tar.gz: e1368e7bab672530a501aea3e201cdd2f72f5083708102d631165b4468df56a842a9a36d8399a901d4c46d750a4c16f195059b2a7868fbc97a216f222f8bada9
|
data/.github/workflows/ci.yml
CHANGED
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`
|
data/bin/instructions.rb.erb
CHANGED
@@ -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
|
data/lib/fisk/instructions.rb
CHANGED
@@ -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
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
799
|
-
|
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
|
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
|
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
|
-
|
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.
|
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-
|
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
|