#    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


require 'metasm/cpu/ia32/opcodes'

module Metasm
class Ia32
	def dbg_register_pc
		@dbg_register_pc ||= :eip
	end
	def dbg_register_sp
		@dbg_register_sp ||= dbg_register_list[7]
	end
	def dbg_register_flags
		@dbg_register_flags ||= :eflags
	end

	def dbg_register_list
		@dbg_register_list ||= [:eax, :ebx, :ecx, :edx, :esi, :edi, :ebp, :esp, :eip]
	end

	def dbg_register_size
		@dbg_register_size ||= Hash.new(32).update(:cs => 16, :ds => 16, :es => 16, :fs => 16, :gs => 16)
	end

	def dbg_flag_list
		@dbg_flag_list ||= [:c, :p, :a, :z, :s, :i, :d, :o]
	end

	DBG_FLAGS = { :c => 0, :p => 2, :a => 4, :z => 6, :s => 7, :t => 8, :i => 9, :d => 10, :o => 11 }
	def dbg_get_flag(dbg, f)
		(dbg.get_reg_value(dbg_register_flags) >> DBG_FLAGS[f]) & 1
	end
	def dbg_set_flag(dbg, f)
		fl = dbg.get_reg_value(dbg_register_flags)
		fl |= 1 << DBG_FLAGS[f]
		dbg.set_reg_value(dbg_register_flags, fl)
	end
	def dbg_unset_flag(dbg, f)
		fl = dbg.get_reg_value(dbg_register_flags)
		fl &= ~(1 << DBG_FLAGS[f])
		dbg.set_reg_value(dbg_register_flags, fl)
	end

	def dbg_enable_singlestep(dbg)
		dbg_set_flag(dbg, :t) if dbg_get_flag(dbg, :t) == 0
	end
	def dbg_disable_singlestep(dbg)
		dbg_unset_flag(dbg, :t) if dbg_get_flag(dbg, :t) != 0
	end

	def dbg_enable_bp(dbg, bp)
		case bp.type
		when :bpx; dbg_enable_bpx( dbg, bp)
		else       dbg_enable_bphw(dbg, bp)
		end
	end

	def dbg_disable_bp(dbg, bp)
		case bp.type
		when :bpx; dbg_disable_bpx( dbg, bp)
		else       dbg_disable_bphw(dbg, bp)
		end
	end

	DBG_BPX = "\xcc"
	DBG_BPX.force_encoding('BINARY') if DBG_BPX.respond_to?(:force_encoding)
	def dbg_enable_bpx(dbg, bp)
		bp.internal[:previous] ||= dbg.memory[bp.address, 1]
		dbg.memory[bp.address, 1] = DBG_BPX
	end

	def dbg_disable_bpx(dbg, bp)
		dbg.memory[bp.address, 1] = bp.internal[:previous]
	end

	# allocate a debug register for a hwbp by checking the list of hwbp existing in dbg
	def dbg_alloc_bphw(dbg, bp)
		if not bp.internal[:dr]
			may = [0, 1, 2, 3]
			dbg.breakpoint_thread.values.each { |bb| may.delete bb.internal[:dr] }
			raise 'alloc_bphw: no free debugregister' if may.empty?
			bp.internal[:dr] = may.first
		end
		bp.internal[:type] ||= :x
		bp.internal[:len]  ||= 1
		bp.internal[:dr]
	end

	def dbg_enable_bphw(dbg, bp)
		nr = dbg_alloc_bphw(dbg, bp)
		dr7 = dbg[:dr7]
		l = { 1 => 0, 2 => 1, 4 => 3, 8 => 2 }[bp.internal[:len]]
		rw = { :x => 0, :w => 1, :r => 3 }[bp.internal[:type]]
		raise "enable_bphw: invalid breakpoint #{bp.inspect}" if not l or not rw
		dr7 &= ~((15 << (16+4*nr)) | (3 << (2*nr)))	# clear
		dr7 |= ((l << 2) | rw) << (16+4*nr)	# set drN len/rw
		dr7 |= 3 << (2*nr)	# enable global/local drN

		dbg["dr#{nr}"] = bp.address
		dbg[:dr7] = dr7
	end

	def dbg_disable_bphw(dbg, bp)
		nr = bp.internal[:dr]
		dr7 = dbg[:dr7]
		dr7 &= ~(3 << (2*nr))
		dbg[:dr7] = dr7
	end

	def dbg_check_pre_run(dbg)
		if dbg[:dr6] == 0 and dbg[:dr7] == 0
			dbg[:dr7] = 0x10000	# some OS (eg Windows) only return dr6 if dr7 != 0
		end
		dbg[:dr6] = 0 if dbg[:dr6] & 0x400f != 0
	end

	def dbg_evt_bpx(dbg, b)
		if b.address == dbg.pc-1
			dbg.pc -= 1
		end
	end

	def dbg_find_bpx(dbg)
		return if dbg[:dr6] & 0x4000 != 0
		pc = dbg.pc
		dbg.breakpoint[pc-1] || dbg.breakpoint[pc]
	end

	def dbg_find_hwbp(dbg)
		dr6 = dbg[:dr6]
		return if dr6 & 0xf == 0
		dn = (0..3).find { |n| dr6 & (1 << n) }
		dbg.breakpoint_thread.values.find { |b| b.internal[:dr] == dn }
	end

	def dbg_need_stepover(dbg, addr, di)
		di and ((di.instruction.prefix and di.instruction.prefix[:rep]) or di.opcode.props[:saveip])
	end

	def dbg_end_stepout(dbg, addr, di)
		di and di.opcode.name == 'ret'
	end

	# return (yield) a list of [addr, symbolic name]
	def dbg_stacktrace(dbg, rec=500)
		ret = []
		s = dbg.addrname!(dbg.pc)
		yield(dbg.pc, s) if block_given?
		ret << [dbg.pc, s]
		fp = dbg.get_reg_value(dbg_register_list[6])
		stack = dbg.get_reg_value(dbg_register_list[7]) - 8
		while fp > stack and fp <= stack+0x10000 and rec != 0
			rec -= 1
			ra = dbg.resolve_expr Indirection[fp+4, 4]
			s = dbg.addrname!(ra)
			yield(ra, s) if block_given?
			ret << [ra, s]
			stack = fp	# ensure we walk the stack upwards
			fp = dbg.resolve_expr Indirection[fp, 4]
		end
		ret
	end

	# retrieve the current function return value
	# only valid at function exit
	def dbg_func_retval(dbg)
		dbg.get_reg_value(dbg_register_list[0])
	end
	def dbg_func_retval_set(dbg, val)
		dbg.set_reg_value(dbg_register_list[0], val)
	end

	# retrieve the current function return address
	# to be called only on entry of the subfunction
	def dbg_func_retaddr(dbg)
		dbg.memory_read_int(dbg_register_list[7])
	end
	def dbg_func_retaddr_set(dbg, ret)
		dbg.memory_write_int(dbg_register_list[7], ret)
	end

	# retrieve the current function arguments
	# only valid at function entry (eg right after the call)
	def dbg_func_arg(dbg, argnr)
		dbg.memory_read_int(Expression[:esp, :+, 4*(argnr+1)])
	end
	def dbg_func_arg_set(dbg, argnr, arg)
		dbg.memory_write_int(Expression[:esp, :+, 4*(argnr+1)], arg)
	end

	def dbg_resolve_pc(di, fbd, pc_reg, dbg_ctx)
		a = di.instruction.args.map { |aa| symbolic(aa) }

		cond = case di.opcode.name
		when 'jz', 'je';    dbg_ctx.get_flag(:z)
		when 'jnz', 'jne'; !dbg_ctx.get_flag(:z)
		when 'jo';   dbg_ctx.get_flag(:o)
		when 'jno'; !dbg_ctx.get_flag(:o)
		when 'js';   dbg_ctx.get_flag(:s)
		when 'jns'; !dbg_ctx.get_flag(:s)
		when 'jc', 'jb', 'jnae';   dbg_ctx.get_flag(:c)
		when 'jnc', 'jnb', 'jae'; !dbg_ctx.get_flag(:c)
		when 'jbe', 'jna';  dbg_ctx.get_flag(:c) or   dbg_ctx.get_flag(:z)
		when 'jnbe', 'ja'; !dbg_ctx.get_flag(:c) and !dbg_ctx.get_flag(:z)
		when 'jl', 'jnge'; dbg_ctx.get_flag(:s) != dbg_ctx.get_flag(:o)
		when 'jnl', 'jge'; dbg_ctx.get_flag(:s) == dbg_ctx.get_flag(:o)
		when 'jle', 'jng';  dbg_ctx.get_flag(:z) or  dbg_ctx.get_flag(:s) != dbg_ctx.get_flag(:o)
		when 'jnle', 'jg'; !dbg_ctx.get_flag(:z) and dbg_ctx.get_flag(:s) == dbg_ctx.get_flag(:o)
		when 'jp', 'jpe';   dbg_ctx.get_flag(:p)
		when 'jnp', 'jpo'; !dbg_ctx.get_flag(:p)
		when 'loop';             dbg_ctx[dbg_register_list[2]] != 0
		when 'loopz', 'loope';   dbg_ctx[dbg_register_list[2]] != 0 and  dbg_ctx.get_flag(:z)
		when 'loopnz', 'loopne'; dbg_ctx[dbg_register_list[2]] != 0 and !dbg_ctx.get_flag(:z)
		when 'jcxz', 'jecxz', 'jrcxz'
			mask = {?c => 0xffff, ?e => 0xffff_ffff, ?r => -1}[di.opcode.name[1]]
			dbg_ctx[dbg_register_list[2]] & mask == 0
		else return super(di, fbd, pc_reg, dbg_ctx)
		end

		if cond
			fbd[pc_reg] = a.last
		else
			fbd[pc_reg] = di.next_addr
		end
	end
end
end
