#    This file is part of Metasm, the Ruby assembly manipulation suite
#    Copyright (C) 2006-2009 Yoann GUILLOT
#
#    Licence is LGPL, see LICENCE in the top-level directory

module Metasm
# this class implements a high-level debugging API (abstract superclass)
class Debugger
	class Breakpoint
		attr_accessor :address,
			# context where the bp was defined
			:pid, :tid,
			# bool: oneshot ?
			:oneshot,
			# current bp state: :active, :inactive (internal use), :disabled (user-specified)
			:state,
			# type: type of breakpoint (:bpx = soft, :hwbp = hard, :bpm = memory)
			:type,
			# Expression if this is a conditionnal bp
			# may be a Proc, String or Expression, evaluated every time the breakpoint hits
			# if it returns 0 or false, the breakpoint is ignored
			:condition,
			# Proc to run if this bp has a callback
			:action,
			# Proc to run to emulate the overwritten instr behavior
			# used to avoid unset/singlestep/re-set, more multithread friendly
			# may be a DecodedInstruction for lazy initialization, see Debugger#init_bpx/has_emul_instr(bpx)
			:emul_instr,
			# internal data, cpu-specific (overwritten byte for a softbp, memory type/size for hwbp..)
			:internal,
			# reference breakpoints sharing a target implementation (same hw debug register, soft bp addr...)
			#  shared is an array of Breakpoints, the same Array object in all shared breakpoints
			#  owner is a hash key => shared (dbg.breakpoint)
			#  key is an identifier for the Bp class in owner (bp.address)
			:hash_shared, :hash_owner, :hash_key,
			# user-defined breakpoint-specific stuff
			:userdata

		# append the breakpoint to hash_owner + hash_shared
		def add(owner=@hash_owner)
			@hash_owner = owner
			@hash_key ||= @address
			return add_bpm if @type == :bpm
			if pv = owner[@hash_key]
				@hash_shared  = pv.hash_shared
				@internal   ||= pv.internal
				@emul_instr ||= pv.emul_instr
			else
				owner[@hash_key] = self
				@hash_shared = []
			end
			@hash_shared << self
		end

		# register a bpm: add references to all page start covered in @hash_owner
		def add_bpm
			m = @address + @internal[:len]
			a = @address & -0x1000
			@hash_shared = [self]

			@internal ||= {}
			@internal[:orig_prot] ||= {}
			while a < m
				if pv = @hash_owner[a]
					if not pv.hash_shared.include?(self)
						pv.hash_shared.concat @hash_shared-pv.hash_shared
						@hash_shared.each { |bpm| bpm.hash_shared = pv.hash_shared }
					end
					@internal[:orig_prot][a] = pv.internal[:orig_prot][a]
				else
					@hash_owner[a] = self
				end
				a += 0x1000
			end
		end

		# delete the breakpoint from hash_shared, and hash_owner if empty
		def del
			return del_bpm if @type == :bpm
			@hash_shared.delete self
			if @hash_shared.empty?
				@hash_owner.delete @hash_key
			elsif @hash_owner[@hash_key] == self
				@hash_owner[@hash_key] = @hash_shared.first
			end
		end

		# unregister a bpm
		def del_bpm
			m = @address + @internal[:len]
			a = @address & -0x1000
			@hash_shared.delete self
			while a < m
				pv = @hash_owner[a]
				if pv == self
					if opv = @hash_shared.find { |bpm|
							bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] > a
						}
						@hash_owner[a] = opv
					else
						@hash_owner.delete a

						# split hash_shared on disjoint ranges
						prev_shared = @hash_shared.find_all { |bpm|
							bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] <= a
						}

						prev_shared.each { |bpm|
							bpm.hash_shared = prev_shared
							@hash_shared.delete bpm
						}
					end
				end
				a += 0x1000
			end
		end
	end

	# per-process data
	attr_accessor :memory, :cpu, :disassembler, :breakpoint, :breakpoint_memory,
		:modulemap, :symbols, :symbols_len
	# per-thread data
	attr_accessor :state, :info, :breakpoint_thread, :singlestep_cb, :run_method,
		:run_args, :breakpoint_cause

	# which/where per-process/thread stuff is stored
	attr_accessor :pid_stuff, :tid_stuff, :pid_stuff_list, :tid_stuff_list

	# global debugger callbacks, called whenever such event occurs
	attr_accessor :callback_singlestep, :callback_bpx, :callback_hwbp, :callback_bpm,
		:callback_exception, :callback_newthread, :callback_endthread,
		:callback_newprocess, :callback_endprocess, :callback_loadlibrary

	# 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
	attr_accessor :pass_all_exceptions, :ignore_newthread, :ignore_endthread,
		:trace_children

	# link to the user-interface object if available
	attr_accessor :gui

	# initializes the disassembler internal data - subclasses should call super()
	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

	def dasm; disassembler; end

	def shortname; self.class.name.split('::').last.downcase; end

	attr_reader :pid
	# change pid and associated cached data
	# this will also re-load the previously selected tid for this process
	def pid=(npid)
		return if npid == pid
		raise "invalid pid" if not check_pid(npid)
		swapout_pid
		@pid = npid
		swapin_pid
	end
	alias set_pid pid=

	attr_reader :tid
	def tid=(ntid)
		return if ntid == tid
		raise "invalid tid" if not check_tid(ntid)
		swapout_tid
		@tid = ntid
		swapin_tid
	end
	alias set_tid tid=

	# 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
	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
	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

	# initialize the disassembler from @cpu/@memory
	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

	# we're switching focus from one pid to another, save current pid data
	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
	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

	# we're switching focus from one pid to another, load current pid data
	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
	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

	# delete references to the current pid
	# switch to another pid, set @state = :dead if none available
	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
	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...
	def del_tid_notid
		del_pid
	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
	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
	alias set_context switch_context

	# iterate over all pids, yield in the context of this pid
	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 the current process, yield in its context
	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

	# iterate over all tids of all pids, yield in their context
	def each_pid_tid(&b)
		each_pid { each_tid { b.call } }
	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
	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

	# remove a breakpoint
	def del_bp(b)
		disable_bp(b)
		b.del
	end

	# activate an inactive breakpoint
	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

	# deactivate an active breakpoint
	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


	# delete all breakpoints defined in the current thread
	def del_all_breakpoints_thread
		@breakpoint_thread.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) }
	end

	# delete all breakpoints for the current process and all its threads
	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

	# calls do_enable_bpm for bpms, or @cpu.dbg_enable_bp
	def do_enable_bp(b)
		if b.type == :bpm; do_enable_bpm(b)
		else @cpu.dbg_enable_bp(self, b)
		end
	end

	# calls do_disable_bpm for bpms, or @cpu.dbg_disable_bp
	def do_disable_bp(b)
		if b.type == :bpm; do_disable_bpm(b)
		else @cpu.dbg_disable_bp(self, b)
		end
	end

	# called in the context of the target when a bpx is to be initialized
	# may (lazily) initialize b.emul_instr for virtual singlestep
	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)
	def init_bpx_disassemble(addr)
		@disassembler.disassemble_fast_block(addr)
		@disassembler.di_at(addr)
	end

	# checks if bp has an emul_instr
	# do the lazy initialization if needed
	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

	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

	# sets a breakpoint on execution
	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

	# 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
	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

	# sets a memory breakpoint
	# mtype is :r :w :rw or :x
	# mlen is the size of the memory zone to cover
	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


	# define the lambda to use to log stuff
	def set_log_proc(l=nil, &b)
		@log_proc = l || b
	end

	# show information to the user, uses log_proc if defined
	def log(*a)
		if @log_proc
			a.each { |aa| @log_proc[aa] }
		else
			puts(*a) if $VERBOSE
		end
	end


	# marks the current cache of memory/regs invalid
	def invalidate
		@memory.invalidate if @memory
	end

	# invalidates the EncodedData backend for the dasm sections
	def dasm_invalidate
		disassembler.sections.each_value { |s| s.data.invalidate if s.data.respond_to?(:invalidate) } if disassembler
	end

	# return all breakpoints set on a specific address (or all bp)
	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

	# return on of the breakpoints at address addr
	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


	# 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)
	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


	# called when the target stops due to a singlestep exception
	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

	# returns true if the singlestep is due to us
	def find_singlestep
		return @cpu.dbg_find_singlestep(self) if @cpu.respond_to?(:dbg_find_singlestep)
		@run_method == :singlestep
	end

	# called when the target stops due to a soft breakpoint exception
	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

	# return the breakpoint that is responsible for the evt_bpx
	def find_bp_bpx
		return @cpu.dbg_find_bpx(self) if @cpu.respond_to?(:dbg_find_bpx)
		@breakpoint[pc]
	end

	# called when the target stops due to a hwbp exception
	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

	# return the breakpoint that is responsible for the evt_hwbp
	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

	# 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)
	def evt_hwbp_singlestep
		if b = find_bp_hwbp
			evt_hwbp(b)
		else
			evt_singlestep
		end
	end

	# called when the target stops due to a memory exception caused by a memory bp
	# called by evt_exception
	def evt_bpm(b)
		@state = :stopped
		@info = 'bpm'

		callback_bpm[b] if callback_bpm

		post_evt_bp(b)
	end

	# return a bpm whose page coverage includes the fault described in info
	def find_bp_bpm(info)
		@breakpoint_memory[info[:fault_addr] & -0x1000]
	end

	# returns true if the fault described in info is valid to trigger b
	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

	# handles breakpoint conditions/callbacks etc
	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

	# 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)
	# ...
	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

	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

	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

	def evt_newprocess(info={})
		@state = :stopped
		@info = 'new process'

		callback_newprocess[info] if callback_newprocess
	end

	def evt_endprocess(info={})
		@state = :stopped
		@info = 'end process'
		@dead_process = true

		callback_endprocess[info] if callback_endprocess
	end

	def evt_loadlibrary(info={})
		@state = :stopped
		@info = 'loadlibrary'

		callback_loadlibrary[info] if callback_loadlibrary
	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
	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

	# 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
	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

	# checks if @breakpoint_cause is valid, or was obsoleted by the user changing pc
	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

	# checks if the running target has stopped (nonblocking)
	# returns false if no debug event happened
	def check_target
		do_check_target
	end

	# waits until the running target stops (due to a breakpoint, fault, etc)
	def wait_target
		do_wait_target while @state == :running
	end

	# resume execution of the target
	# bypasses a software breakpoint on pc if needed
	# thread breakpoints must be manually disabled before calling continue
	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
	alias run continue

	# continue ; wait_target
	def continue_wait
		continue
		wait_target
	end

	# resume execution of the target one instruction at a time
	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

	# singlestep ; wait_target
	def singlestep_wait(&b)
		singlestep(&b)
		wait_target
	end

	# tests if the specified instructions should be stepover() using singlestep or
	# by putting a breakpoint at next_addr
	def need_stepover(di = di_at(pc))
		di and @cpu.dbg_need_stepover(self, di.address, di)
	end

	# stepover: singlesteps, but do not enter in subfunctions
	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
	def stepover_wait
		stepover
		wait_target
	end

	# checks if an instruction should stop the stepout() (eg it is a return instruction)
	def end_stepout(di = di_at(pc))
		di and @cpu.dbg_end_stepout(self, di.address, di)
	end

	# stepover until finding the last instruction of the function
	def stepout
		# TODO thread-local bps
		while not end_stepout
			stepover
			wait_target
		end
		do_singlestep
	end

	def stepout_wait
		stepout
		wait_target
	end

	# set a singleshot breakpoint, run the process, and wait
	def go(target, cond=nil)
		bpx(target, true, cond)
		continue_wait
	end

	# continue_wait until @state == :dead
	def run_forever
		continue_wait until @state == :dead
	end

	# decode the Instruction at the address, use the @disassembler cache if available
	def di_at(addr)
		@disassembler.di_at(addr) || @disassembler.disassemble_instruction(addr)
	end

	# list the general purpose register names available for the target
	def register_list
		@cpu.dbg_register_list
	end

	# hash { register_name => register_size_in_bits }
	def register_size
		@cpu.dbg_register_size
	end

	# retrieves the name of the register holding the program counter (address of the next instruction)
	def register_pc
		@cpu.dbg_register_pc
	end

	# retrieve the name of the register holding the stack pointer
	def register_sp
		@cpu.dbg_register_sp
	end

	# then name of the register holding the cpu flags
	def register_flags
		@cpu.dbg_register_flags
	end

	# list of flags available in the flag register
	def flag_list
		@cpu.dbg_flag_list
	end

	# retreive the value of the program counter register (eip)
	def pc
		get_reg_value(register_pc)
	end
	alias ip pc

	# change the value of pc
	def pc=(v)
		set_reg_value(register_pc, v)
	end
	alias ip= pc=

	# retrieve the value of the stack pointer register
	def sp
		get_reg_value(register_sp)
	end

	# update the stack pointer
	def sp=(v)
		set_reg_value(register_sp, v)
	end

	# retrieve the value of a flag (0/1)
	def get_flag_value(f)
		@cpu.dbg_get_flag(self, f)
	end

	# retrieve the value of a flag (true/false)
	def get_flag(f)
		get_flag_value(f) != 0
	end

	# change the value of a flag
	def set_flag_value(f, v)
		(v && v != 0) ? set_flag(f) : unset_flag(f)
	end

	# switch the value of a flag (true->false, false->true)
	def toggle_flag(f)
		set_flag_value(f, 1-get_flag_value(f))
	end

	# set the value of the flag to true
	def set_flag(f)
		@cpu.dbg_set_flag(self, f)
	end

	# set the value of the flag to false
	def unset_flag(f)
		@cpu.dbg_unset_flag(self, f)
	end

	# returns the name of the module containing addr or nil
	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')
	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
	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

	# loads the symbols from a mapped module
	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

	# scan the target memory for loaded libraries, load their symbols
	def scansyms(addr=0, max=@memory.length-0x1000-addr)
		while addr <= max
			loadsyms(addr)
			addr += 0x1000
		end
	end

	# load symbols from all libraries found by the OS module
	def loadallsyms(&b)
		modules.each { |m|
			b.call(m.addr) if b
			loadsyms(m.addr, m.path)
		}
	end

	# see Disassembler#load_map
	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

	# parses the expression contained in arg
	def parse_expr(arg)
		parse_expr!(arg.dup)
	end

	# parses the expression contained in arg, updates arg to point after the expr
	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

	# 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
	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
	alias resolve resolve_expr

	# return/yield an array of [addr, addr symbolic name] corresponding to the current stack trace
	def stacktrace(maxdepth=500, &b)
		@cpu.dbg_stacktrace(self, maxdepth, &b)
	end

	# accepts a range or begin/end address to read memory, or a register name
	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
	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


	# read an int from the target memory, int of sz bytes (defaults to cpu.size)
	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
	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

	# retrieve an argument (call at a function entrypoint)
	def func_arg(nr)
		@cpu.dbg_func_arg(self, nr)
	end
	def func_arg_set(nr, val)
		@cpu.dbg_func_arg_set(self, nr, val)
	end

	# retrieve a function returned value (call at func exitpoint)
	def func_retval
		@cpu.dbg_func_retval(self)
	end
	def func_retval_set(val)
		@cpu.dbg_func_retval_set(self, val)
	end
	def func_retval=(val)
		@cpu.dbg_func_retval_set(self, val)
	end

	# retrieve a function return address (call at func entry/exit)
	def func_retaddr
		@cpu.dbg_func_retaddr(self)
	end
	def func_retaddr_set(addr)
		@cpu.dbg_func_retaddr_set(self, addr)
	end
	def func_retaddr=(addr)
		@cpu.dbg_func_retaddr_set(self, addr)
	end

	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

	# return the list of memory mappings of the current process
	# array of [start, len, perms, infos]
	def mappings
		[[0, @memory.length]]
	end

	# return a list of Process::Modules (with a #path, #addr) for the current process
	def modules
		[]
	end

	# list debugged pids
	def list_debug_pids
		@pid_stuff.keys | [@pid].compact
	end

	# return a list of OS::Process listing all alive processes (incl not debugged)
	# default version only includes current debugged pids
	def list_processes
		list_debug_pids.map { |p| OS::Process.new(p) }
	end

	# check if pid is valid
	def check_pid(pid)
		list_processes.find { |p| p.pid == pid }
	end

	# list debugged tids
	def list_debug_tids
		@tid_stuff.keys | [@tid].compact
	end

	# list of thread ids existing in the current process (incl not debugged)
	# default version only lists debugged tids
	alias list_threads list_debug_tids

	# check if tid is valid for the current process
	def check_tid(tid)
		list_threads.include?(tid)
	end

	# see EData#pattern_scan
	# scans only mapped areas of @memory, using os_process.mappings
	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

	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
end
end
