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 +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
|