Class: Ragweed::Debuggertux

Inherits:
Object show all
Defined in:
lib/ragweed/debuggertux.rb

Overview

Debugger class for Linux You can use this class in 2 ways:

(1) You can create instances of Debuggertux and use them to set and handle

breakpoints.

(2) If you want to do more advanced event handling, you can subclass from

debugger and define your own on_whatever events. If you handle an event
that Debuggertux already handles, call "super", too.

Defined Under Namespace

Classes: Breakpoint

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pid, opts = {}) ⇒ Debuggertux

init object p: pid of process to be debugged opts: default options for automatically doing things (attach and install)



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/ragweed/debuggertux.rb', line 65

def initialize(pid, opts = {}) # Debuggertux Class
  if p.to_i.kind_of? Fixnum
    @pid = pid.to_i
  else
    raise "Provide a PID"
  end

  @opts = opts

  default_opts(opts)
  @installed = false
  @attached = false
  @use_ptrace_for_search = false

  @mapped_regions = Hash.new
  @breakpoints = Hash.new
  @opts.each { |k, v| try(k) if v }

  @process = Ragweed::Process.new(@pid)
end

Instance Attribute Details

#breakpointsObject

Returns the value of attribute breakpoints.



16
17
18
# File 'lib/ragweed/debuggertux.rb', line 16

def breakpoints
  @breakpoints
end

#exitedObject (readonly)

Returns the value of attribute exited.



15
16
17
# File 'lib/ragweed/debuggertux.rb', line 15

def exited
  @exited
end

#mapped_regionsObject

Returns the value of attribute mapped_regions.



16
17
18
# File 'lib/ragweed/debuggertux.rb', line 16

def mapped_regions
  @mapped_regions
end

#pidObject (readonly)

Returns the value of attribute pid.



15
16
17
# File 'lib/ragweed/debuggertux.rb', line 15

def pid
  @pid
end

#processObject

Returns the value of attribute process.



16
17
18
# File 'lib/ragweed/debuggertux.rb', line 16

def process
  @process
end

#signalObject (readonly)

Returns the value of attribute signal.



15
16
17
# File 'lib/ragweed/debuggertux.rb', line 15

def signal
  @signal
end

#statusObject (readonly)

Returns the value of attribute status.



15
16
17
# File 'lib/ragweed/debuggertux.rb', line 15

def status
  @status
end

#use_ptrace_for_searchObject

Returns the value of attribute use_ptrace_for_search.



16
17
18
# File 'lib/ragweed/debuggertux.rb', line 16

def use_ptrace_for_search
  @use_ptrace_for_search
end

Class Method Details

.find_by_regex(rx) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ragweed/debuggertux.rb', line 86

def self.find_by_regex(rx)
  Dir.glob("/proc/*/cmdline").each do |x|
    x.gsub(/^\/proc\/(\d+)\/cmdline$/) do |ln|
      f = File.read(ln)
      if f =~ rx and $1.to_i != ::Process.pid.to_i
        return f
      end
    end
  end
  nil
end

.shared_libraries(p) ⇒ Object

Parse procfs and create a hash containing a listing of each mapped shared object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/ragweed/debuggertux.rb', line 197

def self.shared_libraries(p)
  raise "pid is 0" if p.to_i == 0

  if @shared_objects
    @shared_objects.clear
  else
    @shared_objects = Hash.new
  end

  File.open("/proc/#{p}/maps") do |f|
    f.each_line do |l|
      if l =~ /[a-zA-Z0-9].so/ && l =~ /xp /
        lib = l.split(' ', 6)
        sa = l.split('-', 0)

        next if lib[5] =~ /vdso/

        lib = lib[5].strip
        lib.gsub!(/[\s\n]+/, "")
        @shared_objects.store(sa[0], lib)
      end
    end
  end
  @shared_objects
end

.threads(pid) ⇒ Object



453
454
455
456
457
458
459
460
461
462
# File 'lib/ragweed/debuggertux.rb', line 453

