tduehr-ragweed 0.1.5

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 (62) hide show
  1. data/History.txt +15 -0
  2. data/README.rdoc +35 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +30 -0
  5. data/examples/hittracertux.rb +48 -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 +419 -0
  14. data/lib/ragweed/debuggertux.rb +347 -0
  15. data/lib/ragweed/detour.rb +223 -0
  16. data/lib/ragweed/ptr.rb +48 -0
  17. data/lib/ragweed/rasm/isa.rb +1046 -0
  18. data/lib/ragweed/rasm/util.rb +26 -0
  19. data/lib/ragweed/rasm.rb +53 -0
  20. data/lib/ragweed/sbuf.rb +197 -0
  21. data/lib/ragweed/trampoline.rb +103 -0
  22. data/lib/ragweed/utils.rb +87 -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 +53 -0
  34. data/lib/ragweed/wraposx/constants.rb +101 -0
  35. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  36. data/lib/ragweed/wraposx/region_info.rb +244 -0
  37. data/lib/ragweed/wraposx/thread_context.rb +203 -0
  38. data/lib/ragweed/wraposx/thread_info.rb +213 -0
  39. data/lib/ragweed/wraposx/wraposx.rb +376 -0
  40. data/lib/ragweed/wraposx.rb +53 -0
  41. data/lib/ragweed/wraptux/constants.rb +68 -0
  42. data/lib/ragweed/wraptux/threads.rb +3 -0
  43. data/lib/ragweed/wraptux/wraptux.rb +76 -0
  44. data/lib/ragweed/wraptux.rb +53 -0
  45. data/lib/ragweed.rb +84 -0
  46. data/spec/ragweed_spec.rb +7 -0
  47. data/spec/spec_helper.rb +16 -0
  48. data/tasks/ann.rake +80 -0
  49. data/tasks/bones.rake +20 -0
  50. data/tasks/gem.rake +201 -0
  51. data/tasks/git.rake +40 -0
  52. data/tasks/notes.rake +27 -0
  53. data/tasks/post_load.rake +34 -0
  54. data/tasks/rdoc.rake +51 -0
  55. data/tasks/rubyforge.rake +55 -0
  56. data/tasks/setup.rb +292 -0
  57. data/tasks/spec.rake +54 -0
  58. data/tasks/svn.rake +47 -0
  59. data/tasks/test.rake +40 -0
  60. data/tasks/zentest.rake +36 -0
  61. data/test/test_ragweed.rb +0 -0
  62. metadata +127 -0
@@ -0,0 +1,347 @@
1
+ require 'ragweed/wraptux'
2
+
3
+ ## Modeled after wraposx written by tduehr
4
+
5
+ module Ragweed; end
6
+
7
+ ## Debugger class for Linux
8
+ ## You can use this class in 2 ways:
9
+ ##
10
+ ## (1) You can create instances of Debuggertux and use them to set and handle
11
+ ## breakpoints.
12
+ ##
13
+ ## (2) If you want to do more advanced event handling, you can subclass from
14
+ ## debugger and define your own on_whatever events. If you handle an event
15
+ ## that Debuggertux already handles, call "super", too.
16
+ class Ragweed::Debuggertux
17
+ include Ragweed
18
+
19
+ attr_reader :pid
20
+ attr_reader :status
21
+ attr_reader :exited
22
+ attr_accessor :breakpoints
23
+
24
+ ## Class to handle installing/uninstalling breakpoints
25
+ class Breakpoint
26
+
27
+ INT3 = 0xCC ## obviously x86 specific debugger here
28
+
29
+ attr_accessor :orig
30
+ attr_reader :addr
31
+ attr_accessor :function
32
+
33
+ ## bp: parent for method_missing calls
34
+ ## ip: insertion point
35
+ ## callable: lambda to be called when breakpoint is hit
36
+ ## name: name of breakpoint
37
+ def initialize(bp, ip, callable, name = "")
38
+ @@bpid ||= 0
39
+ @bp = bp
40
+ @function = name
41
+ @addr = ip
42
+ @callable = callable
43
+ @installed = false
44
+ @orig = 0
45
+ @bpid = (@@bpid += 1)
46
+ end ## breakpoint initialize
47
+
48
+ ## Install a breakpoint (replace instruction with int3)
49
+ def install
50
+ ## Replace the original instruction with an int3
51
+ @orig = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, $ppid, @addr, 0) ## Backup the old inst
52
+ if @orig != -1
53
+ new = (@orig & ~0xff) | INT3;
54
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, new)
55
+ @installed = true
56
+ else
57
+ @installed = false
58
+ end
59
+ end
60
+
61
+ ## Uninstall the breakpoint
62
+ def uninstall
63
+ ## Put back the original instruction
64
+ if @orig != INT3
65
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, @orig)
66
+ @installed = false
67
+ end
68
+ end
69
+
70
+ def installed?; @installed; end
71
+ def call(*args); @callable.call(*args) if @callable != nil; end
72
+ def method_missing(meth, *args); @bp.send(meth, *args); end
73
+ end ## Breakpoint Class
74
+
75
+ ## init object
76
+ ## p: pid of process to be debugged
77
+ ## opts: default options for automatically doing things (attach and install)
78
+ def initialize(pid,opts={}) ## Debuggertux Class
79
+ @pid = pid
80
+
81
+ ## FIXME - globals are bad...
82
+ $ppid = @pid ## temporary work around
83
+ @opts = opts
84
+
85
+ default_opts(opts)
86
+ @installed = false
87
+ @attached = false
88
+
89
+ ## Store all breakpoints in this hash
90
+ @breakpoints = Hash.new do |h, k|
91
+ bps = Array.new
92
+ def bps.call(*args); each {|bp| bp.call(*args)}; end
93
+ def bps.install; each {|bp| bp.install}; end
94
+ def bps.uninstall; each {|bp| bp.uninstall}; end
95
+ def bps.orig; each {|bp| dp.orig}; end
96
+ h[k] = bps
97
+ end
98
+ @opts.each {|k, v| try(k) if v}
99
+ end
100
+
101
+ ## This is crude!
102
+ def self.find_by_regex(rx)
103
+ a = Dir.entries("/proc/")
104
+ a.delete_if do |x| x == '.' end
105
+ a.delete_if do |x| x == '..' end
106
+ a.delete_if do |x| x =~ /[a-z]/ end
107
+ a.each do |x|
108
+ f = File.read("/proc/#{x}/cmdline")
109
+ if f =~ rx
110
+ return x
111
+ end
112
+ end
113
+ end
114
+
115
+ ## Return an array of thread PIDs
116
+ def self.get_thread_pids(pid)
117
+ a = Dir.entries("/proc/#{pid}/task/")
118
+ a.delete_if do |x| x == '.' end
119
+ a.delete_if do |x| x == '..' end
120
+ end
121
+
122
+ def install_bps
123
+ @breakpoints.each do |k,v|
124
+ v.install
125
+ end
126
+ @installed = true
127
+ end
128
+
129
+ def uninstall_bps
130
+ @breakpoints.each do |k,v|
131
+ v.uninstall
132
+ end
133
+ @installed = false
134
+ end
135
+
136
+ ## Attach calls install_bps so dont forget to call breakpoint_set
137
+ ## BEFORE attach or explicitly call install_bps
138
+ def attach(opts=@opts)
139
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
140
+ @attached = true
141
+ self.install_bps if (opts[:install] and not @installed)
142
+ end
143
+
144
+ def continue
145
+ on_continue
146
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::CONTINUE, @pid, 0, 0)
147
+ end
148
+
149
+ def detach
150
+ on_detach
151
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
152
+ end
153
+
154
+ def stepp
155
+ on_stepp
156
+ ret = Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
157
+ end
158
+
159
+ ## Adds a breakpoint to be installed
160
+ ## ip: Insertion point
161
+ ## name: name of breakpoint
162
+ ## callable: object to .call at breakpoint
163
+ def breakpoint_set(ip, name="", callable=nil, &block)
164
+ if not callable and block_given?
165
+ callable = block
166
+ end
167
+ @breakpoints[ip] << Breakpoint.new(self, ip, callable, name)
168
+ end
169
+
170
+ ## remove breakpoint with id bpid at insertion point or
171
+ ## remove all breakpoints at insertion point if bpid not given
172
+ ## ip: Insertion point
173
+ ## bpid: id of breakpoint to be removed
174
+ def breakpoint_clear(ip, bpid=nil)
175
+ if not bpid
176
+ @breakpoints[ip].uninstall
177
+ @breakpoints[ip].delete ip
178
+ else
179
+ found = nil
180
+ @breakpoints[ip].each_with_index do |bp, i|
181
+ if bp.bpid == bpid
182
+ found = i
183
+ if bp.orig != Breakpoint::INT3
184
+ if @breakpoints[op][i+1]
185
+ @breakpoints[ip][i + 1].orig = bp.orig
186
+ else
187
+ bp.uninstall
188
+ end
189
+ end
190
+ end
191
+ end
192
+ raise "could not find bp ##{bpid} at ##{ip}" if not found
193
+ @breakpoints[ip].delete_at(found) if found
194
+ end
195
+ end
196
+
197
+ ##loop for wait()
198
+ ##times: the number of wait calls to make
199
+ ## if nil loop will continue indefinitely
200
+ def loop(times=nil)
201
+ if times.kind_of? Numeric
202
+ times.times do
203
+ self.wait
204
+ end
205
+ elsif times.nil?
206
+ self.wait while not @exited
207
+ end
208
+ end
209
+
210
+ ## This wait must be smart, it has to wait for a signal
211
+ ## when SIGTRAP is received we need to see if one of our
212
+ ## breakpoints has fired. If it has then execute the block
213
+ ## originally stored with it. If its a different signal,
214
+ ## then process it accordingly and move on
215
+ def wait(opts = 0)
216
+ r = Wraptux::waitpid(@pid,opts)
217
+ status = r[1]
218
+ wstatus = status & 0x7f
219
+ signal = status >> 8
220
+ found = false
221
+ if r[0] != 0 ## Check the ret
222
+ case ## FIXME - I need better logic (use Signal module)
223
+ when wstatus == 0 ##WIFEXITED
224
+ @exited = true
225
+ self.on_exit
226
+ when wstatus != 0x7f ##WIFSIGNALED
227
+ @exited = false
228
+ self.on_signal
229
+ when signal == Wraptux::Signal::SIGINT
230
+ self.continue
231
+ when signal == Wraptux::Signal::SIGSEGV
232
+ self.on_segv
233
+ when signal == Wraptux::Signal::SIGILL
234
+ self.on_illegalinst
235
+ when signal == Wraptux::Signal::SIGTRAP
236
+ ## Check if EIP matches a breakpoint we have set
237
+ r = self.get_registers
238
+ eip = r[:eip]
239
+ eip -= 1
240
+ if @breakpoints.has_key?(eip)
241
+ found = true
242
+ self.on_breakpoint
243
+ else
244
+ puts "We got a SIGTRAP but not at our breakpoint... continuing"
245
+ end
246
+ self.continue
247
+ when signal == Wraptux::Signal::SIGCONT
248
+ self.continue
249
+ when signal == Wraptux::Signal::SIGSTOP
250
+ self.continue
251
+ else
252
+ raise "Add more signal handlers (##{signal})"
253
+ end
254
+ end
255
+ end
256
+
257
+ ## Gets the registers for the given process
258
+ def get_registers
259
+ size = Wraptux::SIZEOFLONG * 17
260
+ regs = Array.new(size)
261
+ regs = regs.to_ptr
262
+ regs.struct!('LLLLLLLLLLLLLLLLL', :ebx,:ecx,:edx,:esi,:edi,:ebp,:eax,:xds,:xes,:xfs,:xgs,:orig_eax,:eip,:xcs,:eflags,:esp,:xss)
263
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
264
+ return regs
265
+ end
266
+
267
+ ## Sets registers for the given process
268
+ def set_registers(r)
269
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, r.to_i)
270
+ end
271
+
272
+ ## Here we need to do something about the bp
273
+ ## we just hit. We have a block to execute
274
+ def on_breakpoint
275
+ r = self.get_registers
276
+ eip = r[:eip]
277
+ eip -= 1
278
+
279
+ ## Call the block associated with the breakpoint
280
+ @breakpoints[eip].call(r, self)
281
+
282
+ if @breakpoints[eip].first.installed?
283
+ @breakpoints[eip].first.uninstall
284
+ r[:eip] = eip
285
+ set_registers(r)
286
+ stepp
287
+ ## ptrace peektext returns -1 upon reinstallation of bp without calling
288
+ ## waitpid() if that occurs the breakpoint cannot be reinstalled
289
+ Wraptux::waitpid(@pid, 0)
290
+ @breakpoints[eip].first.install
291
+ end
292
+ end
293
+
294
+ def print_regs
295
+ regs = self.get_registers
296
+ puts "eip %08x" % regs[:eip]
297
+ puts "edi %08x" % regs[:esi]
298
+ puts "edi %08x" % regs[:edi]
299
+ puts "esp %08x" % regs[:esp]
300
+ puts "eax %08x" % regs[:eax]
301
+ puts "ebx %08x" % regs[:ebx]
302
+ puts "ecx %08x" % regs[:ecx]
303
+ puts "edx %08x" % regs[:edx]
304
+ end
305
+
306
+ def on_exit
307
+ #puts "process exited"
308
+ end
309
+
310
+ def on_illegalinst
311
+ #puts "illegal instruction"
312
+ exit
313
+ end
314
+
315
+ def on_attach
316
+ #puts "attached to process"
317
+ end
318
+
319
+ def on_detach
320
+ #puts "process detached"
321
+ end
322
+
323
+ def on_continue
324
+ #puts "process continued"
325
+ end
326
+
327
+ def on_stopped
328
+ #puts "process stopped"
329
+ end
330
+
331
+ def on_signal
332
+ #puts "process received signal"
333
+ end
334
+
335
+ def on_stepp
336
+ #puts "single stepping"
337
+ end
338
+
339
+ def on_segv
340
+ print_regs
341
+ exit
342
+ end
343
+
344
+ def default_opts(opts)
345
+ @opts = @opts.merge(opts)
346
+ end
347
+ 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. XXX 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
+ #XXX - 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