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.
@@ -1,5 +1,4 @@
1
1
  require ::File.join(::File.dirname(__FILE__),'wraptux')
2
- ## Modeled after wraposx written by tduehr
3
2
 
4
3
  module Ragweed; end
5
4
 
@@ -13,102 +12,87 @@ module Ragweed; end
13
12
  ## debugger and define your own on_whatever events. If you handle an event
14
13
  ## that Debuggertux already handles, call "super", too.
15
14
  class Ragweed::Debuggertux
16
- # include Ragweed
17
-
18
- attr_reader :pid
19
- attr_reader :status
20
- attr_reader :exited
21
- attr_accessor :breakpoints
15
+ attr_reader :pid, :status, :exited
16
+ attr_accessor :breakpoints, :mapped_regions
22
17
 
23
18
  ## Class to handle installing/uninstalling breakpoints
24
19
  class Breakpoint
25
20
 
26
- INT3 = 0xCC ## obviously x86 specific debugger here
21
+ INT3 = 0xCC
27
22
 
28
- attr_accessor :orig
23
+ attr_accessor :orig, :bppid, :function, :installed
29
24
  attr_reader :addr
30
- attr_accessor :function
31
25
 
32
- ## bp: parent for method_missing calls
33
26
  ## ip: insertion point
34
27
  ## callable: lambda to be called when breakpoint is hit
28
+ ## p: process ID
35
29
  ## name: name of breakpoint
36
- def initialize(bp, ip, callable, name = "")
37
- @@bpid ||= 0
38
- @bp = bp
30
+ def initialize(ip, callable, p, name = "")
31
+ @bppid = p
39
32
  @function = name
40
33
  @addr = ip
41
34
  @callable = callable
42
35
  @installed = false
36
+ @exited = false
43
37
  @orig = 0
44
- @bpid = (@@bpid += 1)
45
- end ## breakpoint initialize
38
+ end
46
39
 
47
- ## Install a breakpoint (replace instruction with int3)
48
40
  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
41
+ @orig = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @bppid, @addr, 0)
51
42
  if @orig != -1
52
- new = (@orig & ~0xff) | INT3;
53
- Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, new)
43
+ n = (@orig & ~0xff) | INT3;
44
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, @bppid, @addr, n)
54
45
  @installed = true
55
46
  else
56
47
  @installed = false
57
48
  end
58
49
  end
59
50
 
60
- ## Uninstall the breakpoint
61
51
  def uninstall
62
- ## Put back the original instruction
63
52
  if @orig != INT3
64
- Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, @orig)
53
+ a = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, @bppid, @addr, @orig)
65
54
  @installed = false
66
55
  end
67
56
  end
68
57
 
69
58
  def installed?; @installed; end
70
59
  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
60
+ end
73
61
 
74
62
  ## init object
75
63
  ## p: pid of process to be debugged
76
64
  ## opts: default options for automatically doing things (attach and install)
77
- def initialize(pid,opts={}) ## Debuggertux Class
78
- @pid = pid
65
+ def initialize(pid, opts) ## Debuggertux Class
66
+ if p.to_i.kind_of? Fixnum
67
+ @pid = pid.to_i
68
+ else
69
+ raise "Provide a PID"
70
+ end
79
71
 
80
- ## FIXME - globals are bad...
81
- $ppid = @pid ## temporary work around
82
72
  @opts = opts
83
73
 
84
74
  default_opts(opts)
85
75
  @installed = false
86
76
  @attached = false
87
77
 
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}
78
+ @mapped_regions = Hash.new
79
+ @breakpoints = Hash.new
80
+ @opts.each { |k, v| try(k) if v }
98
81
  end
99
82
 
100
- ## This is crude!
101
83
  def self.find_by_regex(rx)
102
84
  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
85
+ a.delete_if { |x| x == '.' }
86
+ a.delete_if { |x| x == '..' }
87
+ a.delete_if { |x| x =~ /[a-z]/i }
88
+
106
89
  a.each do |x|
107
90
  f = File.read("/proc/#{x}/cmdline")
108
- if f =~ rx
91
+ if f =~ rx and x.to_i != Process.pid.to_i
109
92
  return x
110
93
  end
111
94
  end
95
+ return nil
112
96
  end
113
97
 
114
98
  def install_bps
@@ -125,12 +109,125 @@ class Ragweed::Debuggertux
125
109
  @installed = false
126
110
  end
127
111
 
