From a0be95b7cd870e80b44ddc7923e005c58b5e469f Mon Sep 17 00:00:00 2001 From: Arnaud Diederen Date: Tue, 5 Dec 2017 10:37:09 +0100 Subject: [PATCH] IDAPython for IDA 7.0 SP1 --- Scripts/AsmViewer.py | 96 +++++++++++++------- Scripts/CallStackWalk.py | 106 +++++++++++----------- Scripts/DbgCmd.py | 127 +++++++++++++++------------ Scripts/DrvsDispatch.py | 123 +++++++++++++------------- Scripts/ExchainDump.py | 40 ++++----- Scripts/FindInstructions.py | 103 ++++++++++++---------- Scripts/ImpRef.py | 24 ++--- Scripts/PteDump.py | 42 ++++----- Scripts/SEHGraph.py | 47 +++++----- Scripts/VaDump.py | 17 ++-- Scripts/msdnapihelp.py | 52 +++++------ out_of_tree/parsed_notifications.zip | Bin 579782 -> 579765 bytes python/idc.py | 8 ++ pywraps/py_dbg.py | 28 ++++++ pywraps/py_idd.hpp | 10 ++- pywraps/py_idd.py | 7 +- pywraps/py_kernwin.py | 5 +- swig/diskio.i | 15 ++++ swig/expr.i | 6 ++ swig/fixup.i | 11 +++ swig/idp.i | 14 +++ swig/kernwin.i | 18 ++++ swig/typeinf.i | 6 ++ tools/deploy/header.i.in | 63 ++++++++++++- tools/gen_idc_bc695.py | 4 + 25 files changed, 601 insertions(+), 371 deletions(-) diff --git a/Scripts/AsmViewer.py b/Scripts/AsmViewer.py index 7b78cfe..3b7e951 100644 --- a/Scripts/AsmViewer.py +++ b/Scripts/AsmViewer.py @@ -3,11 +3,14 @@ # The sample will allow you to open an assembly file and display it in color # (c) Hex-Rays # -import idaapi -import idautils -import idc + import os +import ida_idaapi +import ida_kernwin +import ida_lines +import idautils + # ---------------------------------------------------------------------- class asm_colorizer_t(object): def is_id(self, ch): @@ -79,11 +82,38 @@ class asm_colorizer_t(object): x += 1 self.add_line(s) + +class base_asmview_ah_t(ida_kernwin.action_handler_t): + def __init__(self, obj): + ida_kernwin.action_handler_t.__init__(self) + self.obj = obj + + def update(self, ctx): + if self.obj.view and self.obj.view.GetWidget() == ctx.widget: + return ida_kernwin.AST_ENABLE_FOR_WIDGET + else: + return ida_kernwin.AST_DISABLE_FOR_WIDGET + + +class refresh_ah_t(base_asmview_ah_t): + def activate(self, ctx): + self.obj.view.reload_file() + print("Reloaded") + + +class close_ah_t(base_asmview_ah_t): + def activate(self, ctx): + self.obj.view.Close() + print("Closed") + + # ----------------------------------------------------------------------- -class asmview_t(idaapi.simplecustviewer_t, asm_colorizer_t): +class asmview_t(ida_kernwin.simplecustviewer_t, asm_colorizer_t): def Create(self, fn): # Create the customview - if not idaapi.simplecustviewer_t.Create(self, "Viewing file - %s" % os.path.basename(fn)): + if not ida_kernwin.simplecustviewer_t.Create( + self, + "Viewing file - %s" % os.path.basename(fn)): return False self.instruction_list = idautils.GetInstructionList() @@ -95,9 +125,6 @@ class asmview_t(idaapi.simplecustviewer_t, asm_colorizer_t): if not self.reload_file(): return False - self.id_refresh = self.AddPopupMenu("Refresh") - self.id_close = self.AddPopupMenu("Close") - return True def reload_file(self): @@ -123,38 +150,25 @@ class asmview_t(idaapi.simplecustviewer_t, asm_colorizer_t): self.AddLine(s) def as_comment(self, s): - return idaapi.COLSTR(s, idaapi.SCOLOR_RPTCMT) + return ida_lines.COLSTR(s, ida_lines.SCOLOR_RPTCMT) def as_id(self, s): t = s.lower() if t in self.register_list: - return idaapi.COLSTR(s, idaapi.SCOLOR_REG) + return ida_lines.COLSTR(s, ida_lines.SCOLOR_REG) elif t in self.instruction_list: - return idaapi.COLSTR(s, idaapi.SCOLOR_INSN) + return ida_lines.COLSTR(s, ida_lines.SCOLOR_INSN) else: return s def as_string(self, s): - return idaapi.COLSTR(s, idaapi.SCOLOR_STRING) + return ida_lines.COLSTR(s, ida_lines.SCOLOR_STRING) def as_num(self, s): - return idaapi.COLSTR(s, idaapi.SCOLOR_NUMBER) + return ida_lines.COLSTR(s, ida_lines.SCOLOR_NUMBER) def as_directive(self, s): - return idaapi.COLSTR(s, idaapi.SCOLOR_KEYWORD) - - def OnPopupMenu(self, menu_id): - """ - A context (or popup) menu item was executed. - @param menu_id: ID previously registered with AddPopupMenu() - @return: Boolean - """ - if self.id_refresh == menu_id: - return self.reload_file() - elif self.id_close == menu_id: - self.Close() - return True - return False + return ida_lines.COLSTR(s, ida_lines.SCOLOR_KEYWORD) def OnKeydown(self, vkey, shift): """ @@ -170,8 +184,8 @@ class asmview_t(idaapi.simplecustviewer_t, asm_colorizer_t): lineno = self.GetLineNo() if lineno is not None: line, fg, bg = self.GetLine(lineno) - if line and line[0] != idaapi.SCOLOR_INV: - s = idaapi.SCOLOR_INV + line + idaapi.SCOLOR_INV + if line and line[0] != ida_lines.SCOLOR_INV: + s = ida_lines.SCOLOR_INV + line + ida_lines.SCOLOR_INV self.EditLine(lineno, s, fg, bg) self.Refresh() elif vkey == ord('C'): @@ -186,8 +200,11 @@ class asmview_t(idaapi.simplecustviewer_t, asm_colorizer_t): return True # ----------------------------------------------------------------------- -class asmviewplg(idaapi.plugin_t): - flags = idaapi.PLUGIN_KEEP +ACTNAME_REFRESH = "asmview_t::refresh" +ACTNAME_CLOSE = "asmview_t::close" + +class asmviewplg(ida_idaapi.plugin_t): + flags = ida_idaapi.PLUGIN_KEEP comment = "ASM viewer" help = "This is help" wanted_name = "ASM file viewer" @@ -196,17 +213,30 @@ class asmviewplg(idaapi.plugin_t): self.view = None def init(self): - return idaapi.PLUGIN_KEEP + # Register actions + ida_kernwin.register_action( + ida_kernwin.action_desc_t( + ACTNAME_REFRESH, "Refresh", refresh_ah_t(self))) + ida_kernwin.register_action( + ida_kernwin.action_desc_t( + ACTNAME_CLOSE, "Close", close_ah_t(self))) + return ida_idaapi.PLUGIN_KEEP + def run(self, arg): if self.view: self.Close() - fn = idaapi.ask_file(0, "*.asm", "Select ASM file to view") + fn = ida_kernwin.ask_file(0, "*.asm", "Select ASM file to view") if not fn: return self.view = asmview_t() if not self.view.Create(fn): return self.view.Show() + widget = self.view.GetWidget() + + # Attach actions to this widget's popup menu + ida_kernwin.attach_action_to_popup(widget, None, ACTNAME_REFRESH) + ida_kernwin.attach_action_to_popup(widget, None, ACTNAME_CLOSE) def term(self): if self.view: diff --git a/Scripts/CallStackWalk.py b/Scripts/CallStackWalk.py index cb63383..1a5f3cd 100644 --- a/Scripts/CallStackWalk.py +++ b/Scripts/CallStackWalk.py @@ -4,17 +4,19 @@ A script that tries to determine the call stack Run the application with the debugger, suspend the debugger, select a thread and finally run the script. -Copyright (c) 1990-2009 Hex-Rays +Copyright (c) 1990-2017 Hex-Rays ALL RIGHTS RESERVED. - - -v1.0 - initial version -v1.0.1 - added stack segment bitness detection, thus works with 64bit processes too """ -import idaapi -import idc +import ida_ua +import ida_bytes +import ida_kernwin +import ida_funcs +import ida_name +import ida_ida +import ida_idp +import ida_segment +import ida_dbg import idautils -from ida_kernwin import Choose # ----------------------------------------------------------------------- # class to take a copy of a segment_t @@ -24,6 +26,7 @@ class Seg(): self.end_ea = s.end_ea self.perm = s.perm self.bitness = s.bitness + def __cmp__(self, other): return cmp(self.start_ea, other.start_ea) @@ -51,18 +54,18 @@ def IsPrevInsnCall(ea): is a CALL instruction """ global CallPattern - if ea == idaapi.BADADDR or ea < 10: + if ea == ida_idaapi.BADADDR or ea < 10: return None for delta, opcodes in CallPattern: # assume caller's ea caller = ea + delta # get the bytes - bytes = [x for x in GetDataList(caller, len(opcodes), 1)] + bytes = [x for x in idautils.GetDataList(caller, len(opcodes), 1)] # do we have a match? is it a call instruction? if bytes == opcodes: - tmp = idaapi.insn_t() - if idaapi.decode_insn(tmp, caller) and idaapi.is_call_insn(tmp): + insn = ida_ua.insn_t() + if ida_ua.decode_insn(insn, caller) and ida_idp.is_call_insn(insn): return caller return None @@ -79,32 +82,35 @@ def CallStackWalk(nn): def __init__(self, caller, sp): self.caller = caller self.sp = sp - f = idaapi.get_func(caller) - self.displ = "" + f = ida_funcs.get_func(caller) + self.displ = "%08x: " % caller if f: - self.displ += idc.get_func_name(caller) + self.displ += ida_funcs.get_func_name(caller) t = caller - f.start_ea if t > 0: self.displ += "+" + hex(t) else: self.displ += hex(caller) self.displ += " [" + hex(sp) + "]" + def __str__(self): + return self.displ + # get stack pointer - sp = cpu.Esp - seg = idaapi.getseg(sp) + sp = idautils.cpu.Esp + seg = ida_segment.getseg(sp) if not seg: return (False, "Could not locate stack segment!") stack_seg = Seg(seg) word_size = 2 ** (seg.bitness + 1) callers = [] - sp = cpu.Esp - word_size + sp = idautils.cpu.Esp - word_size while sp < stack_seg.end_ea: sp += word_size ptr = idautils.GetDataList(sp, 1, word_size).next() - seg = idaapi.getseg(ptr) + seg = ida_segment.getseg(ptr) # only accept executable segments - if (not seg) or ((seg.perm & idaapi.SEGPERM_EXEC) == 0): + if (not seg) or ((seg.perm & ida_segment.SEGPERM_EXEC) == 0): continue # try to find caller caller = IsPrevInsnCall(ptr) @@ -118,16 +124,16 @@ def CallStackWalk(nn): if ret: ea = ret[0] # function exists? - f = idaapi.get_func(ea) + f = ida_funcs.get_func(ea) if not f: # create function - idc.add_func(ea, idaapi.BADADDR) + ida_funcs.add_func(ea) # get the flags - f = idc.get_flags(caller) + f = ida_bytes.get_flags(caller) # no code there? - if not is_code(f): - create_insn(caller) + if not ida_bytes.is_code(f): + ida_ua.create_insn(caller) callers.append(Result(caller, sp)) # @@ -135,52 +141,46 @@ def CallStackWalk(nn): # ----------------------------------------------------------------------- # Chooser class -class CallStackWalkChoose(Choose): +class CallStackWalkChoose(ida_kernwin.Choose): def __init__(self, title, items): - Choose.__init__(self, title, [ ["Caller", 16], ["Display", 250] ]) + ida_kernwin.Choose.__init__( + self, + title, + [["Location", 30]]) self.items = items - - def OnGetLine(self, n): - o = self.items[n] - line = [] - line.append("%X" % o.caller) - line.append("%s" % o.displ) - return line + self.modal = True def OnGetSize(self): return len(self.items) + def OnGetLine(self, n): + return [str(self.items[n])] + def OnSelectLine(self, n): - o = self.items[n] - jumpto(o.caller) - return (Choose.NOTHING_CHANGED, ) + ida_kernwin.jumpto(self.items[n].caller) # ----------------------------------------------------------------------- def main(): - if not idaapi.is_debugger_on(): - idc.warning("Please run the process first!") + if not ida_dbg.is_debugger_on(): + ida_kernwin.warning("Please run the process first!") return - if idaapi.get_process_state() != -1: - idc.warning("Please suspend the debugger first!") + if ida_dbg.get_process_state() != -1: + ida_kernwin.warning("Please suspend the debugger first!") return - # only avail from IdaPython r232 - if hasattr(idaapi, "NearestName"): - # get all debug names - dn = idaapi.get_debug_names(idaapi.cvar.inf.min_ea, idaapi.cvar.inf.max_ea) - # initiate a nearest name search (using debug names) - nn = idaapi.NearestName(dn) - else: - nn = None + # get all debug namesp + dn = ida_name.get_debug_names(ida_ida.cvar.inf.min_ea, ida_ida.cvar.inf.max_ea) + # initiate a nearest name search (using debug names) + nn = ida_name.NearestName(dn) ret, callstack = CallStackWalk(nn) if ret: - title = "Call stack walker (thread %X)" % (get_current_thread()) - idaapi.close_chooser(title) + title = "Call stack walker (thread %X)" % (ida_dbg.get_current_thread()) + ida_kernwin.close_chooser(title) c = CallStackWalkChoose(title, callstack) - c.Show() + c.Show(True) else: - idc.warning("Failed to walk the stack:" + callstack) + ida_kernwin.warning("Failed to walk the stack:" + callstack) # ----------------------------------------------------------------------- main() diff --git a/Scripts/DbgCmd.py b/Scripts/DbgCmd.py index d19e564..e3e8e2d 100644 --- a/Scripts/DbgCmd.py +++ b/Scripts/DbgCmd.py @@ -2,37 +2,70 @@ # Debugger command prompt with CustomViewers # (c) Hex-Rays # -import idaapi -import idc -from idaapi import simplecustviewer_t +import ida_idaapi +import ida_kernwin +import ida_lines +import ida_expr +import ida_dbg -def SendDbgCommand(cmd): - """Sends a command to the debugger and returns the output string. - An exception will be raised if the debugger is not running or the current debugger does not export - the 'send_dbg_command' IDC command. - """ - s = idc.eval('send_dbg_command("%s");' % cmd) - if s.startswith("IDC_FAILURE"): - raise Exception, "Debugger command is available only when the debugger is active!" - return s +# The viewer instance +dbgcmd = None # ----------------------------------------------------------------------- -class dbgcmd_t(simplecustviewer_t): +class base_dbgcmd_ah_t(ida_kernwin.action_handler_t): + def __init__(self): + ida_kernwin.action_handler_t.__init__(self) + + def update(self, ctx): + if dbgcmd and ctx.widget == dbgcmd.GetWidget(): + return ida_kernwin.AST_ENABLE_FOR_WIDGET + else: + return ida_kernwin.AST_DISABLE_FOR_WIDGET + +# ----------------------------------------------------------------------- +class clear_dbgcmd_ah_t(base_dbgcmd_ah_t): + def activate(self, ctx): + dbgcmd.ResetOutput() + +# ----------------------------------------------------------------------- +class newcmd_dbgcmd_ah_t(base_dbgcmd_ah_t): + def activate(self, ctx): + dbgcmd.IssueCommand() + +# ----------------------------------------------------------------------- +class close_dbgcmd_ah_t(base_dbgcmd_ah_t): + def activate(self, ctx): + dbgcmd.Close() + +# ----------------------------------------------------------------------- +# Register actions (if needed) +ACTNAME_CLEAR = "dbgcmd:clear" +ACTNAME_NEWCMD = "dbgcmd:newcmd" +ACTNAME_CLOSE = "dbgcmd:close" +ida_kernwin.register_action( + ida_kernwin.action_desc_t( + ACTNAME_CLEAR, "Clear", clear_dbgcmd_ah_t(), "x")) +ida_kernwin.register_action( + ida_kernwin.action_desc_t( + ACTNAME_NEWCMD, "New command", newcmd_dbgcmd_ah_t(), "Insert")) +ida_kernwin.register_action( + ida_kernwin.action_desc_t( + ACTNAME_CLOSE, "Close", close_dbgcmd_ah_t(), "Escape")) + +# ----------------------------------------------------------------------- +class dbgcmd_t(ida_kernwin.simplecustviewer_t): def Create(self): # Form the title title = "Debugger command window" # Create the customview - if not simplecustviewer_t.Create(self, title): + if not ida_kernwin.simplecustviewer_t.Create(self, title): return False self.last_cmd = "" - self.menu_clear = self.AddPopupMenu("Clear") - self.menu_cmd = self.AddPopupMenu("New command") - self.ResetOutput() return True def IssueCommand(self): - s = idaapi.ask_str(self.last_cmd, 0, "Please enter a debugger command") + s = ida_kernwin.ask_str(self.last_cmd, 0, "Please enter a debugger command") if not s: return @@ -40,43 +73,24 @@ class dbgcmd_t(simplecustviewer_t): self.last_cmd = s # Add it using a different color - self.AddLine("debugger>" + idaapi.COLSTR(s, idaapi.SCOLOR_VOIDOP)) + self.AddLine("debugger>" + ida_lines.COLSTR(s, ida_lines.SCOLOR_VOIDOP)) - try: - r = SendDbgCommand(s).split("\n") - for s in r: - self.AddLine(idaapi.COLSTR(s, idaapi.SCOLOR_LIBNAME)) - except: - self.AddLine(idaapi.COLSTR("Debugger is not active or does not export send_dbg_command()", idaapi.SCOLOR_ERROR)) + ok, out = ida_dbg.send_dbg_command(s) + if ok: + for line in out.split("\n"): + self.AddLine(ida_lines.COLSTR(line, ida_lines.SCOLOR_LIBNAME)) + else: + self.AddLine( + ida_lines.COLSTR( + "Debugger is not active or does not export ida_dbg.send_dbg_command() (%s)" % out, + ida_lines.SCOLOR_ERROR)) self.Refresh() def ResetOutput(self): self.ClearLines() - self.AddLine(idaapi.COLSTR("Please press INS to enter command; X to clear output", idaapi.SCOLOR_AUTOCMT)) + self.AddLine(ida_lines.COLSTR("Please press INS to enter command; X to clear output", ida_lines.SCOLOR_AUTOCMT)) self.Refresh() - def OnKeydown(self, vkey, shift): - # ESCAPE? - if vkey == 27: - self.Close() - # VK_INSERT - elif vkey == 45: - self.IssueCommand() - elif vkey == ord('X'): - self.ResetOutput() - else: - return False - return True - - def OnPopupMenu(self, menu_id): - if menu_id == self.menu_clear: - self.ResetOutput() - elif menu_id == self.menu_cmd: - self.IssueCommand() - else: - # Unhandled - return False - return True # ----------------------------------------------------------------------- def show_win(): @@ -85,17 +99,16 @@ def show_win(): print "Failed to create debugger command line!" return None x.Show() + + # Attach actions to this widget's popup menu + widget = x.GetWidget() + ida_kernwin.attach_action_to_popup(widget, None, ACTNAME_CLEAR) + ida_kernwin.attach_action_to_popup(widget, None, ACTNAME_NEWCMD) + ida_kernwin.attach_action_to_popup(widget, None, ACTNAME_CLOSE) return x -try: - # created already? - dbgcmd +if dbgcmd is not None: dbgcmd.Close() - del dbgcmd -except: - pass + dbgcmd = None dbgcmd = show_win() -if not dbgcmd: - del dbgcmd - diff --git a/Scripts/DrvsDispatch.py b/Scripts/DrvsDispatch.py index ae7c298..defbb74 100644 --- a/Scripts/DrvsDispatch.py +++ b/Scripts/DrvsDispatch.py @@ -2,30 +2,34 @@ A script to demonstrate how to send commands to the debugger and then parse and use the output in IDA -Copyright (c) 1990-2009 Hex-Rays +Copyright (c) 1990-2017 Hex-Rays ALL RIGHTS RESERVED. """ import re -import idc -from ida_kernwin import Choose + +import ida_idaapi +import ida_expr +import ida_kernwin +import ida_dbg + +# ----------------------------------------------------------------------- +def WinDbg_command(cmd): + ok, s = ida_dbg.send_dbg_command(cmd) + return s if ok else False # ----------------------------------------------------------------------- def CmdDriverList(): - s = idc.eval('send_dbg_command("lm o");') - if "IDC_FAILURE" in s: return False - return s + return WinDbg_command("lm o") # ----------------------------------------------------------------------- def CmdDrvObj(drvname, flag=2): - return idc.eval('send_dbg_command("!drvobj %s %d");' % (drvname, flag)) + return WinDbg_command("!drvobj %s %d" % (drvname, flag)) # ----------------------------------------------------------------------- def CmdReloadForce(): - s = idc.eval('send_dbg_command(".reload /f");') - if "IDC_FAILURE" in s: return False - return True + return WinDbg_command(".reload /f") # ----------------------------------------------------------------------- # class to hold dispatch entry information @@ -33,82 +37,83 @@ class DispatchEntry: def __init__(self, addr, name): self.addr = addr self.name = name + def __repr__(self): + return "%08X: %s" % (self.addr, self.name) # ----------------------------------------------------------------------- def GetDriverDispatch(): - # return a list of arrays of the form: [addr, name] - ret_list = [] - - # build the RE for parsing output from the "lm o" command - re_drv = re.compile('^[a-f0-9]+\s+[a-f0-9]+\s+(\S+)', re.I) - # build the RE for parsing output from the "!drvobj DRV_NAME 2" command - re_tbl = re.compile('^\[\d{2}\]\s+IRP_MJ_(\S+)\s+([0-9a-f]+)', re.I) + # return a list of arrays of the form: [addr, name] + ret_list = [] - # force reloading of module symbols - if not CmdReloadForce(): - print "Could not communicate with WinDbg, make sure the debugger is running!" - return None + # build the RE for parsing output from the "lm o" command + re_drv = re.compile('^[a-f0-9]+\s+[a-f0-9]+\s+(\S+)', re.I) - # get driver list - lm_out = CmdDriverList() - if not lm_out: - return "Failed to get driver list!" + # build the RE for parsing output from the "!drvobj DRV_NAME 2" command + re_tbl = re.compile('^\[\d{2}\]\s+IRP_MJ_(\S+)\s+([0-9a-f]+)', re.I) - # for each line - for line in lm_out.split("\n"): - # parse - r = re_drv.match(line) - if not r: continue + # force reloading of module symbols + if not CmdReloadForce(): + print "Could not communicate with WinDbg, make sure the debugger is running!" + return None - # extract driver name - drvname = r.group(1).strip() - - # execute "drvobj" command - tbl_out = CmdDrvObj(drvname) - - if not tbl_out: - print "Failed to get driver object for", drvname - continue + # get driver list + lm_out = CmdDriverList() + if not lm_out: + return "Failed to get driver list!" # for each line - for line in tbl_out.split("\n"): + for line in lm_out.split("\n"): # parse - r = re_tbl.match(line) + r = re_drv.match(line) if not r: continue - disp_addr = int(r.group(2), 16) # convert hex string to number - disp_name = "Dispatch" + r.group(1) - ret_list.append(DispatchEntry(disp_addr, drvname + "_" + disp_name)) - return ret_list + # extract driver name + drvname = r.group(1).strip() + + # execute "drvobj" command + tbl_out = CmdDrvObj(drvname) + + if not tbl_out: + print "Failed to get driver object for", drvname + continue + + # for each line + for line in tbl_out.split("\n"): + # parse + r = re_tbl.match(line) + if not r: continue + disp_addr = int(r.group(2), 16) # convert hex string to number + disp_name = "Dispatch" + r.group(1) + ret_list.append(DispatchEntry(disp_addr, drvname + "_" + disp_name)) + + return ret_list # ----------------------------------------------------------------------- # Chooser class -class DispatchChoose(Choose): +class DispatchChoose(ida_kernwin.Choose): def __init__(self, title, items): - Choose.__init__(self, title, [ ["Address", 16], ["Name", 250] ]) + ida_kernwin.Choose.__init__( + self, + title, + [["Address", 30]], + width=250) self.items = items - def OnGetLine(self, n): - o = self.items[n] - line = [] - line.append("%08X" % o.addr) - line.append("%s" % o.name) - return line - def OnGetSize(self): return len(self.items) + def OnGetLine(self, n): + return [str(self.items[n])] + def OnSelectLine(self, n): - o = self.items[n] - Jump(o.addr) - return (Choose.NOTHING_CHANGED, ) + ida_kernwin.jumpto(self.items[n].addr) # ----------------------------------------------------------------------- # main r = GetDriverDispatch() if r: c = DispatchChoose("Dispatch table browser", r) - c.Show() + c.Show(True) else: - print "Failed to retrieve dispatchers list!" + print "Failed to retrieve dispatchers list!" diff --git a/Scripts/ExchainDump.py b/Scripts/ExchainDump.py index 75c729d..bb29f28 100644 --- a/Scripts/ExchainDump.py +++ b/Scripts/ExchainDump.py @@ -7,56 +7,48 @@ ALL RIGHTS RESERVED. """ -import idc import re + import ida_kernwin -from ida_kernwin import Choose # class to store parsed results class exchain: def __init__(self, m): - self.name = m.group(1) - self.addr = int(m.group(2), 16) + self.name = m.group(1) + self.addr = int(m.group(2), 16) # Chooser class -class MyChoose(Choose): +class MyChoose(ida_kernwin.Choose): def __init__(self, title, items): - Choose.__init__(self, title, [ ["Address", 16], ["Name", 250] ]) + ida_kernwin.Choose.__init__(self, title, [ ["Address", 16], ["Name", 250] ]) self.items = items def OnGetLine(self, n): o = self.items[n] - line = [] - line.append("%08X" % o.addr) - line.append("%s" % o.name) - return line + return ["%08X" % o.addr, o.name] def OnGetSize(self): return len(self.items) def OnSelectLine(self, n): - o = self.items[n] - Jump(o.addr) - return (Choose.NOTHING_CHANGED, ) + ida_kernwin.jumpto(self.items[n].addr) + return (ida_kernwin.Choose.NOTHING_CHANGED, ) -# main def main(): - s = idc.eval('send_dbg_command("!exchain")') - if "IDC_FAILURE" in s: - return (False, "Cannot execute the command") + ok, s = ida_dbg.send_dbg_command("!exchain") + if not ok: + return (False, "Cannot execute the command (%s)" % s) matches = re.finditer(r'[^:]+: ([^\(]+) \(([^\)]+)\)\n', s) - L = [] - for x in matches: - L.append(exchain(x)) - if not L: + entries = [exchain(x) for x in matches] + if not entries: return (False, "Nothing to display: Could parse the result!") - # Get a Choose instance - chooser = MyChoose("Exchain choose", L) - # Run the chooser + # Show a list of results, and let the user possibly jump to one of those + chooser = MyChoose("Exchain choose", entries) chooser.Show() return (True, "Success!") + ok, r = main() if not ok: print r diff --git a/Scripts/FindInstructions.py b/Scripts/FindInstructions.py index d85d67c..8c50017 100644 --- a/Scripts/FindInstructions.py +++ b/Scripts/FindInstructions.py @@ -1,5 +1,5 @@ """ -FindInstructions.py: A script to help you find desired opcodes/instructions in a database +A script to help you find desired opcodes/instructions in a database The script accepts opcodes and assembly statements (which will be assembled) separated by semicolon @@ -13,15 +13,22 @@ The general syntax is: * To specify in which context the instructions should be assembled, pass asm_where=ea: find("jmp dword ptr [esp]", asm_where=here()) -Copyright (c) 1990-2009 Hex-Rays +Copyright (c) 1990-2017 Hex-Rays ALL RIGHTS RESERVED. - -v1.0 - initial version """ -import idaapi +import re + +import ida_idaapi +import ida_lines +import ida_segment +import ida_kernwin +import ida_bytes +import ida_ua +import ida_ida +import ida_search +import ida_funcs + import idautils -import idc -from ida_kernwin import Choose # ----------------------------------------------------------------------- def FindInstructions(instr, asm_where=None): @@ -31,8 +38,9 @@ def FindInstructions(instr, asm_where=None): """ if not asm_where: # get first segment - asm_where = get_first_seg() - if asm_where == idaapi.BADADDR: + seg = ida_segment.get_first_seg() + asm_where = seg.start_ea if seg else ida_idaapi.BADADDR + if asm_where == ida_idaapi.BADADDR: return (False, "No segments defined") # regular expression to distinguish between opcodes and instructions @@ -49,7 +57,7 @@ def FindInstructions(instr, asm_where=None): buf = ''.join([chr(int(x, 16)) for x in line.split()]) else: # assemble the instruction - ret, buf = Assemble(asm_where, line) + ret, buf = idautils.Assemble(asm_where, line) if not ret: return (False, "Failed to assemble:"+line) # add the assembled buffer @@ -66,63 +74,66 @@ def FindInstructions(instr, asm_where=None): # find all binary strings print "Searching for: [%s]" % bin_str - ea = get_inf_attr(INF_MIN_EA) + ea = ida_ida.cvar.inf.min_ea ret = [] while True: - ea = find_binary(ea, SEARCH_DOWN, bin_str) - if ea == idaapi.BADADDR: + ea = ida_search.find_binary(ea, ida_idaapi.BADADDR, bin_str, 16, ida_search.SEARCH_DOWN) + if ea == ida_idaapi.BADADDR: break ret.append(ea) - msg(".") + ida_kernwin.msg(".") ea += tlen if not ret: return (False, "Could not match [%s]" % bin_str) - msg("\n") + ida_kernwin.msg("\n") return (True, ret) # ----------------------------------------------------------------------- # Chooser class -class SearchResultChoose(Choose): +class SearchResultChoose(ida_kernwin.Choose): def __init__(self, title, items): - Choose.__init__(self, title, [ ["Address", 16], ["Results", 250] ]) + ida_kernwin.Choose.__init__( + self, + title, + [["Address", 30], ["Function (or segment)", 25], ["Instruction", 20]], + width=250) self.items = items - def OnGetLine(self, n): - o = self.items[n] - line = [] - line.append("%08X" % o.ea) - line.append("%s" % o.display) - return line - def OnGetSize(self): return len(self.items) + def OnGetLine(self, n): + i = self.items[n] + ea = i.ea + return [ + hex(i.ea), + i.funcname_or_segname, + i.text + ] + def OnSelectLine(self, n): - o = self.items[n] - Jump(o.ea) - return (Choose.NOTHING_CHANGED, ) + ida_kernwin.jumpto(self.items[n].ea) # ----------------------------------------------------------------------- # class to represent the results class SearchResult: def __init__(self, ea): self.ea = ea - if not is_code(get_flags(ea)): - create_insn(ea) - t = idaapi.generate_disasm_line(ea) + self.funcname_or_segname = "" + self.text = "" + if not ida_bytes.is_code(ida_bytes.get_flags(ea)): + ida_ua.create_insn(ea) + + # text + t = ida_lines.generate_disasm_line(ea) if t: - line = idaapi.tag_remove(t) - else: - line = "" - func = get_func_name(ea) - self.display = "" - if func: - self.display += func + ": " - else: - n = get_segm_name(ea) - if n: - self.display += n + ": " - self.display += line + self.text = ida_lines.tag_remove(t) + + # funcname_or_segname + n = ida_funcs.get_func_name(ea) \ + or ida_segment.get_segm_name(ida_segment.getseg(ea)) + if n: + self.funcname_or_segname = n # ----------------------------------------------------------------------- def find(s=None, x=False, asm_where=None): @@ -132,16 +143,16 @@ def find(s=None, x=False, asm_where=None): if x: results = [] for ea in ret: - seg = idaapi.getseg(ea) - if (not seg) or (seg.perm & idaapi.SEGPERM_EXEC) == 0: + seg = ida_segment.getseg(ea) + if (not seg) or (seg.perm & ida_segment.SEGPERM_EXEC) == 0: continue results.append(SearchResult(ea)) else: results = [SearchResult(ea) for ea in ret] title = "Search result for: [%s]" % s - idaapi.close_chooser(title) + ida_kernwin.close_chooser(title) c = SearchResultChoose(title, results) - c.Show() + c.Show(True) else: print ret diff --git a/Scripts/ImpRef.py b/Scripts/ImpRef.py index 9d9471c..fe6827a 100644 --- a/Scripts/ImpRef.py +++ b/Scripts/ImpRef.py @@ -4,12 +4,14 @@ # # (c) Hex-Rays # - -import idaapi -import idc -import idautils import re +import ida_kernwin +import ida_nalt +import ida_funcs + +import idautils + # ----------------------------------------------------------------------- def find_imported_funcs(dllname): def imp_cb(ea, name, ord): @@ -19,12 +21,12 @@ def find_imported_funcs(dllname): return True imports = [] - nimps = idaapi.get_import_module_qty() + nimps = ida_nalt.get_import_module_qty() for i in xrange(0, nimps): - name = idaapi.get_import_module_name(i) + name = ida_nalt.get_import_module_name(i) if re.match(dllname, name, re.IGNORECASE) is None: continue - idaapi.enum_import_names(i, imp_cb) + ida_nalt.enum_import_names(i, imp_cb) return imports @@ -38,9 +40,9 @@ def find_import_ref(dllname): for xref in idautils.XrefsTo(ea): # check if referrer is a thunk ea = xref.frm - f = idaapi.get_func(ea) - if f and (f.flags & idaapi.FUNC_THUNK) != 0: - imports.append([f.start_ea, idaapi.get_func_name(f.start_ea), 0]) + f = ida_funcs.get_func(ea) + if f and (f.flags & ida_funcs.FUNC_THUNK) != 0: + imports.append([f.start_ea, ida_funcs.get_func_name(f.start_ea), 0]) #print "\t%x %s: from a thunk, parent added %x" % (ea, name, f.start_ea) continue @@ -54,7 +56,7 @@ def find_import_ref(dllname): # ----------------------------------------------------------------------- def main(): - dllname = idaapi.ask_str('kernel32', 0, "Enter module name") + dllname = ida_kernwin.ask_str('kernel32', 0, "Enter module name") if not dllname: print("Cancelled") return diff --git a/Scripts/PteDump.py b/Scripts/PteDump.py index 705e7c0..37a168d 100644 --- a/Scripts/PteDump.py +++ b/Scripts/PteDump.py @@ -1,8 +1,9 @@ -import idaapi -import idc -from ida_kernwin import Choose -def parse_pte(str): +import ida_kernwin +import ida_segment +import ida_dbg + +def parse_pte(s): try: parse_pte.re except: @@ -10,15 +11,17 @@ def parse_pte(str): parse_pte.items = ('pde', 'pte', 'pdec', 'ptec', 'pdepfn', 'pdepfns', 'ptepfn', 'ptepfns') m = parse_pte.re.search(s) + if not m: + return None r = {} for i in range(0, len(parse_pte.items)): r[parse_pte.items[i]] = m.group(i+1) return r -class MyChoose(Choose): +class MyChoose(ida_kernwin.Choose): def __init__(self, title, ea1, ea2): - Choose.__init__(self, title, [ ["VA", 10], ["PTE attr", 30] ]) + ida_kernwin.Choose.__init__(self, title, [ ["VA", 10], ["PTE attr", 30] ]) self.ea1 = ea1 self.ea2 = ea2 self.icon = 5 @@ -26,17 +29,14 @@ class MyChoose(Choose): self.Refresh() def OnGetLine(self, n): - print("getline %d" % n) return self.items[n] def OnGetSize(self): - n = len(self.items) - self.Refresh() - return n + return len(self.items) def OnRefresh(self, n): - print("refresh %d" % n) - return None # call standard refresh + self.Refresh() + return (ida_kernwin.Choose.ALL_CHANGED, ) def Refresh(self): items = [] @@ -44,11 +44,12 @@ class MyChoose(Choose): ea1 = self.ea1 npages = (self.ea2 - ea1) / PG for i in range(npages): - r = idc.send_dbg_command("!pte %x" % ea1) - if not r: + ok, r = ida_dbg.send_dbg_command("!pte %x" % ea1) + if not ok: return False r = parse_pte(r) - items.append([hex(ea1), r['ptepfns']]) + if r: + items.append([hex(ea1), r['ptepfns']]) ea1 += PG self.items = items @@ -66,16 +67,17 @@ def DumpPTE(ea1, ea2): PG = 0x1000 npages = (ea2 - ea1) / PG for i in range(npages): - r = idc.send_dbg_command("!pte %x" % ea1) - if not r: + ok, r = ida_dbg.send_dbg_command("!pte %x" % ea1) + if not ok: return False - print r r = parse_pte(r) - print("VA: %08X PTE: %s PDE: %s" % (ea1, r['ptepfns'], r['pdepfns'])) + if r: + print("VA: %08X PTE: %s PDE: %s" % (ea1, r['ptepfns'], r['pdepfns'])) ea1 += PG def DumpSegPTE(ea): - DumpPTE(idc.get_segm_start(ea), idc.get_segm_end(ea)) + s = ida_segment.getseg(ea) + DumpPTE(s.start_ea, s.end_ea) DumpSegPTE(here()) diff --git a/Scripts/SEHGraph.py b/Scripts/SEHGraph.py index 6ad3a0c..61d72f8 100644 --- a/Scripts/SEHGraph.py +++ b/Scripts/SEHGraph.py @@ -4,26 +4,24 @@ A script that graphs all the exception handlers in a given process It will be easy to see what thread uses what handler and what handlers are commonly used between threads -Copyright (c) 1990-2009 Hex-Rays +Copyright (c) 1990-2017 Hex-Rays ALL RIGHTS RESERVED. - - -v1.0 - initial version - """ -import idaapi -import idautils -import idc +import ida_kernwin +import ida_graph +import ida_idd +import ida_dbg +import ida_funcs -from idaapi import GraphViewer +import idautils # ----------------------------------------------------------------------- # Since Windbg debug module does not support get_thread_sreg_base() # we will call the debugger engine "dg" command and parse its output def WindbgGetRegBase(tid): - s = idc.eval('send_dbg_command("dg %x")' % cpu.fs) - if "IDC_FAILURE" in s: + ok, s = ida_dbg.send_dbg_command("dg %x" % idautils.cpu.fs) + if not ok: return 0 m = re.compile("[0-9a-f]{4} ([0-9a-f]{8})") t = m.match(s.split('\n')[-2]) @@ -33,8 +31,8 @@ def WindbgGetRegBase(tid): # ----------------------------------------------------------------------- def GetFsBase(tid): - idc.select_thread(tid) - base = idaapi.dbg_get_thread_sreg_base(tid, cpu.fs) + ida_dbg.select_thread(tid) + base = ida_idd.dbg_get_thread_sreg_base(tid, idautils.cpu.fs) if base != 0: return base return WindbgGetRegBase(tid) @@ -43,7 +41,8 @@ def GetFsBase(tid): # Walks the SEH chain and returns a list of handlers def GetExceptionChain(tid): fs_base = GetFsBase(tid) - exc_rr = get_wide_dword(fs_base) + print("FS_BASE for %s: %s (cpu.fs=%s)" % (repr(tid), repr(fs_base), repr(idautils.cpu.fs))) + exc_rr = ida_bytes.get_wide_dword(fs_base) result = [] while exc_rr != 0xffffffff: prev = get_wide_dword(exc_rr) @@ -53,9 +52,9 @@ def GetExceptionChain(tid): return result # ----------------------------------------------------------------------- -class SEHGraph(GraphViewer): +class SEHGraph(ida_graph.GraphViewer): def __init__(self, title, result): - GraphViewer.__init__(self, title) + ida_graph.GraphViewer.__init__(self, title) self.result = result self.names = {} # ea -> name @@ -74,13 +73,13 @@ class SEHGraph(GraphViewer): # Add each handler for handler in chain: # Check if a function is created at the handler's address - f = idaapi.get_func(handler) + f = ida_funcs.get_func(handler) if not f: # create function - idc.add_func(handler, idaapi.BADADDR) + ida_funcs.add_func(handler) # Node label is function name or address - s = get_func_name(handler) + s = ida_funcs.get_func_name(handler) if not s: s = "%x" % handler @@ -110,7 +109,7 @@ class SEHGraph(GraphViewer): def OnDblClick(self, node_id): is_thread, value, label = self[node_id] if is_thread: - idc.select_thread(value) + ida_dbg.select_thread(value) self.Show() s = "SEH chain for " + hex(value) t = "-" * len(s) @@ -121,18 +120,18 @@ class SEHGraph(GraphViewer): print "%x: %s" % (handler, self.names[handler]) print t else: - idc.jumpto(value) + ida_kernwin.jumpto(value) return True # ----------------------------------------------------------------------- def main(): - if not idaapi.dbg_can_query(): + if not ida_idd.dbg_can_query(): print "The debugger must be active and suspended before using this script!" return # Save current thread id - tid = get_current_thread() + tid = ida_dbg.get_current_thread() # Iterate through all function instructions and take only call instructions result = {} @@ -140,7 +139,7 @@ def main(): result[tid] = GetExceptionChain(tid) # Restore previously selected thread - idc.select_thread(tid) + ida_dbg.select_thread(tid) # Build the graph g = SEHGraph("SEH graph", result) diff --git a/Scripts/VaDump.py b/Scripts/VaDump.py index bcb3d33..82eed1a 100644 --- a/Scripts/VaDump.py +++ b/Scripts/VaDump.py @@ -7,11 +7,10 @@ ALL RIGHTS RESERVED. """ -import idc -from ida_kernwin import Choose - import re +import ida_kernwin + # class to store parsed results class memva: def __init__(self, m): @@ -29,7 +28,7 @@ class memva: self.typestr = "" # Chooser class -class MemChoose(Choose): +class MemChoose(ida_kernwin.Choose): def __init__(self, title, items): headers = [] headers.append(["Base", 10]) @@ -37,7 +36,7 @@ class MemChoose(Choose): headers.append(["State", 20]) headers.append(["Protect", 20]) headers.append(["Type", 20]) - Choose.__init__(self, title, headers) + ida_kernwin.Choose.__init__(self, title, headers) self.items = items def OnGetLine(self, n): @@ -55,13 +54,13 @@ class MemChoose(Choose): def OnSelectLine(self, n): o = self.items[n] - idc.jumpto(o.base) - return (NOTHING_CHANGED, ) + ida_kernwin.jumpto(o.base) + return (ida_kernwin.Choose.NOTHING_CHANGED, ) # main def main(): - s = idc.eval('send_dbg_command("!vadump")') - if "IDC_FAILURE" in s: + ok, s = ida_dbg.send_dbg_command("!vadump") + if not ok: return (False, "Cannot execute the command") matches = re.finditer(r'BaseAddress:\s*?(\w+?)\n' \ diff --git a/Scripts/msdnapihelp.py b/Scripts/msdnapihelp.py index b920c6e..72881f9 100644 --- a/Scripts/msdnapihelp.py +++ b/Scripts/msdnapihelp.py @@ -5,30 +5,33 @@ This script fetches the API reference (from MSDN) of a given highlighted identif and returns the results in a new web browser page. This script depends on the feedparser package: http://code.google.com/p/feedparser/ - -10/05/2010 -- initial version - - """ -import idaapi +# ----------------------------------------------------------------------- +import ida_kernwin +import ida_name +import ida_idaapi + +try: + import feedparser +except: + ida_kernwin.warning('Feedparser package not installed') # ----------------------------------------------------------------------- -class msdnapihelp_plugin_t(idaapi.plugin_t): - flags = idaapi.PLUGIN_UNL +class msdnapihelp_plugin_t(ida_idaapi.plugin_t): + flags = ida_idaapi.PLUGIN_UNL comment = "Online MSDN API Help" help = "Help me" wanted_name = "MSDN API Help" wanted_hotkey = "F3" def init(self): - return idaapi.PLUGIN_OK + return ida_idaapi.PLUGIN_OK @staticmethod def sanitize_name(name): - t = idaapi.FUNC_IMPORT_PREFIX + t = ida_name.FUNC_IMPORT_PREFIX if name.startswith(t): return name[len(t):] return name @@ -36,28 +39,25 @@ class msdnapihelp_plugin_t(idaapi.plugin_t): def run(self, arg): # Get the highlighted identifier - v = idaapi.get_current_viewer() - id = ida_kernwin.get_highlight(v)[0] - if not id: + v = ida_kernwin.get_current_viewer() + ident, ok = ida_kernwin.get_highlight(v) + if not ok: print "No identifier was highlighted" return - import webbrowser - - try: - import feedparser - except: - idaapi.warning('Feedparser package not installed') - return - - id = self.sanitize_name(id) - print "Looking up '%s' in MSDN online" % id - d = feedparser.parse("http://social.msdn.microsoft.com/Search/Feed.aspx?locale=en-us&format=RSS&Query=%s" % id) + ident = self.sanitize_name(ident) + print "Looking up '%s' in MSDN online" % ident + qurl = "https://social.msdn.microsoft.com/search/en-US/feed?query=%s&format=RSS&theme=feed%%2fen-us" + d = feedparser.parse(qurl % ident) if len(d['entries']) > 0: url = d['entries'][0].link - webbrowser.open_new_tab(url) + if arg > 0: + print("URL: %s" % url) + else: + import webbrowser + webbrowser.open_new_tab(url) else: - print "API documentation not found for: %s" % id + print "API documentation not found for: %s" % ident def term(self): diff --git a/out_of_tree/parsed_notifications.zip b/out_of_tree/parsed_notifications.zip index 2cec574c2e2fd86c52717404e859dfce3d56a932..cdeb21ae5a6c3e45769c3ec28ac158907a0f3c84 100644 GIT binary patch delta 12828 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 UpFi0)W#KkhiGT7Oc$`uG4?}u)?EnA( delta 12745 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 diff --git a/python/idc.py b/python/idc.py index 6d3183f..24f5838 100644 --- a/python/idc.py +++ b/python/idc.py @@ -2611,6 +2611,7 @@ AF_FINAL = 0x80000000 # Final pass of analysis INF_AF2 = 44 # uint32; Analysis flags 2 AF2_DOEH = 0x00000001 # Handle EH information +AF2_DORTTI = 0x00000002 # Handle RTTI information INF_BASEADDR = 48 # uval_t; base paragraph of the program INF_START_SS = 52 # int32; value of SS at the start @@ -8338,6 +8339,13 @@ if sys.modules["__main__"].IDAPYTHON_COMPAT_695_API: def MakeStr(ea, endea): return create_strlit(ea, endea) + def GetProcessorName(): + return ida_ida.cvar.inf.procname + + def SegStart(ea): return get_segm_start(ea) + def SegEnd(ea): return get_segm_end(ea) + def SetSegmentType(ea, type): return set_segm_type(ea, type) + # Convenience functions: def here(): return get_screen_ea() def is_mapped(ea): return (prev_addr(ea+1)==ea) diff --git a/pywraps/py_dbg.py b/pywraps/py_dbg.py index 87b22ef..04b8909 100644 --- a/pywraps/py_dbg.py +++ b/pywraps/py_dbg.py @@ -1,5 +1,7 @@ # +import ida_idaapi import ida_idd +import ida_expr def get_tev_reg_val(tev, reg): rv = ida_idd.regval_t() @@ -27,6 +29,32 @@ def get_tev_reg_mem_ea(tev, idx): if get_insn_tev_reg_mem(tev, mis): if idx >= 0 and idx < mis.size(): return mis[idx].ea + +def send_dbg_command(command): + """ + Send a direct command to the debugger backend, and + retrieve the result as a string. + + Note: any double-quotes in 'command' must be backslash-escaped. + Note: this only works with some debugger backends: Bochs, WinDbg, GDB. + + Returns: (True, ) on success, or (False, ) on failure + """ + rv = ida_expr.idc_value_t() + err = ida_expr.eval_idc_expr(rv, ida_idaapi.BADADDR, """send_dbg_command("%s");""" % command) + if err: + return False, "eval_idc_expr() failed: %s" % err + vtype = ord(rv.vtype) + if vtype == ida_expr.VT_STR: + s = rv.c_str() + if "IDC_FAILURE" in s: + return False, "eval_idc_expr() reported an error: %s" % s + return True, s + elif vtype == ida_expr.VT_LONG: + return True, str(rv.num) + else: + return False, "eval_idc_expr(): wrong return type: %d" % vtype + # # diff --git a/pywraps/py_idd.hpp b/pywraps/py_idd.hpp index f426871..52c97f5 100644 --- a/pywraps/py_idd.hpp +++ b/pywraps/py_idd.hpp @@ -236,11 +236,15 @@ static PyObject *dbg_get_thread_sreg_base(PyObject *py_tid, PyObject *py_sreg_va { PYW_GIL_CHECK_LOCKED_SCOPE(); - if ( !dbg_can_query() || !PyInt_Check(py_tid) || !PyInt_Check(py_sreg_value) ) + if ( !dbg_can_query() + || (!PyInt_Check(py_tid) && !PyLong_Check(py_tid)) + || (!PyInt_Check(py_sreg_value) && !PyLong_Check(py_sreg_value)) ) + { Py_RETURN_NONE; + } ea_t answer; - thid_t tid = PyInt_AsLong(py_tid); - int sreg_value = PyInt_AsLong(py_sreg_value); + thid_t tid = PyLong_AsLong(py_tid); + int sreg_value = PyLong_AsLong(py_sreg_value); if ( internal_get_sreg_base(&answer, tid, sreg_value) != 1 ) Py_RETURN_NONE; diff --git a/pywraps/py_idd.py b/pywraps/py_idd.py index 45ca764..3a008ad 100644 --- a/pywraps/py_idd.py +++ b/pywraps/py_idd.py @@ -224,11 +224,14 @@ class Appcall_callable__(object): class Appcall_consts__(object): """Helper class used by Appcall.Consts attribute It is used to retrieve constants via attribute access""" - def __init__(self, default=0): + def __init__(self, default=None): self.__default = default def __getattr__(self, attr): - return Appcall__.valueof(attr, self.__default) + v = Appcall__.valueof(attr, self.__default) + if v is None: + raise ValueError, "No constant with name " + attr + return v # ----------------------------------------------------------------------- class Appcall__(object): diff --git a/pywraps/py_kernwin.py b/pywraps/py_kernwin.py index d426796..0a5b77b 100644 --- a/pywraps/py_kernwin.py +++ b/pywraps/py_kernwin.py @@ -91,7 +91,10 @@ chtype_segreg=chtype_srcp close_tform=close_widget find_tform=find_widget get_current_tform=get_current_widget -get_highlighted_identifier=get_highlight +def get_highlighted_identifier(): + thing = get_highlight(get_current_widget()) + if thing and thing[1]: + return thing[0] get_tform_title=get_widget_title get_tform_type=get_widget_type is_chooser_tform=is_chooser_widget diff --git a/swig/diskio.i b/swig/diskio.i index 070e564..fea5496 100644 --- a/swig/diskio.i +++ b/swig/diskio.i @@ -67,6 +67,21 @@ %ignore close_linput; %rename (close_linput) py_close_linput; +%apply qstrvec_t *out { qstrvec_t *dirs }; + +%cstring_output_buf_and_size_returning_charptr( + 1, + char *buf, + size_t bufsize, + const char *filename, + const char *subdir); // getsysfile +%cstring_output_buf_and_size_returning_charptr( + 3, + linput_t *li, + int64 fpos, + char *buf, + size_t bufsize); // qlgetz + %include "diskio.hpp" %{ diff --git a/swig/expr.i b/swig/expr.i index c5291fd..02d2caa 100644 --- a/swig/expr.i +++ b/swig/expr.i @@ -63,6 +63,12 @@ %ignore compile_idc_text; %rename (compile_idc_text) py_compile_idc_text; +%cstring_output_buf_and_size_returning_charptr( + 1, + char *buf, + size_t bufsize, + const char *file); // get_idc_filename + %nonnul_argument_prototype( bool py_compile_idc_file(const char *nonnul_line, qstring *errbuf), const char *nonnul_line); diff --git a/swig/fixup.i b/swig/fixup.i index 009569c..6395e59 100644 --- a/swig/fixup.i +++ b/swig/fixup.i @@ -7,4 +7,15 @@ %ignore register_custom_fixup; %ignore unregister_custom_fixup; +%cstring_output_qstring_returning_charptr( + 1, + qstring *buf, + ea_t source); // fixup_data_t::get_desc + +%cstring_output_qstring_returning_charptr( + 1, + qstring *buf, + ea_t source, + const fixup_data_t &fd); // get_fixup_desc + %include "fixup.hpp" diff --git a/swig/idp.i b/swig/idp.i index 6f95b45..ee69655 100644 --- a/swig/idp.i +++ b/swig/idp.i @@ -80,6 +80,20 @@ static PyObject *AssembleLine(ea_t ea, ea_t cs, ea_t ip, bool use32, const char *nonnul_line), const char *nonnul_line); +#ifndef SWIGIMPORTED // let's not modify the wrappers for modules %import'ing us (e.g., typeinf.i, hexrays.i) +%cstring_output_buf_and_size_returning_charptr( + 1, + char *buf, + size_t bufsize); // get_idp_name +#endif // SWIGIMPORTED + +%cstring_output_qstring_returning_charptr( + 1, + qstring *out, + const char *name, + uint32 disable_mask, + int demreq); // ev_demangle_name + %include "idp.hpp" %include "config.hpp" diff --git a/swig/kernwin.i b/swig/kernwin.i index 22ffe02..669376d 100644 --- a/swig/kernwin.i +++ b/swig/kernwin.i @@ -131,6 +131,9 @@ extern plugin_t PLUGIN; %ignore place_t__serialize; %ignore place_t::deserialize; %ignore place_t__deserialize; +%ignore place_t::generate; +%ignore place_t__generate; +%rename (generate) py_generate; %ignore register_place_class; %ignore register_loc_converter; @@ -297,6 +300,21 @@ static void _py_unregister_compiled_form(PyObject *py_form, bool shutdown); static enumplace_t *as_enumplace_t(place_t *p) { return (enumplace_t *) p; } static structplace_t *as_structplace_t(place_t *p) { return (structplace_t *) p; } static simpleline_place_t *as_simpleline_place_t(place_t *p) { return (simpleline_place_t *) p; } + + PyObject *py_generate(void *ud, int maxsize) + { + qstrvec_t lines; + int deflnnum = 0; + color_t pfx_color = 0; + bgcolor_t bgcolor = DEFCOLOR; + int generated = $self->generate(&lines, &deflnnum, &pfx_color, &bgcolor, ud, maxsize); + PyObject *tuple = PyTuple_New(4); + PyTuple_SetItem(tuple, 0, qstrvec2pylist(lines)); + PyTuple_SetItem(tuple, 1, PyLong_FromLong(deflnnum)); + PyTuple_SetItem(tuple, 2, PyLong_FromLong(uchar(pfx_color))); + PyTuple_SetItem(tuple, 3, PyLong_FromLong(bgcolor)); + return tuple; + } } %extend twinpos_t { diff --git a/swig/typeinf.i b/swig/typeinf.i index 58dc76f..009fa6e 100644 --- a/swig/typeinf.i +++ b/swig/typeinf.i @@ -255,6 +255,12 @@ %ignore remove_tinfo_pointer; %rename (remove_tinfo_pointer) py_remove_tinfo_pointer; +%cstring_output_buf_and_size_returning_charptr( + 2, + ea_t ea, + char *buf, + size_t bufsize); // idc_guess_type, idc_get_type + %include "typeinf.hpp" // Custom wrappers diff --git a/tools/deploy/header.i.in b/tools/deploy/header.i.in index 720cef4..165923a 100644 --- a/tools/deploy/header.i.in +++ b/tools/deploy/header.i.in @@ -468,6 +468,28 @@ static PyObject *type##_get_clink_ptr(PyObject *self) } %enddef +//------------------------------------------------------------------------- +// and a helper for overriding the 'argout' of some of those functions, +// that return the input char* as return code; those build on recent +// versions of Xcode (>= 9.0), and need special care +%define %cstring_output_buf_and_size_returning_charptr(BUFIDX, ...) +%typemap(argout) (__VA_ARGS__) { + // cstring_output_buf_and_size_returning_charptr's argout + Py_XDECREF(resultobj); + if (result != NULL) + { + resultobj = PyString_FromString($BUFIDX); + } + else + { + Py_INCREF(Py_None); + resultobj = Py_None; + } + qfree($BUFIDX); +} +%enddef + + //--------------------------------------------------------------------- // IN/OUT qstring //--------------------------------------------------------------------- @@ -491,8 +513,43 @@ static PyObject *type##_get_clink_ptr(PyObject *self) // Nothing. We certainly don't want 'temp' to be deleted. } -//--------------------------------------------------------------------- -// md5/sha256 hash retrieval +//------------------------------------------------------------------------- +// see %cstring_output_buf_and_size_returning_charptr above +%define %cstring_output_qstring_returning_charptr(BUFIDX, ...) +%typemap(argout) (__VA_ARGS__) { + // cstring_output_qstring_returning_charptr's argout + Py_XDECREF(resultobj); + if (result != NULL) + { + resultobj = PyString_FromStringAndSize($BUFIDX->begin(), $BUFIDX->length()); + } + else + { + Py_INCREF(Py_None); + resultobj = Py_None; + } +} +%enddef + +//------------------------------------------------------------------------- +// OUT qstrvec_t +//------------------------------------------------------------------------- +%typemap(in,numinputs=0) qstrvec_t *out (qstrvec_t temp) { + $1 = &temp; +} +%typemap(argout) qstrvec_t *out +{ + Py_XDECREF(resultobj); + resultobj = qstrvec2pylist(*($1)); +} +%typemap(freearg) qstrvec_t* out +{ + // Nothing. We certainly don't want 'temp' to be deleted. +} + +//------------------------------------------------------------------------- +// md5/sha256 hash retrieval +//------------------------------------------------------------------------- %typemap(in, numinputs=0) uchar hash[ANY] (uchar temp[$1_dim0]) { $1 = temp; @@ -771,7 +828,7 @@ struct wrapped_array_t { %{ -static PyObject *qstrvec2pylist(qstrvec_t &vec) +static PyObject *qstrvec2pylist(const qstrvec_t &vec) { size_t n = vec.size(); PyObject *py_list = PyList_New(n); diff --git a/tools/gen_idc_bc695.py b/tools/gen_idc_bc695.py index 23c12a9..01afe72 100644 --- a/tools/gen_idc_bc695.py +++ b/tools/gen_idc_bc695.py @@ -64,6 +64,10 @@ forbidden = [ "SaveBase", "eval", "MakeStr", + "GetProcessorName", + "SegStart", + "SegEnd", + "SetSegmentType", ] symbols_modules = {