Class: Ragweed::Debugger32

Inherits:
Object show all
Includes:
Ragweed
Defined in:
lib/ragweed/debugger32.rb,
lib/ragweed/wrap32/hooks.rb

Overview

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

(1) You can create instances of Debugger 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 Debugger already handles, call "super", too.

Defined Under Namespace

Classes: Breakpoint

Constant Summary

Constants included from Ragweed

LIBPATH, PATH, VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Ragweed

libpath, path, require_all_libs_relative_to, require_os_libs_relative_to, version

Constructor Details

#initialize(p) ⇒ Debugger32

Returns a new instance of Debugger32.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ragweed/debugger32.rb', line 79

def initialize(p)
  # grab debug privilege at least once
  @@token ||= Ragweed::Wrap32::ProcessToken.new.grant('seDebugPrivilege')

  p = Process.new(p) if p.kind_of? Numeric
  @p = p
  @steppers = []
  @handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
  @attached = false

  ## breakpoints is a hash with a key being the breakpoint
  ## addr and the value being a Breakpoint class
  @breakpoints = Hash.new

  ## We want to ignore ntdll!DbgBreakPoint 
  @ntdll_dbg_break_point = @p.get_proc_remote('ntdll!DbgBreakPoint')
end

Instance Attribute Details

#eventObject (readonly)

This will preserve the last event seen, but as read only useful if you want to pass around the ragweed object after an event has occured (post-mortem crash analysis)



18
19
20
# File 'lib/ragweed/debugger32.rb', line 18

def event
  @event
end

Class Method Details

.find_by_regex(rx) ⇒ Object



70
71
72
73
74
75
76
77
# File 'lib/ragweed/debugger32.rb', line 70

def self.find_by_regex(rx)
  Ragweed::Wrap32::all_processes do |p|
    if p.szExeFile =~ rx
      return self.new(p.th32ProcessID)
    end
  end
  nil
end

Instance Method Details

#attachObject

This is called implicitly by Debugger#wait. Attaches to the child process for debugging



397
398
399
400
401
402
403
404
405
# File 'lib/ragweed/debugger32.rb', line 397

def attach
  Ragweed::Wrap32::debug_active_process(@p.pid)
  Ragweed::Wrap32::debug_set_process_kill_on_exit
  @attached = true
  try(:on_attach)
  @breakpoints.each_pair do |k, bp|
    bp.install
  end
end

#breakpoint_clear(ip) ⇒ Object

Clear a breakpoint by ip



176
177
178
179
180
181
182
183
184
185
# File 'lib/ragweed/debugger32.rb', line 176

def breakpoint_clear(ip)
  bp = @breakpoints[ip]

  if bp.nil?
      return nil
  end

  bp.uninstall
  @breakpoints.delete(ip)
end

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

set a breakpoint given an address, which can also be a string in the form “module!function”, as in, “user32!SendMessageW”. Be aware that the symbol lookup takes place in an injected thread; it’s safer to use literal addresses when possible.

to handle the breakpoint, pass a block to this method, which will be called when the breakpoint hits.

breakpoints are always re-set after firing. If you don’t want them to be re-set, unset them manually.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/ragweed/debugger32.rb', line 146

def breakpoint_set(ip, callable=nil, &block)
  callable = block if not callable and block_given?

  def_status = false

  # This is usually 'Module!Function' or 'Module!0x1234'
  if @p.is_breakpoint_deferred(ip) == true
      def_status = true
  else
      def_status = false
      ip = @p.get_proc_remote(ip)
  end

  # If we cant immediately set the breakpoint
  # mark it as deferred and wait till later
  # Sometimes *_proc_remote() will return the
  # name indicating failure (just in case)
  if ip == 0 or ip == 0xFFFFFFFF or ip.kind_of? String
    def_status = true
  else
    def_status = false
  end

  # Dont want duplicate breakpoint objects
  @breakpoints.each_key { |k| if k == ip then return end }
  bp = Breakpoint.new(@p, ip, def_status, callable)
  @breakpoints[ip] = bp
end

#context(tid_or_event) ⇒ Object

convenience: either from a TID or a BreakpointEvent, get the thread context.



127
128
129
130
131
132
133
134
# File 'lib/ragweed/debugger32.rb', line 127

def context(tid_or_event)
  if not tid_or_event.kind_of? Numeric
    tid = tid_or_event.tid
  else
    tid = tid_or_event
  end
  Ragweed::Wrap32::open_thread(tid) { |h| Ragweed::Wrap32::get_thread_context(h) }