112
+ ## This has not been fully tested yet
113
+ def set_options(option)
114
+ r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETOPTIONS, @pid, 0, option)
115
+ end
116
+
128
117
  ## Attach calls install_bps so dont forget to call breakpoint_set
129
118
  ## BEFORE attach or explicitly call install_bps
130
119
  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)
120
+ r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
121
+ if r != -1
122
+ @attached = true
123
+ on_attach
124
+ self.install_bps if (opts[:install] and not @installed)
125
+ else
126
+ raise "Attach failed!"
127
+ end
128
+ end
129
+
130
+ ## This method returns a hash of mapped regions
131
+ ## The hash is also stored as @mapped_regions
132
+ ## key = Start address of region
133
+ ## value = Size of the region
134
+ def mapped
135
+ @mapped_regions.clear if @mapped_regions
136
+
137
+ File.read("/proc/#{pid}/maps").each_line do |l|
138
+ s,e = l.split('-')
139
+ e = e.split(' ').first
140
+ sz = e.to_i(16) - s.to_i(16)
141
+ @mapped_regions.store(s.to_i(16), sz)
142
+ end
143
+ end
144
+
145
+ ## Return a name for a range if possible
146
+ def get_mapping_name(val)
147
+ File.read("/proc/#{pid}/maps").each_line do |l|
148
+ base = l.split('-').first
149
+ max = l[0,17].split('-',2)[1]
150
+ if base.to_i(16) <= val && val <= max.to_i(16)
151
+ return l.split(' ').last
152
+ end
153
+ end
154
+ nil
155
+ end
156
+
157
+ ## Parse procfs and create a hash containing
158
+ ## a listing of each mapped shared object
159
+ def self.shared_libraries(p)
160
+
161
+ raise "pid is 0" if p.to_i == 0
162
+
163
+ if @shared_objects
164
+ @shared_objects.clear
165
+ else
166
+ @shared_objects = Hash.new
167
+ end
168
+
169
+ File.read("/proc/#{p}/maps").each_line do |l|
170
+ if l =~ /[a-zA-Z0-9].so/ && l =~ /xp /
171
+ lib = l.split(' ', 6)
172
+ sa = l.split('-', 0)
173
+
174
+ if lib[5] =~ /vdso/
175
+ next
176
+ end
177
+
178
+ lib = lib[5].strip
179
+ lib.gsub!(/[\s\n]+/, "")
180
+ @shared_objects.store(sa[0], lib)
181
+ end
182
+ end
183
+ return @shared_objects
184
+ end
185
+
186
+ ## Search a specific page for a value
187
+ ## Should be used by most of the search_* methods
188
+ def search_page(base, max, val)
189
+ loc = Array.new
190
+
191
+ while base.to_i < max.to_i
192
+ r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @pid, base, 0)
193
+ if r == val
194
+ loc.push(base)
195
+ end
196
+ base += 1
197
+ end
198
+
199
+ loc
200
+ end
201
+
202
+ ## Search the heap for a value
203
+ def search_heap(val)
204
+ loc = Array.new
205
+ File.read("/proc/#{pid}/maps").each_line do |l|
206
+ if l =~ /\[heap\]/
207
+ s,e = l.split('-')
208
+ e = e.split(' ').first
209
+ s = s.to_i(16)
210
+ e = e.to_i(16)
211
+ sz = e - s
212
+ max = s + sz
213
+ loc = search_page(s, max, val)
214
+ end
215
+ end
216
+ loc
217
+ end
218
+
219
+ ## Search all mapped regions for a value
220
+ def search_process(val)
221
+ loc = Array.new
222
+ self.mapped
223
+ @mapped_regions.each_pair do |k,v|
224
+ if k == 0 or v == 0
225
+ next
226
+ end
227
+ max = k+v
228
+ loc.concat(search_page(k, max, val))
229
+ end
230
+ loc
134
231
  end
135
232
 
136
233
  def continue
@@ -143,8 +240,8 @@ class Ragweed::Debuggertux
143
240
  Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
144
241
  end
145
242
 
146
- def stepp
147
- on_stepp
243
+ def single_step
244
+ on_single_step
148
245
  ret = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
149
246
  end
150
247
 
@@ -156,39 +253,20 @@ class Ragweed::Debuggertux
156
253
  if not callable and block_given?
157
254
  callable = block
158
255
  end
