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.
@@ -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: