fisk 2.3.0 → 2.3.2
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 +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
|