159
- @breakpoints[ip] << Breakpoint.new(self, ip, callable, name)
256
+ @breakpoints.each_key { |k| if k == ip then return end }
257
+ bp = Breakpoint.new(ip, callable, @pid, name)
258
+ @breakpoints[ip] = bp
160
259
  end
161
260
 
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
261
+ ## Remove a breakpoint by ip
262
+ def breakpoint_clear(ip)
263
+ bp = @breakpoints[ip]
264
+ return nil if bp.nil?
265
+ bp.uninstall
187
266
  end
188
267
 
189
- ##loop for wait()
190
- ##times: the number of wait calls to make
191
- ## if nil loop will continue indefinitely
268
+ ## loop for wait()
269
+ ## times: the number of wait calls to make
192
270
  def loop(times=nil)
193
271
  if times.kind_of? Numeric
194
272
  times.times do
@@ -199,19 +277,28 @@ class Ragweed::Debuggertux
199
277
  end
200
278
  end
201
279
 
280
+ def wexitstatus(status)
281
+ (((status) & 0xff00) >> 8)
282
+ end
283
+
284
+ def wtermsig(status)
285
+ ((status) & 0x7f)
286
+ end
287
+
202
288
  ## This wait must be smart, it has to wait for a signal
203
289
  ## when SIGTRAP is received we need to see if one of our
204
290
  ## breakpoints has fired. If it has then execute the block
205
291
  ## originally stored with it. If its a different signal,
206
292
  ## then process it accordingly and move on
207
293
  def wait(opts = 0)
208
- r = Ragweed::Wraptux::waitpid(@pid,opts)
209
- status = r[1]
210
- wstatus = status & 0x7f
211
- signal = status >> 8
294
+ r, status = Ragweed::Wraptux::waitpid(@pid, opts)
295
+ wstatus = wtermsig(status)
296
+ signal = wexitstatus(status)
297
+ event_code = (status >> 16)
212
298
  found = false
213
- if r[0] != 0 ## Check the ret
214
- case ## FIXME - I need better logic (use Signal module)
299
+
300
+ if r[0] != -1 ## Check the ret
301
+ case ## FIXME - I need better logic (use Signal module)
215
302
  when wstatus == 0 ##WIFEXITED
216
303
  @exited = true
217
304
  self.on_exit
@@ -225,119 +312,159 @@ class Ragweed::Debuggertux
225
312
  when signal == Ragweed::Wraptux::Signal::SIGILL
226
313
  self.on_illegalinst
227
314
  when signal == Ragweed::Wraptux::Signal::SIGTRAP
228
- ## Check if EIP matches a breakpoint we have set
315
+ self.on_sigtrap
229
316
  r = self.get_registers
230
- eip = r[:eip]
317
+ eip = r.eip
231
318
  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"
319
+ case
320
+ when @breakpoints.has_key?(eip)
321
+ found = true
322
+ self.on_breakpoint
323
+ self.continue
324
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::FORK
325
+ p = FFI::MemoryPointer.new(:int, 1)
326
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETEVENTMSG, @pid, 0, p.to_i)
327
+ ## Fix up the PID in each breakpoint
328
+ if (1..65535) === p.get_int32(0) && @opts[:fork] == true
329
+ @breakpoints.each_pair do |k,v|
330
+ v.each do |b|
331
+ b.bppid = p[:pid]
332
+ end
333
+ end
334
+
335
+ @pid = p[:pid]
336
+ self.on_fork_child(@pid)
337
+ end
338
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXEC
339
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::CLONE
340
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::VFORK
341
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXIT
342
+ ## Not done yet
343
+ else
344
+ self.continue
237
345
  end
238
- self.continue
346
+ when signal == Ragweed::Wraptux::Signal::SIGCHLD
347
+ self.on_sigchild
348
+ when signal == Ragweed::Wraptux::Signal::SIGTERM
349
+ self.on_sigterm
239
350
  when signal == Ragweed::Wraptux::Signal::SIGCONT
240
351
  self.continue
241
352
  when signal == Ragweed::Wraptux::Signal::SIGSTOP
353
+ self.on_sigstop
354
+ Ragweed::Wraptux::kill(@pid, Ragweed::Wraptux::Signal::SIGCONT)
242
355
  self.continue
356
+ when signal == Ragweed::Wraptux::Signal::SIGWINCH
357
+ self.continue
243
358
  else
244
359
  raise "Add more signal handlers (##{signal})"
245
360
  end
246
361
  end
247
362
  end
248
363
 
249
- ## Return an array of thread PIDs
250
364
  def self.threads(pid)
251
- a = Dir.entries("/proc/#{pid}/task/")
365
+ begin
366
+ a = Dir.entries("/proc/#{pid}/task/")
367
+ rescue
368
+ puts "No such PID: #{pid}"
369
+ return
370
+ end
252
371
  a.delete_if { |x| x == '.' }
253
372
  a.delete_if { |x| x == '..' }
254
373
  end
255
374
 
256
- ## Gets the registers for the given process
257
375
  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)
376
+ regs = FFI::MemoryPointer.new(Ragweed::Wraptux::PTRegs, 1)
262
377
  Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
263
- return regs
378
+ return Ragweed::Wraptux::PTRegs.new regs
264
379
  end
265
380
 
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)
381
+ def set_registers(regs)
382
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, regs.to_ptr.address)
269
383
  end
270
384
 
271
385
  ## Here we need to do something about the bp
272
- ## we just hit. We have a block to execute
386
+ ## we just hit. We have a block to execute.
387
+ ## Remember if you implement this on your own
388
+ ## make sure to call super, and also realize
389
+ ## EIP won't look correct until this runs
273
390
  def on_breakpoint
274
- r = self.get_registers
275
- eip = r[:eip]
391
+ r = get_registers
392
+ eip = r.eip
276
393
  eip -= 1
277
394
 
278
395
  ## Call the block associated with the breakpoint
279
396
  @breakpoints[eip].call(r, self)
280
397
 
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
398
+ ## The block may have called breakpoint_clear
399
+ del = true if !@breakpoints[eip].installed?
400
+
401
+ ## Uninstall and single step the bp
402
+ @breakpoints[eip].uninstall
403
+ r.eip = eip
404
+ set_registers(r)
405
+ single_step
406
+
407
+ ## ptrace peektext returns -1 upon reinstallation of bp without calling
408
+ ## waitpid() if that occurs the breakpoint cannot be reinstalled
409
+ Ragweed::Wraptux::waitpid(@pid, 0)
410
+
411
+ if del == true
412
+ ## The breakpoint block may have called breakpoint_clear
413
+ @breakpoints.delete(eip)
414
+ else
415
+ @breakpoints[eip].install
290
416
  end
291
- end
417
+ end
292
418
 
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]
419
+ def print_registers
420
+ regs = get_registers
421
+ puts "eip %08x" % regs.eip
422
+ puts "esi %08x" % regs.esi
423
+ puts "edi %08x" % regs.edi
424
+ puts "esp %08x" % regs.esp
425
+ puts "eax %08x" % regs.eax
426
+ puts "ebx %08x" % regs.ebx
427
+ puts "ecx %08x" % regs.ecx
428
+ puts "edx %08x" % regs.edx
303
429
  end
304
430
 
305
431
  def on_exit
306
- #puts "process exited"
307
432
  end
308
433
 
309
434
  def on_illegalinst
310
- #puts "illegal instruction"
311
- exit
312
435
  end
313
436
 
314
437
  def on_attach
315
- #puts "attached to process"
316
438
  end
317
439
 
318
440
  def on_detach
319
- #puts "process detached"
441
+ end
442
+
443
+ def on_sigchild
444
+ end
445
+
446
+ def on_sigterm
447
+ end
448
+
449
+ def on_sigtrap
320
450
  end
321
451
 
322
452
  def on_continue
323
- #puts "process continued"
324
453
  end
325
454
 
326
- def on_stopped
327
- #puts "process stopped"
455
+ def on_sigstop
328
456
  end
329
457
 
330
458
  def on_signal
331
- #puts "process received signal"
332
459
  end
333
460
 
334
- def on_stepp
335
- #puts "single stepping"
461
+ def on_single_step
462
+ end
463
+
464
+ def on_fork_child(pid)
336
465
  end
337
466
 
338
467
  def on_segv
339
- print_regs
340
- exit
341
468
  end
342
469
 
343
470
  def default_opts(opts)
data/lib/ragweed/rasm.rb CHANGED
@@ -5,7 +5,7 @@ module Ragweed; end
5
5
  module Ragweed::Rasm
6
6
 
7
7
  # :stopdoc:
8
- VERSION = '0.1.7.3'
8
+ VERSION = File.read(File.join(File.dirname(__FILE__),"..","..","VERSION")).strip
9
9
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
10
10
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
11
11
  # :startdoc: