class Metasm::Debugger
this class implements a high-level debugging API (abstract superclass)
Attributes
per-process data
per-thread data
per-process data
per-thread data
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
global debugger callbacks, called whenever such event occurs
per-process data
per-process data
link to the user-interface object if available
global switches, specify wether to break on exception/thread event
can be a Proc that is evaluated (arg = info parameter of the evt_func)
#trace_children is a bool to tell if we should debug subprocesses spawned
by the target
global switches, specify wether to break on exception/thread event
can be a Proc that is evaluated (arg = info parameter of the evt_func)
#trace_children is a bool to tell if we should debug subprocesses spawned
by the target
per-thread data
per-process data
per-process data
global switches, specify wether to break on exception/thread event
can be a Proc that is evaluated (arg = info parameter of the evt_func)
#trace_children is a bool to tell if we should debug subprocesses spawned
by the target
which/where per-process/thread stuff is stored
which/where per-process/thread stuff is stored
per-thread data
per-thread data
per-thread data
per-thread data
per-process data
per-process data
which/where per-process/thread stuff is stored
which/where per-process/thread stuff is stored
global switches, specify wether to break on exception/thread event
can be a Proc that is evaluated (arg = info parameter of the evt_func)
#trace_children is a bool to tell if we should debug subprocesses spawned
by the target
Public Class Methods
initializes the disassembler internal data - subclasses should call super()
# File metasm/debug.rb, line 145 def initialize @pid_stuff = {} @tid_stuff = {} @log_proc = nil @state = :dead @info = '' # stuff saved when we switch pids @pid_stuff_list = [:memory, :cpu, :disassembler, :symbols, :symbols_len, :modulemap, :breakpoint, :breakpoint_memory, :tid, :tid_stuff, :dead_process] @tid_stuff_list = [:state, :info, :breakpoint_thread, :singlestep_cb, :run_method, :run_args, :breakpoint_cause, :dead_thread] @callback_loadlibrary = lambda { |h| loadsyms(h[:address]) ; continue } @callback_newprocess = lambda { |h| log "process #{@pid} attached" } @callback_endprocess = lambda { |h| log "process #{@pid} died" } initialize_newpid initialize_newtid end
Public Instance Methods
accepts a range or begin/end address to read memory, or a register name
# File metasm/debug.rb, line 1289 def [](arg0, arg1=nil) if arg1 arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer @memory[arg0, arg1].to_str elsif arg0.kind_of? ::Range arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer @memory[arg0].to_str else get_reg_value(arg0) end end
accepts a range or begin/end address to write memory, or a register name
# File metasm/debug.rb, line 1304 def []=(arg0, arg1, val=nil) arg1, val = val, arg1 if not val if arg1 arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer @memory[arg0, arg1] = val elsif arg0.kind_of? ::Range arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer @memory[arg0] = val else set_reg_value(arg0, val) end end
create a thread/process breakpoint addr can be a numeric address, an Expression that is resolved, or
a String that is parsed+resolved
info's keys are set to the breakpoint standard keys are :type, :oneshot, :condition, :action returns the Breakpoint object
# File metasm/debug.rb, line 360 def add_bp(addr, info={}) info[:pid] ||= @pid # dont define :tid for bpx, otherwise on del_bp we may switch_context to this thread that may not be stopped -> cant ptrace_write info[:tid] ||= @tid if info[:pid] == @pid and info[:type] == :hwbp b = Breakpoint.new info.each { |k, v| b.send("#{k}=", v) } switch_context(b) { addr = resolve_expr(addr) if not addr.kind_of? ::Integer b.address = addr b.hash_owner ||= case b.type when :bpm; @breakpoint_memory when :hwbp; @breakpoint_thread when :bpx; @breakpoint end # XXX bpm may hash_share with an :active, but be larger and still need enable() b.add enable_bp(b) if not info[:state] } b end
returns the name of the module containing addr or nil
# File metasm/debug.rb, line 1101 def addr2module(addr) @modulemap.keys.find { |k| @modulemap[k][0] <= addr and @modulemap[k][1] > addr } end
returns a string describing addr in term of symbol (eg 'libc.so.6!printf+2f')
# File metasm/debug.rb, line 1106 def addrname(addr) (addr2module(addr) || '???') + '!' + if s = @symbols[addr] ? addr : @symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr } @symbols[s] + (addr == s ? '' : ('+%x' % (addr-s))) else '%08x' % addr end end
same as addrname, but scan preceding addresses if no symbol matches
# File metasm/debug.rb, line 1115 def addrname!(addr) (addr2module(addr) || '???') + '!' + if s = @symbols[addr] ? addr : @symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr } || @symbols.keys.sort.find_all { |s_| s_ < addr and s_ + 0x10000 > addr }.max @symbols[s] + (addr == s ? '' : ('+%x' % (addr-s))) else '%08x' % addr end end
return all breakpoints set on a specific address (or all bp)
# File metasm/debug.rb, line 580 def all_breakpoints(addr=nil) ret = [] if addr if b = @breakpoint[addr] ret |= b.hash_shared end else @breakpoint.each_value { |bb| ret |= bb.hash_shared } end @breakpoint_thread.each_value { |bb| next if addr and bb.address != addr ret |= bb.hash_shared } @breakpoint_memory.each_value { |bb| next if addr and (bb.address+bb.internal[:len] <= addr or bb.address > addr) ret |= bb.hash_shared } ret end
sets a memory breakpoint mtype is :r :w :rw or :x mlen is the size of the memory zone to cover
# File metasm/debug.rb, line 542 def bpm(addr, mtype=:r, mlen=4096, oneshot=false, cond=nil, &action) h = { :type => :bpm } addr = resolve_expr(addr) if not addr.kind_of? ::Integer h[:hash_key] = addr & -4096 # XXX actually referenced at addr, addr+4096, ... addr+len h[:internal] = { :type => mtype, :len => mlen } h[:oneshot] = true if oneshot h[:condition] = cond if cond h[:action] = action if action add_bp(addr, h) end
sets a breakpoint on execution
# File metasm/debug.rb, line 514 def bpx(addr, oneshot=false, cond=nil, &action) h = { :type => :bpx } h[:oneshot] = true if oneshot h[:condition] = cond if cond h[:action] = action if action add_bp(addr, h) end
returns true if the fault described in info is valid to trigger b
# File metasm/debug.rb, line 726 def check_bpm_range(b, info) return if b.address+b.internal[:len] <= info[:fault_addr] return if b.address >= info[:fault_addr] + info[:fault_len] case b.internal[:type] when :r; info[:fault_access] == :r # or info[:fault_access] == :x when :w; info[:fault_access] == :w when :x; info[:fault_access] == :x # XXX non-NX cpu => check pc is in bpm range ? when :rw; true end end
checks if @breakpoint_cause is valid, or was obsoleted by the user changing pc
# File metasm/debug.rb, line 896 def check_breakpoint_cause if bp = @breakpoint_cause and (bp.type == :bpx or (bp.type == :hwbp and bp.internal[:type] == :x)) and pc != bp.address bp = @breakpoint_cause = nil end bp end
check if pid is valid
# File metasm/debug.rb, line 1403 def check_pid(pid) list_processes.find { |p| p.pid == pid } end
to be called right before resuming execution of the target run_m is the method that should be called if the execution is stopped due to a side-effect of the debugger (bpx with wrong condition etc) returns nil if the execution should be avoided (just deleted the dead thread/process)
# File metasm/debug.rb, line 614 def check_pre_run(run_m, *run_a) if @dead_process del_pid return elsif @dead_thread del_tid return elsif @state == :running return end @cpu.dbg_check_pre_run(self) if @cpu.respond_to?(:dbg_check_pre_run) @breakpoint_cause = nil @run_method = run_m @run_args = run_a @info = nil true end
checks if the running target has stopped (nonblocking) returns false if no debug event happened
# File metasm/debug.rb, line 907 def check_target do_check_target end
check if tid is valid for the current process
# File metasm/debug.rb, line 1417 def check_tid(tid) list_threads.include?(tid) end
resume execution of the target bypasses a software breakpoint on pc if needed thread breakpoints must be manually disabled before calling continue
# File metasm/debug.rb, line 919 def continue if b = check_breakpoint_cause and b.hash_shared.find { |bb| bb.state == :active } singlestep_bp(b) { next if not check_pre_run(:continue) do_continue } else return if not check_pre_run(:continue) do_continue end end
continue ; #wait_target
# File metasm/debug.rb, line 933 def continue_wait continue wait_target end
# File metasm/debug.rb, line 164 def dasm; disassembler; end
invalidates the EncodedData backend for the dasm sections
# File metasm/debug.rb, line 575 def dasm_invalidate disassembler.sections.each_value { |s| s.data.invalidate if s.data.respond_to?(:invalidate) } if disassembler end
delete all breakpoints for the current process and all its threads
# File metasm/debug.rb, line 427 def del_all_breakpoints each_tid { del_all_breakpoints_thread } @breakpoint.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } @breakpoint_memory.values.uniq.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } end
delete all breakpoints defined in the current thread
# File metasm/debug.rb, line 422 def del_all_breakpoints_thread @breakpoint_thread.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } end
remove a breakpoint
# File metasm/debug.rb, line 389 def del_bp(b) disable_bp(b) b.del end
delete references to the current pid switch to another pid, set @state = :dead if none available
# File metasm/debug.rb, line 271 def del_pid @pid_stuff.delete @pid if @pid = @pid_stuff.keys.first swapin_pid else @state = :dead @info = '' @tid = nil end end
delete references to the current thread
# File metasm/debug.rb, line 283 def del_tid @tid_stuff.delete @tid if @tid = @tid_stuff.keys.first swapin_tid else del_tid_notid end end
wipe the whole process when no TID is left XXX we may have a pending evt_newthread…
# File metasm/debug.rb, line 294 def del_tid_notid del_pid end
decode the Instruction at the address, use the @disassembler cache if available
# File metasm/debug.rb, line 1014 def di_at(addr) @disassembler.di_at(addr) || @disassembler.disassemble_instruction(addr) end
deactivate an active breakpoint
# File metasm/debug.rb, line 411 def disable_bp(b, newstate = :inactive) return if b.state != :active b.state = newstate return if b.hash_shared.find { |bb| bb.state == :active } switch_context(b) { do_disable_bp(b) } end
calls do_disable_bpm for bpms, or @cpu.dbg_disable_bp
# File metasm/debug.rb, line 441 def do_disable_bp(b) if b.type == :bpm; do_disable_bpm(b) else @cpu.dbg_disable_bp(self, b) end end
calls do_enable_bpm for bpms, or @cpu.dbg_enable_bp
# File metasm/debug.rb, line 434 def do_enable_bp(b) if b.type == :bpm; do_enable_bpm(b) else @cpu.dbg_enable_bp(self, b) end end
iterate over all pids, yield in the context of this pid
# File metasm/debug.rb, line 326 def each_pid(&b) # ensure @pid is last, so that we finish in the current context lst = @pid_stuff.keys - [@pid] lst << @pid return lst if not b lst.each { |p| set_pid p b.call } end
iterate over all tids of all pids, yield in their context
# File metasm/debug.rb, line 349 def each_pid_tid(&b) each_pid { each_tid { b.call } } end
iterate over all tids of the current process, yield in its context
# File metasm/debug.rb, line 338 def each_tid(&b) lst = @tid_stuff.keys - [@tid] lst << @tid return lst if not b lst.each { |t| set_tid t rescue next b.call } end
# File metasm/debug.rb, line 499 def emulinstr_resv(e) r = e flags = Expression[r].externals.uniq.find_all { |f| f.to_s =~ /flags?_(.+)/i } if flags.first bd = {} flags.each { |f| f.to_s =~ /flags?_(.+)/i bd[f] = get_flag_value($1.downcase.to_sym) } r = r.bind(bd) end resolve(r) end
activate an inactive breakpoint
# File metasm/debug.rb, line 395 def enable_bp(b) return if b.state == :active if not b.hash_shared.find { |bb| bb.state == :active } switch_context(b) { if not b.internal init_bpx(b) if b.type == :bpx b.internal ||= {} b.hash_shared.each { |bb| bb.internal ||= b.internal } end do_enable_bp(b) } end b.state = :active end
checks if an instruction should stop the stepout() (eg it is a return instruction)
# File metasm/debug.rb, line 983 def end_stepout(di = di_at(pc)) di and @cpu.dbg_end_stepout(self, di.address, di) end
called when the target stops due to a memory exception caused by a memory bp called by #evt_exception
# File metasm/debug.rb, line 711 def evt_bpm(b) @state = :stopped @info = 'bpm' callback_bpm[b] if callback_bpm post_evt_bp(b) end
called when the target stops due to a soft breakpoint exception
# File metasm/debug.rb, line 657 def evt_bpx(b=nil) b ||= find_bp_bpx # TODO handle race: # bpx foo ; thread hits foo ; we bc foo ; os notify us of bp hit but we already cleared everything related to 'bpx foo' -> unhandled bp exception return evt_exception(:type => 'breakpoint') if not b @state = :stopped @info = 'breakpoint' @cpu.dbg_evt_bpx(self, b) if @cpu.respond_to?(:dbg_evt_bpx) callback_bpx[b] if callback_bpx post_evt_bp(b) end
# File metasm/debug.rb, line 838 def evt_endprocess(info={}) @state = :stopped @info = 'end process' @dead_process = true callback_endprocess[info] if callback_endprocess end
# File metasm/debug.rb, line 816 def evt_endthread(info={}) @state = :stopped @info = 'end thread' # mark the thread as to be deleted on next check_pre_run @dead_thread = true callback_endthread[info] if callback_endthread ign = ignore_endthread ign = ign[info] if ign.kind_of? Proc if ign continue end end
called whenever the target stops due to an exception type may be:
-
'access violation', :fault_addr, :fault_len, :fault_access (:r/:w/:x)
anything else for other exceptions (access violation is special to handle bpm) …
# File metasm/debug.rb, line 783 def evt_exception(info={}) if info[:type] == 'access violation' and b = find_bp_bpm(info) info[:fault_len] ||= 1 b.internal.update info return evt_bpm(b) end @state = :stopped @info = "exception #{info[:type]}" callback_exception[info] if callback_exception pass = pass_all_exceptions pass = pass[info] if pass.kind_of? Proc if pass pass_current_exception resume_badbreak end end
called when the target stops due to a hwbp exception
# File metasm/debug.rb, line 679 def evt_hwbp(b=nil) b ||= find_bp_hwbp return evt_exception(:type => 'hwbp') if not b @state = :stopped @info = 'hwbp' @cpu.dbg_evt_hwbp(self, b) if @cpu.respond_to?(:dbg_evt_hwbp) callback_hwbp[b] if callback_hwbp post_evt_bp(b) end
called for archs where the same interrupt is generated for hwbp and singlestep checks if a hwbp matches, then call #evt_hwbp, else call #evt_singlestep (which will forward to #evt_exception if singlestep does not match either)
# File metasm/debug.rb, line 701 def evt_hwbp_singlestep if b = find_bp_hwbp evt_hwbp(b) else evt_singlestep end end
# File metasm/debug.rb, line 846 def evt_loadlibrary(info={}) @state = :stopped @info = 'loadlibrary' callback_loadlibrary[info] if callback_loadlibrary end
# File metasm/debug.rb, line 831 def evt_newprocess(info={}) @state = :stopped @info = 'new process' callback_newprocess[info] if callback_newprocess end
# File metasm/debug.rb, line 803 def evt_newthread(info={}) @state = :stopped @info = 'new thread' callback_newthread[info] if callback_newthread ign = ignore_newthread ign = ign[info] if ign.kind_of? Proc if ign continue end end
called when the target stops due to a singlestep exception
# File metasm/debug.rb, line 634 def evt_singlestep(b=nil) b ||= find_singlestep return evt_exception(:type => 'singlestep') if not b @state = :stopped @info = 'singlestep' @cpu.dbg_evt_singlestep(self) if @cpu.respond_to?(:dbg_evt_singlestep) callback_singlestep[] if callback_singlestep if cb = @singlestep_cb @singlestep_cb = nil cb.call # call last, as the cb may change singlestep_cb/state/etc end end
return a bpm whose page coverage includes the fault described in info
# File metasm/debug.rb, line 721 def find_bp_bpm(info) @breakpoint_memory[info[:fault_addr] & -0x1000] end
return the breakpoint that is responsible for the #evt_bpx
# File metasm/debug.rb, line 673 def find_bp_bpx return @cpu.dbg_find_bpx(self) if @cpu.respond_to?(:dbg_find_bpx) @breakpoint[pc] end
return the breakpoint that is responsible for the #evt_hwbp
# File metasm/debug.rb, line 693 def find_bp_hwbp return @cpu.dbg_find_hwbp(self) if @cpu.respond_to?(:dbg_find_hwbp) @breakpoint_thread.find { |b| b.address == pc } end
return on of the breakpoints at address addr
# File metasm/debug.rb, line 604 def find_breakpoint(addr=nil, &b) return @breakpoint[addr] if @breakpoint[addr] and (not b or b.call(@breakpoint[addr])) all_breakpoints(addr).find { |bp| b.call bp } end
returns true if the singlestep is due to us
# File metasm/debug.rb, line 651 def find_singlestep return @cpu.dbg_find_singlestep(self) if @cpu.respond_to?(:dbg_find_singlestep) @run_method == :singlestep end
list of flags available in the flag register
# File metasm/debug.rb, line 1044 def flag_list @cpu.dbg_flag_list end
retrieve an argument (call at a function entrypoint)
# File metasm/debug.rb, line 1334 def func_arg(nr) @cpu.dbg_func_arg(self, nr) end
# File metasm/debug.rb, line 1337 def func_arg_set(nr, val) @cpu.dbg_func_arg_set(self, nr, val) end
retrieve a function return address (call at func entry/exit)
# File metasm/debug.rb, line 1353 def func_retaddr @cpu.dbg_func_retaddr(self) end
# File metasm/debug.rb, line 1359 def func_retaddr=(addr) @cpu.dbg_func_retaddr_set(self, addr) end
# File metasm/debug.rb, line 1356 def func_retaddr_set(addr) @cpu.dbg_func_retaddr_set(self, addr) end
retrieve a function returned value (call at func exitpoint)
# File metasm/debug.rb, line 1342 def func_retval @cpu.dbg_func_retval(self) end
# File metasm/debug.rb, line 1348 def func_retval=(val) @cpu.dbg_func_retval_set(self, val) end
# File metasm/debug.rb, line 1345 def func_retval_set(val) @cpu.dbg_func_retval_set(self, val) end
retrieve the value of a flag (true/false)
# File metasm/debug.rb, line 1076 def get_flag(f) get_flag_value(f) != 0 end
retrieve the value of a flag (0/1)
# File metasm/debug.rb, line 1071 def get_flag_value(f) @cpu.dbg_get_flag(self, f) end
set a singleshot breakpoint, run the process, and wait
# File metasm/debug.rb, line 1003 def go(target, cond=nil) bpx(target, true, cond) continue_wait end
checks if bp has an emul_instr do the lazy initialization if needed
# File metasm/debug.rb, line 469 def has_emul_instr(bp) if bp.emul_instr.kind_of?(DecodedInstruction) if di = bp.emul_instr and fdbd = @disassembler.get_fwdemu_binding(di, register_pc) and fdbd.all? { |k, v| (k.kind_of?(Symbol) or k.kind_of?(Indirection)) and k != :incomplete_binding and v != Expression::Unknown } # setup a lambda that will mimic, using the debugger primitives, the actual execution of the instruction bp.emul_instr = lambda { fdbd.map { |k, v| k = Indirection[emulinstr_resv(k.pointer), k.len] if k.kind_of?(Indirection) [k, emulinstr_resv(v)] }.each { |k, v| if k.to_s =~ /flags?_(.+)/i f = $1.downcase.to_sym set_flag_value(f, v) elsif k.kind_of?(Symbol) set_reg_value(k, v) elsif k.kind_of?(Indirection) memory_write_int(k.pointer, v, k.len) end } } bp.hash_shared.each { |bb| bb.emul_instr = bp.emul_instr } else bp.hash_shared.each { |bb| bb.emul_instr = nil } end end bp.emul_instr end
sets a hardware breakpoint mtype in :r :w :x mlen is the size of the memory zone to cover mlen may be constrained by the architecture
# File metasm/debug.rb, line 526 def hwbp(addr, mtype=:x, mlen=1, oneshot=false, cond=nil, &action) h = { :type => :hwbp } h[:hash_owner] = @breakpoint_thread addr = resolve_expr(addr) if not addr.kind_of? ::Integer mtype = mtype.to_sym h[:hash_key] = [addr, mtype, mlen] h[:internal] = { :type => mtype, :len => mlen } h[:oneshot] = true if oneshot h[:condition] = cond if cond h[:action] = action if action add_bp(addr, h) end
called in the context of the target when a bpx is to be initialized may (lazily) initialize b.emul_instr for virtual singlestep
# File metasm/debug.rb, line 449 def init_bpx(b) # dont bother setting stuff up if it is never to be used return if b.oneshot and not b.condition # lazy setup of b.emul_instr: delay building emulating lambda to if/when actually needed # we still need to disassemble now and update @disassembler, before we patch the memory for the bpx di = init_bpx_disassemble(b.address) b.hash_shared.each { |bb| bb.emul_instr = di } end
retrieve the di at a given address, disassemble if needed TODO make it so this doesn't interfere with other 'real' disassembler later commands, eg disassemble() or disassemble_fast_deep() (right now, when they see the block already present they stop all processing)
# File metasm/debug.rb, line 462 def init_bpx_disassemble(addr) @disassembler.disassemble_fast_block(addr) @disassembler.di_at(addr) end
initialize the disassembler from @cpu/@memory
# File metasm/debug.rb, line 221 def initialize_disassembler return if not @memory or not @cpu @disassembler = Shellcode.decode(@memory, @cpu).disassembler gui.swapin_pid if gui.respond_to?(:swapin_pid) end
creates stuff related to a new process being debugged includes disassembler, modulemap, symbols, breakpoints subclasses should check that @pid maps to a real process and raise() otherwise to be called with @pid/@tid set, calls initialize_memory+initialize_cpu
# File metasm/debug.rb, line 194 def initialize_newpid return if not pid @pid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) } @symbols = {} @symbols_len = {} @modulemap = {} @breakpoint = {} @breakpoint_memory = {} @tid_stuff = {} initialize_cpu initialize_memory initialize_disassembler end
subclasses should check that @tid maps to a real thread and raise() otherwise
# File metasm/debug.rb, line 210 def initialize_newtid return if not tid @tid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) } @state = :stopped @info = 'new' @breakpoint_thread = {} gui.swapin_tid if @disassembler and gui.respond_to?(:swapin_tid) end
marks the current cache of memory/regs invalid
# File metasm/debug.rb, line 570 def invalidate @memory.invalidate if @memory end
list debugged pids
# File metasm/debug.rb, line 1392 def list_debug_pids @pid_stuff.keys | [@pid].compact end
list debugged tids
# File metasm/debug.rb, line 1408 def list_debug_tids @tid_stuff.keys | [@tid].compact end
return a list of OS::Process listing all alive processes (incl not debugged) default version only includes current debugged pids
# File metasm/debug.rb, line 1398 def list_processes list_debug_pids.map { |p| OS::Process.new(p) } end
list of thread ids existing in the current process (incl not debugged) default version only lists debugged tids
see Metasm::Disassembler#load_map
# File metasm/debug.rb, line 1194 def load_map(str, off=0) str = File.read(str) if File.exist?(str) sks = @disassembler.sections.keys.sort str.each_line { |l| case l.strip when /^([0-9A-F]+)\s+(\w+)\s+(\w+)/i # kernel.map style a = $1.to_i(16) + off n = $3 when /^([0-9A-F]+):([0-9A-F]+)\s+([a-z_]\w+)/i # IDA style # see Disassembler for comments a = sks[$1.to_i(16)] + $2.to_i(16) + off n = $3 else next end @disassembler.set_label_at(a, n, false) @symbols[a] = n } end
# File metasm/debug.rb, line 1363 def load_plugin(plugin_filename) if not File.exist?(plugin_filename) and defined? Metasmdir # try autocomplete pf = File.join(Metasmdir, 'samples', 'dbg-plugins', plugin_filename) if File.exist?(pf) plugin_filename = pf elsif File.exist?(pf + '.rb') plugin_filename = pf + '.rb' end end if (not File.exist?(plugin_filename) or File.directory?(plugin_filename)) and File.exist?(plugin_filename + '.rb') plugin_filename += '.rb' end instance_eval File.read(plugin_filename) end
load symbols from all libraries found by the OS module
# File metasm/debug.rb, line 1186 def loadallsyms(&b) modules.each { |m| b.call(m.addr) if b loadsyms(m.addr, m.path) } end
loads the symbols from a mapped module
# File metasm/debug.rb, line 1126 def loadsyms(addr, name='%08x'%addr.to_i) if addr.kind_of? String modules.each { |m| if m.path =~ /#{addr}/i addr = m.addr name = File.basename m.path break end } return if not addr.kind_of? Integer end return if not peek = @memory.get_page(addr, 4) if peek == "\x7fELF" cls = LoadedELF elsif peek[0, 2] == "MZ" and @memory[addr+@memory[addr+0x3c,4].unpack('V').first, 4] == "PE\0\0" cls = LoadedPE else return end begin e = cls.load @memory[addr, 0x1000_0000] e.load_address = addr e.decode_header e.decode_exports rescue # cache the error so that we dont hit it every time @modulemap[addr.to_s(16)] ||= [addr, addr+0x1000] return end if n = e.module_name and n != name name = n end @modulemap[name] ||= [addr, addr+e.module_size] cnt = 0 e.module_symbols.each { |n_, a, l| cnt += 1 a += addr @disassembler.set_label_at(a, n_, false) @symbols[a] = n_ # XXX store "lib!sym" ? if l and l > 1; @symbols_len[a] = l else @symbols_len.delete a # we may overwrite an existing symbol, keep len in sync end } log "loaded #{cnt} symbols from #{name}" true end
show information to the user, uses log_proc if defined
# File metasm/debug.rb, line 560 def log(*a) if @log_proc a.each { |aa| @log_proc[aa] } else puts(*a) if $VERBOSE end end
return the list of memory mappings of the current process array of [start, len, perms, infos]
# File metasm/debug.rb, line 1382 def mappings [[0, @memory.length]] end
read an int from the target memory, int of sz bytes (defaults to cpu.size)
# File metasm/debug.rb, line 1321 def memory_read_int(addr, sz=@cpu.size/8) addr = resolve_expr(addr) if not addr.kind_of? ::Integer Expression.decode_imm(@memory, sz, @cpu, addr) end
write an int in the target memory
# File metasm/debug.rb, line 1327 def memory_write_int(addr, val, sz=@cpu.size/8) addr = resolve_expr(addr) if not addr.kind_of? ::Integer val = resolve_expr(val) if not val.kind_of? ::Integer @memory[addr, sz] = Expression.encode_imm(val, sz, @cpu) end
return a list of Process::Modules (with a path, addr) for the current process
# File metasm/debug.rb, line 1387 def modules [] end
tests if the specified instructions should be stepover() using singlestep or by putting a breakpoint at next_addr
# File metasm/debug.rb, line 961 def need_stepover(di = di_at(pc)) di and @cpu.dbg_need_stepover(self, di.address, di) end
parses the expression contained in arg
# File metasm/debug.rb, line 1215 def parse_expr(arg) parse_expr!(arg.dup) end
parses the expression contained in arg, updates arg to point after the expr
# File metasm/debug.rb, line 1220 def parse_expr!(arg, &b) return if not e = IndExpression.parse_string!(arg) { |s| # handle 400000 -> 0x400000 # XXX no way to override and force decimal interpretation.. if s.length > 4 and not @disassembler.get_section_at(s.to_i) and @disassembler.get_section_at(s.to_i(16)) s.to_i(16) else s.to_i end } # resolve ambiguous symbol names/hex values bd = {} e.externals.grep(::String).each { |ex| if not v = register_list.find { |r| ex.downcase == r.to_s.downcase } || (b && b.call(ex)) || symbols.index(ex) lst = symbols.values.find_all { |s| s.downcase.include? ex.downcase } case lst.length when 0 if ex =~ /^[0-9a-f]+$/i and @disassembler.get_section_at(ex.to_i(16)) v = ex.to_i(16) else raise "unknown symbol name #{ex}" end when 1 v = symbols.index(lst.first) log "using #{lst.first} for #{ex}" else suggest = lst[0, 50].join(', ') suggest = suggest[0, 125] + '...' if suggest.length > 128 raise "ambiguous symbol name #{ex}: #{suggest} ?" end end bd[ex] = v } e = e.bind(bd) e end
see EData#pattern_scan scans only mapped areas of @memory, using os_process.mappings
# File metasm/debug.rb, line 1423 def pattern_scan(pat, start=0, len=@memory.length-start, &b) ret = [] mappings.each { |maddr, mlen, perm, *o_| next if perm !~ /r/i mlen -= start-maddr if maddr < start maddr = start if maddr < start mlen = start+len-maddr if maddr+mlen > start+len next if mlen <= 0 EncodedData.new(read_mapped_range(maddr, mlen)).pattern_scan(pat) { |off| off += maddr ret << off if not b or b.call(off) } } ret end
retreive the value of the program counter register (eip)
# File metasm/debug.rb, line 1049 def pc get_reg_value(register_pc) end
change the value of pc
# File metasm/debug.rb, line 1055 def pc=(v) set_reg_value(register_pc, v) end
change pid and associated cached data this will also re-load the previously selected tid for this process
# File metasm/debug.rb, line 171 def pid=(npid) return if npid == pid raise "invalid pid" if not check_pid(npid) swapout_pid @pid = npid swapin_pid end
handles breakpoint conditions/callbacks etc
# File metasm/debug.rb, line 738 def post_evt_bp(b) @breakpoint_cause = b found_valid_active = false pre_callback_pc = pc # XXX may have many active bps with callback that continue/singlestep/singlestep{}... b.hash_shared.dup.find_all { |bb| # ignore inactive bps next if bb.state != :active # ignore out-of-range bpms next if bb.type == :bpm and not check_bpm_range(bb, b.internal) # check condition case bb.condition when nil; cd = 1 when Proc; cd = bb.condition.call when String, Expression; cd = resolve_expr(bb.condition) else raise "unknown bp condition #{bb.condition.inspect}" end next if not cd or cd == 0 found_valid_active = true # oneshot del_bp(bb) if bb.oneshot bb.action }.each { |bb| bb.action.call } # discard @breakpoint_cause if a bp callback did modify register_pc @breakpoint_cause = nil if pc != pre_callback_pc # we did break due to a bp whose condition is not true: resume # (unless a callback already resumed) resume_badbreak(b) if not found_valid_active and @state == :stopped end
# File metasm/debug.rb, line 1439 def read_mapped_range(addr, len) # try to use a single get_page call s = @memory.get_page(addr, len) || '' s.length == len ? s : (s = @memory[addr, len] ? s.to_str : nil) end
then name of the register holding the cpu flags
# File metasm/debug.rb, line 1039 def register_flags @cpu.dbg_register_flags end
list the general purpose register names available for the target
# File metasm/debug.rb, line 1019 def register_list @cpu.dbg_register_list end
retrieves the name of the register holding the program counter (address of the next instruction)
# File metasm/debug.rb, line 1029 def register_pc @cpu.dbg_register_pc end
hash { register_name => register_size_in_bits }
# File metasm/debug.rb, line 1024 def register_size @cpu.dbg_register_size end
retrieve the name of the register holding the stack pointer
# File metasm/debug.rb, line 1034 def register_sp @cpu.dbg_register_sp end
resolves an expression involving register values and/or memory indirection using the current context uses register_list, get_reg_value, @mem, @cpu :tid/:pid resolve to current thread
# File metasm/debug.rb, line 1263 def resolve_expr(e) e = parse_expr(e) if e.kind_of? ::String bd = { :tid => @tid, :pid => @pid } Expression[e].externals.each { |ex| next if bd[ex] case ex when ::Symbol; bd[ex] = get_reg_value(ex) when ::String; bd[ex] = @symbols.index(ex) || @disassembler.prog_binding[ex] || 0 end } Expression[e].bind(bd).reduce { |i| if i.kind_of? Indirection and p = i.pointer.reduce and p.kind_of? ::Integer i.len ||= @cpu.size/8 p &= (1 << @cpu.size) - 1 if p < 0 Expression.decode_imm(@memory, i.len, @cpu, p) end } end
called when we did break due to a breakpoint whose condition is invalid resume execution as if we never stopped disable offending bp + singlestep if needed
# File metasm/debug.rb, line 856 def resume_badbreak(b=nil) # ensure we didn't delete b if b and b.hash_shared.find { |bb| bb.state == :active } rm = @run_method if rm == :singlestep singlestep_bp(b) else ra = @run_args singlestep_bp(b) { send rm, *ra } end else send @run_method, *@run_args end end
#continue_wait until @state == :dead
# File metasm/debug.rb, line 1009 def run_forever continue_wait until @state == :dead end
scan the target memory for loaded libraries, load their symbols
# File metasm/debug.rb, line 1178 def scansyms(addr=0, max=@memory.length-0x1000-addr) while addr <= max loadsyms(addr) addr += 0x1000 end end
set the value of the flag to true
# File metasm/debug.rb, line 1091 def set_flag(f) @cpu.dbg_set_flag(self, f) end
change the value of a flag
# File metasm/debug.rb, line 1081 def set_flag_value(f, v) (v && v != 0) ? set_flag(f) : unset_flag(f) end
define the lambda to use to log stuff
# File metasm/debug.rb, line 555 def set_log_proc(l=nil, &b) @log_proc = l || b end
# File metasm/debug.rb, line 166 def shortname; self.class.name.split('::').last.downcase; end
resume execution of the target one instruction at a time
# File metasm/debug.rb, line 939 def singlestep(&b) @singlestep_cb = b bp = check_breakpoint_cause return if not check_pre_run(:singlestep) if bp and bp.hash_shared.find { |bb| bb.state == :active } and has_emul_instr(bp) @state = :stopped bp.emul_instr.call invalidate evt_singlestep(true) else do_singlestep end end
singlesteps over an active breakpoint and run its block if the breakpoint provides an emulation stub, run that, otherwise disable the breakpoint, singlestep, and re-enable
# File metasm/debug.rb, line 874 def singlestep_bp(bp, &b) if has_emul_instr(bp) @state = :stopped bp.emul_instr.call b.call if b else bp.hash_shared.each { |bb| disable_bp(bb, :temp_inactive) if bb.state == :active } # this *should* work with different bps stopping the current instr prev_sscb = @singlestep_cb singlestep { bp.hash_shared.each { |bb| enable_bp(bb) if bb.state == :temp_inactive } prev_sscb[] if prev_sscb b.call if b } end end
singlestep ; #wait_target
# File metasm/debug.rb, line 954 def singlestep_wait(&b) singlestep(&b) wait_target end
retrieve the value of the stack pointer register
# File metasm/debug.rb, line 1061 def sp get_reg_value(register_sp) end
update the stack pointer
# File metasm/debug.rb, line 1066 def sp=(v) set_reg_value(register_sp, v) end
return/yield an array of [addr, addr symbolic name] corresponding to the current stack trace
# File metasm/debug.rb, line 1284 def stacktrace(maxdepth=500, &b) @cpu.dbg_stacktrace(self, maxdepth, &b) end
stepover until finding the last instruction of the function
# File metasm/debug.rb, line 988 def stepout # TODO thread-local bps while not end_stepout stepover wait_target end do_singlestep end
# File metasm/debug.rb, line 997 def stepout_wait stepout wait_target end
stepover: singlesteps, but do not enter in subfunctions
# File metasm/debug.rb, line 966 def stepover di = di_at(pc) if need_stepover(di) bpx di.next_addr, true, Expression[:tid, :==, @tid] continue else singlestep end end
stepover ; #wait_target
# File metasm/debug.rb, line 977 def stepover_wait stepover wait_target end
we're switching focus from one pid to another, load current pid data
# File metasm/debug.rb, line 249 def swapin_pid return initialize_newpid if not @pid_stuff[@pid] @pid_stuff_list.each { |fld| instance_variable_set("@#{fld}", @pid_stuff[@pid][fld]) } swapin_tid gui.swapin_pid if gui.respond_to?(:swapin_pid) end
we're switching focus from one tid to another, load current tid data
# File metasm/debug.rb, line 260 def swapin_tid return initialize_newtid if not @tid_stuff[@tid] @tid_stuff_list.each { |fld| instance_variable_set("@#{fld}", @tid_stuff[@tid][fld]) } gui.swapin_tid if gui.respond_to?(:swapin_tid) end
we're switching focus from one pid to another, save current pid data
# File metasm/debug.rb, line 228 def swapout_pid return if not pid swapout_tid gui.swapout_pid if gui.respond_to?(:swapout_pid) @pid_stuff[@pid] ||= {} @pid_stuff_list.each { |fld| @pid_stuff[@pid][fld] = instance_variable_get("@#{fld}") } end
we're switching focus from one tid to another, save current tid data
# File metasm/debug.rb, line 239 def swapout_tid return if not tid gui.swapout_tid if gui.respond_to?(:swapout_tid) @tid_stuff[@tid] ||= {} @tid_stuff_list.each { |fld| @tid_stuff[@tid][fld] = instance_variable_get("@#{fld}") } end
change the debugger to a specific pid/tid if given a block, run the block and then restore the original pid/tid pid may be an object that respond to pid/#tid
# File metasm/debug.rb, line 302 def switch_context(npid, ntid=nil, &b) if npid.respond_to?(:pid) ntid ||= npid.tid npid = npid.pid end oldpid = pid oldtid = tid set_pid npid set_tid ntid if ntid if b # shortcut begin..ensure overhead return b.call if oldpid == pid and oldtid == tid begin b.call ensure set_pid oldpid set_tid oldtid end end end
# File metasm/debug.rb, line 181 def tid=(ntid) return if ntid == tid raise "invalid tid" if not check_tid(ntid) swapout_tid @tid = ntid swapin_tid end
switch the value of a flag (true->false, false->true)
# File metasm/debug.rb, line 1086 def toggle_flag(f) set_flag_value(f, 1-get_flag_value(f)) end
set the value of the flag to false
# File metasm/debug.rb, line 1096 def unset_flag(f) @cpu.dbg_unset_flag(self, f) end
waits until the running target stops (due to a breakpoint, fault, etc)
# File metasm/debug.rb, line 912 def wait_target do_wait_target while @state == :running end