Class: Ragweed::Debuggerosx
- Includes:
- Ragweed
- Defined in:
- lib/ragweed/debuggerosx.rb
Overview
Debugger class for Mac OS X You can use this class in 2 ways:
(1) You can create instances of Debuggerosx 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 Debuggerosx already handles, call "super", too.
Defined Under Namespace
Classes: Breakpoint
Constant Summary
Constants included from Ragweed
Instance Attribute Summary collapse
-
#breakpoints ⇒ Object
Returns the value of attribute breakpoints.
-
#exited ⇒ Object
readonly
Returns the value of attribute exited.
-
#pid ⇒ Object
readonly
Returns the value of attribute pid.
-
#status ⇒ Object
readonly
Returns the value of attribute status.
-
#task ⇒ Object
readonly
Returns the value of attribute task.
Instance Method Summary collapse
-
#attach(opts = @opts) ⇒ Object
attach to @pid for debugging opts is a hash for automatically firing other functions as an overide for @opts returns 0 on no error.
- #attached? ⇒ Boolean
-
#breakpoint_clear(ip, bpid = nil) ⇒ Object
removes breakpoint from child process ip: insertion point of breakpoints to be removed bpid: id of breakpoint to be removed.
-
#breakpoint_set(ip, name = "", callable = nil, &block) ⇒ Object
adds a breakpoint and callable block to be installed into child process ip: address of insertion point callable: object to receive call() when this breakpoint is hit.
-
#continue(addr = 1, data = 0) ⇒ Object
continue stopped child process.
-
#detach(opts = @opts) ⇒ Object
remove breakpoints and release child opts is a hash for automatically firing other functions as an overide for @opts returns 0 on no error.
- #get_heap_ranges ⇒ Object
-
#get_mapping_by_name(name, exact = true) ⇒ Object
XXX watch this space for an object to hold this information Get memory ranges by mapping name name: name of memory range to search for exact: if true require an exact match, otherwise use regex.
-
#get_registers(thread = nil) ⇒ Object
returns a Ragweed::Wraposx::ThreadContext object containing the register states thread: thread to get the register state of.
- #get_stack_ranges ⇒ Object
-
#hook(opts = @opts) ⇒ Object
(also: #attach_mach)
deprecated
Deprecated.
-
This function will change to attach_mach and instead become similar to the Debugger32#hook function used for tracing the entry and exit of functions in the child.
-
- #hooked? ⇒ Boolean
-
#initialize(p, opts = {}) ⇒ Debuggerosx
constructor
init object p: pid of process to be debugged opts: default options for automatically doing things (attach, install, and hook).
-
#install_bps ⇒ Object
installs all breakpoints into child process add breakpoints to install via breakpoint_set.
- #installed? ⇒ Boolean
-
#kill(sig = 0) ⇒ Object
sends a signal to process with id @pid sig: signal to be sent to process @pid.
-
#loop(times = nil) ⇒ Object
loop calls to wait.
-
#on_attach ⇒ Object
Fired when attaching to the child process succeeds.
-
#on_breakpoint(thread) ⇒ Object
default method for breakpoint handling thread: id of the thread stopped at a breakpoint.
-
#on_continue ⇒ Object
Called when the child process is continued.
-
#on_detach ⇒ Object
Fired when detaching from the child process succeeds.
-
#on_exit(status) ⇒ Object
Called with the child process’s status on exit Implementations overriding this function should either set @exited to true or call super.
-
#on_signal(signal) ⇒ Object
Called with the signal used to exit kill the child process Implementations overriding this function should either set @exited to true or call super.
-
#on_single_step ⇒ Object
Fired when single stepping at every step Not currently used in OSX.
-
#on_stop(signal) ⇒ Object
Called when the child process is stopped with the signal used.
-
#region_info(addr, flavor = :basic) ⇒ Object
returns information about a memory region addr: address contained in the memory region - usually the start address flavor: type of information to retrieve.
-
#resume(thread = nil) ⇒ Object
resumes thread that has been suspended via thread_suspend thread: thread id of thread to be resumed.
-
#resume_task ⇒ Object
decrement our tasks suspend count.
-
#set_registers(thread, regs) ⇒ Object
sets the register state of a thread thread: thread id to set registers for regs: Ragweed::Wraposx::ThreadContext object containing the new register state for the thread.
-
#stepp(addr = 1, data = 0) ⇒ Object
Do not use this function unless you know what you’re doing! It causes a kernel panic in some situations (fine if the trap flag is set in theory) same arguments as Debugerosx#continue single steps the child process.
-
#suspend(thread = nil) ⇒ Object
suspends thread (increments the suspend count) thread: thread id of thread to be suspended defaults to first thread.
-
#suspend_task ⇒ Object
increment our tasks suspend count.
-
#thread_update(thread = nil, sig = 0) ⇒ Object
sends a signal to a thread of the child’s this option to ptrace is undocumented in OS X, usage pulled from gdb and googling thread: id of thread to which a signal is to be sent sig: signal to be sent to child’s thread.
-
#threads ⇒ Object
returns an array of the thread ids of the child process.
-
#unhook(opts = @opts) ⇒ Object
deprecated
Deprecated.
-
This will be removed at some point.
-
-
#uninstall_bps ⇒ Object
removes all breakpoints from child process.
-
#wait(opts = 0) ⇒ Object
wait for process and run callback on return then continue child This is usually called by loop() FIXME - need to do signal handling better (loop through threads only for breakpoints and stepping) opts: option flags to waitpid(2).
Methods included from Ragweed
libpath, path, require_all_libs_relative_to, require_os_libs_relative_to, version
Constructor Details
#initialize(p, opts = {}) ⇒ Debuggerosx
init object p: pid of process to be debugged opts: default options for automatically doing things (attach, install, and hook)
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/ragweed/debuggerosx.rb', line 75 def initialize(p,opts={}) if p.kind_of? Numeric @pid = p else # coming soon: find process by name raise "Provide a PID" end @opts = opts default_opts(opts) @installed = false @attached = false @hooked = false @breakpoints = Hash.new do |h, k| bps = Array.new def bps.call(*args); each {|bp| bp.call(*args)}; end def bps.install; each {|bp| bp.install}; end def bps.uninstall; each {|bp| bp.uninstall}; end def bps.orig; each {|bp| dp.orig}; end h[k] = bps end @opts.each {|k, v| try(k) if v} end |
Instance Attribute Details
#breakpoints ⇒ Object
Returns the value of attribute breakpoints.
21 22 23 |
# File 'lib/ragweed/debuggerosx.rb', line 21 def breakpoints @breakpoints end |
#exited ⇒ Object (readonly)
Returns the value of attribute exited.
20 21 22 |
# File 'lib/ragweed/debuggerosx.rb', line 20 def exited @exited end |
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
17 18 19 |
# File 'lib/ragweed/debuggerosx.rb', line 17 def pid @pid end |
#status ⇒ Object (readonly)
Returns the value of attribute status.
18 19 20 |
# File 'lib/ragweed/debuggerosx.rb', line 18 def status @status end |
#task ⇒ Object (readonly)
Returns the value of attribute task.
19 20 21 |
# File 'lib/ragweed/debuggerosx.rb', line 19 def task @task end |
Instance Method Details
#attach(opts = @opts) ⇒ Object
attach to @pid for debugging opts is a hash for automatically firing other functions as an overide for @opts returns 0 on no error
227 228 229 230 231 232 233 234 235 |
# File 'lib/ragweed/debuggerosx.rb', line 227 def attach(opts=@opts) r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::ATTACH,@pid,0,0) # Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,1,0) @attached = true on_attach self.hook(opts) if (opts[:hook] and not @hooked) self.install_bps if (opts[:install] and not @installed) return r.first end |
#attached? ⇒ Boolean
417 |
# File 'lib/ragweed/debuggerosx.rb', line 417 def attached?; @attached; end |
#breakpoint_clear(ip, bpid = nil) ⇒ Object
removes breakpoint from child process ip: insertion point of breakpoints to be removed bpid: id of breakpoint to be removed
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/ragweed/debuggerosx.rb', line 303 def breakpoint_clear(ip, bpid=nil) if not bpid @breakpoints[ip].uninstall @breakpoints.delete ip else found = nil @breakpoints[ip].each_with_index do |bp, i| if bp.bpid == bpid found = i if bp.orig != Breakpoint::INT3 if @breakpoints[ip][i+1] @breakpoints[ip][i + 1].orig = bp.orig else bp.uninstall end end end end raise "couldn't find #{ ip }" if not found @breakpoints[ip].delete_at(found) if found end end |
#breakpoint_set(ip, name = "", callable = nil, &block) ⇒ Object
adds a breakpoint and callable block to be installed into child process ip: address of insertion point callable: object to receive call() when this breakpoint is hit
293 294 295 296 297 298 |
# File 'lib/ragweed/debuggerosx.rb', line 293 def breakpoint_set(ip, name="", callable=nil, &block) if not callable and block_given? callable = block end @breakpoints[ip] << Breakpoint.new(self, ip, callable, name) end |
#continue(addr = 1, data = 0) ⇒ Object
continue stopped child process. addr: address from which to continue child. defaults to current position. data: signal to be sent to child. defaults to no signal.
395 396 397 |
# File 'lib/ragweed/debuggerosx.rb', line 395 def continue(addr = 1, data = 0) Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,addr,data) end |
#detach(opts = @opts) ⇒ Object
remove breakpoints and release child opts is a hash for automatically firing other functions as an overide for @opts returns 0 on no error
240 241 242 243 244 245 246 247 |
# File 'lib/ragweed/debuggerosx.rb', line 240 def detach(opts=@opts) self.uninstall_bps if @installed r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::DETACH,@pid,0,Ragweed::Wraposx::Wait::UNTRACED) @attached = false on_detach self.unhook(opts) if opts[:hook] and @hooked return r.first end |
#get_heap_ranges ⇒ Object
475 476 477 |
# File 'lib/ragweed/debuggerosx.rb', line 475 def get_heap_ranges get_mapping_by_name "MALLOC", false end |
#get_mapping_by_name(name, exact = true) ⇒ Object
XXX watch this space for an object to hold this information Get memory ranges by mapping name name: name of memory range to search for exact: if true require an exact match, otherwise use regex
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 |
# File 'lib/ragweed/debuggerosx.rb', line 452 def get_mapping_by_name name, exact = true ret = [] IO.popen("vmmap -interleaved #{@pid}") do |pipe| pipe.each_line do |line| next if pipe.lineno < 5 break if line == "==== Legend\n" rtype, saddr, eaddr, sz, perms, sm, purpose = line.scan(/^([[:graph:]]+(?:\s[[:graph:]]+)?)\s+([[:xdigit:]]+)-([[:xdigit:]]+)\s+\[\s+([[:digit:]]+[A-Z])\s*\]\s+([-rwx\/]+)\s+SM=(COW|PRV|NUL|ALI|SHM|ZER|S\/A)\s+(.*)$/). first if exact && (rtype == name || purpose == name) ret << [saddr, eaddr].map{|x| x.to_i(16)} elsif rtype && purpose && (rtype.match(name) || purpose.match(name)) ret << [saddr, eaddr].map{|x| x.to_i(16)} end end end ret end |
#get_registers(thread = nil) ⇒ Object
returns a Ragweed::Wraposx::ThreadContext object containing the register states thread: thread to get the register state of
378 379 380 381 |
# File 'lib/ragweed/debuggerosx.rb', line 378 def get_registers(thread=nil) thread ||= self.threads.first Ragweed::Wraposx.thread_get_state(thread, Ragweed::Wraposx::ThreadContext::X86_THREAD_STATE) end |
#get_stack_ranges ⇒ Object
471 472 473 |
# File 'lib/ragweed/debuggerosx.rb', line 471 def get_stack_ranges get_mapping_by_name "Stack", false end |
#hook(opts = @opts) ⇒ Object Also known as: attach_mach
-
This function will change to attach_mach and instead become similar to the Debugger32#hook function used for tracing the entry and exit of functions in the child.
get task port for @pid and store in @task so mach calls can be made opts is a hash for automatically firing other functions as an overide for @opts returns the task port for @pid
253 254 255 256 257 258 |
# File 'lib/ragweed/debuggerosx.rb', line 253 def hook(opts=@opts) @task = Ragweed::Wraposx::task_for_pid(@pid) @hooked = true self.attach(opts) if opts[:attach] and not @attached return @task end |
#hooked? ⇒ Boolean
416 |
# File 'lib/ragweed/debuggerosx.rb', line 416 def hooked?; @hooked; end |
#install_bps ⇒ Object
installs all breakpoints into child process add breakpoints to install via breakpoint_set
208 209 210 211 212 213 214 |
# File 'lib/ragweed/debuggerosx.rb', line 208 def install_bps self.hook if not @hooked @breakpoints.each do |k,v| v.install end @installed = true end |
#installed? ⇒ Boolean
418 |
# File 'lib/ragweed/debuggerosx.rb', line 418 def installed?; @installed; end |
#kill(sig = 0) ⇒ Object
sends a signal to process with id @pid sig: signal to be sent to process @pid
286 287 288 |
# File 'lib/ragweed/debuggerosx.rb', line 286 def kill(sig = 0) Ragweed::Wraposx::kill(@pid,sig) end |
#loop(times = nil) ⇒ Object
loop calls to wait. This is the main mode this class will be used at runtime. First, install the desired breakpoints. Then, run loop().
times: number of times to loop
if nil this will loop until @exited is set
105 106 107 108 109 110 111 112 113 |
# File 'lib/ragweed/debuggerosx.rb', line 105 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 |
#on_attach ⇒ Object
Fired when attaching to the child process succeeds.
173 174 |
# File 'lib/ragweed/debuggerosx.rb', line 173 def on_attach end |
#on_breakpoint(thread) ⇒ Object
default method for breakpoint handling thread: id of the thread stopped at a breakpoint
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 |
# File 'lib/ragweed/debuggerosx.rb', line 328 def on_breakpoint(thread) r = self.get_registers(thread) # rewind eip to correct position r.eip -= 1 # don't use r.eip since it may be changed by breakpoint callback eip = r.eip # clear stuff set by INT3 # r.esp -=4 # r.ebp = r.esp # fire callback @breakpoints[eip].call(thread, r, self) if @breakpoints[eip].first.installed? # uninstall breakpoint to continue past it @breakpoints[eip].first.uninstall # set trap flag so we don't go too far before reinserting breakpoint r.eflags |= Ragweed::Wraposx::EFlags::TRAP # set registers to commit eip and eflags changes self.set_registers(thread, r) # step once self.stepp # now we wait() to prevent a race condition that'll SIGBUS us # Yup, a race condition where the child may not complete a single # instruction before the parent completes many Ragweed::Wraposx::waitpid(@pid,0) # reset the breakpoint @breakpoints[eip].first.install end end |
#on_continue ⇒ Object
Called when the child process is continued. If used by an implementation, this will be a very noisy function.
203 204 |
# File 'lib/ragweed/debuggerosx.rb', line 203 def on_continue end |
#on_detach ⇒ Object
Fired when detaching from the child process succeeds.
177 178 |
# File 'lib/ragweed/debuggerosx.rb', line 177 def on_detach end |
#on_exit(status) ⇒ Object
Called with the child process’s status on exit Implementations overriding this function should either set @exited to true or call super.
187 188 189 |
# File 'lib/ragweed/debuggerosx.rb', line 187 def on_exit(status) @exited = true end |
#on_signal(signal) ⇒ Object
Called with the signal used to exit kill the child process Implementations overriding this function should either set @exited to true or call super.
193 194 195 |
# File 'lib/ragweed/debuggerosx.rb', line 193 def on_signal(signal) @exited = true end |
#on_single_step ⇒ Object
Fired when single stepping at every step Not currently used in OSX
182 183 |
# File 'lib/ragweed/debuggerosx.rb', line 182 def on_single_step end |
#on_stop(signal) ⇒ Object
Called when the child process is stopped with the signal used.
198 199 |
# File 'lib/ragweed/debuggerosx.rb', line 198 def on_stop(signal) end |
#region_info(addr, flavor = :basic) ⇒ Object
returns information about a memory region addr: address contained in the memory region - usually the start address flavor: type of information to retrieve. May be specified as either symbol [:basic, :extended, :top] or by integer flavor id.
Currently, only the basic flavor is supported by Apple.
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'lib/ragweed/debuggerosx.rb', line 424 def region_info(addr, flavor = :basic) flav = case flavor when :basic Ragweed::Wraposx::Vm::RegionBasicInfo::FLAVOR # Extended and Top info flavors are included in case Apple re implements them when :extended Ragweed::Wraposx::Vm::RegionExtendedInfo::FLAVOR when :top Ragweed::Wraposx::Vm::RegionTopInfo::FLAVOR when Integer flavor else warn "Unknown flavor requested. Returning RegionBasicInfo." Ragweed::Wraposx::RegionBasicInfo::FLAVOR end if Ragweed::Wraposx.respond_to? :vm_region_64 Ragweed::Wraposx.vm_region_64(@task, addr, flav) else Ragweed::Wraposx.vm_region(@task, addr, flav) end end |
#resume(thread = nil) ⇒ Object
resumes thread that has been suspended via thread_suspend thread: thread id of thread to be resumed
272 273 274 275 |
# File 'lib/ragweed/debuggerosx.rb', line 272 def resume(thread = nil) thread ||= self.threads.first Ragweed::Wraposx::thread_resume(thread) end |
#resume_task ⇒ Object
decrement our tasks suspend count
367 368 369 |
# File 'lib/ragweed/debuggerosx.rb', line 367 def resume_task Ragweed::Wraposx::task_resume(@task) end |
#set_registers(thread, regs) ⇒ Object
sets the register state of a thread thread: thread id to set registers for regs: Ragweed::Wraposx::ThreadContext object containing the new register state for the thread
386 387 388 389 390 |
# File 'lib/ragweed/debuggerosx.rb', line 386 def set_registers(thread, regs) # XXX - needs updated conditions # raise "Must supply registers and thread to set" if (not (thread and regs) or not thread.kind_of? Numeric or not regs.kind_of? Ragweed::Wraposx::ThreadContext) Ragweed::Wraposx.thread_set_state(thread, regs.class::FLAVOR, regs) end |
#stepp(addr = 1, data = 0) ⇒ Object
Do not use this function unless you know what you’re doing! It causes a kernel panic in some situations (fine if the trap flag is set in theory) same arguments as Debugerosx#continue single steps the child process
403 404 405 |
# File 'lib/ragweed/debuggerosx.rb', line 403 def stepp(addr = 1, data = 0) Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::STEP,@pid,addr,data) end |
#suspend(thread = nil) ⇒ Object
suspends thread (increments the suspend count) thread: thread id of thread to be suspended defaults to first thread
279 280 281 282 |
# File 'lib/ragweed/debuggerosx.rb', line 279 def suspend(thread = nil) thread ||= self.threads.first Ragweed::Wraposx::thread_suspend(thread) end |
#suspend_task ⇒ Object
increment our tasks suspend count
372 373 374 |
# File 'lib/ragweed/debuggerosx.rb', line 372 def suspend_task Ragweed::Wraposx::task_suspend(@task) end |
#thread_update(thread = nil, sig = 0) ⇒ Object
sends a signal to a thread of the child’s this option to ptrace is undocumented in OS X, usage pulled from gdb and googling thread: id of thread to which a signal is to be sent sig: signal to be sent to child’s thread
411 412 413 414 |
# File 'lib/ragweed/debuggerosx.rb', line 411 def thread_update(thread = nil, sig = 0) thread = thread or self.threads.first Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::THUPDATE,@pid,thread,sig) end |
#threads ⇒ Object
returns an array of the thread ids of the child process
361 362 363 364 |
# File 'lib/ragweed/debuggerosx.rb', line 361 def threads self.hook if not @hooked Ragweed::Wraposx::task_threads(@task) end |
#unhook(opts = @opts) ⇒ Object
-
This will be removed at some point.
theoretically to close the task port but, no way to close the port has yet been found. This function currently does little/nothing.
265 266 267 268 |
# File 'lib/ragweed/debuggerosx.rb', line 265 def unhook(opts=@opts) self.detach(opts) if opts[:attach] and @attached self.unintsall_bps if opts[:install] and @installed end |
#uninstall_bps ⇒ Object
removes all breakpoints from child process
217 218 219 220 221 222 |
# File 'lib/ragweed/debuggerosx.rb', line 217 def uninstall_bps @breakpoints.each do |k,v| v.uninstall end @installed = false end |
#wait(opts = 0) ⇒ Object
wait for process and run callback on return then continue child This is usually called by loop() FIXME - need to do signal handling better (loop through threads only for breakpoints and stepping) opts: option flags to waitpid(2)
returns an array containing the pid of the stopped or terminated child and the status of that child r: pid of stopped/terminated child or 0 if Ragweed::Wraposx::Wait:NOHANG was passed and there was nothing to report r: staus of child or 0 if Ragweed::Wraposx::Wait:NOHANG was passed and there was nothing to report
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/ragweed/debuggerosx.rb', line 123 def wait(opts = 0) r = Ragweed::Wraposx::waitpid(@pid,opts) status = r[1] wstatus = status & 0x7f signal = status >> 8 found = false if r[0] != 0 #r[0] == 0 iff wait had nothing to report and NOHANG option was passed case when wstatus == 0 #WIFEXITED @exited = true try(:on_exit, signal) when wstatus != 0x7f #WIFSIGNALED @exited = false try(:on_signaled, wstatus) when signal != 0x13 #WIFSTOPPED self.threads.each do |t| if @breakpoints.has_key?(self.get_registers(t).eip-1) found = true try(:on_breakpoint, t) end end if not found # no breakpoint so iterate through Signal constants to find the current SIG Signal.list.each do |sig, val| try("on_sig#{ sig.downcase }".intern) if signal == val end end try(:on_stop, signal) begin self.continue rescue Errno::EBUSY # Yes this happens and it's wierd # Not sure it should happen if $DEBUG puts 'unable to self.continue' puts self.get_registers end retry end when signal == 0x13 #WIFCONTINUED try(:on_continue) else raise "Unknown signal '#{signal}' recieved: This should not happen - ever." end end return r end |