def self.threads(pid)
  a = []
  begin
    a = Dir.entries("/proc/#{pid}/task/")
    a.delete_if {|x| x == '.' || x == '..'}
  rescue
    puts "No such PID: #{pid}"
  end
  a
end

Instance Method Details

#attach(opts = @opts) ⇒ Object

Attach calls install_bps so dont forget to call breakpoint_set BEFORE attach or explicitly call install_bps



119
120
121
122
123
124
125
126
127
128
# File 'lib/ragweed/debuggertux.rb', line 119

def attach(opts=@opts)
  r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
  if r != -1
      @attached = true
      on_attach
      self.install_bps if (opts[:install] and not @installed)
  else
      raise "Attach failed!"
  end
end

#breakpoint_clear(ip) ⇒ Object

Remove a breakpoint by ip



346
347
348
349
350
# File 'lib/ragweed/debuggertux.rb', line 346

def breakpoint_clear(ip)
  bp = @breakpoints[ip]
  return nil if bp.nil?
  bp.uninstall
end

#breakpoint_set(ip, name = "", callable = nil, &block) ⇒ Object

Adds a breakpoint to be installed ip: Insertion point name: name of breakpoint callable: object to .call at breakpoint



336
337
338
339
340
341
342
343
# File 'lib/ragweed/debuggertux.rb', line 336

def breakpoint_set(ip, name="", callable=nil, &block)
  if not callable and block_given?
    callable = block
  end
  @breakpoints.each_key { |k| if k == ip then return end }
  bp = Breakpoint.new(ip, callable, @pid, name)
  @breakpoints[ip] = bp
end

#continueObject



317
318
319
320
# File 'lib/ragweed/debuggertux.rb', line 317

def continue
  on_continue
  Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::CONTINUE, @pid, 0, 0)
end

#default_opts(opts) ⇒ Object



552
553
554
# File 'lib/ragweed/debuggertux.rb', line 552

def default_opts(opts)
  @opts = @opts.merge(opts)
end

#detachObject



322
323
324
325
# File 'lib/ragweed/debuggertux.rb', line 322

def detach
  on_detach
  Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
end

#get_heap_rangeObject Also known as: heap_range

Helper method for retrieving heap range



190
191
192
# File 'lib/ragweed/debuggertux.rb', line 190

def get_heap_range
  get_mapping_by_name('[heap]')
end

#get_mapping_by_name(name, exact = true) ⇒ Object Also known as: mapping_by_name

Return a range via mapping name



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/ragweed/debuggertux.rb', line 164

def get_mapping_by_name(name, exact = true)
  ret = []
  File.open("/proc/#{pid}/maps") do |f|
    f.each_line do |l|
      range, perms, offset, dev, inode, pathname = l.chomp.split(" ",6)
      base, max = range.split('-').map{|x| x.to_i(16)}
      if pathname
        if exact && pathname == name
          ret << range.split('-').map{|x| x.to_i(16)}
        elsif pathname.match(name) and exact == false
          ret << range.split('-').map{|x| x.to_i(16)}
        end
      end
    end
  end
  ret
end

#get_mapping_name(val) ⇒ Object Also known as: mapping_name

Return a name for a range if possible. greedy match returns the first found



149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/ragweed/debuggertux.rb', line 149

def get_mapping_name(val)
  File.open("/proc/#{pid}/maps") do |f|
    f.each_line do |l|
      range, perms, offset, dev, inode, pathname  = l.chomp.split(" ")
      base, max = range.split('-').map{|x| x.to_i(16)}
      if base <= val && val <= max
        return pathname
      end
    end
  end
  nil
end

#get_registersObject



464
465
466
467
468
# File 'lib/ragweed/debuggertux.rb', line 464

def get_registers
  regs = FFI::MemoryPointer.new(Ragweed::Wraptux::PTRegs, 1)
  Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
  return Ragweed::Wraptux::PTRegs.new regs
end

#get_stack_rangeObject Also known as: stack_range

Helper method for retrieving stack range



184
185
186
# File 'lib/ragweed/debuggertux.rb', line 184