end

#get_dll_name(ev) ⇒ Object

FIX: this method should be a bit more descriptive in its naming



227
228
229
230
231
232
233
234
# File 'lib/ragweed/debugger32.rb', line 227

def get_dll_name(ev)
  name = Ragweed::Wrap32::get_mapped_filename(@p.handle, ev.base_of_dll, 1024)
  name.gsub!(/[\n]+/,'')
  name.gsub!(/[^\x21-\x7e]/,'')
  i = name.index('\0')
  i ||= name.size
  return name[0, i]
end

#hook(ip, nargs, callable = nil, &block) ⇒ Object

Hook function calls nargs is the number of arguments taken by function at ip callable/block is called with ev, ctx, dir (:enter or :leave), and args Array (see examples/hook_notepad.rb) default handler prints arguments



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ragweed/wrap32/hooks.rb', line 6

def hook(ip, nargs, callable=nil, &block)

  callable ||= block || lambda do |ev,ctx,dir,args|
    # puts args.map{|a| "%08x" % a}.join(',')
  end

  breakpoint_set(ip) do |ev,ctx|
    esp = process.read32(ctx.esp)
    nargs = nargs.to_i

    if nargs >= 1
      args = (1..nargs).map {|i| process.read32(ctx.esp + 4*i)}
    end

    # set exit bpoint
    # We cant always set a leave bp due to
    # calling conventions but we can avoid
    # a crash by setting a breakpoint on
    # the wrong address. So we attempt to
    # get an idea of where the instruction
    # is mapped.
    eip = ctx.eip
    if esp != 0 #and esp > (eip & 0xf0000000)
      breakpoint_set(esp) do |ev,ctx|
        callable.call(ev, ctx, :leave, args)
        breakpoint_clear(esp)
      end.install
    end

    # Call the block sent to hook()
    callable.call(ev, ctx, :enter, args)
  end
end

#loopObject

Debug loop



389
390
391
392
393
# File 'lib/ragweed/debugger32.rb', line 389

def loop
  while true
    wait
  end
end

#on_alignment(ev) ⇒ Object

This method is abstract.


301
# File 'lib/ragweed/debugger32.rb', line 301

def on_alignment(ev)            end

#on_attachObject

This method is abstract.


323
# File 'lib/ragweed/debugger32.rb', line 323

def on_attach()                 end

#on_bounds(ev) ⇒ Object

This method is abstract.


303
# File 'lib/ragweed/debugger32.rb', line 303

def on_bounds(ev)               end

#on_breakpoint(ev) ⇒ Object

handle a breakpoint event: call handlers for the breakpoint, step past and reset it.



189
190
191
192
193
194
195
196
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
222
223
224
# File 'lib/ragweed/debugger32.rb', line 189

def on_breakpoint(ev)
    ctx = context(ev)
    eip = ev.exception_address

    if eip == @ntdll_dbg_break_point
      return
    end

    @breakpoints[eip].uninstall

    # Call the block passed to breakpoint_set
    # which may have been passed through hook()
    @breakpoints[eip].call(ev, ctx)

    # single step past the instruction...
    step(ev.tid, (onestep = lambda do |ev, ctx|
      if ev.exception_address != eip
        # ... then re-install the breakpoint ...
        if not @breakpoints[eip].nil?
          @breakpoints[eip].install
        end
        # ... and stop single-stepping.
        unstep(ev.tid, onestep)
      end
    end))

    # Put execution back where it's supposed to be...
    Ragweed::Wrap32::open_thread(ev.tid) do |h|
      ctx = context(ev)
      ctx.eip = eip ## eip was ev.exception_address
      Ragweed::Wrap32::set_thread_context(h, ctx)
    end

  # Tell the target to stop handling this event
  @handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
end

#on_buffer_overrun(ev) ⇒ Object

This method is abstract.


319
# File 'lib/ragweed/debugger32.rb', line 319

def on_buffer_overrun(ev)       end

#on_create_process(ev) ⇒ Object

This method is abstract.


287
# File 'lib/ragweed/debugger32.rb', line 287

def on_create_process(ev)       end

#on_create_thread(ev) ⇒ Object

This method is abstract.


289
# File 'lib/ragweed/debugger32.rb', line 289

def on_create_thread(ev)        end

#on_divide_by_zero(ev) ⇒ Object

This method is abstract.


305
# File 'lib/ragweed/debugger32.rb', line 305

def on_divide_by_zero(ev)       end

