fisk 2.3.0 → 2.3.2
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 +59 -13
- data/test/test_fisk.rb +10 -0
- data/test/test_fisk_written_size.rb +6 -1
- data/test/test_performance_check.rb +32 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eff395693048ed439f385f46b091872a7080058a68f9f859db2d8aedf4125573
|
4
|
+
data.tar.gz: a2070197036bb1a45b8451529e01cd17b7dc0f5ec1c4ee15f996c2079085cc8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28f8284e846de0ca14f2fd2a991be01ea79c0e72f96fbfc3b8a6b0c5d6561892ed2046c9cff4391a3cf2b8f38a225b89f8c89046fb9a06a38813e56f7db4825b
|
7
|
+
data.tar.gz: f6f9a5b64ace9e6edc805a63115ead36ea7d19c0c86f809593f9418f524559b044b77b727055cf206c066708cfdc33b453a137ff0416d75abc84bfaf36f969a4
|
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
|
@@ -70,6 +89,10 @@ class Fisk
|
|
70
89
|
|
71
90
|
def register?; true; end
|
72
91
|
|
92
|
+
def integer?; false; end
|
93
|
+
|
94
|
+
def to_i; @value; end
|
95
|
+
|
73
96
|
def to_register
|
74
97
|
self
|
75
98
|
end
|
@@ -104,7 +127,9 @@ class Fisk
|
|
104
127
|
end
|
105
128
|
|
106
129
|
class Temp < Operand
|
107
|
-
|
130
|
+
include OperandSize
|
131
|
+
|
132
|
+
attr_reader :name, :type, :size
|
108
133
|
attr_accessor :register, :start_point, :end_point
|
109
134
|
|
110
135
|
def initialize name, type
|
@@ -112,6 +137,7 @@ class Fisk
|
|
112
137
|
@type = type
|
113
138
|
@start_point = nil
|
114
139
|
@end_point = nil
|
140
|
+
@size = compute_size(type)
|
115
141
|
end
|
116
142
|
|
117
143
|
def temp_register?; true; end
|
@@ -508,9 +534,15 @@ class Fisk
|
|
508
534
|
end
|
509
535
|
end
|
510
536
|
|
511
|
-
|
537
|
+
# params:
|
538
|
+
# :check_performance: Raise a SuboptimalPerformance error on write if suboptimal
|
539
|
+
# instructions are detected.
|
540
|
+
#
|
541
|
+
def initialize check_performance: false
|
512
542
|
@instructions = []
|
513
543
|
@labels = {}
|
544
|
+
@check_performance = check_performance
|
545
|
+
@performance_warnings = []
|
514
546
|
# A set of temp registers recorded as we see them (not at allocation time)
|
515
547
|
@temp_registers = Set.new
|
516
548
|
yield self if block_given?
|
@@ -763,6 +795,8 @@ class Fisk
|
|
763
795
|
|
764
796
|
# Encode all instructions and write them to +buffer+. +buffer+ should be an
|
765
797
|
# IO object.
|
798
|
+
# If the performance check is enabled, a runtime error is raised if suboptimal
|
799
|
+
# instructions are found.
|
766
800
|
def write_to buffer, metadata: {}
|
767
801
|
labels = {}
|
768
802
|
comments = {}
|
@@ -777,7 +811,7 @@ class Fisk
|
|
777
811
|
elsif insn.comment?
|
778
812
|
comments.update({buffer.pos => insn.message}) { |_, *lines| lines.join($/) }
|
779
813
|
elsif insn.lazy?
|
780
|
-
insn
|
814
|
+
write_instruction insn, buffer, labels
|
781
815
|
instructions.unshift(*@instructions)
|
782
816
|
@instructions.clear
|
783
817
|
else
|
@@ -793,19 +827,24 @@ class Fisk
|
|
793
827
|
metadata[:comments] = comments
|
794
828
|
@instructions = backup
|
795
829
|
|
796
|
-
|
830
|
+
if !unresolved.empty?
|
831
|
+
pos = buffer.pos
|
832
|
+
unresolved.each do |req|
|
833
|
+
insn = req.insn
|
834
|
+
buffer.seek req.io_seek_pos, IO::SEEK_SET
|
835
|
+
write_instruction insn, buffer, labels
|
836
|
+
end
|
837
|
+
buffer.seek pos, IO::SEEK_SET
|
838
|
+
end
|
797
839
|
|
798
|
-
|
799
|
-
|
800
|
-
insn = req.insn
|
801
|
-
buffer.seek req.io_seek_pos, IO::SEEK_SET
|
802
|
-
insn.encode buffer, labels
|
840
|
+
if !@performance_warnings.empty?
|
841
|
+
raise Errors::SuboptimalPerformance.new(@performance_warnings)
|
803
842
|
end
|
804
|
-
buffer.seek pos, IO::SEEK_SET
|
805
843
|
|
806
844
|
buffer
|
807
845
|
end
|
808
846
|
|
847
|
+
# If the performance check is enabled, warnings are added to @performance_warnings.
|
809
848
|
def gen_with_insn insns, params
|
810
849
|
forms = insns.forms.find_all do |insn|
|
811
850
|
if insn.operands.length == params.length
|
@@ -826,7 +865,7 @@ class Fisk
|
|
826
865
|
Valid forms:
|
827
866
|
#{valid_forms}
|
828
867
|
eostr
|
829
|
-
raise
|
868
|
+
raise Errors::InvalidInstructionError, msg
|
830
869
|
end
|
831
870
|
|
832
871
|
form = forms.first
|
@@ -857,7 +896,14 @@ class Fisk
|
|
857
896
|
end
|
858
897
|
end
|
859
898
|
|
860
|
-
insn
|
899
|
+
if insn.nil?
|
900
|
+
insn = Instruction.new(insns, form, params)
|
901
|
+
|
902
|
+
if @check_performance
|
903
|
+
warning = insns.check_performance(params)
|
904
|
+
@performance_warnings << warning if warning
|
905
|
+
end
|
906
|
+
end
|
861
907
|
|
862
908
|
@instructions << insn
|
863
909
|
|
data/test/test_fisk.rb
CHANGED
@@ -12,6 +12,10 @@ class FiskTest < Fisk::Test
|
|
12
12
|
assert_predicate Fisk::Registers::RDI, :register?
|
13
13
|
end
|
14
14
|
|
15
|
+
def test_rax_is_not_an_integer
|
16
|
+
refute_predicate Fisk::Registers::RAX, :integer?
|
17
|
+
end
|
18
|
+
|
15
19
|
def test_push_all
|
16
20
|
regs = Fisk::Registers.constants.grep(/^R[A-Z0-9]{1,2}/).find_all { |r|
|
17
21
|
Fisk::Registers.const_get(r).type == "r64"
|
@@ -163,6 +167,12 @@ class FiskTest < Fisk::Test
|
|
163
167
|
assert_equal sprintf("%#02x", expected_pos), i.op_str.to_s
|
164
168
|
end
|
165
169
|
|
170
|
+
def test_invalid_instruction
|
171
|
+
assert_raises(Fisk::Errors::InvalidInstructionError) do
|
172
|
+
fisk.mov(fisk.imm(5), fisk.imm(5))
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
166
176
|
%w{ rax rcx rdx rbx rsp rbp rsi rdi r8 r9 r10 }.each do |reg|
|
167
177
|
define_method "test_#{reg}_to_offset" do
|
168
178
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Patterson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-04-07 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
|
@@ -1342,7 +1343,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1342
1343
|
- !ruby/object:Gem::Version
|
1343
1344
|
version: '0'
|
1344
1345
|
requirements: []
|
1345
|
-
rubygems_version: 3.
|
1346
|
+
rubygems_version: 3.5.0.dev
|
1346
1347
|
signing_key:
|
1347
1348
|
specification_version: 4
|
1348
1349
|
summary: Write assembly in Ruby!
|
@@ -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
|