def get_stack_range
  get_mapping_by_name('[stack]')
end

#install_bpsObject



98
99
100
101
102
103
# File 'lib/ragweed/debuggertux.rb', line 98

def install_bps
  @breakpoints.each do |k,v|
    v.install
  end
  @installed = true
end

#loop(times = nil) ⇒ Object

loop for wait() times: the number of wait calls to make



354
355
356
357
358
359
360
361
362
# File 'lib/ragweed/debuggertux.rb', line 354

def loop(times=nil)
  if times.kind_of? Numeric
    times.times do
      self.wait
    end
  elsif times.nil?
    self.wait while not @exited
  end
end

#mappedObject

This method returns a hash of mapped regions The hash is also stored as @mapped_regions key = Start address of region value = Size of the region



134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/ragweed/debuggertux.rb', line 134

def mapped
  @mapped_regions.clear if @mapped_regions
  File.open("/proc/#{pid}/maps") do |f|
    f.each_line do |l|
      e = l.split(' ',2).first
      s,e = e.split('-').map{|x| x.to_i(16)}
      sz = e - s
      @mapped_regions.store(s, sz)
    end
  end
  @mapped_regions
end

#on_attachObject

This method is abstract.


509
# File 'lib/ragweed/debuggertux.rb', line 509

def on_attach()              end

#on_breakpointObject

Here we need to do something about the bp we just hit. We have a block to execute. Remember if you implement this on your own make sure to call super, and also realize EIP won’t look correct until this runs



479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/ragweed/debuggertux.rb', line 479

def on_breakpoint
  r = get_registers
  eip = r.eip
  eip -= 1

  # Call the block associated with the breakpoint
  @breakpoints[eip].call(r, self)

  # The block may have called breakpoint_clear
  del = true if !@breakpoints[eip].installed?

  # Uninstall and single step the bp
  @breakpoints[eip].uninstall
  r.eip = eip
  set_registers(r)
  single_step

  # ptrace peektext returns -1 upon reinstallation of bp without calling
  # waitpid() if that occurs the breakpoint cannot be reinstalled
  Ragweed::Wraptux::waitpid(@pid, 0)

  if del == true
      # The breakpoint block may have called breakpoint_clear
      @breakpoints.delete(eip)
  else
      @breakpoints[eip].install
  end
end

#on_continueObject

This method is abstract.


515
# File 'lib/ragweed/debuggertux.rb', line 515

def on_continue()            end

#on_detachObject

This method is abstract.


511
# File 'lib/ragweed/debuggertux.rb', line 511

def on_detach()              end

#on_exitObject

This method is abstract.


517
# File 'lib/ragweed/debuggertux.rb', line 517

def on_exit()                end

#on_fork_child(pid) ⇒ Object

This method is abstract.


529
# File 'lib/ragweed/debuggertux.rb', line 529

def on_fork_child(pid)       end

#on_illegal_instructionObject

This method is abstract.


525
# File 'lib/ragweed/debuggertux.rb', line 525

def on_illegal_instruction() end

#on_iot_trapObject

This method is abstract.


537
# File 'lib/ragweed/debuggertux.rb', line 537

def on_iot_trap()            end

#on_segvObject

This method is abstract.


523
# File 'lib/ragweed/debuggertux.rb', line 523

def on_segv()                end

#on_sigchildObject

This method is abstract.


531
# File 'lib/ragweed/debuggertux.rb', line 531

def on_sigchild()            end

#on_sigintObject

This method is abstract.


521
# File 'lib/ragweed/debuggertux.rb', line 521

def on_sigint()              end

#on_signalObject

This method is abstract.


519
# File 'lib/ragweed/debuggertux.rb', line 519

def on_signal()              end

#on_sigstopObject

This method is abstract.


535
# File 'lib/ragweed/debuggertux.rb', line 535

def on_sigstop()             end

#on_sigtermObject

This method is abstract.


533
# File 'lib/ragweed/debuggertux.rb', line 533

def on_sigterm()             end

#on_sigtrapObject

This method is abstract.