#on_exit_process(ev) ⇒ Object

This is sort of insane but most of my programs are just debug loops, so if you don’t do this, they just hang when the target closes.



282
283
284
# File 'lib/ragweed/debugger32.rb', line 282

def on_exit_process(ev)
  exit(1)
end

#on_exit_thread(ev) ⇒ Object

This method is abstract.


291
# File 'lib/ragweed/debugger32.rb', line 291

def on_exit_thread(ev)          end

#on_guard_page(ev) ⇒ Object

This method is abstract.


299
# File 'lib/ragweed/debugger32.rb', line 299

def on_guard_page(ev)           end

#on_heap_corruption(ev) ⇒ Object

This method is abstract.


317
# File 'lib/ragweed/debugger32.rb', line 317

def on_heap_corruption(ev)      end

#on_illegal_instruction(ev) ⇒ Object

This method is abstract.


311
# File 'lib/ragweed/debugger32.rb', line 311

def on_illegal_instruction(ev)  end

#on_int_overflow(ev) ⇒ Object

This method is abstract.


307
# File 'lib/ragweed/debugger32.rb', line 307

def on_int_overflow(ev)         end

#on_invalid_disposition(ev) ⇒ Object

This method is abstract.


321
# File 'lib/ragweed/debugger32.rb', line 321

def on_invalid_disposition(ev)  end

#on_invalid_handle(ev) ⇒ Object

This method is abstract.


309
# File 'lib/ragweed/debugger32.rb', line 309

def on_invalid_handle(ev)       end

#on_load_dll(ev) ⇒ Object



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
261
262
# File 'lib/ragweed/debugger32.rb', line 236

def on_load_dll(ev)
  dll_name = get_dll_name(ev)

  ## Temporary breakpoint dup is used because
  ## Ruby 1.9 will not support insertion into
  ## @breakpoints while iterating through it
  tmp_bp = @breakpoints.dup
  tmp_bp.each_pair do |k,bp|
      ## If the bp is a string its probably deferred
      next if !bp.addr.kind_of?String

      m,f = bp.addr.split('!')

      if dll_name =~ /#{m}/i
          deferred = bp.deferred

          bp.deferred = false if deferred == true

          new_addr = bp.deferred_install(ev.file_handle, ev.base_of_dll)

          if !new_addr.nil?
              @breakpoints[new_addr] = bp.dup
              @breakpoints.delete(k)
          end
      end
  end
end

#on_output_debug_string(ev) ⇒ Object

This method is abstract.


293
# File 'lib/ragweed/debugger32.rb', line 293

def on_output_debug_string(ev)  end

#on_priv_instruction(ev) ⇒ Object

This method is abstract.


313
# File 'lib/ragweed/debugger32.rb', line 313

def on_priv_instruction(ev)     end

#on_rip(ev) ⇒ Object

This method is abstract.


295
# File 'lib/ragweed/debugger32.rb', line 295

def on_rip(ev)                  end

#on_single_step(ev) ⇒ Object

handle a single-step event



265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/ragweed/debugger32.rb', line 265

def on_single_step(ev)
  ctx = context(ev)
  Ragweed::Wrap32::open_thread(ev.tid) do |h|
    ## re-enable the trap flag before our handler,
    ## which may choose to disable it.
    ctx.single_step(true)
    Ragweed::Wrap32.set_thread_context(h, ctx)
  end
  
  @steppers.each {|s| s.call(ev, ctx)}
      
  @handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
end

#on_stack_overflow(ev) ⇒ Object

This method is abstract.


315
# File 'lib/ragweed/debugger32.rb', line 315

def on_stack_overflow(ev)       end

#on_unload_dll(ev) ⇒ Object

This method is abstract.


297
# File 'lib/ragweed/debugger32.rb', line 297

def on_unload_dll(ev)           end

#processObject

Get a handle to the process so you can mess with it.



68
# File 'lib/ragweed/debugger32.rb', line 68

def process; @p; end

#releaseObject

Let go of the target.



408
409
410
411
412
413
414
# File 'lib/ragweed/debugger32.rb', line 408

def release
  Ragweed::Wrap32::debug_active_process_stop(@p.pid)
  @attached = false
  @breakpoints.each_pair do |k, bp|
    bp.uninstall
  end
end

#step(tid, callable) ⇒ Object

single-step the thread (by TID). “callable” is something that honors .call, like a Proc. In a dubious design decision: the “handle” to the single stepper is the Proc object itself. See Debugger#on_breakpoint for an example of how to use this.



