ragweed 0.1.7.3 → 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|