Skip to content

Commit

Permalink
uEmu: various fixes and improvements
Browse files Browse the repository at this point in the history
- Create fresh unicorn engine on start and release it on reset
- For ARM LE/BE show prompt to enable VFP
- Add missing API for backwards compatibility with IDA 6.95
- Print correct number of instruction bytes for trace
- Left-align instruction bytes for trace
- Fix end address parameter in emu_start
- Fix thumb detection for IDA 7.1
- Fix popup menu for "CPU Context Edit"
- Fix popup menu duplication for "CPU Context" view
- Minor refactoring and renaming
- Remove semicolons (kudos to @bkerler)
  • Loading branch information
alexhude committed Aug 2, 2018
1 parent 233f2ff commit da9befe
Showing 1 changed file with 65 additions and 42 deletions.
107 changes: 65 additions & 42 deletions uEmu.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@
IDAAPI_GetBytes = get_bytes
IDAAPI_AskYN = ask_yn
IDAAPI_AskFile = ask_file
IDAAPI_AskLong = ask_long
IDAAPI_NextHead = next_head
IDAAPI_GetDisasm = generate_disasm_line
IDAAPI_NextThat = next_that
IDAAPI_Jump = jumpto
# classes
IDAAPI_Choose = Choose
else:
Expand All @@ -65,9 +67,11 @@
IDAAPI_GetBytes = get_many_bytes
IDAAPI_AskYN = AskYN
IDAAPI_AskFile = AskFile
IDAAPI_AskLong = AskLong
IDAAPI_NextHead = NextHead
IDAAPI_GetDisasm = GetDisasmEx
IDAAPI_NextThat = nextthat
IDAAPI_Jump = Jump
# classes
IDAAPI_Choose = Choose2

Expand Down Expand Up @@ -298,7 +302,10 @@ def get_register_map(arch):
@staticmethod
def is_thumb_ea(ea):
if ph.id == PLFM_ARM and not ph.flag & PR_USE64:
t = get_segreg(ea, 20) # get T flag
if IDA_SDK_VERSION >= 700:
t = get_sreg(ea, "T") # get T flag
else:
t = get_segreg(ea, 20) # get T flag
return t is not BADSEL and t is not 0
else:
return False
Expand Down Expand Up @@ -382,6 +389,7 @@ def finish_populating_widget_popup(self, widget, popup):
attach_action_to_popup(widget, popup, "-", None)
attach_dynamic_action_to_popup(widget, popup, action_desc_t(None, "Change Context", self.PopupActionHandler(self.form, self.form.menu_update), None, None, -1))
attach_action_to_popup(widget, popup, "-", None)

if self.hooks == None:
self.hooks = Hooks(self)
self.hooks.hook()
Expand Down Expand Up @@ -476,6 +484,8 @@ def SetContent(self, address, context):
self.lastAddress = address

def OnClose(self):
self.hooks.unhook()
self.hooks = None
self.owner.context_view_closed()

# === uEmuMemoryView
Expand Down Expand Up @@ -516,7 +526,7 @@ def SetContent(self, context):
self.ClearLines()

if context is None:
return;
return

memory = context.mem_read(self.address, self.size)

