ragweed 0.1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/History.txt +32 -0
  2. data/README.rdoc +35 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +34 -0
  5. data/examples/hittracertux.rb +49 -0
  6. data/examples/hittracerx.rb +63 -0
  7. data/examples/hook_notepad.rb +9 -0
  8. data/examples/snicker.rb +183 -0
  9. data/examples/tux-example.rb +23 -0
  10. data/lib/ragweed/arena.rb +55 -0
  11. data/lib/ragweed/blocks.rb +128 -0
  12. data/lib/ragweed/debugger32.rb +338 -0
  13. data/lib/ragweed/debuggerosx.rb +427 -0
  14. data/lib/ragweed/debuggertux.rb +346 -0
  15. data/lib/ragweed/detour.rb +223 -0
  16. data/lib/ragweed/ptr.rb +48 -0
  17. data/lib/ragweed/rasm/bblock.rb +73 -0
  18. data/lib/ragweed/rasm/isa.rb +1115 -0
  19. data/lib/ragweed/rasm.rb +59 -0
  20. data/lib/ragweed/sbuf.rb +197 -0
  21. data/lib/ragweed/trampoline.rb +103 -0
  22. data/lib/ragweed/utils.rb +156 -0
  23. data/lib/ragweed/wrap32/debugging.rb +163 -0
  24. data/lib/ragweed/wrap32/device.rb +49 -0
  25. data/lib/ragweed/wrap32/event.rb +50 -0
  26. data/lib/ragweed/wrap32/hooks.rb +23 -0
  27. data/lib/ragweed/wrap32/overlapped.rb +46 -0
  28. data/lib/ragweed/wrap32/process.rb +506 -0
  29. data/lib/ragweed/wrap32/process_token.rb +59 -0
  30. data/lib/ragweed/wrap32/thread_context.rb +208 -0
  31. data/lib/ragweed/wrap32/winx.rb +16 -0
  32. data/lib/ragweed/wrap32/wrap32.rb +526 -0
  33. data/lib/ragweed/wrap32.rb +59 -0
  34. data/lib/ragweed/wraposx/constants.rb +122 -0
  35. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  36. data/lib/ragweed/wraposx/region_info.rb +254 -0
  37. data/lib/ragweed/wraposx/thread_context.rb +203 -0
  38. data/lib/ragweed/wraposx/thread_info.rb +227 -0
  39. data/lib/ragweed/wraposx/wraposx.rb +433 -0
  40. data/lib/ragweed/wraposx.rb +59 -0
  41. data/lib/ragweed/wraptux/constants.rb +68 -0
  42. data/lib/ragweed/wraptux/threads.rb +7 -0
  43. data/lib/ragweed/wraptux/wraptux.rb +76 -0
  44. data/lib/ragweed/wraptux.rb +59 -0
  45. data/lib/ragweed.rb +84 -0
  46. data/ragweed.gemspec +34 -0
  47. data/spec/ragweed_spec.rb +7 -0
  48. data/spec/spec_helper.rb +16 -0
  49. data/tasks/ann.rake +80 -0
  50. data/tasks/bones.rake +20 -0
  51. data/tasks/gem.rake +201 -0
  52. data/tasks/git.rake +40 -0
  53. data/tasks/notes.rake +27 -0
  54. data/tasks/post_load.rake +34 -0
  55. data/tasks/rdoc.rake +51 -0
  56. data/tasks/rubyforge.rake +55 -0
  57. data/tasks/setup.rb +292 -0
  58. data/tasks/spec.rake +54 -0
  59. data/tasks/svn.rake +47 -0
  60. data/tasks/test.rake +40 -0
  61. data/tasks/zentest.rake +36 -0
  62. data/test/test_ragweed.rb +0 -0
  63. metadata +132 -0
