ragweed 0.1.7.3 → 0.2.0.pre1
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.
- data/README.rdoc +33 -8
- data/Rakefile +80 -23
- data/VERSION +1 -0
- data/examples/hittracertux.rb +2 -6
- data/examples/hook_notepad.rb +1 -1
- data/examples/tux-example.rb +3 -2
- data/lib/.DS_Store +0 -0
- data/lib/ragweed/debugger32.rb +188 -145
- data/lib/ragweed/debuggerosx.rb +13 -13
- data/lib/ragweed/debuggertux.rb +267 -140
- data/lib/ragweed/rasm.rb +1 -1
- data/lib/ragweed/wrap32/debugging.rb +184 -64
- data/lib/ragweed/wrap32/hooks.rb +27 -11
- data/lib/ragweed/wrap32/process.rb +114 -7
- data/lib/ragweed/wrap32/process_token.rb +23 -7
- data/lib/ragweed/wrap32/thread_context.rb +100 -166
- data/lib/ragweed/wrap32/wrap32.rb +127 -72
- data/lib/ragweed/wrap32.rb +1 -1
- data/lib/ragweed/wraposx/constants.rb +1 -9
- data/lib/ragweed/wraposx/region_info.rb +209 -188
- data/lib/ragweed/wraposx/structs.rb +102 -0
- data/lib/ragweed/wraposx/thread_context.rb +636 -159
- data/lib/ragweed/wraposx/thread_info.rb +40 -107
- data/lib/ragweed/wraposx/thread_info.rb.old +121 -0
- data/lib/ragweed/wraposx/wraposx.rb +154 -231
- data/lib/ragweed/wraposx.rb +2 -1
- data/lib/ragweed/wraptux/constants.rb +46 -22
- data/lib/ragweed/wraptux/struct_helpers.rb +25 -0
- data/lib/ragweed/wraptux/threads.rb +0 -0
- data/lib/ragweed/wraptux/wraptux.rb +58 -62
- data/lib/ragweed/wraptux.rb +3 -4
- data/lib/ragweed.rb +36 -8
- data/ragweed.gemspec +85 -15
- metadata +50 -18
data/lib/ragweed/debugger32.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
require ::File.join(::File.dirname(__FILE__),'wrap32')
|
2
2
|
|
3
|
-
# I am not particularly proud of this code, which I basically debugged
|
4
|
-
# into existence, but it does work.
|
5
|
-
|
6
3
|
# Debugger class for win32
|
7
4
|
# You can use this class in 2 ways:
|
8
5
|
#
|
@@ -15,47 +12,58 @@ require ::File.join(::File.dirname(__FILE__),'wrap32')
|
|
15
12
|
class Ragweed::Debugger32
|
16
13
|
include Ragweed
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
## Breakpoint class. Handles the actual setting,
|
16
|
+
## removal and triggers for breakpoints.
|
17
|
+
## no user servicable parts.
|
21
18
|
class Breakpoint
|
19
|
+
|
22
20
|
INT3 = 0xCC
|
23
|
-
attr_accessor :orig
|
24
|
-
attr_accessor :bpid
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
attr_accessor :orig, :deferred, :addr
|
23
|
+
|
24
|
+
def initialize(process, ip, def_status, callable)
|
25
|
+
@process = process
|
29
26
|
@addr = ip
|
30
27
|
@callable = callable
|
31
|
-
@
|
28
|
+
@deferred = def_status
|
29
|
+
@orig = 0
|
32
30
|
end
|
31
|
+
|
32
|
+
def addr; @addr; end
|
33
33
|
|
34
34
|
def install
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
if @addr == 0 or @deferred == true
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
o = @process.read8(@addr)
|
40
|
+
|
41
|
+
if(orig != INT3)
|
42
|
+
@orig = o
|
43
|
+
@process.write8(@addr, INT3)
|
44
|
+
Ragweed::Wrap32::flush_instruction_cache(@process.handle)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def deferred_install(h, base)
|
49
|
+
@addr = @process.get_deferred_proc_remote(@addr, h, base)
|
50
|
+
self.install
|
51
|
+
return @addr
|
40
52
|
end
|
41
53
|
|
42
54
|
def uninstall
|
43
55
|
if(@orig != INT3)
|
44
|
-
process.write8(@addr, @orig)
|
45
|
-
Ragweed::Wrap32::flush_instruction_cache(@
|
56
|
+
@process.write8(@addr, @orig)
|
57
|
+
Ragweed::Wrap32::flush_instruction_cache(@process.handle)
|
46
58
|
end
|
47
59
|
end
|
48
60
|
|
49
61
|
def call(*args); @callable.call(*args); end
|
50
|
-
|
51
|
-
end
|
62
|
+
end ## End Breakpoint class
|
52
63
|
|
53
|
-
|
64
|
+
## Get a handle to the process so you can mess with it.
|
54
65
|
def process; @p; end
|
55
66
|
|
56
|
-
# This is how you normally create a debug instance: with a regex
|
57
|
-
# on the image name of the process.
|
58
|
-
# d = Debugger.find_by_regex /notepad/i
|
59
67
|
def self.find_by_regex(rx)
|
60
68
|
Ragweed::Wrap32::all_processes do |p|
|
61
69
|
if p.szExeFile =~ rx
|
@@ -64,179 +72,201 @@ class Ragweed::Debugger32
|
|
64
72
|
end
|
65
73
|
nil
|
66
74
|
end
|
67
|
-
|
68
|
-
# If you want to get one by hand, and not by Debugger#find_by_regex,
|
69
|
-
# pass this either a PID or a Process object.
|
75
|
+
|
70
76
|
def initialize(p)
|
71
|
-
|
72
|
-
@@token ||= Ragweed::Wrap32::ProcessToken.new.grant(
|
77
|
+
## grab debug privilege at least once
|
78
|
+
@@token ||= Ragweed::Wrap32::ProcessToken.new.grant('seDebugPrivilege')
|
73
79
|
|
74
80
|
p = Process.new(p) if p.kind_of? Numeric
|
75
81
|
@p = p
|
76
82
|
@steppers = []
|
77
83
|
@handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
|
78
|
-
@first = true
|
79
84
|
@attached = false
|
80
85
|
|
81
|
-
|
82
|
-
|
83
|
-
@
|
84
|
-
if k.kind_of? String
|
85
|
-
ip = @p.get_proc_remote(k)
|
86
|
-
else
|
87
|
-
ip = k
|
88
|
-
end
|
89
|
-
raise "no such location" if ip == 0 or ip == 0xFFFFFFFF
|
90
|
-
h[k] = ip
|
91
|
-
end
|
86
|
+
## breakpoints is a hash with a key being the breakpoint
|
87
|
+
## addr and the value being a Breakpoint class
|
88
|
+
@breakpoints = Hash.new
|
92
89
|
|
93
|
-
|
94
|
-
|
95
|
-
@breakpoints = Hash.new do |h, k|
|
96
|
-
bps = Array.new
|
97
|
-
def bps.call(*args); each {|bp| bp.call(*args)}; end
|
98
|
-
def bps.install; each {|bp| bp.install}; end
|
99
|
-
def bps.uninstall; each {|bp| bp.uninstall}; end
|
100
|
-
h[k] = bps
|
101
|
-
end
|
90
|
+
## We want to ignore ntdll!DbgBreakPoint
|
91
|
+
@ntdll_dbg_break_point = @p.get_proc_remote('ntdll!DbgBreakPoint')
|
102
92
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
93
|
+
|
94
|
+
## single-step the thread (by TID). "callable" is something that honors
|
95
|
+
## .call, like a Proc. In a dubious design decision: the "handle" to the
|
96
|
+
## single stepper is the Proc object itself. See Debugger#on_breakpoint
|
97
|
+
## for an example of how to use this.
|
108
98
|
def step(tid, callable)
|
109
99
|
if @steppers.empty?
|
110
100
|
Ragweed::Wrap32::open_thread(tid) do |h|
|
111
|
-
ctx = Ragweed::Wrap32::
|
101
|
+
ctx = Ragweed::Wrap32::get_thread_context(h)
|
112
102
|
ctx.single_step(true)
|
113
|
-
|
103
|
+
Ragweed::Wrap32::set_thread_context(h, ctx)
|
114
104
|
end
|
115
105
|
end
|
116
106
|
@steppers << callable
|
117
107
|
end
|
118
108
|
|
119
|
-
|
120
|
-
|
121
|
-
|
109
|
+
## turn off single-stepping for one callable (you can have more than one
|
110
|
+
## at a time). In other words, when you pass a Proc to Debugger#step, save
|
111
|
+
## it somewhere, and later pass it to "unstep" to turn it off.
|
122
112
|
def unstep(tid, callable)
|
123
113
|
@steppers = @steppers.reject {|x| x == callable}
|
124
114
|
if @steppers.empty?
|
125
115
|
Ragweed::Wrap32::open_thread(tid) do |h|
|
126
|
-
ctx = Ragweed::Wrap32::
|
116
|
+
ctx = Ragweed::Wrap32::get_thread_context(h)
|
127
117
|
ctx.single_step(false)
|
128
|
-
|
118
|
+
Ragweed::Wrap32::set_thread_context(h, ctx)
|
129
119
|
end
|
130
120
|
end
|
131
121
|
end
|
132
122
|
|
133
|
-
|
123
|
+
## convenience: either from a TID or a BreakpointEvent, get the thread context.
|
134
124
|
def context(tid_or_event)
|
135
125
|
if not tid_or_event.kind_of? Numeric
|
136
126
|
tid = tid_or_event.tid
|
137
127
|
else
|
138
128
|
tid = tid_or_event
|
139
129
|
end
|
140
|
-
Ragweed::Wrap32::open_thread(tid) {|h| Ragweed::Wrap32::
|
130
|
+
Ragweed::Wrap32::open_thread(tid) { |h| Ragweed::Wrap32::get_thread_context(h) }
|
141
131
|
end
|
142
132
|
|
143
|
-
|
144
|
-
|
145
|
-
|
133
|
+
## set a breakpoint given an address, which can also be a string in the form
|
134
|
+
## "module!function", as in, "user32!SendMessageW". Be aware that the symbol
|
135
|
+
## lookup takes place in an injected thread; it's safer to use literal addresses
|
136
|
+
## when possible.
|
146
137
|
#
|
147
|
-
|
148
|
-
|
138
|
+
## to handle the breakpoint, pass a block to this method, which will be called
|
139
|
+
## when the breakpoint hits.
|
149
140
|
#
|
150
|
-
|
151
|
-
|
152
|
-
#
|
153
|
-
# returns a numeric id that can be used to clear the breakpoint.
|
141
|
+
## breakpoints are always re-set after firing. If you don't want them to be
|
142
|
+
## re-set, unset them manually.
|
154
143
|
def breakpoint_set(ip, callable=nil, &block)
|
155
144
|
if not callable and block_given?
|
156
145
|
callable = block
|
157
146
|
end
|
158
|
-
ip = @resolve[ip]
|
159
|
-
@breakpoints[ip] << Breakpoint.new(self, ip, callable)
|
160
|
-
end
|
161
147
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
ip
|
166
|
-
|
167
|
-
@breakpoints[ip].uninstall
|
168
|
-
@breakpoints.delete ip
|
148
|
+
def_status = false
|
149
|
+
|
150
|
+
## This is usually 'Module!Function' or 'Module!0x1234'
|
151
|
+
if @p.is_breakpoint_deferred(ip) == true
|
152
|
+
def_status = true
|
169
153
|
else
|
170
|
-
|
171
|
-
|
172
|
-
if bp.bpid == bpid
|
173
|
-
found = i
|
174
|
-
if bp.orig != Breakpoint::INT3
|
175
|
-
if @breakpoints[ip][i+1]
|
176
|
-
@breakpoints[ip][i + 1].orig = bp.orig
|
177
|
-
else
|
178
|
-
bp.uninstall
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
raise "couldn't find #{ ip }" if not found
|
184
|
-
@breakpoints[ip].delete_at(found) if found
|
154
|
+
def_status = false
|
155
|
+
ip = @p.get_proc_remote(ip)
|
185
156
|
end
|
157
|
+
|
158
|
+
## If we cant immediately set the breakpoint
|
159
|
+
## mark it as deferred and wait till later
|
160
|
+
## Sometimes *_proc_remote() will return the
|
161
|
+
## name indicating failure (just in case)
|
162
|
+
if ip == 0 or ip == 0xFFFFFFFF or ip.kind_of? String
|
163
|
+
def_status = true
|
164
|
+
else
|
165
|
+
def_status = false
|
166
|
+
end
|
167
|
+
|
168
|
+
## Dont want duplicate breakpoint objects
|
169
|
+
@breakpoints.each_key { |k| if k == ip then return end }
|
170
|
+
bp = Breakpoint.new(@p, ip, def_status, callable)
|
171
|
+
@breakpoints[ip] = bp
|
172
|
+
end
|
173
|
+
|
174
|
+
## Clear a breakpoint by ip
|
175
|
+
def breakpoint_clear(ip)
|
176
|
+
bp = @breakpoints[ip]
|
177
|
+
|
178
|
+
if bp.nil?
|
179
|
+
return nil
|
180
|
+
end
|
181
|
+
|
182
|
+
bp.uninstall
|
183
|
+
@breakpoints.delete(ip)
|
186
184
|
end
|
187
185
|
|
188
|
-
|
189
|
-
|
186
|
+
## handle a breakpoint event:
|
187
|
+
## call handlers for the breakpoint, step past and reset it.
|
190
188
|
def on_breakpoint(ev)
|
191
|
-
if @first
|
192
|
-
|
193
|
-
# DbgUiRemoteInjectWhatever actually injects a thread into the
|
194
|
-
# target process, which explicitly issues an INT3. We never care
|
195
|
-
# about this breakpoint right now.
|
196
|
-
@first = false
|
197
|
-
else
|
198
189
|
ctx = context(ev)
|
199
190
|
eip = ev.exception_address
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
191
|
+
|
192
|
+
if eip == @ntdll_dbg_break_point
|
193
|
+
return
|
194
|
+
end
|
195
|
+
|
196
|
+
@breakpoints[eip].uninstall
|
197
|
+
|
198
|
+
## Call the block passed to breakpoint_set
|
199
|
+
## which may have been passed through hook()
|
204
200
|
@breakpoints[eip].call(ev, ctx)
|
205
201
|
|
206
|
-
|
202
|
+
## single step past the instruction...
|
207
203
|
step(ev.tid, (onestep = lambda do |ev, ctx|
|
208
204
|
if ev.exception_address != eip
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
@breakpoints[eip].first.install
|
205
|
+
## ... then re-install the breakpoint ...
|
206
|
+
if not @breakpoints[eip].nil?
|
207
|
+
@breakpoints[eip].install
|
213
208
|
end
|
214
|
-
|
215
|
-
# ... and stop single-stepping.
|
209
|
+
## ... and stop single-stepping.
|
216
210
|
unstep(ev.tid, onestep)
|
217
211
|
end
|
218
|
-
end))
|
212
|
+
end))
|
219
213
|
|
220
|
-
|
214
|
+
## Put execution back where it's supposed to be...
|
221
215
|
Ragweed::Wrap32::open_thread(ev.tid) do |h|
|
222
216
|
ctx = context(ev)
|
223
|
-
ctx.eip = eip
|
224
|
-
|
217
|
+
ctx.eip = eip ## eip was ev.exception_address
|
218
|
+
Ragweed::Wrap32::set_thread_context(h, ctx)
|
225
219
|
end
|
226
|
-
end
|
227
220
|
|
228
|
-
|
221
|
+
## Tell the target to stop handling this event
|
229
222
|
@handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
|
230
223
|
end
|
231
224
|
|
232
|
-
|
225
|
+
## FIX: this method should be a bit more descriptive in its naming
|
226
|
+
def get_dll_name(ev)
|
227
|
+
name = Ragweed::Wrap32::get_mapped_filename(@p.handle, ev.base_of_dll, 256)
|
228
|
+
name.gsub!(/[\n]+/,'')
|
229
|
+
name.gsub!(/[^\x21-\x7e]/,'')
|
230
|
+
i = name.index('0')
|
231
|
+
i ||= name.size
|
232
|
+
return name[0, i]
|
233
|
+
end
|
234
|
+
|
235
|
+
def on_load_dll(ev)
|
236
|
+
dll_name = get_dll_name(ev)
|
237
|
+
|
238
|
+
@breakpoints.each_pair do |k,bp|
|
239
|
+
if !bp.addr.kind_of?String
|
240
|
+
next
|
241
|
+
end
|
242
|
+
|
243
|
+
m,f = bp.addr.split('!')
|
244
|
+
|
245
|
+
if dll_name =~ /#{m}/i
|
246
|
+
deferred = bp.deferred
|
247
|
+
|
248
|
+
if deferred == true
|
249
|
+
bp.deferred = false
|
250
|
+
end
|
251
|
+
|
252
|
+
new_addr = bp.deferred_install(ev.file_handle, ev.base_of_dll)
|
253
|
+
|
254
|
+
if !new_addr.nil?
|
255
|
+
@breakpoints[new_addr] = bp.dup
|
256
|
+
@breakpoints.delete(k)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
## handle a single-step event
|
233
263
|
def on_single_step(ev)
|
234
264
|
ctx = context(ev)
|
235
265
|
Ragweed::Wrap32::open_thread(ev.tid) do |h|
|
236
|
-
|
237
|
-
|
266
|
+
## re-enable the trap flag before our handler,
|
267
|
+
## which may choose to disable it.
|
238
268
|
ctx.single_step(true)
|
239
|
-
|
269
|
+
Ragweed::Wrap32.set_thread_context(h, ctx)
|
240
270
|
end
|
241
271
|
|
242
272
|
@steppers.each {|s| s.call(ev, ctx)}
|
@@ -244,18 +274,31 @@ class Ragweed::Debugger32
|
|
244
274
|
@handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
|
245
275
|
end
|
246
276
|
|
247
|
-
|
248
|
-
|
249
|
-
|
277
|
+
## This is sort of insane but most of my programs are just
|
278
|
+
## debug loops, so if you don't do this, they just hang when
|
279
|
+
## the target closes.
|
250
280
|
def on_exit_process(ev)
|
251
281
|
exit(1)
|
252
282
|
end
|
253
283
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
284
|
+
## TODO: Implement each of these
|
285
|
+
def on_create_process(ev) end
|
286
|
+
def on_create_thread(ev) end
|
287
|
+
def on_exit_thread(ev) end
|
288
|
+
def on_output_debug_string(ev) end
|
289
|
+
def on_rip(ev) end
|
290
|
+
def on_unload_dll(ev) end
|
291
|
+
def on_alignment(ev) end
|
292
|
+
def on_bounds(ev) end
|
293
|
+
def on_divide_by_zero(ev) end
|
294
|
+
def on_int_overflow(ev) end
|
295
|
+
def on_invalid_handle(ev) end
|
296
|
+
def on_priv_instruction(ev) end
|
297
|
+
def on_stack_overflow(ev) end
|
298
|
+
def on_invalid_disposition(ev) end
|
299
|
+
|
300
|
+
## Read through me to see all the random events
|
301
|
+
## you can hook in a subclass.
|
259
302
|
def wait
|
260
303
|
self.attach() if not @attached
|
261
304
|
|
@@ -309,30 +352,30 @@ class Ragweed::Debugger32
|
|
309
352
|
@handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
|
310
353
|
end
|
311
354
|
|
312
|
-
|
355
|
+
## Debug loop
|
313
356
|
def loop
|
314
357
|
while true
|
315
358
|
wait
|
316
359
|
end
|
317
360
|
end
|
318
361
|
|
319
|
-
|
320
|
-
|
362
|
+
## This is called implicitly by Debugger#wait.
|
363
|
+
## Attaches to the child process for debugging
|
321
364
|
def attach
|
322
365
|
Ragweed::Wrap32::debug_active_process(@p.pid)
|
323
366
|
Ragweed::Wrap32::debug_set_process_kill_on_exit
|
324
367
|
@attached = true
|
325
|
-
@breakpoints.
|
326
|
-
|
368
|
+
@breakpoints.each_pair do |k, bp|
|
369
|
+
bp.install
|
327
370
|
end
|
328
371
|
end
|
329
372
|
|
330
|
-
|
373
|
+
## Let go of the target.
|
331
374
|
def release
|
332
375
|
Ragweed::Wrap32::debug_active_process_stop(@p.pid)
|
333
376
|
@attached = false
|
334
|
-
@breakpoints.
|
335
|
-
|
377
|
+
@breakpoints.each_pair do |k, bp|
|
378
|
+
bp.uninstall
|
336
379
|
end
|
337
380
|
end
|
338
381
|
end
|
data/lib/ragweed/debuggerosx.rb
CHANGED
@@ -149,13 +149,15 @@ class Ragweed::Debuggerosx
|
|
149
149
|
rescue Errno::EBUSY
|
150
150
|
# Yes this happens and it's wierd
|
151
151
|
# Not sure it should happen
|
152
|
-
|
153
|
-
|
152
|
+
if $DEBUG
|
153
|
+
puts 'unable to self.continue'
|
154
|
+
puts self.get_registers
|
155
|
+
end
|
154
156
|
retry
|
155
157
|
end
|
156
158
|
when signal == 0x13 #WIFCONTINUED
|
157
159
|
try(:on_continue)
|
158
|
-
else
|
160
|
+
else
|
159
161
|
raise "Unknown signal '#{signal}' recieved: This should not happen - ever."
|
160
162
|
end
|
161
163
|
end
|
@@ -163,32 +165,28 @@ class Ragweed::Debuggerosx
|
|
163
165
|
end
|
164
166
|
|
165
167
|
# these event functions are stubs. Implementations should override these
|
166
|
-
def
|
167
|
-
pp "Single stepping #{ thread } (on_single_step)"
|
168
|
-
pp Ragweed::Wraposx::ThreadInfo.get(thread)
|
168
|
+
def on_attach
|
169
169
|
end
|
170
170
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
171
|
+
def on_detach
|
172
|
+
end
|
173
|
+
|
174
|
+
def on_single_step
|
175
|
+
#puts Ragweed::Wraposx::ThreadInfo.get(thread).inspect
|
174
176
|
end
|
175
177
|
|
176
178
|
def on_exit(status)
|
177
|
-
pp "Exited! (on_exit)"
|
178
179
|
@exited = true
|
179
180
|
end
|
180
181
|
|
181
182
|
def on_signal(signal)
|
182
|
-
pp "Exited with signal #{ signal } (on_signal)"
|
183
183
|
@exited = true
|
184
184
|
end
|
185
185
|
|
186
186
|
def on_stop(signal)
|
187
|
-
pp "#Stopped with signal #{ signal } (on_stop)"
|
188
187
|
end
|
189
188
|
|
190
189
|
def on_continue
|
191
|
-
pp "Continued! (on_continue)"
|
192
190
|
end
|
193
191
|
|
194
192
|
# installs all breakpoints into child process
|
@@ -216,6 +214,7 @@ class Ragweed::Debuggerosx
|
|
216
214
|
r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::ATTACH,@pid,0,0)
|
217
215
|
# Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,1,0)
|
218
216
|
@attached = true
|
217
|
+
on_attach
|
219
218
|
self.hook(opts) if (opts[:hook] and not @hooked)
|
220
219
|
self.install_bps if (opts[:install] and not @installed)
|
221
220
|
return r
|
@@ -228,6 +227,7 @@ class Ragweed::Debuggerosx
|
|
228
227
|
self.uninstall_bps if @installed
|
229
228
|
r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::DETACH,@pid,0,Ragweed::Wraposx::Wait::UNTRACED)
|
230
229
|
@attached = false
|
230
|
+
on_detach
|
231
231
|
self.unhook(opts) if opts[:hook] and @hooked
|
232
232
|
return r
|
233
233
|
end
|