101
102
103
104
105
106
107
108
109
110
# File 'lib/ragweed/debugger32.rb', line 101

def step(tid, callable)
  if @steppers.empty?
    Ragweed::Wrap32::open_thread(tid) do |h|
      ctx = Ragweed::Wrap32::get_thread_context(h)
      ctx.single_step(true)
      Ragweed::Wrap32::set_thread_context(h, ctx)
    end
  end
  @steppers << callable    
end

#unstep(tid, callable) ⇒ Object

turn off single-stepping for one callable (you can have more than one at a time). In other words, when you pass a Proc to Debugger#step, save it somewhere, and later pass it to “unstep” to turn it off.



115
116
117
118
119
120
121
122
123
124
# File 'lib/ragweed/debugger32.rb', line 115

def unstep(tid, callable)
  @steppers = @steppers.reject {|x| x == callable}
  if @steppers.empty?
    Ragweed::Wrap32::open_thread(tid) do |h|
      ctx = Ragweed::Wrap32::get_thread_context(h)
      ctx.single_step(false)
      Ragweed::Wrap32::set_thread_context(h, ctx)
    end
  end
end

#waitObject

Read through me to see all the random events you can hook in a subclass.



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/ragweed/debugger32.rb', line 327

def wait
  self.attach() if not @attached

  @event = ev = Ragweed::Wrap32::wait_for_debug_event
  return if not ev
  case ev.code
  when Ragweed::Wrap32::DebugCodes::CREATE_PROCESS
    try(:on_create_process, ev)
  when Ragweed::Wrap32::DebugCodes::CREATE_THREAD
    try(:on_create_thread, ev)
  when Ragweed::Wrap32::DebugCodes::EXIT_PROCESS
    try(:on_exit_process, ev)
  when Ragweed::Wrap32::DebugCodes::EXIT_THREAD
    try(:on_exit_thread, ev)
  when Ragweed::Wrap32::DebugCodes::LOAD_DLL
    try(:on_load_dll, ev)
  when Ragweed::Wrap32::DebugCodes::OUTPUT_DEBUG_STRING
    try(:on_output_debug_string, ev)
  when Ragweed::Wrap32::DebugCodes::RIP
    try(:on_rip, ev)
  when Ragweed::Wrap32::DebugCodes::UNLOAD_DLL
    try(:on_unload_dll, ev)
  when Ragweed::Wrap32::DebugCodes::EXCEPTION
    case ev.exception_code
    when Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION
      try(:on_access_violation, ev)
    when Ragweed::Wrap32::ExceptionCodes::GUARD_PAGE
      try(:on_guard_page, ev)
    when Ragweed::Wrap32::ExceptionCodes::BREAKPOINT 
      try(:on_breakpoint, ev)
    when Ragweed::Wrap32::ExceptionCodes::ALIGNMENT  
      try(:on_alignment, ev)
    when Ragweed::Wrap32::ExceptionCodes::SINGLE_STEP 
      try(:on_single_step, ev)
    when Ragweed::Wrap32::ExceptionCodes::BOUNDS 
      try(:on_bounds, ev)
    when Ragweed::Wrap32::ExceptionCodes::DIVIDE_BY_ZERO 
      try(:on_divide_by_zero, ev)
    when Ragweed::Wrap32::ExceptionCodes::INT_OVERFLOW 
      try(:on_int_overflow, ev)
    when Ragweed::Wrap32::ExceptionCodes::INVALID_HANDLE 
      try(:on_invalid_handle, ev)
    when Ragweed::Wrap32::ExceptionCodes::ILLEGAL_INSTRUCTION
      try(:on_illegal_instruction, ev)
    when Ragweed::Wrap32::ExceptionCodes::PRIV_INSTRUCTION
      try(:on_priv_instruction, ev)
    when Ragweed::Wrap32::ExceptionCodes::STACK_OVERFLOW 
      try(:on_stack_overflow, ev)
    when Ragweed::Wrap32::ExceptionCodes::HEAP_CORRUPTION
      try(:on_heap_corruption, ev)
    when Ragweed::Wrap32::ExceptionCodes::BUFFER_OVERRUN
      try(:on_buffer_overrun, ev)
    when Ragweed::Wrap32::ExceptionCodes::INVALID_DISPOSITION 
      try(:on_invalid_disposition, ev)
    end
  end

  Ragweed::Wrap32::continue_debug_event(ev.pid, ev.tid, @handled)
  @handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
end