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



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

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



456
457
458
459
460
461
462
463
464
465
# File 'lib/ragweed/debuggertux.rb', line 456

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



122
123
124
125
126
127
128
129
130
131
# File 'lib/ragweed/debuggertux.rb', line 122

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

#attached?Boolean

Returns:

  • (Boolean)


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

def attached?; @attached; end

#breakpoint_clear(ip) ⇒ Object

Remove a breakpoint by ip



349
350
351
352
353
# File 'lib/ragweed/debuggertux.rb', line 349

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



339
340
341
342
343
344
345
346
# File 'lib/ragweed/debuggertux.rb', line 339

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



320
321
322
323
# File 'lib/ragweed/debuggertux.rb', line 320

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

#default_opts(opts) ⇒ Object



555
556
557
# File 'lib/ragweed/debuggertux.rb', line 555

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

#detachObject



325
326
327
328
# File 'lib/ragweed/debuggertux.rb', line 325

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



193
194
195
# File 'lib/ragweed/debuggertux.rb', line 193

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



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

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



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ragweed/debuggertux.rb', line 152

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



467
468
469
470
471
# File 'lib/ragweed/debuggertux.rb', line 467

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



187
188
189
# File 'lib/ragweed/debuggertux.rb', line 187

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

#installed?Boolean

Returns:

  • (Boolean)


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

def installed?; @installed; end

#loop(times = nil) ⇒ Object

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



357
358
359
360
361
362
363
364
365
# File 'lib/ragweed/debuggertux.rb', line 357

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



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/ragweed/debuggertux.rb', line 137

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.


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

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



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
507
508
509
# File 'lib/ragweed/debuggertux.rb', line 482

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.


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

def on_continue()            end

#on_detachObject

This method is abstract.


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

def on_detach()              end

#on_exitObject

This method is abstract.


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

def on_exit()                end

#on_fork_child(pid) ⇒ Object

This method is abstract.


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

def on_fork_child(pid)       end

#on_illegal_instructionObject

This method is abstract.


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

def on_illegal_instruction() end

#on_iot_trapObject

This method is abstract.


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

def on_iot_trap()            end

#on_segvObject

This method is abstract.


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

def on_segv()                end

#on_sigchildObject

This method is abstract.


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

def on_sigchild()            end

#on_sigintObject

This method is abstract.


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

def on_sigint()              end

#on_signalObject

This method is abstract.


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

def on_signal()              end

#on_sigstopObject

This method is abstract.


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

def on_sigstop()             end

#on_sigtermObject

This method is abstract.


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

def on_sigterm()             end

#on_sigtrapObject

This method is abstract.


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

def on_sigtrap()             end

#on_single_stepObject

This method is abstract.


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

def on_single_step()         end


542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/ragweed/debuggertux.rb', line 542

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



299
300
301
# File 'lib/ragweed/debuggertux.rb', line 299

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

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



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

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



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

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



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

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



309
310
311
312
313
314
315
316
317
318
# File 'lib/ragweed/debuggertux.rb', line 309

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



304
305
306
# File 'lib/ragweed/debuggertux.rb', line 304

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

#set_options(option) ⇒ Object

This has not been fully tested yet



116
117
118
# File 'lib/ragweed/debuggertux.rb', line 116

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

#set_registers(regs) ⇒ Object



473
474
475
# File 'lib/ragweed/debuggertux.rb', line 473

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



228
229
230
# File 'lib/ragweed/debuggertux.rb', line 228

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

#single_stepObject



330
331
332
333
# File 'lib/ragweed/debuggertux.rb', line 330

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



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
452
453
454
# File 'lib/ragweed/debuggertux.rb', line 380

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



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

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

#wtermsig(status) ⇒ Object



371
372
373
# File 'lib/ragweed/debuggertux.rb', line 371

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