@@ -0,0 +1,346 @@
1
+ require ::File.join(::File.dirname(__FILE__),'wraptux')
2
+ ## Modeled after wraposx written by tduehr
3
+
4
+ module Ragweed; end
5
+
6
+ ## Debugger class for Linux
7
+ ## You can use this class in 2 ways:
8
+ ##
9
+ ## (1) You can create instances of Debuggertux and use them to set and handle
10
+ ## breakpoints.
11
+ ##
12
+ ## (2) If you want to do more advanced event handling, you can subclass from
13
+ ## debugger and define your own on_whatever events. If you handle an event
14
+ ## that Debuggertux already handles, call "super", too.
15
+ class Ragweed::Debuggertux
16
+ # include Ragweed
17
+
18
+ attr_reader :pid
19
+ attr_reader :status
20
+ attr_reader :exited
21
+ attr_accessor :breakpoints
22
+
23
+ ## Class to handle installing/uninstalling breakpoints
24
+ class Breakpoint
25
+
26
+ INT3 = 0xCC ## obviously x86 specific debugger here
27
+
28
+ attr_accessor :orig
29
+ attr_reader :addr
30
+ attr_accessor :function
31
+
32
+ ## bp: parent for method_missing calls
33
+ ## ip: insertion point
34
+ ## callable: lambda to be called when breakpoint is hit
35
+ ## name: name of breakpoint
36
+ def initialize(bp, ip, callable, name = "")
37
+ @@bpid ||= 0
38
+ @bp = bp
39
+ @function = name
40
+ @addr = ip
41
+ @callable = callable
42
+ @installed = false
43
+ @orig = 0
44
+ @bpid = (@@bpid += 1)
45
+ end ## breakpoint initialize
46
+
47
+ ## Install a breakpoint (replace instruction with int3)
48
+ def install
49
+ ## Replace the original instruction with an int3
50
+ @orig = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, $ppid, @addr, 0) ## Backup the old inst
51
+ if @orig != -1
52
+ new = (@orig & ~0xff) | INT3;
53
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, new)
54
+ @installed = true
55
+ else
56
+ @installed = false
57
+ end
58
+ end
59
+
60
+ ## Uninstall the breakpoint
61
+ def uninstall
62
+ ## Put back the original instruction
63
+ if @orig != INT3
64
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, @orig)
65
+ @installed = false
66
+ end
67
+ end
68
+
69
+ def installed?; @installed; end
70
+ def call(*args); @callable.call(*args) if @callable != nil; end
71
+ def method_missing(meth, *args); @bp.send(meth, *args); end
72
+ end ## Breakpoint Class
73
+
74
+ ## init object
75
+ ## p: pid of process to be debugged
76
+ ## opts: default options for automatically doing things (attach and install)
77
+ def initialize(pid,opts={}) ## Debuggertux Class
78
+ @pid = pid
79
+
80
+ ## FIXME - globals are bad...
81
+ $ppid = @pid ## temporary work around
82
+ @opts = opts
83
+
84
+ default_opts(opts)
85
+ @installed = false
86
+ @attached = false
87
+
88
+ ## Store all breakpoints in this hash
89
+ @breakpoints = Hash.new do |h, k|
90
+ bps = Array.new
91
+ def bps.call(*args); each {|bp| bp.call(*args)}; end
92
+ def bps.install; each {|bp| bp.install}; end
93
+ def bps.uninstall; each {|bp| bp.uninstall}; end
94
+ def bps.orig; each {|bp| dp.orig}; end
95
+ h[k] = bps
96
+ end
97
+ @opts.each {|k, v| try(k) if v}
98
+ end
99
+
100
+ ## This is crude!
101
+ def self.find_by_regex(rx)
102
+ a = Dir.entries("/proc/")
103
+ a.delete_if do |x| x == '.'; end
104
+ a.delete_if do |x| x == '..'; end
105
+ a.delete_if do |x| x =~ /[a-z]/; end
106
+ a.each do |x|
107
+ f = File.read("/proc/#{x}/cmdline")
108
+ if f =~ rx
109
+ return x
110
+ end
111
+ end
112
+ end
113
+
114
+ def install_bps
115
+ @breakpoints.each do |k,v|
116
+ v.install
117
+ end
118
+ @installed = true
119
+ end
120
+
121
+ def uninstall_bps
122
+ @breakpoints.each do |k,v|
123
+ v.uninstall
124
+ end
125
+ @installed = false
126
+ end
127
+
128
+ ## Attach calls install_bps so dont forget to call breakpoint_set
129
+ ## BEFORE attach or explicitly call install_bps
130
+ def attach(opts=@opts)
131
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
132
+ @attached = true
133
+ self.install_bps if (opts[:install] and not @installed)
134
+ end
135
+
136
+ def continue
137
+ on_continue
138
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::CONTINUE, @pid, 0, 0)
139
+ end
140
+
141
+ def detach
142
+ on_detach
143
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
144
+ end
145
+
146
+ def stepp
147
+ on_stepp
148
+ ret = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
149
+ end
150
+
151
+ ## Adds a breakpoint to be installed
152
+ ## ip: Insertion point
153
+ ## name: name of breakpoint
154
+ ## callable: object to .call at breakpoint
155
+ def breakpoint_set(ip, name="", callable=nil, &block)
156
+ if not callable and block_given?
157
+ callable = block
158
+ end
159
+ @breakpoints[ip] << Breakpoint.new(self, ip, callable, name)
160
+ end
161
+
162
+ ## remove breakpoint with id bpid at insertion point or
163
+ ## remove all breakpoints at insertion point if bpid not given
164
+ ## ip: Insertion point
165
+ ## bpid: id of breakpoint to be removed
166
+ def breakpoint_clear(ip, bpid=nil)
167
+ if not bpid
168
+ @breakpoints[ip].uninstall
169
+ @breakpoints[ip].delete ip
170
+ else
171
+ found = nil
172
+ @breakpoints[ip].each_with_index do |bp, i|
173
+ if bp.bpid == bpid
174
+ found = i
175
+ if bp.orig != Breakpoint::INT3
176
+ if @breakpoints[op][i+1]
177
+ @breakpoints[ip][i + 1].orig = bp.orig
178
+ else
179
+ bp.uninstall
180
+ end
181
+ end
182
+ end
183
+ end
184
+ raise "could not find bp ##{bpid} at ##{ip}" if not found
185
+ @breakpoints[ip].delete_at(found) if found
186
+ end
187
+ end
188
+
189
+ ##loop for wait()
190
+ ##times: the number of wait calls to make
191
+ ## if nil loop will continue indefinitely
192
+ def loop(times=nil)
193
+ if times.kind_of? Numeric
194
+ times.times do
195
+ self.wait
196
+ end
197
+ elsif times.nil?
198
+ self.wait while not @exited
199
+ end
200
+ end
201
+
202
+ ## This wait must be smart, it has to wait for a signal
203
+ ## when SIGTRAP is received we need to see if one of our
204
+ ## breakpoints has fired. If it has then execute the block
205
+ ## originally stored with it. If its a different signal,
206
+ ## then process it accordingly and move on
207
+ def wait(opts = 0)
208
+ r = Ragweed::Wraptux::waitpid(@pid,opts)
209
+ status = r[1]
210
+ wstatus = status & 0x7f
211
+ signal = status >> 8
212
+ found = false
213
+ if r[0] != 0 ## Check the ret
214
+ case ## FIXME - I need better logic (use Signal module)
215
+ when wstatus == 0 ##WIFEXITED
216
+ @exited = true
217
+ self.on_exit
218
+ when wstatus != 0x7f ##WIFSIGNALED
219
+ @exited = false
220
+ self.on_signal
221
+ when signal == Ragweed::Wraptux::Signal::SIGINT
222
+ self.continue
223
+ when signal == Ragweed::Wraptux::Signal::SIGSEGV
224
+ self.on_segv
225
+ when signal == Ragweed::Wraptux::Signal::SIGILL
226
+ self.on_illegalinst
227
+ when signal == Ragweed::Wraptux::Signal::SIGTRAP
228
+ ## Check if EIP matches a breakpoint we have set
229
+ r = self.get_registers
230
+ eip = r[:eip]
231
+ eip -= 1
232
+ if @breakpoints.has_key?(eip)
233
+ found = true
234
+ self.on_breakpoint
235
+ else
236
+ puts "We got a SIGTRAP but not at our breakpoint... continuing"
237
+ end
238
+ self.continue
239
+ when signal == Ragweed::Wraptux::Signal::SIGCONT
240
+ self.continue
241
+ when signal == Ragweed::Wraptux::Signal::SIGSTOP
242
+ self.continue
243
+ else
244
+ raise "Add more signal handlers (##{signal})"
245
+ end
246
+ end
247
+ end
248
+
249
+ ## Return an array of thread PIDs
250
+ def self.threads(pid)
251
+ a = Dir.entries("/proc/#{pid}/task/")
252
+ a.delete_if { |x| x == '.' }
253
+ a.delete_if { |x| x == '..' }
254
+ end
255
+
256
+ ## Gets the registers for the given process
257
+ def get_registers
258
+ size = Ragweed::Wraptux::SIZEOFLONG * 17
259
+ regs = Array.new(size)
260
+ regs = regs.to_ptr
261
+ regs.struct!('LLLLLLLLLLLLLLLLL', :ebx,:ecx,:edx,:esi,:edi,:ebp,:eax,:xds,:xes,:xfs,:xgs,:orig_eax,:eip,:xcs,:eflags,:esp,:xss)
262
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
263
+ return regs
264
+ end
265
+
266
+ ## Sets registers for the given process
267
+ def set_registers(r)
268
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, r.to_i)
269
+ end
270
+
271
+ ## Here we need to do something about the bp
272
+ ## we just hit. We have a block to execute
273
+ def on_breakpoint
274
+ r = self.get_registers
275
+ eip = r[:eip]
276
+ eip -= 1
277
+
278
+ ## Call the block associated with the breakpoint
279
+ @breakpoints[eip].call(r, self)
280
+
281
+ if @breakpoints[eip].first.installed?
282
+ @breakpoints[eip].first.uninstall
283
+ r[:eip] = eip
284
+ set_registers(r)
285
+ stepp
286
+ ## ptrace peektext returns -1 upon reinstallation of bp without calling
287
+ ## waitpid() if that occurs the breakpoint cannot be reinstalled
288
+ Ragweed::Wraptux::waitpid(@pid, 0)
289
+ @breakpoints[eip].first.install
290
+ end
291
+ end
292
+
293
+ def print_regs
294
+ regs = self.get_registers
295
+ puts "eip %08x" % regs[:eip]
296
+ puts "edi %08x" % regs[:esi]
297
+ puts "edi %08x" % regs[:edi]
298
+ puts "esp %08x" % regs[:esp]
299
+ puts "eax %08x" % regs[:eax]
300
+ puts "ebx %08x" % regs[:ebx]
301
+ puts "ecx %08x" % regs[:ecx]
302
+ puts "edx %08x" % regs[:edx]
303
+ end
304
+
305
+ def on_exit
306
+ #puts "process exited"
307
+ end
308
+
309
+ def on_illegalinst
310
+ #puts "illegal instruction"
311
+ exit
312
+ end
313
+
314
+ def on_attach
315
+ #puts "attached to process"
316
+ end
317
+
318
+ def on_detach
319
+ #puts "process detached"
320
+ end
321
+
322
+ def on_continue
323
+ #puts "process continued"
324
+ end
325
+
326
+ def on_stopped
327
+ #puts "process stopped"
328
+ end
329
+
330
+ def on_signal
331
+ #puts "process received signal"
332
+ end
333
+
334
+ def on_stepp
335
+ #puts "single stepping"
336
+ end
337
+
338
+ def on_segv
339
+ print_regs
340
+ exit
341
+ end
342
+
343
+ def default_opts(opts)
344
+ @opts = @opts.merge(opts)
345
+ end
346
+ end
@@ -0,0 +1,223 @@
1
+ class Ragweed::Detour
2
+ # "Ghetto Detours", as Scott Stender might say. Patch subprograms
3
+ # in to running programs as a hooking mechanism.
4
+ class Detour
5
+ attr_reader :snarfed
6
+ attr_reader :dpoint
7
+ attr_reader :stack
8
+
9
+ # Easiest way to do this is just to ask WinProcess#detour. Wants
10
+ # "p" to be a pointer into the process, presumable returned from
11
+ # WinProcess#get_proc.
12
+ #
13
+ # In theory, "p" should be OK anywhere as long as there are 5
14
+ # bytes of instructions before the end of the basic block its
15
+ # in. In practice, this only really stands a chance of working
16
+ # if "p" points to a function prologue.
17
+ def initialize(p, opts={})
18
+ @p = p.p
19
+ @dpoint = p
20
+ @opts = opts
21
+ @a = @opts[:arena] || @p.arena
22
+ @stack = @a.alloc(2048)
23
+ @snarfed = snarf_prologue
24
+ end
25
+
26
+ # Release the detour and its associated memory, unpatch the
27
+ # target function.
28
+ def release
29
+ @dpoint.write(@snarfed)
30
+ @a.release if not @opts[:arena]
31
+ end
32
+
33
+ # Patch the target function. There is a 70% chance this will
34
+ # totally fuck your process.
35
+ #
36
+ # You would be wise to have the threads in the process suspended
37
+ # while you do this, but I'm not going to do it for you.
38
+ def call
39
+ # a Detours-style trampoline --- the location we patch the
40
+ # target function to jump to --- consists of:
41
+ #
42
+ # - A stack switch (to push/pop w/o fucking the program)
43
+ # - A context save
44
+ # - The Detour code
45
+ # - A context restore
46
+ # - A stack restore
47
+ # - The code we patched out of the target
48
+ # - A jump back to the target function (after the prologue)
49
+
50
+ # Do this now to make room for the (probably 5 byte) jump.
51
+ # We don't know what the address will be until we allocate.
52
+ jumpback = (Jmp 0xDEADBEEF) # patch back later
53
+
54
+ # Build the trampoline
55
+ tramp = trampoline(@stack).assemble
56
+
57
+ # Figure out how big the whole mess will be, allocate it
58
+ tsz = tramp.size + @snarfed.size + jumpback.to_s.size
59
+ tbuf = @a.alloc(tsz + 10)
60
+
61
+ # assume trampoline is ahead of the patched program text;
62
+ # jump to [dpoint+patch]
63
+ jumpback.dst = (@dpoint.to_i + @snarfed.size) - (tbuf + tsz)
64
+
65
+ # Write it into memory. It's not "live" yet because we haven't
66
+ # patched the target function.
67
+ @p.write(tbuf, tramp + @snarfed + jumpback.to_s)
68
+
69
+ # But now it is. =)
70
+ @p.write(@dpoint, injection(tbuf).assemRASble)
71
+ end
72
+
73
+ # Hook function. Override this in subclasses to provide different
74
+ # behavior.
75
+ def inner_block
76
+ i = Rasm::Subprogram.new
77
+ i.<< Int(3)
78
+ end
79
+
80
+ private
81
+
82
+ # No user-servicable parts below.
83
+
84
+ # Pull at least 5 bytes of instructions out of the prologue, using
85
+ # the disassembler, to make room for our patch jump. Save it, so
86
+ # we can unpatch later.
87
+ def snarf_prologue
88
+ i = 0
89
+ (buf = @dpoint.read(20)).distorm.each do |insn|
90
+ i += insn.size
91
+ break if i >= 5
92
+ end
93
+ buf[0...i]
94
+ end
95
+
96
+ # Create the Jmp instruction that implements the patch; you can't
97
+ # do this until you know where the trampoline was actually injected
98
+ # into the process.
99
+ def injection(tramp)
100
+ here = @dpoint
101
+ there = tramp
102
+
103
+ if there < here
104
+ goto = -((here - there) + 5)
105
+ else
106
+ goto = there - here
107
+ end
108
+
109
+ i = Rasm::Subprogram.new
110
+ i.<< Rasm::Jmp(goto.to_i)
111
+ end
112
+
113
+ # Create the detours trampoline:
114
+ def trampoline(stack)
115
+ i = Rasm::Subprogram.new
116
+ i.concat push_stack(stack) # 1. Give us a new stack
117
+ i.concat save_all # 2. Save all the GPRs just in case
118
+ i.concat inner_block # 3. The hook function
119
+ i.concat restore_all # 4. Restore all the GPRs.
120
+ i.concat pop_stack # 5. Restore the stack
121
+ return i
122
+ end
123
+
124
+ # Swap in a new stack, pushing the old stack address
125
+ # onto the top of it.
126
+ def push_stack(addr, sz=2048)
127
+ i = Rasm::Subprogram.new
128
+ i.<< Rasm::Push(eax)
129
+ i.<< Rasm::Mov(eax, addr+(sz-4))
130
+ i.<< Rasm::Mov([eax], esp)
131
+ i.<< Rasm::Pop(eax)
132
+ i.<< Rasm::Mov(esp, addr+(sz-4))
133
+ end
134
+
135
+ # Swap out the new stack.
136
+ def pop_stack
137
+ i = Rasm::Subprogram.new
138
+ i.<< Rasm::Pop(esp)
139
+ i.<< Rasm::Add(esp, 4)
140
+ end
141
+
142
+ # Just push all the registers in order
143
+ def save_all
144
+ i = Rasm::Subprogram.new
145
+ [eax,ecx,edx,ebx,ebp,esi,edi].each do |r|
146
+ i.<< Rasm::Push(r)
147
+ end
148
+ i
149
+ end
150
+
151
+ # Just pop all the registers
152
+ def restore_all
153
+ i = Rasm::Subprogram.new
154
+ [edi,esi,ebp,ebx,edx,ecx,eax].each do |r|
155
+ i.<< Rasm::Pop(r)
156
+ end
157
+ i
158
+ end
159
+ end
160
+
161
+ # A breakpoint implemented as a Detour. TODO not tested.
162
+ class Dbreak < Detour
163
+ attr_reader :ev1, :ev2
164
+
165
+ # accepts:
166
+ # :ev1: reuse events from somewhere else
167
+ # :ev2:
168
+ def initialize(*args)
169
+ super
170
+ @ev1 = @opts[:ev1] || WinEvent.new
171
+ @ev2 = @opts[:ev2] || WinEvent.new
172
+
173
+ # create the state block that the eventpair shim wants:
174
+ mem = @a.alloc(100)
175
+ @data = mem
176
+
177
+ # ghetto vtbl
178
+ swch = ["OpenProcess",
179
+ "DuplicateHandle",
180
+ "ResetEvent",
181
+ "SetEvent",
182
+ "WaitForSingleObject",
183
+ "GetCurrentThreadId"].
184
+ map {|x| @p.get_proc("kernel32!#{x}").to_i}.
185
+ pack("LLLLLL")
186
+
187
+ # ghetto instance vars
188
+ state = [@p.w.get_current_process_id, @ev1.handle, @ev2.handle].
189
+ pack("LLL")
190
+ @data.write(swch + state)
191
+ end
192
+
193
+ def inner_block
194
+ i = Rasm::Subprogram.new
195
+ i.<< Push(eax)
196
+ i.<< Xor(eax, eax)
197
+ i.<< Or(eax, @data)
198
+ i.<< Push(eax)
199
+ i.<< Call(1) # cheesy in the extreme: fake a call
200
+ # so I don't have to change my event shim
201
+ i.<< Nop.new
202
+ i.<< Nop.new
203
+ i.<< Nop.new
204
+ i.<< Nop.new
205
+ i.<< Nop.new
206
+ s = event_pair_stub
207
+ s[-1] = Add(esp, 4)
208
+ i.concat(s)
209
+ i.<< Pop(eax)
210
+ return i
211
+ end
212
+
213
+ # in theory, loop on this breakpoint
214
+ def on(&block)
215
+ puts "#{ @p.pid }: #{ @ev1.handle }" # in case we need to release
216
+ loop do
217
+ @ev1.wait
218
+ yield
219
+ @ev2.signal
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,48 @@
1
+ # TODO: make read/write work for other oses
2
+
3
+ class Ragweed::Ptr
4
+ # A dubious achievement. Wrap Integers in a pointer class, which,
5
+ # when you call to_s, returns the marshalled type, and which exports
6
+ # read/write methods.
7
+ attr_accessor :p
8
+ attr_reader :val
9
+
10
+ # ptr-to-zero?
11
+ def null?
12
+ @val == 0
13
+ end
14
+
15
+ # initialize with a number or another pointer (implements copy-ctor)
16
+ def initialize(i)
17
+ if i.kind_of? self.class
18
+ @val = i.val
19
+ @p = i.p
20
+ elsif not i
21
+ @val = 0
22
+ else
23
+ @val = i
24
+ end
25
+ end
26
+
27
+ # return the raw pointer bits
28
+ def to_s; @val.to_l32; end
29
+
30
+ # return the underlying number
31
+ def to_i; @val; end
32
+
33
+ # only works if you attach a process
34
+ def write(arg); p.write(self, arg); end
35
+ def read(sz); p.read(self, sz); end
36
+
37
+ # everything else: work like an integer --- also, where these
38
+ # calls return numbers, turn them back into pointers, so pointer
39
+ # math doesn't shed the class wrapper
40
+ def method_missing(meth, *args)
41
+ ret = @val.send meth, *args
42
+ if ret.kind_of? Numeric
43
+ ret = Ptr.new(ret)
44
+ ret.p = self.p
45
+ end
46
+ ret
47
+ end
48
+ end
@@ -0,0 +1,73 @@
1
+ module Ragweed; end
2
+ module Ragweed::Rasm
3
+ # Ruby inline assembler.
4
+ class Bblock
5
+ # Don't call this directly; use Bblock#make
6
+ def initialize
7
+ @insns = Ragweed::Rasm::Subprogram.new
8
+ end
9
+
10
+ # Wrap the methods of Rasm::Subprogram we care about:
11
+
12
+ # Assemble the instructions, which also calculates appropriate
13
+ # jump labels.
14
+ def assemble; @insns.assemble; end
15
+
16
+ # Disassemble the block (after it's been assembled) into
17
+ # Frasm objects.
18
+ def disassemble; @insns.disassemble; end
19
+
20
+ # Generate a human-readable assembly listing.
21
+ def listing; @insns.dump_disassembly; end
22
+
23
+ # Append more instructions to a previously created block;
24
+ # see Bblock#make
25
+ def append(&block)
26
+ instance_eval(&block)
27
+ end
28
+
29
+ # Takes a block argument, containing (mostly) assembly
30
+ # instructions, as interpreted by Rasm. For example:
31
+ #
32
+ # Bblock.make {
33
+ # push ebp
34
+ # mov ebp, esp
35
+ # push ebx
36
+ # xor ebx, ebx
37
+ # addl esp, 4
38
+ # pop ebp
39
+ # ret
40
+ # }
41
+ #
42
+ # Each of those instructions is in fact the name of a class
43
+ # in Rasm, lowercased; Bblock has a method_missing that catches
44
+ # and instantiates them.
45
+ #
46
+ # Your block can contain arbitrary Ruby, but remember that it
47
+ # runs in the scope of an anonymous class and so cannot directly
48
+ # reference instance variables.
49
+ def self.make(&block)
50
+ c = Bblock.new
51
+ c.instance_eval(&block)
52
+ c
53
+ end
54
+
55
+ # method to fix collision with Kernel#sub properly
56
+ def sub(*args)
57
+ Ragweed::Rasm::Sub.new(*args)
58
+ end
59
+
60
+ def method_missing(meth, *args)
61
+ k = Ragweed::Rasm.const_get(meth.to_s.capitalize)
62
+
63
+ # If it's a class, it's an assembly opcode; otherwise,
64
+ # it's a register or operand.
65
+ if k.class == Class
66
+ @insns << (k = k.new(*args))
67
+ else
68
+ k
69
+ end
70
+ k
71
+ end
72
+ end
73
+ end