fisk 1.0.0
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 +7 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +118 -0
- data/Rakefile +29 -0
- data/bin/build-machine.rb +11 -0
- data/bin/machine.rb.erb +100 -0
- data/examples/print-string.rb +26 -0
- data/lib/fisk.rb +240 -0
- data/lib/fisk/helpers.rb +73 -0
- data/lib/fisk/machine.rb +35 -0
- data/lib/fisk/machine/encoding.rb +78 -0
- data/lib/fisk/machine/generated.rb +124746 -0
- data/test/helper.rb +8 -0
- data/test/test_fisk.rb +153 -0
- data/test/test_run_fisk.rb +128 -0
- metadata +60 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require "fisk"
|
2
|
+
require "fisk/helpers"
|
3
|
+
|
4
|
+
module Printer
|
5
|
+
fisk = Fisk.new
|
6
|
+
|
7
|
+
jitbuf = Fisk::Helpers.jitbuffer 4096
|
8
|
+
|
9
|
+
str = "fooooooooo\n"
|
10
|
+
wrapper = Fiddle::Pointer[str]
|
11
|
+
|
12
|
+
fisk.asm(jitbuf) do
|
13
|
+
push rbp
|
14
|
+
mov rdi, imm32(1) # File number for stdout
|
15
|
+
mov rsi, imm64(wrapper.to_i) # Address of the char * backing str
|
16
|
+
mov rdx, imm32(str.bytesize) # Number of bytes in the string
|
17
|
+
mov rax, imm32(0x02000004) # write syscall on macOS x86_64
|
18
|
+
syscall
|
19
|
+
pop rbp
|
20
|
+
ret
|
21
|
+
end
|
22
|
+
|
23
|
+
define_singleton_method :print!, &jitbuf.to_function([], Fiddle::TYPE_VOID)
|
24
|
+
end
|
25
|
+
|
26
|
+
Printer.print!
|
data/lib/fisk.rb
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fisk/machine/encoding"
|
4
|
+
require "fisk/machine"
|
5
|
+
|
6
|
+
class Fisk
|
7
|
+
module Registers
|
8
|
+
class Register < Struct.new(:name, :type, :value)
|
9
|
+
def works? type
|
10
|
+
type == self.name || type == self.type
|
11
|
+
end
|
12
|
+
|
13
|
+
def unknown_label?; false; end
|
14
|
+
end
|
15
|
+
|
16
|
+
EAX = Register.new "eax", "r32", 0
|
17
|
+
ECX = Register.new "ecx", "r32", 1
|
18
|
+
EDX = Register.new "edx", "r32", 2
|
19
|
+
EBX = Register.new "ebx", "r32", 3
|
20
|
+
ESP = Register.new "esp", "r32", 4
|
21
|
+
EBP = Register.new "ebp", "r32", 5
|
22
|
+
ESI = Register.new "esi", "r32", 6
|
23
|
+
EDI = Register.new "edi", "r32", 7
|
24
|
+
|
25
|
+
RAX = Register.new "rax", "r64", 0
|
26
|
+
RCX = Register.new "rcx", "r64", 1
|
27
|
+
RDX = Register.new "rdx", "r64", 2
|
28
|
+
RBX = Register.new "rbx", "r64", 3
|
29
|
+
RSP = Register.new "rsp", "r64", 4
|
30
|
+
RBP = Register.new "rbp", "r64", 5
|
31
|
+
RSI = Register.new "rsi", "r64", 6
|
32
|
+
RDI = Register.new "rdi", "r64", 7
|
33
|
+
8.times do |i|
|
34
|
+
i += 8
|
35
|
+
const_set "R#{i}", Register.new("r#{i}", "r64", i)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Operand < Struct.new(:value)
|
40
|
+
def type; value; end
|
41
|
+
def works? type; self.type == type; end
|
42
|
+
def unknown_label?; false; end
|
43
|
+
end
|
44
|
+
|
45
|
+
class M64 < Operand
|
46
|
+
def type
|
47
|
+
"m64"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def m64 x
|
52
|
+
M64.new x
|
53
|
+
end
|
54
|
+
|
55
|
+
class Imm8 < Operand
|
56
|
+
def type
|
57
|
+
"imm8"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Imm32 < Operand
|
62
|
+
def type
|
63
|
+
"imm32"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Imm64 < Operand
|
68
|
+
def type
|
69
|
+
"imm64"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Rel8 < Operand
|
74
|
+
def type
|
75
|
+
"rel8"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Rel32 < Operand
|
80
|
+
def type
|
81
|
+
"rel32"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class MOffs64 < Operand
|
86
|
+
def type
|
87
|
+
"moffs64"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Lit < Operand
|
92
|
+
def type
|
93
|
+
value.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
attr_reader :position
|
98
|
+
|
99
|
+
def initialize
|
100
|
+
@instructions = []
|
101
|
+
@labels = {}
|
102
|
+
@position = 0
|
103
|
+
end
|
104
|
+
|
105
|
+
class UnknownLabel < Struct.new(:name, :assembler, :insn)
|
106
|
+
def works? type
|
107
|
+
type == "rel32"
|
108
|
+
end
|
109
|
+
|
110
|
+
def unknown_label?; true; end
|
111
|
+
|
112
|
+
def value
|
113
|
+
label = assembler.label_for(name)
|
114
|
+
label.position - (insn.position + insn.bytesize)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Label < Struct.new(:name, :position)
|
119
|
+
end
|
120
|
+
|
121
|
+
def label_for name
|
122
|
+
@labels.fetch name
|
123
|
+
end
|
124
|
+
|
125
|
+
def label name
|
126
|
+
UnknownLabel.new(name, self)
|
127
|
+
end
|
128
|
+
|
129
|
+
def make_label name
|
130
|
+
@labels[name] = Label.new(name, position)
|
131
|
+
end
|
132
|
+
|
133
|
+
Registers.constants.grep(/^[A-Z0-9]*$/).each do |const|
|
134
|
+
val = Registers.const_get const
|
135
|
+
define_method(const.downcase) { val }
|
136
|
+
end
|
137
|
+
|
138
|
+
class Instruction
|
139
|
+
attr_reader :position
|
140
|
+
|
141
|
+
def initialize insn, operands, position
|
142
|
+
@insn = insn
|
143
|
+
@operands = operands
|
144
|
+
@position = position
|
145
|
+
end
|
146
|
+
|
147
|
+
def encodings
|
148
|
+
@insn.encodings
|
149
|
+
end
|
150
|
+
|
151
|
+
def encode buffer
|
152
|
+
encoding = @insn.encodings.first
|
153
|
+
encoding.encode buffer, @operands
|
154
|
+
end
|
155
|
+
|
156
|
+
def bytesize
|
157
|
+
@insn.encodings.first.bytesize
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def gen name, params
|
162
|
+
insns = Machine.instruction_with_name(name)
|
163
|
+
forms = insns.forms.find_all do |insn|
|
164
|
+
if insn.operands.length == params.length
|
165
|
+
params.zip(insn.operands).all? { |want_op, have_op|
|
166
|
+
want_op.works?(have_op.type)
|
167
|
+
}
|
168
|
+
else
|
169
|
+
false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
raise NotImplementedError, "couldn't find instruction #{name}" if forms.length == 0
|
174
|
+
|
175
|
+
insn = nil
|
176
|
+
|
177
|
+
insn = forms.first
|
178
|
+
|
179
|
+
insn = Instruction.new(insn, params, position)
|
180
|
+
@position += insn.bytesize
|
181
|
+
@instructions << insn
|
182
|
+
params.each { |param| param.insn = insn if param.unknown_label? }
|
183
|
+
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
Machine.instructions.keys.each do |insn|
|
188
|
+
define_method(insn.downcase) do |*params|
|
189
|
+
gen insn, params
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def moffs64 val
|
194
|
+
MOffs64.new val
|
195
|
+
end
|
196
|
+
|
197
|
+
def imm8 val
|
198
|
+
Imm8.new val
|
199
|
+
end
|
200
|
+
|
201
|
+
def imm32 val
|
202
|
+
Imm32.new val
|
203
|
+
end
|
204
|
+
|
205
|
+
def imm64 val
|
206
|
+
Imm64.new val
|
207
|
+
end
|
208
|
+
|
209
|
+
def rel8 val
|
210
|
+
Rel8.new val
|
211
|
+
end
|
212
|
+
|
213
|
+
def rel32 val
|
214
|
+
Rel32.new val
|
215
|
+
end
|
216
|
+
|
217
|
+
def lit val
|
218
|
+
# to_s because we're getting the value from JSON as a string
|
219
|
+
Lit.new val
|
220
|
+
end
|
221
|
+
|
222
|
+
def asm buf = StringIO.new(''.b), &block
|
223
|
+
@position = 0
|
224
|
+
instance_eval(&block)
|
225
|
+
write_to_buffer buf
|
226
|
+
buf
|
227
|
+
end
|
228
|
+
|
229
|
+
def to_binary
|
230
|
+
io = StringIO.new ''.b
|
231
|
+
write_to_buffer io
|
232
|
+
io.string
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def write_to_buffer buffer
|
238
|
+
@instructions.each { |insn| insn.encode buffer }
|
239
|
+
end
|
240
|
+
end
|
data/lib/fisk/helpers.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require "fiddle"
|
2
|
+
|
3
|
+
class Fisk
|
4
|
+
module Helpers
|
5
|
+
include Fiddle
|
6
|
+
|
7
|
+
class Fiddle::Function
|
8
|
+
def to_proc
|
9
|
+
this = self
|
10
|
+
lambda { |*args| this.call(*args) }
|
11
|
+
end
|
12
|
+
end unless Function.method_defined?(:to_proc)
|
13
|
+
|
14
|
+
# from sys/mman.h on macOS
|
15
|
+
PROT_READ = 0x01
|
16
|
+
PROT_WRITE = 0x02
|
17
|
+
PROT_EXEC = 0x04
|
18
|
+
MAP_PRIVATE = 0x0002
|
19
|
+
|
20
|
+
if RUBY_PLATFORM =~ /darwin/
|
21
|
+
MAP_ANON = 0x1000
|
22
|
+
else
|
23
|
+
MAP_ANON = 0x20
|
24
|
+
end
|
25
|
+
|
26
|
+
mmap_ptr = Handle::DEFAULT["mmap"]
|
27
|
+
mmap_func = Function.new mmap_ptr, [TYPE_VOIDP,
|
28
|
+
TYPE_SIZE_T,
|
29
|
+
TYPE_INT,
|
30
|
+
TYPE_INT,
|
31
|
+
TYPE_INT,
|
32
|
+
TYPE_INT], TYPE_VOIDP, name: "mmap"
|
33
|
+
|
34
|
+
memcpy_ptr = Handle::DEFAULT["memcpy"]
|
35
|
+
memcpy_func = Function.new memcpy_ptr, [TYPE_VOIDP,
|
36
|
+
TYPE_VOIDP,
|
37
|
+
TYPE_SIZE_T], TYPE_VOIDP, name: "memcpy"
|
38
|
+
|
39
|
+
# Expose the mmap system call
|
40
|
+
define_singleton_method :mmap, &mmap_func
|
41
|
+
|
42
|
+
# Expose the memcpy system call
|
43
|
+
define_singleton_method :memcpy, &memcpy_func
|
44
|
+
|
45
|
+
def self.mmap_jit size
|
46
|
+
ptr = mmap 0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0
|
47
|
+
ptr.size = size
|
48
|
+
ptr
|
49
|
+
end
|
50
|
+
|
51
|
+
class JITBuffer
|
52
|
+
attr_reader :memory, :pos
|
53
|
+
|
54
|
+
def initialize memory
|
55
|
+
@memory = memory
|
56
|
+
@pos = 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def putc byte
|
60
|
+
@memory[@pos] = byte
|
61
|
+
@pos += 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_function params, ret
|
65
|
+
Fiddle::Function.new memory.to_i, params, ret
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.jitbuffer size
|
70
|
+
JITBuffer.new mmap_jit size
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/fisk/machine.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class Fisk
|
2
|
+
class Machine
|
3
|
+
Instructions = {}
|
4
|
+
|
5
|
+
Operand = Struct.new(:type, :input, :output)
|
6
|
+
|
7
|
+
def self.instructions
|
8
|
+
Instructions
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.instruction_with_name name
|
12
|
+
Instructions[name]
|
13
|
+
end
|
14
|
+
|
15
|
+
class Form
|
16
|
+
attr_reader :operands, :encodings
|
17
|
+
|
18
|
+
def initialize operands, encodings
|
19
|
+
@operands = operands
|
20
|
+
@encodings = encodings
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Instruction
|
25
|
+
attr_reader :name, :forms
|
26
|
+
|
27
|
+
def initialize name, forms
|
28
|
+
@name = name
|
29
|
+
@forms = forms
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require "fisk/machine/generated"
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Fisk
|
2
|
+
class Machine
|
3
|
+
class Encoding
|
4
|
+
private
|
5
|
+
|
6
|
+
def add_modrm buffer, operands, mode, reg, rm
|
7
|
+
reg = get_operand_value(reg, operands) & 0x7
|
8
|
+
rm = get_operand_value(rm, operands) & 0x7
|
9
|
+
buffer.putc ((mode << 6) | (reg << 3) | rm)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_immediate buffer, operands, value, size
|
13
|
+
value = get_operand_value(value, operands)
|
14
|
+
write_num buffer, value, size
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_code_offset buffer, operands, value, size
|
18
|
+
value = get_operand_value(value, operands)
|
19
|
+
write_num buffer, value, size
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_data_offset buffer, operands, value, size
|
23
|
+
value = get_operand_value(value, operands)
|
24
|
+
write_num buffer, value, size
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_rex buffer, operands, mandatory, w, r, x, b
|
28
|
+
return if mandatory == false
|
29
|
+
|
30
|
+
rex = 0b0100
|
31
|
+
rex = (rex << 1) | check_rex(w, operands)
|
32
|
+
rex = (rex << 1) | check_rex(r, operands)
|
33
|
+
rex = (rex << 1) | check_rex(x, operands)
|
34
|
+
rex = (rex << 1) | check_rex(b, operands)
|
35
|
+
buffer.putc rex
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_opcode buffer, operands, byte, addend
|
39
|
+
if addend
|
40
|
+
byte |= get_operand_value(addend, operands)
|
41
|
+
end
|
42
|
+
|
43
|
+
buffer.putc byte
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_operand_value v, operands
|
47
|
+
case v
|
48
|
+
when /^#(\d+)$/
|
49
|
+
operands[$1.to_i].value
|
50
|
+
when /^(\d+)$/
|
51
|
+
$1.to_i
|
52
|
+
else
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_rex v, operands
|
58
|
+
return 0 unless v
|
59
|
+
|
60
|
+
case v
|
61
|
+
when /^(\d+)$/
|
62
|
+
v.to_i
|
63
|
+
when /^#(\d+)$/
|
64
|
+
(operands[$1.to_i].value >> 3)
|
65
|
+
else
|
66
|
+
raise NotImplementedError, v
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def write_num buffer, num, size
|
71
|
+
size.times {
|
72
|
+
buffer.putc(num & 0xFF)
|
73
|
+
num >>= 8
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|