Expand Down Expand Up @@ -687,9 +697,9 @@ def __init__(self):
uEmu Settings
<Follow PC:{chk_followpc}>
<Convert to Code automatically:{chk_forcecode}>
<Trace instructions:{trc_inst}>{emu_group}>
<Trace instructions:{chk_trace}>{emu_group}>
""", {
'emu_group': Form.ChkGroupControl(("chk_followpc", "chk_forcecode", "trc_inst")),
'emu_group': Form.ChkGroupControl(("chk_followpc", "chk_forcecode", "chk_trace")),
})

# === uEmuUnicornEngine
Expand Down Expand Up @@ -745,7 +755,7 @@ class uEmuContextInitDialog(IDAAPI_Choose):
def __init__(self, regs, flags=0, width=None, height=None, embedded=False):
IDAAPI_Choose.__init__(
self,
"uEmu CPU Context",
"uEmu CPU Context Edit",
[ ["Register", 10], ["Value", 30] ],
flags = flags,
width = width,
Expand Down Expand Up @@ -829,10 +839,8 @@ def __init__(self, owner):
arch = UEMU_HELPERS.get_arch()

self.uc_reg_pc = uc_setup[arch][0]
self.mu = Uc(uc_setup[arch][1], uc_setup[arch][2])

self.mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, self.hook_mem_invalid)
self.mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, self.hook_mem_access)
self.uc_arch = uc_setup[arch][1]
self.uc_mode = uc_setup[arch][2]
uemu_log("CPU arch set to [ %s ]" % (arch))

def is_active(self):
Expand Down Expand Up @@ -876,9 +884,9 @@ def set_context(self, context):

if not self.emuActive:
if self.owner.trace_inst():
bytes = IDAAPI_GetBytes(self.pc, 4)
bytes = IDAAPI_GetBytes(self.pc, IDAAPI_NextHead(self.pc) - self.pc)
bytes_hex = " ".join("{:02X}".format(ord(c)) for c in bytes)
uemu_log("* TRACE<I> 0x%X | %16s | %s" % (self.pc, bytes_hex, IDAAPI_GetDisasm(self.pc, 0)))
uemu_log("* TRACE<I> 0x%X | %-16s | %s" % (self.pc, bytes_hex, IDAAPI_GetDisasm(self.pc, 0)))

self.emuActive = True

Expand All @@ -890,7 +898,7 @@ def is_memory_mapped(self, address):
return False

def jump_to_pc(self):
Jump(self.pc)
IDAAPI_Jump(self.pc)

def map_memory(self, address, size):
# - size is unsigned and must be != 0
Expand Down Expand Up @@ -1067,6 +1075,17 @@ def init_cpu_context(self, pc):
for idx, val in enumerate(cpuContext.items):
self.mu.reg_write(reg_map[idx][1], int(val[1], 0))
self.mu.reg_write(self.uc_reg_pc, pc)

# enable VFP
if UEMU_HELPERS.get_arch() in ["armle", "armbe"]:
if IDAAPI_AskYN(1, "Enable VFP instruction emulation?") == 1:
tmp_val = self.mu.reg_read(UC_ARM_REG_C1_C0_2)
tmp_val = tmp_val | (0xf << 20)
self.mu.reg_write(UC_ARM_REG_C1_C0_2, tmp_val)
enable_vfp = 0x40000000
self.mu.reg_write(UC_ARM_REG_FPEXC, enable_vfp)
uemu_log("VFP enabled")

return True
else:
return False
Expand All @@ -1085,6 +1104,9 @@ def set_cpu_context(self):
return False

def run_from(self, address):
self.mu = Uc(self.uc_arch, self.uc_mode)
self.mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, self.hook_mem_invalid)
self.mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, self.hook_mem_access)
self.pc = address

try:
Expand Down Expand Up @@ -1118,9 +1140,9 @@ def run_from(self, address):
lastAddress = endAligned

if self.owner.trace_inst():
bytes = IDAAPI_GetBytes(self.pc, 4)
bytes = IDAAPI_GetBytes(self.pc, IDAAPI_NextHead(self.pc) - self.pc)
bytes_hex = " ".join("{:02X}".format(ord(c)) for c in bytes)
uemu_log("* TRACE<I> 0x%X | %16s | %s" % (self.pc, bytes_hex, IDAAPI_GetDisasm(self.pc, 0)))
uemu_log("* TRACE<I> 0x%X | %-16s | %s" % (self.pc, bytes_hex, IDAAPI_GetDisasm(self.pc, 0)))

IDAAPI_SetColor(self.pc, CIC_ITEM, UEMU_CONFIG.IDAViewColor_PC)
self.emuActive = True
Expand All @@ -1130,7 +1152,7 @@ def run_from(self, address):

def step_thread_main(self):
try:
self.mu.emu_start(self.pc | 1 if UEMU_HELPERS.is_thumb_ea(self.pc) else self.pc, 4, count=1)
self.mu.emu_start(self.pc | 1 if UEMU_HELPERS.is_thumb_ea(self.pc) else self.pc, -1, count=1)
# vvv Workaround to fix issue when registers are still updated even if emu_stop is called
if self.fix_context is not None:
self.mu.context_restore(self.fix_context)
Expand All @@ -1139,9 +1161,9 @@ def step_thread_main(self):
def result_handler():
self.pc = self.mu.reg_read(self.uc_reg_pc)
if self.owner.trace_inst():
bytes = IDAAPI_GetBytes(self.pc, 4)
bytes = IDAAPI_GetBytes(self.pc, IDAAPI_NextHead(self.pc) - self.pc)
bytes_hex = " ".join("{:02X}".format(ord(c)) for c in bytes)
uemu_log("* TRACE<I> 0x%X | %16s | %s" % (self.pc, bytes_hex, IDAAPI_GetDisasm(self.pc, 0)))
uemu_log("* TRACE<I> 0x%X | %-16s | %s" % (self.pc, bytes_hex, IDAAPI_GetDisasm(self.pc, 0)))

if not IDAAPI_IsCode(IDAAPI_GetFlags(self.pc)) and self.owner.force_code():
uemu_log("Creating code at 0x%X" % (self.pc))
Expand Down Expand Up @@ -1236,8 +1258,8 @@ def interrupt(self):
self.emuRunning = False

def is_breakpoint_reached(self, address):
for idx in range(GetBptQty()):
if GetBptEA(idx) == address and GetBptAttr(address, BPTATTR_FLAGS) & BPT_ENABLED:
for idx in range(IDAAPI_GetBptQty()):
if IDAAPI_GetBptEA(idx) == address and IDAAPI_GetBptAttr(address, BPTATTR_FLAGS) & BPT_ENABLED:
return True
return False

Expand All @@ -1250,6 +1272,7 @@ def reset(self):

IDAAPI_SetColor(self.pc, CIC_ITEM, UEMU_CONFIG.IDAViewColor_Reset)
self.pc = BADADDR
self.mu = None
self.emuActive = False

# === uEmuPlugin
Expand Down Expand Up @@ -1325,13 +1348,13 @@ def ready_to_run(self):
self.unicornEngine = uEmuUnicornEngine(self)

def follow_pc(self):
return self.settings["follow_pc"];
return self.settings["follow_pc"]

def force_code(self):
return self.settings["force_code"];
return self.settings["force_code"]

def trace_inst(self):
return self.settings["trace_inst"];
return self.settings["trace_inst"]

def load_project(self):
filePath = IDAAPI_AskFile(0, "*.emu", "Open eEmu project")
Expand Down Expand Up @@ -1460,38 +1483,38 @@ def update_context(self, address, context):

def emu_start(self):
if self.unicornEngine.is_active():
uemu_log("Emulator is already active");
uemu_log("Emulator is already active")
return

if self.unicornEngine.is_running():
uemu_log("Emulator is running");
uemu_log("Emulator is running")
return

uemu_log("Emulation started")
self.unicornEngine.run_from(IDAAPI_ScreenEA())
uemu_log("Emulation started")

def emu_run(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

if self.unicornEngine.is_running():
uemu_log("Emulator is running");
uemu_log("Emulator is running")
return

self.unicornEngine.run()

def emu_step(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

if self.unicornEngine.is_running():
uemu_log("Emulator is running");
uemu_log("Emulator is running")
return
count = 1
if UEMU_HELPERS.is_alt_pressed():
count = AskLong(1, "Enter Step Count")
count = IDAAPI_AskLong(1, "Enter Step Count")
if count is None or count < 0:
return
if count == 0:
Expand All @@ -1501,18 +1524,18 @@ def emu_step(self):

def emu_stop(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

if not self.unicornEngine.is_running():
uemu_log("Emulator is not running");
uemu_log("Emulator is not running")
return

self.unicornEngine.interrupt()

def emu_reset(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

self.unicornEngine.reset()
Expand All @@ -1524,22 +1547,22 @@ def emu_reset(self):

def jump_to_pc(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

if self.unicornEngine.is_running():
uemu_log("Emulator is running");
uemu_log("Emulator is running")
return

self.unicornEngine.jump_to_pc()

def change_cpu_context(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

if self.unicornEngine.is_running():
uemu_log("Emulator is running");
uemu_log("Emulator is running")
return

self.unicornEngine.set_cpu_context()
Expand All @@ -1553,7 +1576,7 @@ def show_controls(self):

def show_cpu_context(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

if self.cpuContextView is None:
Expand All @@ -1565,7 +1588,7 @@ def show_cpu_context(self):

def show_memory(self, address = 0, size = 16):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

memRangeDlg = uEmuMemoryRangeDialog()
Expand Down Expand Up @@ -1597,19 +1620,19 @@ def show_memory(self, address = 0, size = 16):

def show_mapped(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

mappeduEmuMemoryView = uEmuMappeduMemoryView(self, self.unicornEngine.get_mapped_memory())
mappeduEmuMemoryView.show()

def fetch_segments(self):
if not self.unicornEngine.is_active():
uemu_log("Emulator is not active");
uemu_log("Emulator is not active")
return

if self.unicornEngine.is_running():
uemu_log("Emulator is running");
uemu_log("Emulator is running")
return

self.unicornEngine.fetch_segments()
Expand Down

0 comments on commit da9befe

Please sign in to comment.