527
# File 'lib/ragweed/debuggertux.rb', line 527

def on_sigtrap()             end

#on_single_stepObject

This method is abstract.


513
# File 'lib/ragweed/debuggertux.rb', line 513

def on_single_step()         end


539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/ragweed/debuggertux.rb', line 539

def print_registers
  regs = get_registers
  puts "eip %08x" % regs.eip
  puts "ebp %08x" % regs.ebp
  puts "esi %08x" % regs.esi
  puts "edi %08x" % regs.edi
  puts "esp %08x" % regs.esp
  puts "eax %08x" % regs.eax
  puts "ebx %08x" % regs.ebx
  puts "ecx %08x" % regs.ecx
  puts "edx %08x" % regs.edx
end

#search_heap(val, &block) ⇒ Object

Search the heap for a value, returns an array of matches



296
297
298
# File 'lib/ragweed/debuggertux.rb', line 296

def search_heap(val, &block)
  search_mem_by_name('heap', val, &block)
end

#search_mem_by_name(name, val, &block) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/ragweed/debuggertux.rb', line 259

def search_mem_by_name(name, val, &block)
  loc = []
  File.open("/proc/#{pid}/maps") do |f|
    f.each_line do |l|
      if l =~ /\[#{name}\]/
        s,e = l.split('-')
        e = e.split(' ').first
        s = s.to_i(16)
        e = e.to_i(16)
        sz = e - s
        max = s + sz
        loc << search_page(s, max, val, &block)
      end
    end
  end
  loc    
end

#search_mem_by_permission(perm, val, &block) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/ragweed/debuggertux.rb', line 277

def search_mem_by_permission(perm, val, &block)
  loc = []
  File.open("/proc/#{pid}/maps") do |f|
    f.each_line do |l|
      if l.split(' ')[1] =~ /#{perm}/
        s,e = l.split('-')
        e = e.split(' ').first
        s = s.to_i(16)
        e = e.to_i(16)
        sz = e - s
        max = s + sz
        loc << search_page(s, max, val, &block)
      end
    end
  end
  loc    
end

#search_page(base, max, val, &block) ⇒ Object

Search a specific page for a value Should be used by most search methods



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/ragweed/debuggertux.rb', line 231

def search_page(base, max, val, &block)
  loc = []
  if self.use_ptrace_for_search == true
      while base.to_i < max.to_i
          r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @pid, base, 0)
          loc << base if r == val
          base += 4
          yield loc if block_given?
      end
  else
      sz = max.to_i - base.to_i
      d = File.new("/proc/#{pid}/mem")
      d.seek(base.to_i, IO::SEEK_SET)
      b = d.read(sz)
      i = 0
      while(i < sz)
        if val == b[i,4].unpack('L')
          loc << base.to_i + i
          yield(base.to_i + i) if block_given?
        end
        i += 4
      end
      d.close
  end

  loc
end

#search_process(val, &block) ⇒ Object

Search all mapped regions for a value



306
307
308
309
310
311
312
313
314
315
# File 'lib/ragweed/debuggertux.rb', line 306

def search_process(val, &block)
  loc = []
  self.mapped
  @mapped_regions.each_pair do |k,v|
      next if k == 0 or v == 0
      max = k+v
      loc << search_page(k, max, val, &block)
  end
  loc
end

#search_stack(val, &block) ⇒ Object

Search the stack for a value, returns an array of matches



301
302
303
# File 'lib/ragweed/debuggertux.rb', line 301

def search_stack(val, &block)
  search_mem_by_name('stack', val, &block)
end

#set_options(option) ⇒ Object

This has not been fully tested yet



113
114
115
# File 'lib/ragweed/debuggertux.rb', line 113

def set_options(option)
  r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETOPTIONS, @pid, 0, option)
end

#set_registers(regs) ⇒ Object



470
471
472
# File 'lib/ragweed/debuggertux.rb', line 470

def set_registers(regs)
  Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, regs.to_ptr.address)
end

#shared_librariesObject

instance method for above returns a hash of the mapped shared libraries



