ragweed 0.1.7.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.
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