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.
@@ -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
@@ -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
@@ -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