225
226
227
# File 'lib/ragweed/debuggertux.rb', line 225

def shared_libraries
  self.class.shared_libraries(@pid)
end

#single_stepObject



327
328
329
330
# File 'lib/ragweed/debuggertux.rb', line 327

def single_step
  on_single_step
  ret = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
end

#uninstall_bpsObject



105
106
107
108
109
110
# File 'lib/ragweed/debuggertux.rb', line 105

def uninstall_bps
  @breakpoints.each do |k,v|
    v.uninstall
  end
  @installed = false
end

#wait(opts = 0) ⇒ Object

This wait must be smart, it has to wait for a signal when SIGTRAP is received we need to see if one of our breakpoints has fired. If it has then execute the block originally stored with it. If its a different signal, then process it accordingly and move on



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/ragweed/debuggertux.rb', line 377

def wait(opts = 0)
  r, status = Ragweed::Wraptux::waitpid(@pid, opts)
  wstatus = wtermsig(status)
  @signal = wexitstatus(status)
  event_code = (status >> 16)
  found = false

  if r[0] != -1    ## Check the ret
    case ## FIXME - I need better logic (use Signal module)
    when wstatus == 0 ##WIFEXITED
      @exited = true
      try(:on_exit)
    when wstatus != 0x7f ##WIFSIGNALED
      @exited = false
      try(:on_signal)
    when @signal == Ragweed::Wraptux::Signal::SIGINT
      try(:on_sigint)
      self.continue
    when @signal == Ragweed::Wraptux::Signal::SIGSEGV
      try(:on_segv)
    when @signal == Ragweed::Wraptux::Signal::SIGILL
      try(:on_illegal_instruction)
    when @signal == Ragweed::Wraptux::Signal::SIGIOT
      try(:on_iot_trap)
      self.continue
    when @signal == Ragweed::Wraptux::Signal::SIGTRAP
      try(:on_sigtrap)
      r = self.get_registers
      eip = r.eip
      eip -= 1
      case
        when @breakpoints.has_key?(eip)
          found = true
          try(:on_breakpoint)
          self.continue
        when event_code == Ragweed::Wraptux::Ptrace::EventCodes::FORK
              p = FFI::MemoryPointer.new(:int, 1)
              Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETEVENTMSG, @pid, 0, p.to_i)
              ## Fix up the PID in each breakpoint
              if (1..65535) === p.get_int32(0) && @opts[:fork] == true
                  @breakpoints.each_pair do |k,v|
                      v.each do |b|
                          b.bppid = p[:pid]
                      end
                  end

                  @pid = p[:pid]
                  try(:on_fork_child, @pid)
              end
          when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXEC
          when event_code == Ragweed::Wraptux::Ptrace::EventCodes::CLONE
          when event_code == Ragweed::Wraptux::Ptrace::EventCodes::VFORK
          when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXIT
              ## Not done yet
        else
          self.continue
      end
    when @signal == Ragweed::Wraptux::Signal::SIGCHLD
      try(:on_sigchild)
    when @signal == Ragweed::Wraptux::Signal::SIGTERM
      try(:on_sigterm)
    when @signal == Ragweed::Wraptux::Signal::SIGCONT
      try(:on_continue)
      self.continue
    when @signal == Ragweed::Wraptux::Signal::SIGSTOP
      try(:on_sigstop)
      Ragweed::Wraptux::kill(@pid, Ragweed::Wraptux::Signal::SIGCONT)
      self.continue
    when @signal == Ragweed::Wraptux::Signal::SIGWINCH
      self.continue
    else
      raise "Add more signal handlers (##{@signal})"
    end
  end
end

#wexitstatus(status) ⇒ Object



364
365
366
# File 'lib/ragweed/debuggertux.rb', line 364

def wexitstatus(status)
  (((status) & 0xff00) >> 8)
end

#wtermsig(status) ⇒ Object



368
369
370
# File 'lib/ragweed/debuggertux.rb', line 368

def wtermsig(status)
  ((status) & 0x7f)
end