From 109158fabb32c1985b702b06d81cf5119d9b84d3 Mon Sep 17 00:00:00 2001 From: "elias.bachaalany" Date: Mon, 18 Apr 2011 16:07:00 +0000 Subject: [PATCH] - IDA Pro 6.1 support - Added AskUsingForm() with embedded forms support (check ex_askusingform.py example and formchooser.py in the SDK) - Added idautils.DecodePreviousInstruction() / DecodePrecedingInstruction() - Added idc.BeginTypeUpdating() / EndTypeUpdating() for fast batch type update operations - Added more IDP callbacks - Added UI_Hooks with a few notification events - Added idaapi.process_ui_action() / idc.ProcessUiAction() - Added netnode.index() to get netnode number - Better handling of ea_t values with bitwise negation - Execute statement hotkey (Ctrl-F3), script timeout, and other options are now configurable with Python.cfg - bugfix: idaapi.msg() / error() and warning() so they don't accept vararg - bugfix: processor_t.id constants were incorrect - bugfix: get_debug_names() was broken with IDA64 - Various bugfixes --- CHANGES.txt | 29 +- README.txt | 9 +- Scripts/AsmViewer.py | 216 ++ Scripts/CallStackWalk.py | 175 ++ Scripts/DbgCmd.py | 101 + Scripts/DrvsDispatch.py | 105 + Scripts/ExchainDump.py | 52 + Scripts/FindInstructions.py | 139 ++ Scripts/ImpRef.py | 69 + Scripts/ImportExportViewer.py | 124 ++ Scripts/PteDump.py | 85 + Scripts/SEHGraph.py | 149 ++ Scripts/VaDump.py | 70 + Scripts/msdnapihelp.py | 68 + build.py | 16 +- examples/ex1_idaapi.py | 32 +- examples/ex1_idautils.py | 23 +- examples/ex_add_menu_item.py | 36 +- examples/ex_askusingform.py | 221 +++ examples/ex_choose2.py | 168 +- examples/ex_strings.py | 15 +- examples/ex_uihook.py | 42 + python.cpp | 186 +- python/idautils.py | 1371 ++++++------- python/idc.py | 112 +- python/init.py | 23 +- pywraps.hpp | 37 +- swig/bytes.i | 77 +- swig/dbg.i | 10 + swig/diskio.i | 7 +- swig/expr.i | 13 +- swig/funcs.i | 36 +- swig/gdl.i | 12 +- swig/graph.i | 134 +- swig/idaapi.i | 2507 +++++++++++++----------- swig/idd.i | 21 +- swig/idp.i | 662 +++++-- swig/kernwin.i | 3487 +++++++++++++++++++++++++-------- swig/lines.i | 11 +- swig/loader.i | 4 + swig/nalt.i | 23 +- swig/name.i | 18 +- swig/netnode.i | 10 +- swig/pro.i | 14 +- swig/typeconv.i | 16 +- swig/typeinf.i | 25 +- swig/ua.i | 16 +- 47 files changed, 7623 insertions(+), 3153 deletions(-) create mode 100644 Scripts/AsmViewer.py create mode 100644 Scripts/CallStackWalk.py create mode 100644 Scripts/DbgCmd.py create mode 100644 Scripts/DrvsDispatch.py create mode 100644 Scripts/ExchainDump.py create mode 100644 Scripts/FindInstructions.py create mode 100644 Scripts/ImpRef.py create mode 100644 Scripts/ImportExportViewer.py create mode 100644 Scripts/PteDump.py create mode 100644 Scripts/SEHGraph.py create mode 100644 Scripts/VaDump.py create mode 100644 Scripts/msdnapihelp.py create mode 100644 examples/ex_askusingform.py create mode 100644 examples/ex_uihook.py diff --git a/CHANGES.txt b/CHANGES.txt index b6fdd9e..1f26313 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,20 +1,40 @@ Please see http://code.google.com/p/idapython/source/list for a detailed list of changes. + +Changes from version 1.4.3 to 1.5.0 +------------------------------------ +- IDA Pro 6.1 support +- Added AskUsingForm() with embedded forms support (check ex_askusingform.py example and formchooser.py in the SDK) +- Added idautils.DecodePreviousInstruction() / DecodePrecedingInstruction() +- Added idc.BeginTypeUpdating() / EndTypeUpdating() for fast batch type update operations +- Added more IDP callbacks +- Added UI_Hooks with a few notification events +- Added idaapi.process_ui_action() / idc.ProcessUiAction() +- Added netnode.index() to get netnode number +- Better handling of ea_t values with bitwise negation +- Execute statement hotkey (Ctrl-F3), script timeout, and other options are now configurable with Python.cfg +- bugfix: idaapi.msg() / error() and warning() so they don't accept vararg +- bugfix: processor_t.id constants were incorrect +- bugfix: get_debug_names() was broken with IDA64 +- Various bugfixes + Changes from version 1.4.2 to 1.4.3 ------------------------------------ -- IDA 6.0 support +- IDA Pro 6.0 support - Python CLI now prints expression evaluation result (no need to use print()) - Changed Alt-8 to Ctrl-F3 (because it conflicts with window switching key Alt+n) - Added get_highlighted_identifier() - Added PluginForm class to allow UI development with either PyQt4 or PySide - Added idautils.Entries() to enum entrypoints + Changes from version 1.4.1 to 1.4.2 ------------------------------------ - Added command completion - Added necessary changes so it compiles with Python 2.7 - Wrapped set_user_defined_prefix() + Changes from version 1.4.0 to 1.4.1 ------------------------------------ - Added cli_t @@ -22,6 +42,7 @@ Changes from version 1.4.0 to 1.4.1 - Changed the copyright string to IDAPython Team - Some platform dependant classes are present but useable only where applicable + Changes from version 1.3.0 to 1.4.0 ------------------------------------ - IDA Pro 5.7 support @@ -35,9 +56,9 @@ Changes from version 1.3.0 to 1.4.0 - Documented all manually wrapped functions (check 'pywraps' module in the docs) - Lots of cleanups and fixes + Changes from version 1.2.0 to 1.3.0 ------------------------------------ - - IDA Pro 5.6 support - Added Appcall mechanism - Added procregs to idautils.py (r254) @@ -45,7 +66,6 @@ Changes from version 1.2.0 to 1.3.0 Changes from version 1.1.0 to 1.2.0 ------------------------------------ - - 64-bit support (largely untested) - IDA Pro 5.5 support - Long running (or inifinitely looping) scripts can now be stopped @@ -58,7 +78,6 @@ Changes from version 1.1.0 to 1.2.0 Changes from version 0.9.0 to 1.0.0 ----------------------------------- - - Upgraded IDA Pro base version to 5.1 - Dropped Python 2.4 support - Mac OS X support @@ -76,7 +95,6 @@ Changes from version 0.9.0 to 1.0.0 Changes from version 0.8.0 to 0.9.0 ----------------------------------- - - Upgraded base version to IDA Pro 5.0 - Works with IDA Pro 5.1 - Python 2.4 and 2.5 supported @@ -92,7 +110,6 @@ Changes from version 0.8.0 to 0.9.0 Changes from version 0.7.0 to 0.8.0 ----------------------------------- - - Added support for IDA Pro 4.9 - Dropped support for IDA Pro 4.7 - NOTE: Windows version is linked against Python 2.4. diff --git a/README.txt b/README.txt index 7acf4c2..f90bab5 100644 --- a/README.txt +++ b/README.txt @@ -38,10 +38,10 @@ Mailing list for the project is hosted by Google Groups at INSTALLATION FROM BINARIES -------------------------- -1. Install Python 2.5 or 2.6 from http://www.python.org/ -2. Copy the python and python64 directories to the IDA install directory -3. Copy the plugins to the %IDADIR%\plugins\ - +1. Install 2.6 or 2.7 from http://www.python.org/ +2. Copy "python" directory to %IDADIR% +3. Copy "plugins" directory to the %IDADIR%\plugins\ +4. Copy "python.cfg" to %IDADIR%\cfg USAGE ----- @@ -88,3 +88,4 @@ or The user init file is read and executed at the end of the init process. +Please note that IDAPython can be configured with "python.cfg" file. diff --git a/Scripts/AsmViewer.py b/Scripts/AsmViewer.py new file mode 100644 index 0000000..ae70add --- /dev/null +++ b/Scripts/AsmViewer.py @@ -0,0 +1,216 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to use customview in Python +# 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 + +# ---------------------------------------------------------------------- +class asm_colorizer_t(object): + def is_id(self, ch): + return ch == '_' or ch.isalpha() or '0' <= ch <= '9' + + def get_identifier(self, line, x, e): + i = x + is_digit = line[i].isdigit() + while i < e: + ch = line[i] + if not self.is_id(ch): + if ch != '.' or not is_digit: + break + i += 1 + return (i, line[x:i]) + + def get_quoted_string(self, line, x, e): + quote = line[x] + i = x + 1 + while i < e: + ch = line[i] + if ch == '\\' and line[i+1] == quote: + i += 1 + elif ch == quote: + i += 1 # also take the quote + break + i += 1 + return (i, line[x:i]) + + def colorize(self, lines): + for line in lines: + line = line.rstrip() + if not line: + self.add_line() + continue + x = 0 + e = len(line) + s = "" + while x < e: + ch = line[x] + # String? + if ch == '"' or ch == "'": + x, w = self.get_quoted_string(line, x, e) + s += self.as_string(w) + # Tab? + elif ch == '\t': + s += ' ' * 4 + x += 1 + # Comment? + elif ch == ';': + s += self.as_comment(line[x:]) + # Done with this line + break + elif ch == '.' and x + 1 < e: + x, w = self.get_identifier(line, x + 1, e) + s += self.as_directive(ch + w) + # Identifiers? + elif self.is_id(ch): + x, w = self.get_identifier(line, x, e) + # Number? + if ch.isdigit(): + s += self.as_num(w) + # Other identifier + else: + s += self.as_id(w) + # Output as is + else: + s += ch + x += 1 + self.add_line(s) + +# ----------------------------------------------------------------------- +class asmview_t(idaapi.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)): + return False + + self.instruction_list = idautils.GetInstructionList() + self.instruction_list.extend(["ret"]) + self.register_list = idautils.GetRegisterList() + self.register_list.extend(["eax", "ebx", "ecx", "edx", "edi", "esi", "ebp", "esp"]) + + self.fn = fn + 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): + if not self.colorize_file(self.fn): + self.Close() + return False + return True + + def colorize_file(self, fn): + try: + f = open(fn, "r") + lines = f.readlines() + f.close() + self.ClearLines() + self.colorize(lines) + return True + except: + return False + + def add_line(self, s=None): + if not s: + s = "" + self.AddLine(s) + + def as_comment(self, s): + return idaapi.COLSTR(s, idaapi.SCOLOR_RPTCMT) + + def as_id(self, s): + t = s.lower() + if t in self.register_list: + return idaapi.COLSTR(s, idaapi.SCOLOR_REG) + elif t in self.instruction_list: + return idaapi.COLSTR(s, idaapi.SCOLOR_INSN) + else: + return s + + def as_string(self, s): + return idaapi.COLSTR(s, idaapi.SCOLOR_STRING) + + def as_num(self, s): + return idaapi.COLSTR(s, idaapi.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 + + def OnKeydown(self, vkey, shift): + """ + User pressed a key + @param vkey: Virtual key code + @param shift: Shift flag + @return Boolean. True if you handled the event + """ + # ESCAPE + if vkey == 27: + self.Close() + elif vkey == ord('H'): + 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 + self.EditLine(lineno, s, fg, bg) + self.Refresh() + elif vkey == ord('C'): + self.ClearLines() + self.Refresh() + elif vkey == ord('S'): + print "Selection (x1, y1, x2, y2) = ", self.GetSelection() + elif vkey == ord('I'): + print "Position (line, x, y) = ", self.GetPos(mouse = 0) + else: + return False + return True + +# ----------------------------------------------------------------------- +class asmviewplg(idaapi.plugin_t): + flags = idaapi.PLUGIN_KEEP + comment = "ASM viewer" + help = "This is help" + wanted_name = "ASM file viewer" + wanted_hotkey = "Alt-F8" + def __init__(self): + self.view = None + + def init(self): + return idaapi.PLUGIN_KEEP + def run(self, arg): + if self.view: + self.Close() + fn = idc.AskFile(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() + + def term(self): + if self.view: + self.view.Close() + +def PLUGIN_ENTRY(): + return asmviewplg() \ No newline at end of file diff --git a/Scripts/CallStackWalk.py b/Scripts/CallStackWalk.py new file mode 100644 index 0000000..f1fef78 --- /dev/null +++ b/Scripts/CallStackWalk.py @@ -0,0 +1,175 @@ +""" + +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 +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 idautils + +# ----------------------------------------------------------------------- +# class to take a copy of a segment_t +class Seg(): + def __init__(self, s): + self.startEA = s.startEA + self.endEA = s.endEA + self.perm = s.perm + self.bitness = s.bitness + def __cmp__(self, other): + return cmp(self.startEA, other.startEA) + +# ----------------------------------------------------------------------- +# each item described as: +# [ delta, [ opcode(s) ] ] +#FF10 call d,[eax] +#FF5000 call d,[eax][0] +#FF9044332211 call d,[eax][011223344] +#FF1500000100 call d,[000010000] +#FF9300000000 call d,[ebx][0] +#FF10 call d,[eax] +CallPattern = \ +[ + [-2, [0xFF] ], + [-3, [0xFF] ], + [-5, [0xE8] ], + [-6, [0xFF] ], +] + +# ----------------------------------------------------------------------- +def IsPrevInsnCall(ea): + """ + Given a return address, this function tries to check if previous instruction + is a CALL instruction + """ + global CallPattern + if ea == 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)] + # do we have a match? is it a call instruction? + if bytes == opcodes and idaapi.is_call_insn(caller): + return caller + return None + +# ----------------------------------------------------------------------- +def CallStackWalk(nn): + class Result: + """ + Class holding the result of one call stack item + Each call stack item instance has the following attributes: + caller = ea of caller + displ = display string + sp = stack pointer + """ + def __init__(self, caller, sp): + self.caller = caller + self.sp = sp + f = idaapi.get_func(caller) + self.displ = "%08x: " % caller + if f: + self.displ += idc.GetFunctionName(caller) + t = caller - f.startEA + 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) + 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 + while sp < stack_seg.endEA: + sp += word_size + ptr = idautils.GetDataList(sp, 1, word_size).next() + seg = idaapi.getseg(ptr) + # only accept executable segments + if (not seg) or ((seg.perm & idaapi.SEGPERM_EXEC) == 0): + continue + # try to find caller + caller = IsPrevInsnCall(ptr) + # we have no recognized caller, skip! + if caller is None: + continue + + # do we have a debug name that is near? + if nn: + ret = nn.find(caller) + if ret: + ea = ret[0] + # function exists? + f = idaapi.get_func(ea) + if not f: + # create function + idc.MakeFunction(ea, idaapi.BADADDR) + + # get the flags + f = idc.GetFlags(caller) + # no code there? + if not isCode(f): + MakeCode(caller) + + callers.append(Result(caller, sp)) + # + return (True, callers) + +# ----------------------------------------------------------------------- +# Chooser class +class CallStackWalkChoose(Choose): + def __init__(self, list, title): + Choose.__init__(self, list, title) + self.width = 250 + + def enter(self, n): + o = self.list[n-1] + idc.Jump(o.caller) + +# ----------------------------------------------------------------------- +def main(): + if not idaapi.is_debugger_on(): + idc.Warning("Please run the process first!") + return + if idaapi.get_process_state() != -1: + idc.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.minEA, idaapi.cvar.inf.maxEA) + # initiate a nearest name search (using debug names) + nn = idaapi.NearestName(dn) + else: + nn = None + + ret, callstack = CallStackWalk(nn) + if ret: + title = "Call stack walker (thread %X)" % (GetCurrentThreadId()) + idaapi.close_chooser(title) + c = CallStackWalkChoose(callstack, title) + c.choose() + else: + idc.Warning("Failed to walk the stack:" + callstack) + +# ----------------------------------------------------------------------- +main() \ No newline at end of file diff --git a/Scripts/DbgCmd.py b/Scripts/DbgCmd.py new file mode 100644 index 0000000..602566d --- /dev/null +++ b/Scripts/DbgCmd.py @@ -0,0 +1,101 @@ +# ----------------------------------------------------------------------- +# Debugger command prompt with CustomViewers +# (c) Hex-Rays +# +import idaapi +import idc +from idaapi import simplecustviewer_t + +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 'SendDbgCommand' IDC command. + """ + s = Eval('SendDbgCommand("%s");' % cmd) + if s.startswith("IDC_FAILURE"): + raise Exception, "Debugger command is available only when the debugger is active!" + return s + +# ----------------------------------------------------------------------- +class dbgcmd_t(simplecustviewer_t): + def Create(self): + # Form the title + title = "Debugger command window" + # Create the customview + if not 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.askstr(0, self.last_cmd, "Please enter a debugger command") + if not s: + return + + # Save last command + self.last_cmd = s + + # Add it using a different color + self.AddLine("debugger>" + idaapi.COLSTR(s, idaapi.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 SendDbgCommand()", idaapi.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.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(): + x = dbgcmd_t() + if not x.Create(): + print "Failed to create debugger command line!" + return None + x.Show() + return x + +try: + # created already? + dbgcmd + dbgcmd.Close() + del dbgcmd +except: + pass + +dbgcmd = show_win() +if not dbgcmd: + del dbgcmd + diff --git a/Scripts/DrvsDispatch.py b/Scripts/DrvsDispatch.py new file mode 100644 index 0000000..544e7ba --- /dev/null +++ b/Scripts/DrvsDispatch.py @@ -0,0 +1,105 @@ +""" + +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 +ALL RIGHTS RESERVED. + +""" + +import re +from idaapi import Choose + +# ----------------------------------------------------------------------- +def CmdDriverList(): + s = Eval('WinDbgCommand("lm o");') + if "IDC_FAILURE" in s: return False + return s + +# ----------------------------------------------------------------------- +def CmdDrvObj(drvname, flag=2): + return Eval('WinDbgCommand("!drvobj %s %d");' % (drvname, flag)) + +# ----------------------------------------------------------------------- +def CmdReloadForce(): + s = Eval('WinDbgCommand(".reload /f");') + if "IDC_FAILURE" in s: return False + return True + +# ----------------------------------------------------------------------- +# class to hold dispatch entry information +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) + + # force reloading of module symbols + if not CmdReloadForce(): + print "Could not communicate with WinDbg, make sure the debugger is running!" + return None + + # get driver list + lm_out = CmdDriverList() + if not lm_out: + return "Failed to get driver list!" + + # for each line + for line in lm_out.split("\n"): + # parse + r = re_drv.match(line) + if not r: continue + + # 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): + def __init__(self, list, title): + Choose.__init__(self, list, title) + self.width = 250 + + def enter(self, n): + o = self.list[n-1] + idc.Jump(o.addr) + +# ----------------------------------------------------------------------- +# main +r = GetDriverDispatch() +if r: + c = DispatchChoose(r, "Dispatch table browser") + c.choose() +else: + print "Failed to retrieve dispatchers list!" \ No newline at end of file diff --git a/Scripts/ExchainDump.py b/Scripts/ExchainDump.py new file mode 100644 index 0000000..f8d93b9 --- /dev/null +++ b/Scripts/ExchainDump.py @@ -0,0 +1,52 @@ +""" + +This script shows how to send debugger commands and use the result in IDA + +Copyright (c) 1990-2009 Hex-Rays +ALL RIGHTS RESERVED. + +""" + +import idc +import re + +# class to store parsed results +class exchain: + def __init__(self, m): + self.name = m.group(1) + self.addr = int(m.group(2), 16) + + def __str__(self): + return "%x: %s" % (self.addr, self.name) + +# Chooser class +class MyChoose(Choose): + def __init__(self, list, title): + Choose.__init__(self, list, title) + self.width = 250 + + def enter(self, n): + o = self.list[n-1] + idc.Jump(o.addr) + +# main +def main(): + s = idc.Eval('SendDbgCommand("!exchain")') + if "IDC_FAILURE" in s: + return (False, "Cannot execute the command") + + matches = re.finditer(r'[^:]+: ([^\(]+) \(([^\)]+)\)\n', s) + L = [] + for x in matches: + L.append(exchain(x)) + if not L: + return (False, "Nothing to display: Could parse the result!") + + # Get a Choose instance + chooser = MyChoose(L, "Exchain choose") + # Run the chooser + chooser.choose() + return (True, "Success!") +ok, r = main() +if not ok: + print r diff --git a/Scripts/FindInstructions.py b/Scripts/FindInstructions.py new file mode 100644 index 0000000..353d525 --- /dev/null +++ b/Scripts/FindInstructions.py @@ -0,0 +1,139 @@ +""" +FindInstructions.py: 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 + +The general syntax is: + find(asm or opcodes, x=Bool, asm_where=ea) + +* Example: + find("asm_statement1;asm_statement2;de ea dc 0d e0;asm_statement3;xx yy zz;...") +* To filter-out non-executable segments pass x=True + find("jmp dword ptr [esp]", x=True) +* 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 +ALL RIGHTS RESERVED. + +v1.0 - initial version +""" +import idaapi +import idautils +import idc + +# ----------------------------------------------------------------------- +def FindInstructions(instr, asm_where=None): + """ + Finds instructions/opcodes + @return: Returns a tuple(True, [ ea, ... ]) or a tuple(False, "error message") + """ + if not asm_where: + # get first segment + asm_where = FirstSeg() + if asm_where == idaapi.BADADDR: + return (False, "No segments defined") + + # regular expression to distinguish between opcodes and instructions + re_opcode = re.compile('^[0-9a-f]{2} *', re.I) + + # split lines + lines = instr.split(";") + + # all the assembled buffers (for each instruction) + bufs = [] + for line in lines: + if re_opcode.match(line): + # convert from hex string to a character list then join the list to form one string + buf = ''.join([chr(int(x, 16)) for x in line.split()]) + else: + # assemble the instruction + ret, buf = Assemble(asm_where, line) + if not ret: + return (False, "Failed to assemble:"+line) + # add the assembled buffer + bufs.append(buf) + + # join the buffer into one string + buf = ''.join(bufs) + + # take total assembled instructions length + tlen = len(buf) + + # convert from binary string to space separated hex string + bin_str = ' '.join(["%02X" % ord(x) for x in buf]) + + # find all binary strings + print "Searching for: [%s]" % bin_str + ea = MinEA() + ret = [] + while True: + ea = FindBinary(ea, SEARCH_DOWN, bin_str) + if ea == idaapi.BADADDR: + break + ret.append(ea) + Message(".") + ea += tlen + if not ret: + return (False, "Could not match [%s]" % bin_str) + Message("\n") + return (True, ret) + +# ----------------------------------------------------------------------- +# Chooser class +class SearchResultChoose(Choose): + def __init__(self, list, title): + Choose.__init__(self, list, title) + self.width = 250 + + def enter(self, n): + o = self.list[n-1] + Jump(o.ea) + +# ----------------------------------------------------------------------- +# class to represent the results +class SearchResult: + def __init__(self, ea): + self.ea = ea + if not isCode(GetFlags(ea)): + MakeCode(ea) + t = idaapi.generate_disasm_line(ea) + if t: + line = idaapi.tag_remove(t) + else: + line = "" + func = GetFunctionName(ea) + self.display = hex(ea) + ": " + if func: + self.display += func + ": " + else: + n = SegName(ea) + if n: self.display += n + ": " + self.display += line + + def __str__(self): + return self.display + +# ----------------------------------------------------------------------- +def find(s=None, x=False, asm_where=None): + b, ret = FindInstructions(s, asm_where) + if b: + # executable segs only? + if x: + results = [] + for ea in ret: + seg = idaapi.getseg(ea) + if (not seg) or (seg.perm & idaapi.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) + c = SearchResultChoose(results, title) + c.choose() + else: + print ret + +# ----------------------------------------------------------------------- +print "Please use find('asm_stmt1;xx yy;...', x=Bool,asm_where=ea) to search for instructions or opcodes. Specify x=true to filter out non-executable segments" diff --git a/Scripts/ImpRef.py b/Scripts/ImpRef.py new file mode 100644 index 0000000..5dc8542 --- /dev/null +++ b/Scripts/ImpRef.py @@ -0,0 +1,69 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to enumerate all addresses +# that refer to all imported functions in a given module +# +# (c) Hex-Rays +# + +import idaapi +import idc +import idautils +import re + +# ----------------------------------------------------------------------- +def find_imported_funcs(dllname): + def imp_cb(ea, name, ord): + if not name: + name = '' + imports.append([ea, name, ord]) + return True + + imports = [] + nimps = idaapi.get_import_module_qty() + for i in xrange(0, nimps): + name = idaapi.get_import_module_name(i) + if re.match(dllname, name, re.IGNORECASE) is None: + continue + idaapi.enum_import_names(i, imp_cb) + + return imports + + +# ----------------------------------------------------------------------- +def find_import_ref(dllname): + imports = find_imported_funcs(dllname) + R = dict() + for i, (ea, name,_) in enumerate(imports): + #print "%x -> %s" % (ea, name) + 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.startEA, idaapi.get_func_name(f.startEA), 0]) + #print "\t%x %s: from a thunk, parent added %x" % (ea, name, f.startEA) + continue + + # save results + if not R.has_key(i): + R[i] = [] + + R[i].append(ea) + + return (imports, R) + +# ----------------------------------------------------------------------- +def main(): + dllname = idc.AskStr('kernel32', "Enter module name") + if not dllname: + print("Cancelled") + return + + imports, R = find_import_ref(dllname) + for k, v in R.items(): + print(imports[k][1]) + for ea in v: + print("\t%x" % ea) + +# ----------------------------------------------------------------------- +main() \ No newline at end of file diff --git a/Scripts/ImportExportViewer.py b/Scripts/ImportExportViewer.py new file mode 100644 index 0000000..afe0aac --- /dev/null +++ b/Scripts/ImportExportViewer.py @@ -0,0 +1,124 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to: +# - enumerate imports +# - enumerate entrypoints +# - Use PluginForm class +# - Use PySide with PluginForm to create a Python UI +# +# (c) Hex-Rays +# +import idaapi +import idautils +from idaapi import PluginForm +from PySide import QtGui, QtCore + +# -------------------------------------------------------------------------- +class ImpExpForm_t(PluginForm): + + def imports_names_cb(self, ea, name, ord): + self.items.append((ea, '' if not name else name, ord)) + # True -> Continue enumeration + return True + + + def BuildImports(self): + tree = {} + nimps = idaapi.get_import_module_qty() + + for i in xrange(0, nimps): + name = idaapi.get_import_module_name(i) + if not name: + continue + # Create a list for imported names + self.items = [] + + # Enum imported entries in this module + idaapi.enum_import_names(i, self.imports_names_cb) + + if name not in tree: + tree[name] = [] + tree[name].extend(self.items) + + return tree + + + def BuildExports(self): + return list(idautils.Entries()) + + + def PopulateTree(self): + # Clear previous items + self.tree.clear() + + # Build imports + root = QtGui.QTreeWidgetItem(self.tree) + root.setText(0, "Imports") + + for dll_name, imp_entries in self.BuildImports().items(): + imp_dll = QtGui.QTreeWidgetItem(root) + imp_dll.setText(0, dll_name) + + for imp_ea, imp_name, imp_ord in imp_entries: + item = QtGui.QTreeWidgetItem(imp_dll) + item.setText(0, "%s [0x%08x]" %(imp_name, imp_ea)) + + + # Build exports + root = QtGui.QTreeWidgetItem(self.tree) + root.setText(0, "Exports") + + for exp_i, exp_ord, exp_ea, exp_name in self.BuildExports(): + item = QtGui.QTreeWidgetItem(root) + item.setText(0, "%s [#%d] [0x%08x]" % (exp_name, exp_ord, exp_ea)) + + + def OnCreate(self, form): + """ + Called when the plugin form is created + """ + + # Get parent widget + self.parent = self.FormToPySideWidget(form) + + # Create tree control + self.tree = QtGui.QTreeWidget() + self.tree.setHeaderLabels(("Names",)) + self.tree.setColumnWidth(0, 100) + + # Create layout + layout = QtGui.QVBoxLayout() + layout.addWidget(self.tree) + + self.PopulateTree() + # Populate PluginForm + self.parent.setLayout(layout) + + + def OnClose(self, form): + """ + Called when the plugin form is closed + """ + global ImpExpForm + del ImpExpForm + print "Closed" + + + def Show(self): + """Creates the form is not created or focuses it if it was""" + return PluginForm.Show(self, + "Imports / Exports viewer", + options = PluginForm.FORM_PERSIST) + +# -------------------------------------------------------------------------- +def main(): + global ImpExpForm + + try: + ImpExpForm + except: + ImpExpForm = ImpExpForm_t() + + ImpExpForm.Show() + +# -------------------------------------------------------------------------- +main() \ No newline at end of file diff --git a/Scripts/PteDump.py b/Scripts/PteDump.py new file mode 100644 index 0000000..23e37b5 --- /dev/null +++ b/Scripts/PteDump.py @@ -0,0 +1,85 @@ +import idaapi +import idc +from idaapi import Choose2 + +def parse_pte(str): + try: + parse_pte.re + except: + parse_pte.re = re.compile('PDE at ([0-9a-f]+)\s*PTE at ([0-9a-f]+)\ncontains ([0-9a-f]+)\s*contains ([0-9a-f]+)\npfn ([0-9]+)\s*([^ ]+)\s*pfn ([0-9a-f]+)\s*([^\r\n]+)', re.I | re.M) + parse_pte.items = ('pde', 'pte', 'pdec', 'ptec', 'pdepfn', 'pdepfns', 'ptepfn', 'ptepfns') + + m = parse_pte.re.search(s) + r = {} + for i in range(0, len(parse_pte.items)): + r[parse_pte.items[i]] = m.group(i+1) + return r + +class MyChoose2(Choose2): + + def __init__(self, title, ea1, ea2): + Choose2.__init__(self, title, [ ["VA", 10], ["PTE attr", 30] ]) + self.ea1 = ea1 + self.ea2 = ea2 + self.n = 0 + self.icon = 5 + self.items = [] + self.Refresh() + self.selcount = 0 + + def OnGetLine(self, n): + print("getline %d" % n) + return self.items[n] + + def OnGetSize(self): + n = len(self.items) + self.Refresh() + return n + + def OnRefresh(self, n): + print("refresh %d" % n) + return n + + def Refresh(self): + items = [] + PG = 0x1000 + ea1 = self.ea1 + npages = (self.ea2 - ea1) / PG + for i in range(npages): + r = idc.SendDbgCommand("!pte %x" % ea1) + if not r: + return False + r = parse_pte(r) + items.append([hex(ea1), r['ptepfns']]) + ea1 += PG + + self.items = items + print(self.items) + return True + + @staticmethod + def Execute(ea1, ea2): + c = MyChoose2("PTE Viewer [%x..%x]" % (ea1, ea2), ea1, ea2) + return (c, c.Show()) + + +def DumpPTE(ea1, ea2): + items = [] + PG = 0x1000 + npages = (ea2 - ea1) / PG + for i in range(npages): + r = idc.SendDbgCommand("!pte %x" % ea1) + if not r: + return False + print r + r = parse_pte(r) + print("VA: %08X PTE: %s PDE: %s" % (ea1, r['ptepfns'], r['pdepfns'])) + ea1 += PG + +def DumpSegPTE(ea): + DumpPTE(idc.SegStart(ea), idc.SegEnd(ea)) + +DumpSegPTE(here()) + +#MyChoose2.Execute(0xF718F000, 0xF718F000+0x1000) + diff --git a/Scripts/SEHGraph.py b/Scripts/SEHGraph.py new file mode 100644 index 0000000..9cd3216 --- /dev/null +++ b/Scripts/SEHGraph.py @@ -0,0 +1,149 @@ +""" + +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 +ALL RIGHTS RESERVED. + + +v1.0 - initial version + +""" + +import idaapi +import idautils +import idc + +from idaapi import GraphViewer + +# ----------------------------------------------------------------------- +# 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('WinDbgCommand("dg %x")' % cpu.fs) + if "IDC_FAILURE" in s: + return 0 + m = re.compile("[0-9a-f]{4} ([0-9a-f]{8})") + t = m.match(s.split('\n')[-2]) + if not t: + return 0 + return int(t.group(1), 16) + +# ----------------------------------------------------------------------- +def GetFsBase(tid): + idc.SelectThread(tid) + base = idaapi.dbg_get_thread_sreg_base(tid, cpu.fs) + if base != 0: + return base + return WindbgGetRegBase(tid) + +# ----------------------------------------------------------------------- +# Walks the SEH chain and returns a list of handlers +def GetExceptionChain(tid): + fs_base = GetFsBase(tid) + exc_rr = Dword(fs_base) + result = [] + while exc_rr != 0xffffffff: + prev = Dword(exc_rr) + handler = Dword(exc_rr + 4) + exc_rr = prev + result.append(handler) + return result + +# ----------------------------------------------------------------------- +class SEHGraph(GraphViewer): + def __init__(self, title, result): + GraphViewer.__init__(self, title) + self.result = result + self.names = {} # ea -> name + + def OnRefresh(self): + self.Clear() + addr_id = {} + + for (tid, chain) in self.result.items(): + # Each node data will contain a tuple of the form: (Boolean->Is_thread, Int->Value, String->Label) + # For threads the is_thread will be true and the value will hold the thread id + # For exception handlers, is_thread=False and Value=Handler address + + # Add the thread node + id_parent = self.AddNode( (True, tid, "Thread %X" % tid) ) + + # Add each handler + for handler in chain: + # Check if a function is created at the handler's address + f = idaapi.get_func(handler) + if not f: + # create function + idc.MakeFunction(handler, idaapi.BADADDR) + + # Node label is function name or address + s = GetFunctionName(handler) + if not s: + s = "%x" % handler + + # cache name + self.names[handler] = s + + # Get the node id given the handler address + # We use an addr -> id dictionary so that similar addresses get similar node id + if not addr_id.has_key(handler): + id = self.AddNode( (False, handler, s) ) + addr_id[handler] = id # add this ID + else: + id = addr_id[handler] + + # Link handlers to each other + self.AddEdge(id_parent, id) + id_parent = id + + return True + + def OnGetText(self, node_id): + is_thread, value, label = self[node_id] + if is_thread: + return (label, 0xff00f0) + return label + + def OnDblClick(self, node_id): + is_thread, value, label = self[node_id] + if is_thread: + idc.SelectThread(value) + self.Show() + s = "SEH chain for " + hex(value) + t = "-" * len(s) + print t + print s + print t + for handler in self.result[value]: + print "%x: %s" % (handler, self.names[handler]) + print t + else: + idc.Jump(value) + return True + + +# ----------------------------------------------------------------------- +def main(): + if not idaapi.dbg_can_query(): + print "The debugger must be active and suspended before using this script!" + return + + # Save current thread id + tid = GetCurrentThreadId() + + # Iterate through all function instructions and take only call instructions + result = {} + for tid in idautils.Threads(): + result[tid] = GetExceptionChain(tid) + + # Restore previously selected thread + idc.SelectThread(tid) + + # Build the graph + g = SEHGraph("SEH graph", result) + g.Show() + +main() diff --git a/Scripts/VaDump.py b/Scripts/VaDump.py new file mode 100644 index 0000000..cf606eb --- /dev/null +++ b/Scripts/VaDump.py @@ -0,0 +1,70 @@ +""" + +This script shows how to send debugger commands and use the result in IDA + +Copyright (c) 1990-2009 Hex-Rays +ALL RIGHTS RESERVED. + +""" + +import idc +from idaapi import Choose + +import re + +# class to store parsed results +class memva: + def __init__(self, m): + self.base = int(m.group(1), 16) + self.regionsize = int(m.group(2), 16) + self.state = int(m.group(3), 16) + self.statestr = m.group(4).strip() + self.protect = int(m.group(5), 16) + self.protectstr = m.group(6).strip() + if m.group(7): + self.type = int(m.group(8), 16) + self.typestr = m.group(9).strip() + else: + self.type = 0 + self.typestr = "" + def __str__(self): + return "(Base %08X; RegionSize: %08X; State: %08X/%10s; protect: %08X/%10s; type: %08X/%10s)" % ( + self.base, self.regionsize, self.state, + self.statestr, self.protect, + self.protectstr, self.type, self.typestr) + +# Chooser class +class MemChoose(Choose): + def __init__(self, list, title): + Choose.__init__(self, list, title) + self.width = 250 + + def enter(self, n): + o = self.list[n-1] + idc.Jump(o.base) + +# main +def main(): + s = idc.Eval('SendDbgCommand("!vadump")') + if "IDC_FAILURE" in s: + return (False, "Cannot execute the command") + + matches = re.finditer(r'BaseAddress:\s*?(\w+?)\n' \ + +'RegionSize:\s*?(\w*?)\n' \ + +'State:\s*?(\w*?)\s*?(\w*?)\n' \ + +'Protect:\s*?(\w*?)\s*?(\w*?)\n' \ + +'(Type:\s*?(\w*?)\s*?(\w*?)\n)*', s) + L = [] + for x in matches: + L.append(memva(x)) + if not L: + return (False, "Nothing to display: Could not parse the result!") + + # Get a Choose instance + chooser = MemChoose(L, "Memory choose") + # Run the chooser + chooser.choose() + return (True, "Success!") +r = main() +if not r[0]: + print r[1] diff --git a/Scripts/msdnapihelp.py b/Scripts/msdnapihelp.py new file mode 100644 index 0000000..08117e3 --- /dev/null +++ b/Scripts/msdnapihelp.py @@ -0,0 +1,68 @@ +""" +User contributed script: MSDN API HELP plugin + +This script fetches the API reference (from MSDN) of a given highlighted identifier +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 + +# ----------------------------------------------------------------------- +class msdnapihelp_plugin_t(idaapi.plugin_t): + flags = 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 + + + @staticmethod + def sanitize_name(name): + t = idaapi.FUNC_IMPORT_PREFIX + if name.startswith(t): + return name[len(t):] + return name + + + def run(self, arg): + # Get the highlighted identifier + id = idaapi.get_highlighted_identifier() + if not id: + 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) + if len(d['entries']) > 0: + url = d['entries'][0].link + webbrowser.open_new_tab(url) + else: + print "API documentation not found for: %s" % id + + + def term(self): + pass + + +# ----------------------------------------------------------------------- +def PLUGIN_ENTRY(): + return msdnapihelp_plugin_t() diff --git a/build.py b/build.py index 9ff09e6..69c724a 100644 --- a/build.py +++ b/build.py @@ -24,7 +24,7 @@ from distutils import sysconfig VERBOSE = True IDA_MAJOR_VERSION = 6 -IDA_MINOR_VERSION = 0 +IDA_MINOR_VERSION = 1 if 'IDA' in os.environ: IDA_SDK = os.environ['IDA'] @@ -35,8 +35,8 @@ else: # IDAPython version VERSION_MAJOR = 1 -VERSION_MINOR = 4 -VERSION_PATCH = 3 +VERSION_MINOR = 5 +VERSION_PATCH = 0 # Determine Python version PYTHON_MAJOR_VERSION = int(platform.python_version()[0]) @@ -68,6 +68,7 @@ BINDIST_MANIFEST = [ "CHANGES.txt", "AUTHORS.txt", "STATUS.txt", + "python.cfg", "docs/notes.txt", "examples/chooser.py", "examples/colours.py", @@ -91,6 +92,8 @@ BINDIST_MANIFEST = [ "examples/ex_prefix_plugin.py", "examples/ex_pyside.py", "examples/ex_pyqt.py", + "examples/ex_askusingform.py", + "examples/ex_uihook.py", "examples/ex_imports.py" ] @@ -100,6 +103,7 @@ SRCDIST_MANIFEST = [ "python.cpp", "basetsd.h", "build.py", + "python.cfg", "swig/allins.i", "swig/area.i", "swig/auto.i", @@ -309,7 +313,7 @@ def build_plugin(platform, idasdkdir, plugin_name, ea64): platform_macros = [ "__LINUX__" ] python_libpath = sysconfig.EXEC_PREFIX + os.sep + "lib" python_library = "-lpython%d.%d" % (PYTHON_MAJOR_VERSION, PYTHON_MINOR_VERSION) - ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "gcc64.lnx" or "gcc32.lnx") + ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "x86_linux_gcc_64" or "x86_linux_gcc_32") ida_lib = "" extra_link_parameters = "" # Platform-specific settings for the Windows build @@ -318,7 +322,7 @@ def build_plugin(platform, idasdkdir, plugin_name, ea64): platform_macros = [ "__NT__" ] python_libpath = sysconfig.EXEC_PREFIX + os.sep + "libs" python_library = "python%d%d.lib" % (PYTHON_MAJOR_VERSION, PYTHON_MINOR_VERSION) - ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "vc.w64" or "vc.w32") + ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "x86_win_vc_64" or "x86_win_vc_32") ida_lib = "ida.lib" SWIG_OPTIONS += " -D__NT__ " extra_link_parameters = "" @@ -329,7 +333,7 @@ def build_plugin(platform, idasdkdir, plugin_name, ea64): platform_macros = [ "__MAC__" ] python_libpath = "." python_library = "-framework Python" - ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "gcc64.mac64" or "gcc32.mac") + ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "x86_mac_gcc_64" or "x86_mac_gcc_32") ida_lib = ea64 and "-lida64" or "-lida" extra_link_parameters = "" diff --git a/examples/ex1_idaapi.py b/examples/ex1_idaapi.py index 3fec86a..8902ab8 100644 --- a/examples/ex1_idaapi.py +++ b/examples/ex1_idaapi.py @@ -7,23 +7,27 @@ # from idaapi import * -# Get current ea -ea = get_screen_ea() +def main(): + # Get current ea + ea = get_screen_ea() -# Get segment class -seg = getseg(ea) + # Get segment class + seg = getseg(ea) -# Loop from segment start to end -func = get_func(seg.startEA) + # Loop from segment start to end + func = get_next_func(seg.startEA) + seg_end = seg.endEA + while func is not None and func.startEA < seg_end: + funcea = func.startEA + print "Function %s at 0x%x" % (GetFunctionName(funcea), funcea) -while func != None and func.startEA < seg.endEA: - funcea = func.startEA - print "Function %s at 0x%x" % (GetFunctionName(funcea), funcea) + ref = get_first_cref_to(funcea) - ref = get_first_cref_to(funcea) + while ref != BADADDR: + print " called from %s(0x%x)" % (get_func_name(ref), ref) + ref = get_next_cref_to(funcea, ref) - while ref != BADADDR: - print " called from %s(0x%x)" % (get_func_name(ref), ref) - ref = get_next_cref_to(funcea, ref) + func = get_next_func(funcea) - func = get_next_func(funcea) + +main() \ No newline at end of file diff --git a/examples/ex1_idautils.py b/examples/ex1_idautils.py index d0d76b7..5072f94 100644 --- a/examples/ex1_idautils.py +++ b/examples/ex1_idautils.py @@ -7,14 +7,21 @@ # from idautils import * -# Get current ea -ea = ScreenEA() +def main(): + # Get current ea + ea = ScreenEA() + if ea == idaapi.BADADDR: + print("Could not get get_screen_ea()") + return -# Loop from start to end in the current segment -for funcea in Functions(SegStart(ea), SegEnd(ea)): - print "Function %s at 0x%x" % (GetFunctionName(funcea), funcea) + # Loop from start to end in the current segment + for funcea in Functions(SegStart(ea), SegEnd(ea)): + print("Function %s at 0x%x" % (GetFunctionName(funcea), funcea)) - # Find all code references to funcea - for ref in CodeRefsTo(funcea, 1): - print " called from %s(0x%x)" % (GetFunctionName(ref), ref) + # Find all code references to funcea + for ref in CodeRefsTo(funcea, 1): + print(" called from %s(0x%x)" % (GetFunctionName(ref), ref)) + +if __name__=='__main__': + main() \ No newline at end of file diff --git a/examples/ex_add_menu_item.py b/examples/ex_add_menu_item.py index 0b681bc..e9bed27 100644 --- a/examples/ex_add_menu_item.py +++ b/examples/ex_add_menu_item.py @@ -1,18 +1,18 @@ -import idaapi - -def cb(*args): - print "Callback called!" - return 1 - -try: - if ctx: - idaapi.del_menu_item(ctx) -except: - pass - -ctx = idaapi.add_menu_item("Search/", "X", "", 0, cb, tuple("hello world")) -if ctx is None: - print "Failed to add menu!" - del ctx -else: - print "Menu added successfully!" \ No newline at end of file +import idaapi + +def cb(*args): + print("Callback called!") + return 1 + +try: + ctx + idaapi.del_menu_item(ctx) + print("Menu removed") + del ctx +except: + ctx = idaapi.add_menu_item("Search/", "X", "", 0, cb, tuple("hello world")) + if ctx is None: + print("Failed to add menu!") + del ctx + else: + print("Menu added successfully. Run the script again to delete the menu") \ No newline at end of file diff --git a/examples/ex_askusingform.py b/examples/ex_askusingform.py new file mode 100644 index 0000000..9c6f475 --- /dev/null +++ b/examples/ex_askusingform.py @@ -0,0 +1,221 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to use the Form class +# (c) Hex-Rays +# +from idaapi import Form + +# +# -------------------------------------------------------------------------- +class TestEmbeddedChooserClass(Choose2): + """ + A simple chooser to be used as an embedded chooser + """ + def __init__(self, title, nb = 5, flags=0): + Choose2.__init__(self, + title, + [ ["Address", 10], ["Name", 30] ], + embedded=True, width=30, height=20, flags=flags) + self.n = 0 + self.items = [ self.make_item() for x in xrange(0, nb+1) ] + self.icon = 5 + self.selcount = 0 + + def make_item(self): + r = [str(self.n), "func_%04d" % self.n] + self.n += 1 + return r + + def OnClose(self): + pass + + def OnGetLine(self, n): + print("getline %d" % n) + return self.items[n] + + def OnGetSize(self): + n = len(self.items) + print("getsize -> %d" % n) + return n + +# -------------------------------------------------------------------------- +class MyForm(Form): + def __init__(self): + self.invert = False + self.EChooser = TestEmbeddedChooserClass("E1", flags=Choose2.CH_MULTI) + Form.__init__(self, r"""STARTITEM {id:rNormal} +BUTTON YES* Yeah +BUTTON NO Nope +BUTTON CANCEL Nevermind +Form Test + +{FormChangeCb} +This is a string: +{cStr1}+ +This is an address: +{cAddr1}+ + +Escape\{control} +This is a string: '{cStr2}' +This is a number: {cVal1} + +<#Hint1#Enter name:{iStr1}> +<#Hint2#Select color:{iColor1}> +Browse test +<#Select a file to open#Browse to open:{iFileOpen}> +<#Select a file to save#Browse to save:{iFileSave}> +<#Select dir#Browse for dir:{iDir}> +Type +<#Select type#Write a type:{iType}> +Numbers +<##Enter a selector value:{iSegment}> +<##Enter a raw hex:{iRawHex}> +<##Enter a character:{iChar}> +<##Enter an address:{iAddr}> +Button test +<##Button1:{iButton1}> <##Button2:{iButton2}> + +Check boxes: + + +{cGroup1}> + +Radio boxes: + + +{cGroup2}> + +The end! +""", { + 'cStr1': Form.StringLabel("Hello"), + 'cStr2': Form.StringLabel("StringTest"), + 'cAddr1': Form.NumericLabel(0x401000, Form.FT_ADDR), + 'cVal1' : Form.NumericLabel(99, Form.FT_HEX), + 'iStr1': Form.StringInput(), + 'iColor1': Form.ColorInput(), + 'iFileOpen': Form.FileInput(open=True), + 'iFileSave': Form.FileInput(save=True), + 'iDir': Form.DirInput(), + 'iType': Form.StringInput(tp=Form.FT_TYPE), + 'iSegment': Form.NumericInput(tp=Form.FT_SEG), + 'iRawHex': Form.NumericInput(tp=Form.FT_RAWHEX), + 'iAddr': Form.NumericInput(tp=Form.FT_ADDR), + 'iChar': Form.NumericInput(tp=Form.FT_CHAR), + 'iButton1': Form.ButtonInput(self.OnButton1), + 'iButton2': Form.ButtonInput(self.OnButton2), + 'cGroup1': Form.ChkGroupControl(("rNormal", "rError", "rWarnings")), + 'cGroup2': Form.RadGroupControl(("rRed", "rGreen", "rBlue")), + 'FormChangeCb': Form.FormChangeCb(self.OnFormChange), + 'cEChooser' : Form.EmbeddedChooserControl(self.EChooser) + }) + + + def OnButton1(self, code=0): + print("Button1 pressed") + + + def OnButton2(self, code=0): + print("Button2 pressed") + + + def OnFormChange(self, fid): + if fid == self.iButton1.id: + print("Button1 fchg;inv=%s" % self.invert) + self.SetFocusedField(self.rNormal.id) + self.EnableField(self.rError.id, self.invert) + self.invert = not self.invert + elif fid == self.iButton2.id: + g1 = self.GetControlValue(self.cGroup1) + g2 = self.GetControlValue(self.cGroup2) + d = self.GetControlValue(self.iDir) + f = self.GetControlValue(self.iFileOpen) + print("cGroup2:%x;Dir=%s;fopen=%s;cGroup1:%x" % (g1, d, f, g2)) + elif fid == self.cEChooser.id: + l = self.GetControlValue(self.cEChooser) + print("Chooser: %s" % l) + else: + print(">>fid:%d" % fid) + return 1 + +# -------------------------------------------------------------------------- +def stdalone_main(): + f = MyForm() + f, args = f.Compile() + print args[0] + print args[1:] + f.rNormal.checked = True + f.rWarnings.checked = True + print hex(f.cGroup1.value) + + f.rGreen.selected = True + print f.cGroup2.value + print "Title: '%s'" % f.title + + f.Free() + +# -------------------------------------------------------------------------- +def ida_main(): + # Create form + global f + f = MyForm() + + # Compile (in order to populate the controls) + f.Compile() + + f.iColor1.value = 0x5bffff + f.iDir.value = os.getcwd() + f.rNormal.checked = True + f.rWarnings.checked = True + f.rGreen.selected = True + f.iStr1.value = "Hello" + + # Execute the form + ok = f.Execute() + print("r=%d" % ok) + if ok == 1: + print("f.str1=%s" % f.iStr1.value) + print("f.color1=%x" % f.iColor1.value) + print("f.openfile=%s" % f.iFileOpen.value) + print("f.savefile=%s" % f.iFileSave.value) + print("f.dir=%s" % f.iDir.value) + print("f.type=%s" % f.iType.value) + print("f.seg=%s" % f.iSegment.value) + print("f.rawhex=%x" % f.iRawHex.value) + print("f.char=%x" % f.iChar.value) + print("f.addr=%x" % f.iAddr.value) + print("f.cGroup1=%x" % f.cGroup1.value) + print("f.cGroup2=%x" % f.cGroup2.value) + + sel = f.EChooser.GetEmbSelection() + if sel is None: + print("No selection") + else: + print("Selection: %s" % sel) + # Dispose the form + f.Free() + +# -------------------------------------------------------------------------- +def ida_main_legacy(): + # Here we simply show how to use the old style form format using Python + + # Sample form from kernwin.hpp + s = """Sample dialog box + + +This is sample dialog box for %A +using address %$ + +<~E~nter value:N:32:16::> +""" + + # Use either StringArgument or NumericArgument to pass values to the function + num = Form.NumericArgument('N', value=123) + ok = idaapi.AskUsingForm(s, + Form.StringArgument("PyAskUsingForm").arg, + Form.NumericArgument('$', 0x401000).arg, + num.arg) + if ok == 1: + print("You entered: %x" % num.value) + +# + + +# -------------------------------------------------------------------------- +ida_main() \ No newline at end of file diff --git a/examples/ex_choose2.py b/examples/ex_choose2.py index 69efc4f..5cc2ddc 100644 --- a/examples/ex_choose2.py +++ b/examples/ex_choose2.py @@ -1,84 +1,84 @@ -import idaapi -from idaapi import Choose2 - -class MyChoose2(Choose2): - - def __init__(self, title, nb = 5): - Choose2.__init__(self, title, [ ["Address", 10], ["Name", 30] ]) - self.n = 0 - self.items = [ self.make_item() for x in xrange(0, nb+1) ] - self.icon = 5 - self.selcount = 0 - self.popup_names = ["Inzert", "Del leet", "Ehdeet", "Ree frech"] - print "created", str(self) - - def OnClose(self): - print "closed", str(self) - - def OnEditLine(self, n): - self.items[n][1] = self.items[n][1] + "*" - print "editing", str(n) - - def OnInsertLine(self): - self.items.append(self.make_item()) - print "insert line" - - def OnSelectLine(self, n): - self.selcount += 1 - Warning("[%02d] selectline '%s'" % (self.selcount, n)) - - def OnGetLine(self, n): - print "getline", str(n) - return self.items[n] - - def OnGetSize(self): - print "getsize" - return len(self.items) - - def OnDeleteLine(self, n): - print "del ",str(n) - del self.items[n] - return n - - def OnRefresh(self, n): - print "refresh", n - return n - - def OnCommand(self, n, cmd_id): - if cmd_id == self.cmd_a: - print "command A selected @", n - elif cmd_id == self.cmd_b: - print "command B selected @", n - else: - print "Unknown command:", cmd_id, "@", n - return 1 - - def OnGetIcon(self, n): - r = self.items[n] - t = self.icon + r[1].count("*") - print "geticon", n, t - return t - - def show(self): - t = self.Show() - if t < 0: - return False - self.cmd_a = self.AddCommand("command A") - self.cmd_b = self.AddCommand("command B") - return True - - def make_item(self): - r = [str(self.n), "func_%04d" % self.n] - self.n += 1 - return r - - def OnGetLineAttr(self, n): - print "getlineattr", n - if n == 1: - return [0xFF0000, 0] - -for i in xrange(1, 5+1): - c = MyChoose2("choose2 - sample %d" % i, i*2) - r = c.show() - print r - \ No newline at end of file +import idaapi +from idaapi import Choose2 + +class MyChoose2(Choose2): + + def __init__(self, title, nb = 5): + Choose2.__init__(self, title, [ ["Address", 10], ["Name", 30] ]) + self.n = 0 + self.items = [ self.make_item() for x in xrange(0, nb+1) ] + self.icon = 5 + self.selcount = 0 + self.popup_names = ["Inzert", "Del leet", "Ehdeet", "Ree frech"] + print("created %s" % str(self)) + + def OnClose(self): + print "closed", str(self) + + def OnEditLine(self, n): + self.items[n][1] = self.items[n][1] + "*" + print("editing %d" % n) + + def OnInsertLine(self): + self.items.append(self.make_item()) + print("insert line") + + def OnSelectLine(self, n): + self.selcount += 1 + Warning("[%02d] selectline '%s'" % (self.selcount, n)) + + def OnGetLine(self, n): + print("getline %d" % n) + return self.items[n] + + def OnGetSize(self): + n = len(self.items) + print("getsize -> %d" % n) + return n + + def OnDeleteLine(self, n): + print("del %d " % n) + del self.items[n] + return n + + def OnRefresh(self, n): + print("refresh %d" % n) + return n + + def OnCommand(self, n, cmd_id): + if cmd_id == self.cmd_a: + print "command A selected @", n + elif cmd_id == self.cmd_b: + print "command B selected @", n + else: + print "Unknown command:", cmd_id, "@", n + return 1 + + def OnGetIcon(self, n): + r = self.items[n] + t = self.icon + r[1].count("*") + print "geticon", n, t + return t + + def show(self): + t = self.Show() + if t < 0: + return False + self.cmd_a = self.AddCommand("command A") + self.cmd_b = self.AddCommand("command B") + return True + + def make_item(self): + r = [str(self.n), "func_%04d" % self.n] + self.n += 1 + return r + + def OnGetLineAttr(self, n): + print("getlineattr %d" % n) + if n == 1: + return [0xFF0000, 0] + +for i in xrange(1, 5+1): + c = MyChoose2("choose2 - sample %d" % i, i*2) + r = c.show() + print r diff --git a/examples/ex_strings.py b/examples/ex_strings.py index db93b2e..9a0c4d3 100644 --- a/examples/ex_strings.py +++ b/examples/ex_strings.py @@ -1,6 +1,9 @@ -import idautils - -s = Strings(False) -s.setup(strtypes=Strings.STR_UNICODE | Strings.STR_C) -for i in s: - print "%x: len=%d type=%d -> '%s'" % (i.ea, i.length, i.type, str(i)) +import idautils + +s = idautils.Strings(False) +s.setup(strtypes=Strings.STR_UNICODE | Strings.STR_C) +for i, v in enumerate(s): + if v is None: + print("Failed to retrieve string index %d" % i) + else: + print("%x: len=%d type=%d index=%d-> '%s'" % (v.ea, v.length, v.type, i, str(v))) diff --git a/examples/ex_uihook.py b/examples/ex_uihook.py new file mode 100644 index 0000000..1334f02 --- /dev/null +++ b/examples/ex_uihook.py @@ -0,0 +1,42 @@ +#--------------------------------------------------------------------- +# UI hook example +# +# (c) Hex-Rays +# +# Maintained By: IDAPython Team +# +#--------------------------------------------------------------------- + +import idaapi + +class MyUiHook(idaapi.UI_Hooks): + def __init__(self): + idaapi.UI_Hooks.__init__(self) + self.cmdname = "" + + def preprocess(self, name): + print("IDA preprocessing command: %s" % name) + self.cmdname = name + return 0 + + def postprocess(self): + print("IDA finished processing command: %s" % self.cmdname) + return 0 + + +#--------------------------------------------------------------------- +# Remove an existing hook on second run +try: + ui_hook_stat = "un" + print("UI hook: checking for hook...") + uihook + print("UI hook: unhooking....") + uihook.unhook() + del uihook +except: + print("UI hook: not installed, installing now....") + ui_hook_stat = "" + uihook = MyUiHook() + uihook.hook() + +print("UI hook %sinstalled. Run the script again to %sinstall" % (ui_hook_stat, ui_hook_stat)) diff --git a/python.cpp b/python.cpp index f4cae77..8ef643e 100644 --- a/python.cpp +++ b/python.cpp @@ -49,10 +49,10 @@ #define IDAPYTHON_DISABLE_EXTLANG 4 #define PYTHON_DIR_NAME "python" #define S_IDAPYTHON "IDAPython" +#define S_INIT_PY "init.py" static const char S_IDC_ARGS_VARNAME[] = "ARGV"; static const char S_MAIN[] = "__main__"; static const char S_IDC_RUNPYTHON_STATEMENT[] = "RunPythonStatement"; -static const char S_HOTKEY_RUNSTATEMENT[] = "Ctrl-F3"; static const char S_IDAPYTHON_DATA_NODE[] = "IDAPython_Data"; #ifdef PLUGINFIX @@ -79,6 +79,7 @@ static bool g_menu_installed = false; static int g_run_when = -1; static char g_run_script[QMAXPATH]; static char g_idapython_dir[QMAXPATH]; +static char g_runstmt_hotkey[30] = "Ctrl-F3"; //------------------------------------------------------------------------- // Prototypes and forward declarations @@ -124,6 +125,9 @@ static bool box_displayed; // has the wait box been displayed? static time_t start_time; // the start time of the execution static int script_timeout = 2; static bool g_ui_ready = false; +static bool g_alert_auto_scripts = true; +static bool g_remove_cwd_sys_path = false; + void end_execution(); void begin_execution(); @@ -133,14 +137,15 @@ static int break_check(PyObject *obj, _frame *frame, int what, PyObject *arg) { if ( wasBreak() ) { - /* User pressed Cancel in the waitbox; send KeyboardInterrupt exception */ + // User pressed Cancel in the waitbox; send KeyboardInterrupt exception PyErr_SetInterrupt(); } else if ( !box_displayed && ++ninsns > 10 ) { - /* We check the timer once every 10 calls */ + // We check the timer once every 10 calls ninsns = 0; - if ( script_timeout != 0 && (time(NULL) - start_time > script_timeout) ) /* Timeout elapsed? */ + // Timeout disabled or elapsed? + if ( script_timeout != 0 && (time(NULL) - start_time > script_timeout) ) { box_displayed = true; show_wait_box("Running Python script"); @@ -323,13 +328,13 @@ static void handle_python_error(char *errbuf, size_t errbufsize) static PyObject *GetMainGlobals() { PyObject *module = PyImport_AddModule(S_MAIN); - if ( module == NULL ) - return NULL; - return PyModule_GetDict(module); + return module == NULL ? NULL : PyModule_GetDict(module); } //------------------------------------------------------------------------ -static void PythonEvalOrExec(const char *str, const char *filename = "") +static void PythonEvalOrExec( + const char *str, + const char *filename = "") { // Compile as an expression PyCompilerFlags cf = {0}; @@ -345,11 +350,12 @@ static void PythonEvalOrExec(const char *str, const char *filename = "") } PyObject *py_globals = GetMainGlobals(); + PYW_GIL_ENSURE; PyObject *py_result = PyEval_EvalCode( - (PyCodeObject *) py_code, + (PyCodeObject *) py_code, py_globals, py_globals); - + PYW_GIL_RELEASE; Py_DECREF(py_code); if ( py_result == NULL || PyErr_Occurred() ) @@ -379,7 +385,13 @@ static error_t idaapi idc_runpythonstatement(idc_value_t *argv, idc_value_t *res PyErr_Clear(); begin_execution(); - PyObject *result = PyRun_String(argv[0].c_str(), Py_file_input, globals, globals ); + PYW_GIL_ENSURE; + PyObject *result = PyRun_String( + argv[0].c_str(), + Py_file_input, + globals, + globals); + PYW_GIL_RELEASE; Py_XDECREF(result); end_execution(); @@ -401,6 +413,45 @@ static error_t idaapi idc_runpythonstatement(idc_value_t *argv, idc_value_t *res return eOk; } +//-------------------------------------------------------------------------- +const char *idaapi set_python_options( + const char *keyword, + int value_type, + const void *value) +{ + do + { + if ( value_type == IDPOPT_STR ) + { + if ( qstrcmp(keyword, "EXEC_STATEMENT_HOTKEY" ) == 0 ) + { + qstrncpy(g_runstmt_hotkey, (const char *)value, sizeof(g_runstmt_hotkey)); + break; + } + } + else if ( value_type == IDPOPT_NUM ) + { + if ( qstrcmp(keyword, "SCRIPT_TIMEOUT") == 0 ) + { + script_timeout = int(*(uval_t *)value); + break; + } + else if ( qstrcmp(keyword, "ALERT_AUTO_SCRIPTS") == 0 ) + { + g_alert_auto_scripts = *(uval_t *)value != 0; + break; + } + else if ( qstrcmp(keyword, "REMOVE_CWD_SYS_PATH") == 0 ) + { + g_remove_cwd_sys_path = *(uval_t *)value != 0; + break; + } + } + return IDPOPT_BADKEY; + } while (false); + return IDPOPT_OK; +} + //------------------------------------------------------------------------- // Check for the presence of a file in IDADIR/python and complain on error bool CheckScriptFiles() @@ -408,7 +459,7 @@ bool CheckScriptFiles() static const char *const script_files[] = { S_IDC_MODNAME ".py", - "init.py", + S_INIT_PY, "idaapi.py", "idautils.py" }; @@ -439,9 +490,19 @@ static int PyRunFile(const char *FileName) return 0; } PyErr_Clear(); - PyObject *result = PyRun_File(PyFile_AsFile(PyFileObject), FileName, Py_file_input, globals, globals); + + PYW_GIL_ENSURE; + PyObject *result = PyRun_File( + PyFile_AsFile(PyFileObject), + FileName, + Py_file_input, + globals, + globals); + PYW_GIL_RELEASE; + Py_XDECREF(PyFileObject); Py_XDECREF(result); + return result != NULL && !PyErr_Occurred(); } @@ -461,7 +522,7 @@ void IDAPython_RunStatement(void) if ( history.getblob(statement, &statement_size, 0, 'A') == NULL ) statement[0] = '\0'; - if ( asktext(sizeof(statement), statement, statement, "Enter Python expressions") != NULL ) + if ( asktext(sizeof(statement), statement, statement, "ACCEPT TABS\nEnter Python expressions") != NULL ) { begin_execution(); PyRun_SimpleString(statement); @@ -489,7 +550,13 @@ static bool IDAPython_ExecFile(const char *FileName, char *errbuf, size_t errbuf strrpl(script, '\\', '//'); PyObject *py_script = PyString_FromString(script); - PyObject *py_ret = PyObject_CallFunctionObjArgs(py_execscript, py_script, GetMainGlobals(), NULL); + PYW_GIL_ENSURE; + PyObject *py_ret = PyObject_CallFunctionObjArgs( + py_execscript, + py_script, + GetMainGlobals(), + NULL); + PYW_GIL_RELEASE; Py_DECREF(py_script); Py_DECREF(py_execscript); @@ -516,7 +583,7 @@ static bool IDAPython_ExecFile(const char *FileName, char *errbuf, size_t errbuf } // Cannot be otherwise! else - INTERR(); + INTERR(30154); Py_XDECREF(py_ret); return ok; @@ -527,10 +594,12 @@ static bool IDAPython_ExecFile(const char *FileName, char *errbuf, size_t errbuf static bool RunScript(const char *script) { begin_execution(); + char errbuf[MAXSTR]; bool ok = IDAPython_ExecFile(script, errbuf, sizeof(errbuf)); if ( !ok ) warning("IDAPython: error executing '%s':\n%s", script, errbuf); + end_execution(); return ok; } @@ -612,7 +681,6 @@ bool idaapi IDAPython_extlang_compile( size_t errbufsize) { PyObject *globals = GetMainGlobals(); - QASSERT(globals != NULL); PyCodeObject *code = (PyCodeObject *)Py_CompileString(expr, "", Py_eval_input); if ( code == NULL ) @@ -621,11 +689,11 @@ bool idaapi IDAPython_extlang_compile( return false; } - // set the desired function name + // Set the desired function name Py_XDECREF(code->co_name); code->co_name = PyString_FromString(name); - // create a function out of code + // Create a function out of code PyObject *func = PyFunction_New((PyObject *)code, globals); if ( func == NULL ) @@ -672,16 +740,18 @@ bool idaapi IDAPython_extlang_run( if ( imported_module ) { + PYW_GIL_ENSURE; module = PyImport_ImportModule(modname); + PYW_GIL_RELEASE; } else { module = PyImport_AddModule(S_MAIN); - QASSERT(module != NULL); + QASSERT(30156, module != NULL); } PyObject *globals = PyModule_GetDict(module); - QASSERT(globals != NULL); + QASSERT(30157, globals != NULL); PyObject *func = PyDict_GetItemString(globals, funcname); if ( func == NULL ) @@ -692,12 +762,13 @@ bool idaapi IDAPython_extlang_run( } PyCodeObject *code = (PyCodeObject *) PyFunction_GetCode(func); + PYW_GIL_ENSURE; PyObject *pres = PyEval_EvalCodeEx( code, - globals, NULL, + globals, NULL, &pargs[0], nargs, NULL, 0, NULL, 0, NULL); - + PYW_GIL_RELEASE; ok = return_python_result(result, pres, errbuf, errbufsize); } while ( false ); @@ -705,7 +776,7 @@ bool idaapi IDAPython_extlang_run( if ( imported_module ) Py_XDECREF(module); - + return ok; } @@ -766,7 +837,9 @@ bool idaapi IDAPython_extlang_create_object( ok = false; // Call the constructor + PYW_GIL_ENSURE; PyObject *py_res = PyObject_CallObject(py_cls, pargs.empty() ? NULL : pargs[0]); + PYW_GIL_RELEASE; ok = return_python_result(result, py_res, errbuf, errbufsize); } while ( false ); @@ -944,7 +1017,9 @@ bool idaapi IDAPython_extlang_calcexpr( return false; begin_execution(); + PYW_GIL_ENSURE; PyObject *result = PyRun_String(expr, Py_eval_input, globals, globals); + PYW_GIL_RELEASE; end_execution(); return return_python_result(rv, result, errbuf, errbufsize); @@ -998,7 +1073,9 @@ bool idaapi IDAPython_extlang_call_method( if ( !ok ) break; + PYW_GIL_ENSURE; PyObject *py_res = PyObject_CallObject(py_method, pargs.empty() ? NULL : pargs[0]); + PYW_GIL_RELEASE; ok = return_python_result(result, py_res, errbuf, errbufsize); } while ( false ); @@ -1030,6 +1107,7 @@ extlang_t extlang_python = IDAPython_extlang_call_method }; +//------------------------------------------------------------------------- void enable_extlang_python(bool enable) { if ( enable ) @@ -1042,7 +1120,7 @@ void enable_extlang_python(bool enable) // Execute a line in the Python CLI bool idaapi IDAPython_cli_execute_line(const char *line) { - // do not process empty lines + // Do not process empty lines if ( line[0] == '\0' ) return true; @@ -1052,11 +1130,11 @@ bool idaapi IDAPython_cli_execute_line(const char *line) else last_line += 1; - // skip empty lines + // Skip empty lines if ( last_line[0] != '\0' ) { - // line ends with ":" or begins with a space character? - bool more = last_line[qstrlen(last_line)-1] == ':' || isspace(last_line[0]); + // Line ends with ":" or begins with a space character? + bool more = last_line[qstrlen(last_line)-1] == ':' || qisspace(last_line[0]); if ( more ) return false; } @@ -1080,7 +1158,9 @@ bool idaapi IDAPYthon_cli_complete_line( if ( py_complete == NULL ) return false; + PYW_GIL_ENSURE; PyObject *py_ret = PyObject_CallFunction(py_complete, "sisi", prefix, n, line, x); + PYW_GIL_RELEASE; Py_DECREF(py_complete); @@ -1124,7 +1204,9 @@ void enable_python_cli(bool enable) // Prints the IDAPython copyright banner void py_print_banner() { + PYW_GIL_ENSURE; PyRun_SimpleString("print_banner()"); + PYW_GIL_RELEASE; } //------------------------------------------------------------------------- @@ -1136,8 +1218,11 @@ static void install_python_menus() // Add menu items for all the functions // Note: Different paths are used for the GUI version - add_menu_item("File/IDC command...", "P~y~thon command...", - S_HOTKEY_RUNSTATEMENT, SETMENU_APP, + add_menu_item( + "File/IDC command...", + "P~y~thon command...", + g_runstmt_hotkey, + SETMENU_APP, IDAPython_Menu_Callback, (void *)IDAPYTHON_RUNSTATEMENT); @@ -1277,6 +1362,19 @@ bool IDAPython_Init(void) } #endif + // Read configuration value + read_user_config_file("python.cfg", set_python_options, NULL); + if ( g_alert_auto_scripts ) + { + const char *autofn = pywraps_check_autoscripts(); + if ( autofn != NULL + && askyn_c(0, "HIDECANCEL\nTITLE IDAPython\nThe script '%s' was found in the current directory and will be automatically executed by Python.\n\n" + "Do you want to continue loading IDAPython?", autofn) == 0 ) + { + return false; + } + } + // Start the interpreter Py_Initialize(); if ( !Py_IsInitialized() ) @@ -1285,16 +1383,22 @@ bool IDAPython_Init(void) return false; } + // Enable multi-threading support + if ( !PyEval_ThreadsInitialized() ) + PyEval_InitThreads(); + // Init the SWIG wrapper init_idaapi(); // Set IDAPYTHON_VERSION in Python - qsnprintf(tmp, sizeof(tmp), "IDAPYTHON_VERSION=(%d, %d, %d, '%s', %d)", \ + qsnprintf(tmp, sizeof(tmp), "IDAPYTHON_VERSION=(%d, %d, %d, '%s', %d)\n" + "IDAPYTHON_REMOVE_CWD_SYS_PATH = %s\n", VER_MAJOR, VER_MINOR, VER_PATCH, VER_STATUS, - VER_SERIAL); + VER_SERIAL, + g_remove_cwd_sys_path ? "True" : "False"); PyRun_SimpleString(tmp); // Install extlang. Needs to be done before running init.py @@ -1302,11 +1406,11 @@ bool IDAPython_Init(void) install_extlang(&extlang_python); // Execute init.py (for Python side initialization) - qmakepath(tmp, MAXSTR, g_idapython_dir, "init.py", NULL); + qmakepath(tmp, MAXSTR, g_idapython_dir, S_INIT_PY, NULL); if ( !PyRunFile(tmp) ) { handle_python_error(tmp, sizeof(tmp)); - warning("IDAPython: error executing init.py:\n%s", tmp); + warning("IDAPython: error executing " S_INIT_PY ":\n%s", tmp); return false; } @@ -1357,18 +1461,18 @@ void IDAPython_Term(void) del_menu_item("File/Python command..."); g_menu_installed = false; - // Remove the CLI - enable_python_cli(false); - - // Remove the extlang - remove_extlang(&extlang_python); - // Notify about IDA closing pywraps_nw_notify(NW_TERMIDA_SLOT); // De-init notify_when pywraps_nw_term(); + // Remove the CLI + enable_python_cli(false); + + // Remove the extlang + remove_extlang(&extlang_python); + // De-init pywraps deinit_pywraps(); @@ -1446,5 +1550,5 @@ plugin_t PLUGIN = // the preferred short name of the plugin S_IDAPYTHON, // the preferred hotkey to run the plugin - S_HOTKEY_RUNSTATEMENT + NULL }; diff --git a/python/idautils.py b/python/idautils.py index 44abcb7..6c3d768 100644 --- a/python/idautils.py +++ b/python/idautils.py @@ -1,662 +1,711 @@ -#--------------------------------------------------------------------- -# IDAPython - Python plugin for Interactive Disassembler Pro -# -# Copyright (c) 2004-2010 Gergely Erdelyi -# -# All rights reserved. -# -# For detailed copyright information see the file COPYING in -# the root of the distribution archive. -#--------------------------------------------------------------------- -""" -idautils.py - High level utility functions for IDA -""" -import idaapi -import idc -import types -import os - - -def refs(ea, funcfirst, funcnext): - """ - Generic reference collector - INTERNAL USE ONLY. - """ - ref = funcfirst(ea) - while ref != idaapi.BADADDR: - yield ref - ref = funcnext(ea, ref) - - -def CodeRefsTo(ea, flow): - """ - Get a list of code references to 'ea' - - @param ea: Target address - @param flow: Follow normal code flow or not - @type flow: Boolean (0/1, False/True) - - @return: list of references (may be empty list) - - Example:: - - for ref in CodeRefsTo(ScreenEA(), 1): - print ref - """ - if flow == 1: - return refs(ea, idaapi.get_first_cref_to, idaapi.get_next_cref_to) - else: - return refs(ea, idaapi.get_first_fcref_to, idaapi.get_next_fcref_to) - - -def CodeRefsFrom(ea, flow): - """ - Get a list of code references from 'ea' - - @param ea: Target address - @param flow: Follow normal code flow or not - @type flow: Boolean (0/1, False/True) - - @return: list of references (may be empty list) - - Example:: - - for ref in CodeRefsFrom(ScreenEA(), 1): - print ref - """ - if flow == 1: - return refs(ea, idaapi.get_first_cref_from, idaapi.get_next_cref_from) - else: - return refs(ea, idaapi.get_first_fcref_from, idaapi.get_next_fcref_from) - - -def DataRefsTo(ea): - """ - Get a list of data references to 'ea' - - @param ea: Target address - - @return: list of references (may be empty list) - - Example:: - - for ref in DataRefsTo(ScreenEA()): - print ref - """ - return refs(ea, idaapi.get_first_dref_to, idaapi.get_next_dref_to) - - -def DataRefsFrom(ea): - """ - Get a list of data references from 'ea' - - @param ea: Target address - - @return: list of references (may be empty list) - - Example:: - - for ref in DataRefsFrom(ScreenEA()): - print ref - """ - return refs(ea, idaapi.get_first_dref_from, idaapi.get_next_dref_from) - - -def XrefTypeName(typecode): - """ - Convert cross-reference type codes to readable names - - @param typecode: cross-reference type code - """ - ref_types = { - 0 : 'Data_Unknown', - 1 : 'Data_Offset', - 2 : 'Data_Write', - 3 : 'Data_Read', - 4 : 'Data_Text', - 5 : 'Data_Informational', - 16 : 'Code_Far_Call', - 17 : 'Code_Near_Call', - 18 : 'Code_Far_Jump', - 19 : 'Code_Near_Jump', - 20 : 'Code_User', - 21 : 'Ordinary_Flow' - } - assert typecode in ref_types, "unknown reference type %d" % typecode - return ref_types[typecode] - - -def _copy_xref(xref): - """ Make a private copy of the xref class to preserve its contents """ - class _xref(object): - pass - - xr = _xref() - for attr in [ 'frm', 'to', 'iscode', 'type', 'user' ]: - setattr(xr, attr, getattr(xref, attr)) - return xr - - -def XrefsFrom(ea, flags=0): - """ - Return all references from address 'ea' - - @param ea: Reference address - @param flags: any of idaapi.XREF_* flags - - Example:: - for xref in XrefsFrom(here(), 0): - print xref.type, XrefTypeName(xref.type), \ - 'from', hex(xref.frm), 'to', hex(xref.to) - """ - xref = idaapi.xrefblk_t() - if xref.first_from(ea, flags): - yield _copy_xref(xref) - while xref.next_from(): - yield _copy_xref(xref) - - -def XrefsTo(ea, flags=0): - """ - Return all references to address 'ea' - - @param ea: Reference address - @param flags: any of idaapi.XREF_* flags - - Example:: - for xref in XrefsTo(here(), 0): - print xref.type, XrefTypeName(xref.type), \ - 'from', hex(xref.frm), 'to', hex(xref.to) - """ - xref = idaapi.xrefblk_t() - if xref.first_to(ea, flags): - yield _copy_xref(xref) - while xref.next_to(): - yield _copy_xref(xref) - - -def Threads(): - """Returns all thread IDs""" - for i in xrange(0, idc.GetThreadQty()): - yield idc.GetThreadId(i) - - -def Heads(start=None, end=None): - """ - Get a list of heads (instructions or data) - - @param start: start address (default: inf.minEA) - @param end: end address (default: inf.maxEA) - - @return: list of heads between start and end - """ - if not start: start = idaapi.cvar.inf.minEA - if not end: end = idaapi.cvar.inf.maxEA - - ea = start - if not idc.isHead(idc.GetFlags(ea)): - ea = idaapi.next_head(ea, end) - while ea != idaapi.BADADDR: - yield ea - ea = idaapi.next_head(ea, end) - - -def Functions(start=None, end=None): - """ - Get a list of functions - - @param start: start address (default: inf.minEA) - @param end: end address (default: inf.maxEA) - - @return: list of heads between start and end - - @note: The last function that starts before 'end' is included even - if it extends beyond 'end'. Any function that has its chunks scattered - in multiple segments will be reported multiple times, once in each segment - as they are listed. - """ - if not start: start = idaapi.cvar.inf.minEA - if not end: end = idaapi.cvar.inf.maxEA - - func = idaapi.get_func(start) - if not func: - func = idaapi.get_next_func(start) - while func and func.startEA < end: - startea = func.startEA - yield startea - func = idaapi.get_next_func(startea) - - -def Chunks(start): - """ - Get a list of function chunks - - @param start: address of the function - - @return: list of funcion chunks (tuples of the form (start_ea, end_ea)) - belonging to the function - """ - func_iter = idaapi.func_tail_iterator_t( idaapi.get_func( start ) ) - status = func_iter.main() - while status: - chunk = func_iter.chunk() - yield (chunk.startEA, chunk.endEA) - status = func_iter.next() - - -def Modules(): - """ - Returns a list of module objects with name,size,base and the rebase_to attributes - """ - mod = idaapi.module_info_t() - result = idaapi.get_first_module(mod) - while result: - yield idaapi.object_t(name=mod.name, size=mod.size, base=mod.base, rebase_to=mod.rebase_to) - result = idaapi.get_next_module(mod) - - -def Names(): - """ - Returns a list of names - - @return: List of tuples (ea, name) - """ - for i in xrange(idaapi.get_nlist_size()): - ea = idaapi.get_nlist_ea(i) - name = idaapi.get_nlist_name(i) - yield (ea, name) - - -def Segments(): - """ - Get list of segments (sections) in the binary image - - @return: List of segment start addresses. - """ - for n in xrange(idaapi.get_segm_qty()): - seg = idaapi.getnseg(n) - if seg: - yield seg.startEA - - -def Entries(): - """ - Returns a list of entry points - - @return: List of tuples (index, ordinal, ea, name) - """ - n = idaapi.get_entry_qty() - for i in xrange(0, n): - ordinal = idaapi.get_entry_ordinal(i) - ea = idaapi.get_entry(ordinal) - name = idaapi.get_entry_name(ordinal) - yield (i, ordinal, ea, name) - - -def FuncItems(start): - """ - Get a list of function items - - @param start: address of the function - - @return: ea of each item in the function - """ - func = idaapi.get_func(start) - if not func: - return - fii = idaapi.func_item_iterator_t() - ok = fii.set(func) - while ok: - yield fii.current() - ok = fii.next_code() - - -def DecodeInstruction(ea): - """ - Decodes an instruction and returns an insn_t like class - - @param ea: address to decode - @return: None or a new insn_t instance - """ - inslen = idaapi.decode_insn(ea) - if inslen == 0: - return None - - return idaapi.cmd.copy() - - -def GetDataList(ea, count, itemsize=1): - """ - Get data list - INTERNAL USE ONLY - """ - if itemsize == 1: - getdata = idaapi.get_byte - elif itemsize == 2: - getdata = idaapi.get_word - elif itemsize == 4: - getdata = idaapi.get_long - elif itemsize == 8: - getdata = idaapi.get_qword - else: - raise ValueError, "Invalid data size! Must be 1, 2, 4 or 8" - - endea = ea + itemsize * count - curea = ea - while curea < endea: - yield getdata(curea) - curea += itemsize - - -def PutDataList(ea, datalist, itemsize=1): - """ - Put data list - INTERNAL USE ONLY - """ - putdata = None - - if itemsize == 1: - putdata = idaapi.patch_byte - if itemsize == 2: - putdata = idaapi.patch_word - if itemsize == 4: - putdata = idaapi.patch_long - - assert putdata, "Invalid data size! Must be 1, 2 or 4" - - for val in datalist: - putdata(ea, val) - ea = ea + itemsize - - -def MapDataList(ea, length, func, wordsize=1): - """ - Map through a list of data words in the database - - @param ea: start address - @param length: number of words to map - @param func: mapping function - @param wordsize: size of words to map [default: 1 byte] - - @return: None - """ - PutDataList(ea, map(func, GetDataList(ea, length, wordsize)), wordsize) - - -def GetInputFileMD5(): - """ - Return the MD5 hash of the input binary file - - @return: MD5 string or None on error - """ - return idc.GetInputMD5() - - -class Strings(object): - """ - Returns the string list. - - Example: - s = Strings() - - for i in s: - print "%x: len=%d type=%d -> '%s'" % (i.ea, i.length, i.type, str(i)) - - """ - class StringItem(object): - """ - Class representing each string item. - """ - def __init__(self, si): - self.ea = si.ea - """String ea""" - self.type = si.type - """string type (ASCSTR_xxxxx)""" - self.length = si.length - """string length""" - - def __str__(self): - return idc.GetString(self.ea, self.length, self.type) - - STR_C = 0x0001 - """C-style ASCII string""" - STR_PASCAL = 0x0002 - """Pascal-style ASCII string (length byte)""" - STR_LEN2 = 0x0004 - """Pascal-style, length is 2 bytes""" - STR_UNICODE = 0x0008 - """Unicode string""" - STR_LEN4 = 0x0010 - """Pascal-style, length is 4 bytes""" - STR_ULEN2 = 0x0020 - """Pascal-style Unicode, length is 2 bytes""" - STR_ULEN4 = 0x0040 - """Pascal-style Unicode, length is 4 bytes""" - - def clear_cache(self): - """Clears the strings list cache""" - self.refresh(0, 0) # when ea1=ea2 the kernel will clear the cache - - def __init__(self, default_setup = True): - """ - Initializes the Strings enumeration helper class - - @param default_setup: Set to True to use default setup (C strings, min len 5, ...) - """ - self.size = 0 - if default_setup: - self.setup() - - self._si = idaapi.string_info_t() - - def refresh(self, ea1=None, ea2=None): - """Refreshes the strings list""" - if ea1 is None: - ea1 = idaapi.cvar.inf.minEA - if ea2 is None: - ea2 = idaapi.cvar.inf.maxEA - - idaapi.refresh_strlist(ea1, ea2) - self.size = idaapi.get_strlist_qty() - - def setup(self, - strtypes = STR_C, - minlen = 5, - only_7bit = True, - ignore_instructions = False, - ea1 = None, - ea2 = None, - display_only_existing_strings = False): - - if ea1 is None: - ea1 = idaapi.cvar.inf.minEA - - if ea2 is None: - ea2 = idaapi.cvar.inf.maxEA - - t = idaapi.strwinsetup_t() - t.strtypes = strtypes - t.minlen = minlen - t.only_7bit = only_7bit - t.ea1 = ea1 - t.ea2 = ea2 - t.display_only_existing_strings = display_only_existing_strings - idaapi.set_strlist_options(t) - - # Automatically refreshes - self.refresh() - - def __getitem__(self, index): - """Returns a string item or None""" - if index >= self.size: - raise StopIteration - - if idaapi.get_strlist_item(index, self._si): - return Strings.StringItem(self._si) - - return None - -# ----------------------------------------------------------------------- -def GetIdbDir(): - """ - Get IDB directory - - This function returns directory path of the current IDB database - """ - return os.path.dirname(idaapi.cvar.database_idb) + os.sep - -# ----------------------------------------------------------------------- -def GetRegisterList(): - """Returns the register list""" - return idaapi.ph_get_regnames() - -# ----------------------------------------------------------------------- -def GetInstructionList(): - """Returns the instruction list of the current processor module""" - return [i[0] for i in idaapi.ph_get_instruc() if i[0]] - -# ----------------------------------------------------------------------- -def _Assemble(ea, line): - """ - Please refer to Assemble() - INTERNAL USE ONLY - """ - if type(line) == types.StringType: - lines = [line] - else: - lines = line - ret = [] - for line in lines: - seg = idaapi.getseg(ea) - if not seg: - return (False, "No segment at ea") - ip = ea - (idaapi.ask_selector(seg.sel) << 4) - buf = idaapi.AssembleLine(ea, seg.sel, ip, seg.bitness, line) - if not buf: - return (False, "Assembler failed: " + line) - ea += len(buf) - ret.append(buf) - - if len(ret) == 1: - ret = ret[0] - return (True, ret) - - -def Assemble(ea, line): - """ - Assembles one or more lines (does not display an message dialogs) - If line is a list then this function will attempt to assemble all the lines - This function will turn on batch mode temporarily so that no messages are displayed on the screen - - @param ea: start address - @return: (False, "Error message") or (True, asm_buf) or (True, [asm_buf1, asm_buf2, asm_buf3]) - """ - old_batch = idc.Batch(1) - ret = _Assemble(ea, line) - idc.Batch(old_batch) - return ret - -def _copy_obj(src, dest, skip_list = None): - """ - Copy non private/non callable attributes from a class instance to another - @param src: Source class to copy from - @param dest: If it is a string then it designates the new class type that will be created and copied to. - Otherwise dest should be an instance of another class - @return: A new instance or "dest" - """ - if type(dest) == types.StringType: - # instantiate a new destination class of the specified type name? - dest = new.classobj(dest, (), {}) - for x in dir(src): - # skip special and private fields - if x.startswith("__") and x.endswith("__"): - continue - # skip items in the skip list - if skip_list and x in skip_list: - continue - t = getattr(src, x) - # skip callable - if callable(t): - continue - setattr(dest, x, t) - return dest - -# ----------------------------------------------------------------------- -class _reg_dtyp_t(object): - """ - INTERNAL - This class describes a register's number and dtyp. - The equal operator is overloaded so that two instances can be tested for equality - """ - def __init__(self, reg, dtyp): - self.reg = reg - self.dtyp = dtyp - - def __eq__(self, other): - return (self.reg == other.reg) and (self.dtyp == other.dtyp) - -# ----------------------------------------------------------------------- -class _procregs(object): - """Utility class allowing the users to identify registers in a decoded instruction""" - def __getattr__(self, attr): - ri = idaapi.reg_info_t() - if not idaapi.parse_reg_name(attr, ri): - raise AttributeError() - r = _reg_dtyp_t(ri.reg, ord(idaapi.get_dtyp_by_size(ri.size))) - self.__dict__[attr] = r - return r - - def __setattr__(self, attr, value): - raise AttributeError(attr) - -# ----------------------------------------------------------------------- -class _cpu(object): - "Simple wrapper around GetRegValue/SetRegValue" - def __getattr__(self, name): - #print "cpu.get(%s)"%name - return idc.GetRegValue(name) - - def __setattr__(self, name, value): - #print "cpu.set(%s)"%name - return idc.SetRegValue(value, name) - -# ----------------------------------------------------------------------- -class peutils_t(object): - """ - PE utility class. Retrieves PE information from the database. - - Constants from pe.h - """ - PE_NODE = "$ PE header" # netnode name for PE header - PE_ALT_DBG_FPOS = idaapi.BADADDR & -1 # altval() -> translated fpos of debuginfo - PE_ALT_IMAGEBASE = idaapi.BADADDR & -2 # altval() -> loading address (usually pe.imagebase) - PE_ALT_PEHDR_OFF = idaapi.BADADDR & -3 # altval() -> offset of PE header - PE_ALT_NEFLAGS = idaapi.BADADDR & -4 # altval() -> neflags - PE_ALT_TDS_LOADED = idaapi.BADADDR & -5 # altval() -> tds already loaded(1) or invalid(-1) - PE_ALT_PSXDLL = idaapi.BADADDR & -6 # altval() -> if POSIX(x86) imports from PSXDLL netnode - - def __init__(self): - self.__penode = idaapi.netnode() - self.__penode.create(peutils_t.PE_NODE) - - imagebase = property( - lambda self: self.__penode.altval(peutils_t.PE_ALT_IMAGEBASE) - ) - - header = property( - lambda self: self.__penode.altval(peutils_t.PE_ALT_PEHDR_OFF) - ) - - def __str__(self): - return "peutils_t(imagebase=%s, header=%s)" % (hex(self.imagebase), hex(self.header)) - -# ----------------------------------------------------------------------- -cpu = _cpu() -"""This is a special class instance used to access the registers as if they were attributes of this object. -For example to access the EAX register: - print "%x" % cpu.Eax -""" - -procregs = _procregs() -"""This object is used to access the processor registers. It is useful when decoding instructions and you want to see which instruction is which. -For example: - x = idautils.DecodeInstruction(here()) - if x[0] == procregs.Esp: - print "This operand is the register ESP +#--------------------------------------------------------------------- +# IDAPython - Python plugin for Interactive Disassembler Pro +# +# Copyright (c) 2004-2010 Gergely Erdelyi +# +# All rights reserved. +# +# For detailed copyright information see the file COPYING in +# the root of the distribution archive. +#--------------------------------------------------------------------- +""" +idautils.py - High level utility functions for IDA +""" +import idaapi +import idc +import types +import os + + +def refs(ea, funcfirst, funcnext): + """ + Generic reference collector - INTERNAL USE ONLY. + """ + ref = funcfirst(ea) + while ref != idaapi.BADADDR: + yield ref + ref = funcnext(ea, ref) + + +def CodeRefsTo(ea, flow): + """ + Get a list of code references to 'ea' + + @param ea: Target address + @param flow: Follow normal code flow or not + @type flow: Boolean (0/1, False/True) + + @return: list of references (may be empty list) + + Example:: + + for ref in CodeRefsTo(ScreenEA(), 1): + print ref + """ + if flow == 1: + return refs(ea, idaapi.get_first_cref_to, idaapi.get_next_cref_to) + else: + return refs(ea, idaapi.get_first_fcref_to, idaapi.get_next_fcref_to) + + +def CodeRefsFrom(ea, flow): + """ + Get a list of code references from 'ea' + + @param ea: Target address + @param flow: Follow normal code flow or not + @type flow: Boolean (0/1, False/True) + + @return: list of references (may be empty list) + + Example:: + + for ref in CodeRefsFrom(ScreenEA(), 1): + print ref + """ + if flow == 1: + return refs(ea, idaapi.get_first_cref_from, idaapi.get_next_cref_from) + else: + return refs(ea, idaapi.get_first_fcref_from, idaapi.get_next_fcref_from) + + +def DataRefsTo(ea): + """ + Get a list of data references to 'ea' + + @param ea: Target address + + @return: list of references (may be empty list) + + Example:: + + for ref in DataRefsTo(ScreenEA()): + print ref + """ + return refs(ea, idaapi.get_first_dref_to, idaapi.get_next_dref_to) + + +def DataRefsFrom(ea): + """ + Get a list of data references from 'ea' + + @param ea: Target address + + @return: list of references (may be empty list) + + Example:: + + for ref in DataRefsFrom(ScreenEA()): + print ref + """ + return refs(ea, idaapi.get_first_dref_from, idaapi.get_next_dref_from) + + +def XrefTypeName(typecode): + """ + Convert cross-reference type codes to readable names + + @param typecode: cross-reference type code + """ + ref_types = { + 0 : 'Data_Unknown', + 1 : 'Data_Offset', + 2 : 'Data_Write', + 3 : 'Data_Read', + 4 : 'Data_Text', + 5 : 'Data_Informational', + 16 : 'Code_Far_Call', + 17 : 'Code_Near_Call', + 18 : 'Code_Far_Jump', + 19 : 'Code_Near_Jump', + 20 : 'Code_User', + 21 : 'Ordinary_Flow' + } + assert typecode in ref_types, "unknown reference type %d" % typecode + return ref_types[typecode] + + +def _copy_xref(xref): + """ Make a private copy of the xref class to preserve its contents """ + class _xref(object): + pass + + xr = _xref() + for attr in [ 'frm', 'to', 'iscode', 'type', 'user' ]: + setattr(xr, attr, getattr(xref, attr)) + return xr + + +def XrefsFrom(ea, flags=0): + """ + Return all references from address 'ea' + + @param ea: Reference address + @param flags: any of idaapi.XREF_* flags + + Example:: + for xref in XrefsFrom(here(), 0): + print xref.type, XrefTypeName(xref.type), \ + 'from', hex(xref.frm), 'to', hex(xref.to) + """ + xref = idaapi.xrefblk_t() + if xref.first_from(ea, flags): + yield _copy_xref(xref) + while xref.next_from(): + yield _copy_xref(xref) + + +def XrefsTo(ea, flags=0): + """ + Return all references to address 'ea' + + @param ea: Reference address + @param flags: any of idaapi.XREF_* flags + + Example:: + for xref in XrefsTo(here(), 0): + print xref.type, XrefTypeName(xref.type), \ + 'from', hex(xref.frm), 'to', hex(xref.to) + """ + xref = idaapi.xrefblk_t() + if xref.first_to(ea, flags): + yield _copy_xref(xref) + while xref.next_to(): + yield _copy_xref(xref) + + +def Threads(): + """Returns all thread IDs""" + for i in xrange(0, idc.GetThreadQty()): + yield idc.GetThreadId(i) + + +def Heads(start=None, end=None): + """ + Get a list of heads (instructions or data) + + @param start: start address (default: inf.minEA) + @param end: end address (default: inf.maxEA) + + @return: list of heads between start and end + """ + if not start: start = idaapi.cvar.inf.minEA + if not end: end = idaapi.cvar.inf.maxEA + + ea = start + if not idc.isHead(idc.GetFlags(ea)): + ea = idaapi.next_head(ea, end) + while ea != idaapi.BADADDR: + yield ea + ea = idaapi.next_head(ea, end) + + +def Functions(start=None, end=None): + """ + Get a list of functions + + @param start: start address (default: inf.minEA) + @param end: end address (default: inf.maxEA) + + @return: list of heads between start and end + + @note: The last function that starts before 'end' is included even + if it extends beyond 'end'. Any function that has its chunks scattered + in multiple segments will be reported multiple times, once in each segment + as they are listed. + """ + if not start: start = idaapi.cvar.inf.minEA + if not end: end = idaapi.cvar.inf.maxEA + + func = idaapi.get_func(start) + if not func: + func = idaapi.get_next_func(start) + while func and func.startEA < end: + startea = func.startEA + yield startea + func = idaapi.get_next_func(startea) + + +def Chunks(start): + """ + Get a list of function chunks + + @param start: address of the function + + @return: list of funcion chunks (tuples of the form (start_ea, end_ea)) + belonging to the function + """ + func_iter = idaapi.func_tail_iterator_t( idaapi.get_func( start ) ) + status = func_iter.main() + while status: + chunk = func_iter.chunk() + yield (chunk.startEA, chunk.endEA) + status = func_iter.next() + + +def Modules(): + """ + Returns a list of module objects with name,size,base and the rebase_to attributes + """ + mod = idaapi.module_info_t() + result = idaapi.get_first_module(mod) + while result: + yield idaapi.object_t(name=mod.name, size=mod.size, base=mod.base, rebase_to=mod.rebase_to) + result = idaapi.get_next_module(mod) + + +def Names(): + """ + Returns a list of names + + @return: List of tuples (ea, name) + """ + for i in xrange(idaapi.get_nlist_size()): + ea = idaapi.get_nlist_ea(i) + name = idaapi.get_nlist_name(i) + yield (ea, name) + + +def Segments(): + """ + Get list of segments (sections) in the binary image + + @return: List of segment start addresses. + """ + for n in xrange(idaapi.get_segm_qty()): + seg = idaapi.getnseg(n) + if seg: + yield seg.startEA + + +def Entries(): + """ + Returns a list of entry points + + @return: List of tuples (index, ordinal, ea, name) + """ + n = idaapi.get_entry_qty() + for i in xrange(0, n): + ordinal = idaapi.get_entry_ordinal(i) + ea = idaapi.get_entry(ordinal) + name = idaapi.get_entry_name(ordinal) + yield (i, ordinal, ea, name) + + +def FuncItems(start): + """ + Get a list of function items + + @param start: address of the function + + @return: ea of each item in the function + """ + func = idaapi.get_func(start) + if not func: + return + fii = idaapi.func_item_iterator_t() + ok = fii.set(func) + while ok: + yield fii.current() + ok = fii.next_code() + + + +def DecodePrecedingInstruction(ea): + """ + Decode preceding instruction in the execution flow. + + @param ea: address to decode + @return: (None or the decode instruction, farref) + farref will contain 'true' if followed an xref, false otherwise + """ + prev_addr, farref = idaapi.decode_preceding_insn(ea) + if prev_addr == idaapi.BADADDR: + return (None, False) + else: + return (idaapi.cmd.copy(), farref) + + + +def DecodePreviousInstruction(ea): + """ + Decodes the previous instruction and returns an insn_t like class + + @param ea: address to decode + @return: None or a new insn_t instance + """ + inslen = idaapi.decode_prev_insn(ea) + if inslen == 0: + return None + + return idaapi.cmd.copy() + + +def DecodeInstruction(ea): + """ + Decodes an instruction and returns an insn_t like class + + @param ea: address to decode + @return: None or a new insn_t instance + """ + inslen = idaapi.decode_insn(ea) + if inslen == 0: + return None + + return idaapi.cmd.copy() + + +def GetDataList(ea, count, itemsize=1): + """ + Get data list - INTERNAL USE ONLY + """ + if itemsize == 1: + getdata = idaapi.get_byte + elif itemsize == 2: + getdata = idaapi.get_word + elif itemsize == 4: + getdata = idaapi.get_long + elif itemsize == 8: + getdata = idaapi.get_qword + else: + raise ValueError, "Invalid data size! Must be 1, 2, 4 or 8" + + endea = ea + itemsize * count + curea = ea + while curea < endea: + yield getdata(curea) + curea += itemsize + + +def PutDataList(ea, datalist, itemsize=1): + """ + Put data list - INTERNAL USE ONLY + """ + putdata = None + + if itemsize == 1: + putdata = idaapi.patch_byte + if itemsize == 2: + putdata = idaapi.patch_word + if itemsize == 4: + putdata = idaapi.patch_long + + assert putdata, "Invalid data size! Must be 1, 2 or 4" + + for val in datalist: + putdata(ea, val) + ea = ea + itemsize + + +def MapDataList(ea, length, func, wordsize=1): + """ + Map through a list of data words in the database + + @param ea: start address + @param length: number of words to map + @param func: mapping function + @param wordsize: size of words to map [default: 1 byte] + + @return: None + """ + PutDataList(ea, map(func, GetDataList(ea, length, wordsize)), wordsize) + + +def GetInputFileMD5(): + """ + Return the MD5 hash of the input binary file + + @return: MD5 string or None on error + """ + return idc.GetInputMD5() + + +class Strings(object): + """ + Returns the string list. + + Example: + s = Strings() + + for i in s: + print "%x: len=%d type=%d -> '%s'" % (i.ea, i.length, i.type, str(i)) + + """ + class StringItem(object): + """ + Class representing each string item. + """ + def __init__(self, si): + self.ea = si.ea + """String ea""" + self.type = si.type + """string type (ASCSTR_xxxxx)""" + self.length = si.length + """string length""" + + def __str__(self): + return idc.GetString(self.ea, self.length, self.type) + + STR_C = 0x0001 + """C-style ASCII string""" + STR_PASCAL = 0x0002 + """Pascal-style ASCII string (length byte)""" + STR_LEN2 = 0x0004 + """Pascal-style, length is 2 bytes""" + STR_UNICODE = 0x0008 + """Unicode string""" + STR_LEN4 = 0x0010 + """Pascal-style, length is 4 bytes""" + STR_ULEN2 = 0x0020 + """Pascal-style Unicode, length is 2 bytes""" + STR_ULEN4 = 0x0040 + """Pascal-style Unicode, length is 4 bytes""" + + def clear_cache(self): + """Clears the strings list cache""" + self.refresh(0, 0) # when ea1=ea2 the kernel will clear the cache + + + def __init__(self, default_setup = True): + """ + Initializes the Strings enumeration helper class + + @param default_setup: Set to True to use default setup (C strings, min len 5, ...) + """ + self.size = 0 + if default_setup: + self.setup() + + self._si = idaapi.string_info_t() + + + def refresh(self, ea1=None, ea2=None): + """Refreshes the strings list""" + if ea1 is None: + ea1 = idaapi.cvar.inf.minEA + if ea2 is None: + ea2 = idaapi.cvar.inf.maxEA + + idaapi.refresh_strlist(ea1, ea2) + self.size = idaapi.get_strlist_qty() + + + def setup(self, + strtypes = STR_C, + minlen = 5, + only_7bit = True, + ignore_instructions = False, + ea1 = None, + ea2 = None, + display_only_existing_strings = False): + + if ea1 is None: + ea1 = idaapi.cvar.inf.minEA + + if ea2 is None: + ea2 = idaapi.cvar.inf.maxEA + + t = idaapi.strwinsetup_t() + t.strtypes = strtypes + t.minlen = minlen + t.only_7bit = only_7bit + t.ea1 = ea1 + t.ea2 = ea2 + t.display_only_existing_strings = display_only_existing_strings + idaapi.set_strlist_options(t) + + # Automatically refreshes + self.refresh() + + + def _get_item(self, index): + if not idaapi.get_strlist_item(index, self._si): + return None + else: + return Strings.StringItem(self._si) + + + def __iter__(self): + return (self._get_item(index) for index in xrange(0, self.size)) + + + def __getitem__(self, index): + """Returns a string item or None""" + if index >= self.size: + raise KeyError + else: + return self._get_item(index) + +# ----------------------------------------------------------------------- +def GetIdbDir(): + """ + Get IDB directory + + This function returns directory path of the current IDB database + """ + return os.path.dirname(idaapi.cvar.database_idb) + os.sep + +# ----------------------------------------------------------------------- +def GetRegisterList(): + """Returns the register list""" + return idaapi.ph_get_regnames() + +# ----------------------------------------------------------------------- +def GetInstructionList(): + """Returns the instruction list of the current processor module""" + return [i[0] for i in idaapi.ph_get_instruc() if i[0]] + +# ----------------------------------------------------------------------- +def _Assemble(ea, line): + """ + Please refer to Assemble() - INTERNAL USE ONLY + """ + if type(line) == types.StringType: + lines = [line] + else: + lines = line + ret = [] + for line in lines: + seg = idaapi.getseg(ea) + if not seg: + return (False, "No segment at ea") + ip = ea - (idaapi.ask_selector(seg.sel) << 4) + buf = idaapi.AssembleLine(ea, seg.sel, ip, seg.bitness, line) + if not buf: + return (False, "Assembler failed: " + line) + ea += len(buf) + ret.append(buf) + + if len(ret) == 1: + ret = ret[0] + return (True, ret) + + +def Assemble(ea, line): + """ + Assembles one or more lines (does not display an message dialogs) + If line is a list then this function will attempt to assemble all the lines + This function will turn on batch mode temporarily so that no messages are displayed on the screen + + @param ea: start address + @return: (False, "Error message") or (True, asm_buf) or (True, [asm_buf1, asm_buf2, asm_buf3]) + """ + old_batch = idc.Batch(1) + ret = _Assemble(ea, line) + idc.Batch(old_batch) + return ret + +def _copy_obj(src, dest, skip_list = None): + """ + Copy non private/non callable attributes from a class instance to another + @param src: Source class to copy from + @param dest: If it is a string then it designates the new class type that will be created and copied to. + Otherwise dest should be an instance of another class + @return: A new instance or "dest" + """ + if type(dest) == types.StringType: + # instantiate a new destination class of the specified type name? + dest = new.classobj(dest, (), {}) + for x in dir(src): + # skip special and private fields + if x.startswith("__") and x.endswith("__"): + continue + # skip items in the skip list + if skip_list and x in skip_list: + continue + t = getattr(src, x) + # skip callable + if callable(t): + continue + setattr(dest, x, t) + return dest + +# ----------------------------------------------------------------------- +class _reg_dtyp_t(object): + """ + INTERNAL + This class describes a register's number and dtyp. + The equal operator is overloaded so that two instances can be tested for equality + """ + def __init__(self, reg, dtyp): + self.reg = reg + self.dtyp = dtyp + + def __eq__(self, other): + return (self.reg == other.reg) and (self.dtyp == other.dtyp) + +# ----------------------------------------------------------------------- +class _procregs(object): + """Utility class allowing the users to identify registers in a decoded instruction""" + def __getattr__(self, attr): + ri = idaapi.reg_info_t() + if not idaapi.parse_reg_name(attr, ri): + raise AttributeError() + r = _reg_dtyp_t(ri.reg, ord(idaapi.get_dtyp_by_size(ri.size))) + self.__dict__[attr] = r + return r + + def __setattr__(self, attr, value): + raise AttributeError(attr) + +# ----------------------------------------------------------------------- +class _cpu(object): + "Simple wrapper around GetRegValue/SetRegValue" + def __getattr__(self, name): + #print "cpu.get(%s)"%name + return idc.GetRegValue(name) + + def __setattr__(self, name, value): + #print "cpu.set(%s)"%name + return idc.SetRegValue(value, name) + +# ----------------------------------------------------------------------- +class peutils_t(object): + """ + PE utility class. Retrieves PE information from the database. + + Constants from pe.h + """ + PE_NODE = "$ PE header" # netnode name for PE header + PE_ALT_DBG_FPOS = idaapi.BADADDR & -1 # altval() -> translated fpos of debuginfo + PE_ALT_IMAGEBASE = idaapi.BADADDR & -2 # altval() -> loading address (usually pe.imagebase) + PE_ALT_PEHDR_OFF = idaapi.BADADDR & -3 # altval() -> offset of PE header + PE_ALT_NEFLAGS = idaapi.BADADDR & -4 # altval() -> neflags + PE_ALT_TDS_LOADED = idaapi.BADADDR & -5 # altval() -> tds already loaded(1) or invalid(-1) + PE_ALT_PSXDLL = idaapi.BADADDR & -6 # altval() -> if POSIX(x86) imports from PSXDLL netnode + + def __init__(self): + self.__penode = idaapi.netnode() + self.__penode.create(peutils_t.PE_NODE) + + imagebase = property( + lambda self: self.__penode.altval(peutils_t.PE_ALT_IMAGEBASE) + ) + + header = property( + lambda self: self.__penode.altval(peutils_t.PE_ALT_PEHDR_OFF) + ) + + def __str__(self): + return "peutils_t(imagebase=%s, header=%s)" % (hex(self.imagebase), hex(self.header)) + + def header(self): + """ + Returns the complete PE header as an instance of peheader_t (defined in the SDK). + """ + return self.__penode.valobj() + +# ----------------------------------------------------------------------- +cpu = _cpu() +"""This is a special class instance used to access the registers as if they were attributes of this object. +For example to access the EAX register: + print "%x" % cpu.Eax +""" + +procregs = _procregs() +"""This object is used to access the processor registers. It is useful when decoding instructions and you want to see which instruction is which. +For example: + x = idautils.DecodeInstruction(here()) + if x[0] == procregs.Esp: + print "This operand is the register ESP """ \ No newline at end of file diff --git a/python/idc.py b/python/idc.py index d00d86f..880efab 100644 --- a/python/idc.py +++ b/python/idc.py @@ -702,7 +702,7 @@ def MakeStr(ea, endea): @note: The type of an existing string is returned by GetStringType() """ - return idaapi.make_ascii_string(ea, endea - ea, GetLongPrm(INF_STRTYPE)) + return idaapi.make_ascii_string(ea, 0 if endea == BADADDR else endea - ea, GetLongPrm(INF_STRTYPE)) def MakeData(ea, flags, size, tid): @@ -1710,7 +1710,7 @@ def Byte(ea): might have more 1's. To check if a byte has a value, use functions hasValue(GetFlags(ea)) """ - return idaapi.get_byte(ea) + return idaapi.get_full_byte(ea) def __DbgValue(ea, len): @@ -1781,7 +1781,7 @@ def Word(ea): If the current byte size is different from 8 bits, then the returned value might have more 1's. """ - return idaapi.get_word(ea) + return idaapi.get_full_word(ea) def Dword(ea): @@ -1792,7 +1792,7 @@ def Dword(ea): @return: the value of the double word. If failed returns -1 """ - return idaapi.get_long(ea) + return idaapi.get_full_long(ea) def Qword(ea): @@ -1802,10 +1802,8 @@ def Qword(ea): @param ea: linear address @return: the value of the quadro word. If failed, returns -1 - - @note: this function is available only in the 64-bit version of IDA Pro """ - raise NotImplementedError, "will be implemented in the 64-bit version" + return idaapi.get_qword(ea) def GetFloat(ea): @@ -1839,7 +1837,7 @@ def LocByName(name): @param name: name of program byte @return: address of the name - badaddr - no such name + BADADDR - No such name """ return idaapi.get_name_ea(BADADDR, name) @@ -1964,7 +1962,7 @@ def PrevAddr(ea): return idaapi.prevaddr(ea) -def NextHead(ea, maxea): +def NextHead(ea, maxea=BADADDR): """ Get next defined item (instruction or data) in the program @@ -1977,7 +1975,7 @@ def NextHead(ea, maxea): return idaapi.next_head(ea, maxea) -def PrevHead(ea, minea): +def PrevHead(ea, minea=0): """ Get previous defined item (instruction or data) in the program @@ -2938,6 +2936,17 @@ def AskLong(defval, prompt): return idaapi.asklong(defval, prompt) +def ProcessUiAction(name, flags=0): + """ + Invokes an IDA Pro UI action by name + + @param name: Command name + @param flags: Reserved. Must be zero + @return: Boolean + """ + return idaapi.process_ui_action(name, flags) + + def AskSeg(defval, prompt): """ Ask the user to enter a segment value @@ -3374,7 +3383,7 @@ def SegByName(segname): if not seg: return BADADDR - return seg.startEA + return seg.sel def SetSegDefReg(ea, reg, value): @@ -6258,14 +6267,14 @@ def ParseType(inputtype, flags): """ return idaapi.idc_parse_decl(idaapi.cvar.idati, inputtype, flags) -def ParseTypes(inputtype, flags): +def ParseTypes(inputtype, flags = 0): """ Parse type declarations @param inputtype: file name or C declarations (depending on the flags) @param flags: combination of PT_... constants or 0 - @return: number of errors + @return: number of parsing errors (0 no errors) """ return idaapi.idc_parse_types(inputtype, flags) @@ -6790,7 +6799,6 @@ def GetProcessState(): """ return idaapi.get_process_state() -DSTATE_SUSP_FOR_EVENT = -2 # process is currently suspended to react to a debug event (not used) DSTATE_SUSP = -1 # process is suspended DSTATE_NOTASK = 0 # no process is currently debugged DSTATE_RUN = 1 # process is running @@ -7109,7 +7117,7 @@ def GetRegValue(name): """ rv = idaapi.regval_t() res = idaapi.get_reg_val(name, rv) - assert res, "get_reg_val() failed, bogus name perhaps?" + assert res, "get_reg_val() failed, bogus register name ('%s') perhaps?" % name return rv.ival @@ -7207,13 +7215,27 @@ BPT_SOFT = 4 # Software breakpoint BPTATTR_COUNT = 4 BPTATTR_FLAGS = 5 -BPT_BRK = 0x01 # the debugger stops on this breakpoint -BPT_TRACE = 0x02 # the debugger adds trace information when this breakpoint is reached -BPT_UPDMEM = 0x04 # update memory contents before evaluating bpt condition -BPT_UPDSEG = 0x08 # update memory config before evaluating bpt condition +BPT_BRK = 0x01 # the debugger stops on this breakpoint +BPT_TRACE = 0x02 # the debugger adds trace information when this breakpoint is reached +BPT_UPDMEM = 0x04 # refresh the memory layout and contents before evaluating bpt condition +BPT_ENABLED = 0x08 # enabled? +BPT_LOWCND = 0x10 # condition is calculated at low level (on the server side) BPTATTR_COND = 6 # Breakpoint condition. NOTE: the return value is a string in this case +# Breakpoint location type: +BPLT_ABS = 0 # Absolute address. Attributes: + # - locinfo: absolute address + +BPLT_REL = 1 # Module relative address. Attributes: + # - locpath: the module path + # - locinfo: offset from the module base address + +BPLT_SYM = 2 # Symbolic name. The name will be resolved on DLL load/unload + # events and on naming an address. Attributes: + # - locpath: symbol name + # - locinfo: offset from the symbol base address + def SetBptAttr(address, bptattr, value): """ @@ -7530,19 +7552,47 @@ def WriteTxt(filepath, ea1, ea2): def WriteExe(filepath): return GenerateFile(OFILE_EXE, filepath, 0, BADADDR, 0) -def AddConst(enum_id,name,value): return AddConstEx(enum_id,name,value, idaapi.BADADDR) -def AddStruc(index,name): return AddStrucEx(index,name,0) -def AddUnion(index,name): return AddStrucEx(index,name,1) -def OpStroff(ea,n,strid): return OpStroffEx(ea,n,strid,0) -def OpEnum(ea,n,enumid): return OpEnumEx(ea,n,enumid,0) -def DelConst(constid, v, mask): return DelConstEx(constid, v, 0, mask) -def GetConst(constid, v, mask): return GetConstEx(constid, v, 0, mask) -def AnalyseArea(sEA, eEA): return AnalyzeArea(sEA,eEA) -def MakeStruct(ea,name): return MakeStructEx(ea, -1, name) -def Name(ea): return NameEx(BADADDR, ea) -def GetTrueName(ea): return GetTrueNameEx(BADADDR, ea) -def MakeName(ea, name): return MakeNameEx(ea,name,SN_CHECK) +UTP_STRUCT = idaapi.UTP_STRUCT +UTP_ENUM = idaapi.UTP_ENUM + + +def BeginTypeUpdating(utp): + """ + Begin type updating. Use this function if you + plan to call AddEnumConst or similar type modification functions + many times or from inside a loop + + @param utp: one of UTP_xxxx consts + @return: None + """ + return idaapi.begin_type_updating(utp) + + +def EndTypeUpdating(utp): + """ + End type updating. Refreshes the type system + at the end of type modification operations + + @param utp: one of idaapi.UTP_xxxx consts + @return: None + """ + return idaapi.end_type_updating(utp) + + +def AddConst(enum_id, name,value): return AddConstEx(enum_id, name, value, idaapi.BADADDR) +def AddStruc(index, name): return AddStrucEx(index,name, 0) +def AddUnion(index, name): return AddStrucEx(index,name, 1) +def OpStroff(ea, n, strid): return OpStroffEx(ea,n,strid, 0) +def OpEnum(ea, n, enumid): return OpEnumEx(ea,n,enumid, 0) +def DelConst(constid, v, mask): return DelConstEx(constid, v, 0, mask) +def GetConst(constid, v, mask): return GetConstEx(constid, v, 0, mask) +def AnalyseArea(sEA, eEA): return AnalyzeArea(sEA,eEA) + +def MakeStruct(ea, name): return MakeStructEx(ea, -1, name) +def Name(ea): return NameEx(BADADDR, ea) +def GetTrueName(ea): return GetTrueNameEx(BADADDR, ea) +def MakeName(ea, name): return MakeNameEx(ea,name,SN_CHECK) #def GetFrame(ea): return GetFunctionAttr(ea, FUNCATTR_FRAME) #def GetFrameLvarSize(ea): return GetFunctionAttr(ea, FUNCATTR_FRSIZE) diff --git a/python/init.py b/python/init.py index 650d5b8..905d4e5 100644 --- a/python/init.py +++ b/python/init.py @@ -31,7 +31,7 @@ class IDAPythonStdOut: # Swap out the unprintable characters text = text.decode('ascii', 'replace').encode('ascii', 'replace') # Print to IDA message window - _idaapi.msg(text.replace("%", "%%")) + _idaapi.msg(text) def flush(self): pass @@ -61,9 +61,9 @@ def print_banner(): ] sepline = '-' * (max([len(s) for s in banner])+1) - print sepline - print "\n".join(banner) - print sepline + print(sepline) + print("\n".join(banner)) + print(sepline) # ----------------------------------------------------------------------- @@ -76,9 +76,20 @@ sys.argv = [""] # Have to make sure Python finds our modules sys.path.append(_idaapi.idadir("python")) +# Remove current directory from the top of the patch search +if '' in sys.path: # On non Windows, the empty path is added + sys.path.remove('') + +if os.getcwd() in sys.path: + sys.path.remove(os.getcwd()) + +# ...and add it to the end if needed +if not IDAPYTHON_REMOVE_CWD_SYS_PATH: + sys.path.append(os.getcwd()) + # Import all the required modules -from idaapi import Choose, get_user_idadir, cvar, Choose2, Appcall -from idc import * +from idaapi import Choose, get_user_idadir, cvar, Choose2, Appcall, Form +from idc import * from idautils import * import idaapi diff --git a/pywraps.hpp b/pywraps.hpp index 35c91d5..14c4726 100644 --- a/pywraps.hpp +++ b/pywraps.hpp @@ -5,6 +5,7 @@ // Types #ifndef PYUL_DEFINED #define PYUL_DEFINED + typedef unsigned PY_LONG_LONG PY_ULONG_LONG; #ifdef __EA64__ typedef unsigned PY_LONG_LONG pyul_t; typedef PY_LONG_LONG pyl_t; @@ -56,6 +57,31 @@ typedef qvector ppyobject_vec_t; #define CIP_OK 1 // Success #define CIP_OK_NODECREF 2 // Success but do not decrement its reference +//--------------------------------------------------------------------------- +class CGilStateAuto +{ +private: + PyGILState_STATE state; +public: + CGilStateAuto() + { + state = PyGILState_Ensure(); + } + + ~CGilStateAuto() + { + PyGILState_Release(state); + } +}; +// Declare a variable to acquire/release the GIL +#define PYW_GIL_AUTO_ENSURE CGilStateAuto GIL_STATE_AUTO + +// Macros to acquire/release GIL in a given scope +#define PYW_GIL_ENSURE_N(name) PyGILState_STATE gil_state##name = PyGILState_Ensure() +#define PYW_GIL_RELEASE_N(name) PyGILState_Release(gil_state##name) + +#define PYW_GIL_ENSURE PYW_GIL_ENSURE_N(_) +#define PYW_GIL_RELEASE PYW_GIL_RELEASE_N(_) //------------------------------------------------------------------------ // All the exported functions from PyWraps are forward declared here insn_t *insn_t_get_clink(PyObject *self); @@ -86,7 +112,8 @@ bool PyW_IsSequenceType(PyObject *obj); bool PyW_GetError(qstring *out = NULL); // If an error occured (it calls PyGetError) it displays it and return TRUE -bool PyW_ShowErr(const char *cb_name); +// This function is used when calling callbacks +bool PyW_ShowCbErr(const char *cb_name); // Utility function to create a class instance whose constructor takes zero arguments PyObject *create_idaapi_class_instance0(const char *clsname); @@ -124,6 +151,12 @@ Py_ssize_t pyvar_walk_list( int (idaapi *cb)(PyObject *py_item, Py_ssize_t index, void *ud) = NULL, void *ud = NULL); +// Converts an intvec_t to a Python list object +PyObject *PyW_IntVecToPyList(const intvec_t &intvec); + +// Converts an Python list to an intvec +void PyW_PyListToIntVec(PyObject *py_list, intvec_t &intvec); + // Returns a reference to a class PyObject *get_idaapi_attr(const char *attr); @@ -132,4 +165,6 @@ bool pywraps_nw_term(); bool pywraps_nw_notify(int slot, ...); bool pywraps_nw_init(); +const char *pywraps_check_autoscripts(); + #endif \ No newline at end of file diff --git a/swig/bytes.i b/swig/bytes.i index a52e76f..75efcc7 100644 --- a/swig/bytes.i +++ b/swig/bytes.i @@ -101,7 +101,9 @@ static bool idaapi py_testf_cb(flags_t flags, void *ud) { PyObject *py_flags = PyLong_FromUnsignedLong(flags); + PYW_GIL_ENSURE; PyObject *result = PyObject_CallFunctionObjArgs((PyObject *) ud, py_flags, NULL); + PYW_GIL_RELEASE; bool ret = result != NULL && PyObject_IsTrue(result); Py_XDECREF(result); Py_XDECREF(py_flags); @@ -114,7 +116,8 @@ static ea_t py_npthat(ea_t ea, ea_t bound, PyObject *py_callable, bool next) { if ( !PyCallable_Check(py_callable) ) return BADADDR; - return (next ? nextthat : prevthat)(ea, bound, py_testf_cb, py_callable); + else + return (next ? nextthat : prevthat)(ea, bound, py_testf_cb, py_callable); } @@ -134,8 +137,17 @@ class py_custom_data_type_t size_t nbytes) // size of the future item { py_custom_data_type_t *_this = (py_custom_data_type_t *)ud; - PyObject *py_result = PyObject_CallMethod(_this->py_self, (char *)S_MAY_CREATE_AT, PY_FMT64 PY_FMT64, pyul_t(ea), pyul_t(nbytes)); - PyW_ShowErr(S_MAY_CREATE_AT); + + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + _this->py_self, + (char *)S_MAY_CREATE_AT, + PY_FMT64 PY_FMT64, + pyul_t(ea), + pyul_t(nbytes)); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_MAY_CREATE_AT); bool ok = py_result != NULL && PyObject_IsTrue(py_result); Py_XDECREF(py_result); return ok; @@ -152,9 +164,18 @@ class py_custom_data_type_t // Returns: 0-no such item can be created/displayed // this callback is required only for varsize datatypes py_custom_data_type_t *_this = (py_custom_data_type_t *)ud; - PyObject *py_result = PyObject_CallMethod(_this->py_self, (char *)S_CALC_ITEM_SIZE, PY_FMT64 PY_FMT64, pyul_t(ea), pyul_t(maxsize)); - if ( PyW_ShowErr(S_CALC_ITEM_SIZE) || py_result == NULL ) + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + _this->py_self, + (char *)S_CALC_ITEM_SIZE, + PY_FMT64 PY_FMT64, + pyul_t(ea), + pyul_t(maxsize)); + PYW_GIL_RELEASE; + + if ( PyW_ShowCbErr(S_CALC_ITEM_SIZE) || py_result == NULL ) return 0; + uint64 num = 0; PyW_GetNumber(py_result, &num); Py_XDECREF(py_result); @@ -162,11 +183,11 @@ class py_custom_data_type_t } public: - const char *get_name() const - { - return dt_name.c_str(); + const char *get_name() const + { + return dt_name.c_str(); } - + py_custom_data_type_t() { dtid = -1; @@ -189,6 +210,7 @@ public: // name if ( !PyW_GetStringAttr(py_obj, S_NAME, &dt_name) ) break; + dt.name = dt_name.c_str(); // menu_name (optional) @@ -293,11 +315,14 @@ private: int dtid) // custom data type id { // Build a string from the buffer - PyObject *py_value = PyString_FromStringAndSize((const char *)value, Py_ssize_t(size)); + PyObject *py_value = PyString_FromStringAndSize( + (const char *)value, + Py_ssize_t(size)); if ( py_value == NULL ) return false; py_custom_data_format_t *_this = (py_custom_data_format_t *) ud; + PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod( _this->py_self, (char *)S_PRINTF, @@ -306,11 +331,12 @@ private: pyul_t(current_ea), operand_num, dtid); + PYW_GIL_RELEASE; // Done with the string Py_DECREF(py_value); // Error while calling the function? - if ( PyW_ShowErr(S_PRINTF) || py_result == NULL ) + if ( PyW_ShowCbErr(S_PRINTF) || py_result == NULL ) return false; bool ok = false; @@ -338,6 +364,7 @@ private: qstring *errstr) // buffer for error message { py_custom_data_format_t *_this = (py_custom_data_format_t *) ud; + PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod( _this->py_self, (char *)S_SCAN, @@ -345,9 +372,10 @@ private: input, pyul_t(current_ea), operand_num); + PYW_GIL_RELEASE; // Error while calling the function? - if ( PyW_ShowErr(S_SCAN) || py_result == NULL) + if ( PyW_ShowCbErr(S_SCAN) || py_result == NULL) return false; bool ok = false; @@ -356,6 +384,7 @@ private: // We expect a tuple(bool, string|None) if ( !PyTuple_Check(py_result) || PyTuple_Size(py_result) != 2 ) break; + // Borrow references PyObject *py_bool = PyTuple_GetItem(py_result, 0); PyObject *py_val = PyTuple_GetItem(py_result, 1); @@ -404,8 +433,17 @@ private: // this callback may be missing. { py_custom_data_format_t *_this = (py_custom_data_format_t *) ud; - PyObject *py_result = PyObject_CallMethod(_this->py_self, (char *)S_ANALYZE, PY_FMT64 "i", pyul_t(current_ea),operand_num); - PyW_ShowErr(S_ANALYZE); + + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + _this->py_self, + (char *)S_ANALYZE, + PY_FMT64 "i", + pyul_t(current_ea), + operand_num); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ANALYZE); Py_XDECREF(py_result); } public: @@ -415,7 +453,10 @@ public: py_self = NULL; } - const char *get_name() const { return df_name.c_str(); } + const char *get_name() const + { + return df_name.c_str(); + } int register_df(int dtid, PyObject *py_obj) { @@ -491,9 +532,11 @@ public: Py_INCREF(py_obj); py_self = py_obj; + // Update the format ID py_attr = PyInt_FromLong(dfid); PyObject_SetAttrString(py_obj, S_ID, py_attr); Py_DECREF(py_attr); + py_attr = NULL; } while ( false ); @@ -657,7 +700,7 @@ def register_custom_data_type(dt): """ Registers a custom data type. @param dt: an instance of the data_type_t class - @return: + @return: < 0 if failed to register > 0 data type id """ @@ -726,7 +769,7 @@ def register_custom_data_format(dtid, df): Registers a custom data format with a given data type. @param dtid: data type id @param df: an instance of data_format_t - @return: + @return: < 0 if failed to register > 0 data format id """ diff --git a/swig/dbg.i b/swig/dbg.i index 219f486..36c496a 100644 --- a/swig/dbg.i +++ b/swig/dbg.i @@ -10,8 +10,14 @@ typedef struct %ignore source_file_t; %ignore source_item_t; %ignore srcinfo_provider_t; +%ignore bpt_location_t::print; +%ignore bpt_t::set_cond; +%ignore bpt_t::eval_cond; +%ignore bpt_t::write; +%ignore bpt_t::erase; %rename (get_manual_regions) py_get_manual_regions; %ignore set_manual_regions; +%ignore inform_idc_about_debthread; %include "dbg.hpp" %ignore DBG_Callback; %feature("director") DBG_Hooks; @@ -75,8 +81,12 @@ static PyObject *refresh_debugger_memory() { invalidate_dbgmem_config(); invalidate_dbgmem_contents(BADADDR, 0); + + // Ask the debugger to populate debug names if ( dbg != NULL && dbg->stopped_at_debug_event != NULL ) dbg->stopped_at_debug_event(true); + + // Invalidate the cache isEnabled(0); Py_RETURN_NONE; diff --git a/swig/diskio.i b/swig/diskio.i index 6d280ba..75c2f2b 100644 --- a/swig/diskio.i +++ b/swig/diskio.i @@ -49,7 +49,12 @@ int idaapi py_enumerate_files_cb(const char *file, void *ud) { PyObject *py_file = PyString_FromString(file); - PyObject *py_ret = PyObject_CallFunctionObjArgs((PyObject *)ud, py_file, NULL); + PYW_GIL_ENSURE; + PyObject *py_ret = PyObject_CallFunctionObjArgs( + (PyObject *)ud, + py_file, + NULL); + PYW_GIL_RELEASE; int r = (py_ret == NULL || !PyNumber_Check(py_ret)) ? 1 /* stop enumeration on failure */ : PyInt_AsLong(py_ret); Py_XDECREF(py_file); Py_XDECREF(py_ret); diff --git a/swig/expr.i b/swig/expr.i index 18ded5d..4f79e3a 100644 --- a/swig/expr.i +++ b/swig/expr.i @@ -35,15 +35,17 @@ %ignore expr_printf; %ignore expr_sprintf; %ignore expr_printfer; -%ignore idaapi init_idc; -%ignore idaapi term_idc; -%ignore del_idc_userfuncs; -%ignore del_idc_userdefs; +%ignore init_idc; +%ignore term_idc; +%ignore create_default_idc_classes; +%ignore insn_to_idc; %ignore find_builtin_idc_func; +%ignore idc_mutex; %ignore idc_lx; %ignore idc_vars; %ignore idc_resolve_label; %ignore idc_resolver_ea; +%ignore setup_lowcnd_regfuncs; %cstring_output_maxstr_none(char *errbuf, size_t errbufsize); @@ -83,12 +85,13 @@ bool calc_idc_expr_wrap(ea_t where,const char *line, idc_value_t *rv, char *errb %} %ignore CompileLine(const char *line, char *errbuf, size_t errbufsize, uval_t (idaapi*_getname)(const char *name)=NULL); +%ignore CompileLineEx; %rename (CompileLine) CompileLine_wrap; %inline %{ bool CompileLine_wrap(const char *line, char *errbuf, size_t errbufsize) { - return !CompileLine(line, errbuf, errbufsize); + return !CompileLineEx(line, errbuf, errbufsize); } %} diff --git a/swig/funcs.i b/swig/funcs.i index b2b5503..4551eb7 100644 --- a/swig/funcs.i +++ b/swig/funcs.i @@ -27,6 +27,7 @@ %ignore create_func_eas_array; %ignore auto_add_func_tails; %ignore read_tails; +%rename (get_idasgn_desc) py_get_idasgn_desc; %include "funcs.hpp" @@ -34,18 +35,47 @@ %clear(char *optlibs); %inline %{ +//----------------------------------------------------------------------- /* # def get_fchunk_referer(ea, idx): pass # */ -ea_t get_fchunk_referer(ea_t ea, size_t idx) +static ea_t get_fchunk_referer(ea_t ea, size_t idx) { func_t *pfn = get_fchunk(ea); func_parent_iterator_t dummy(pfn); // read referer info if (idx >= pfn->refqty || pfn->referers == NULL) - return BADADDR; - return pfn->referers[idx]; + return BADADDR; + else + return pfn->referers[idx]; } + +//----------------------------------------------------------------------- +/* +# +def get_idasgn_desc(n): + """ + Get information about a signature in the list. + It returns both: + signame - the name of the signature + optlibs - the names of the optional libraries + + @param n: number of signature in the list (0..get_idasgn_qty()-1) + @return: None on failure or tuple(signame, optlibs) + """ +# +*/ +static PyObject *ida_export py_get_idasgn_desc(int n) +{ + char signame[MAXSTR]; + char optlibs[MAXSTR]; + + if ( get_idasgn_desc(n, signame, sizeof(signame), optlibs, sizeof(optlibs)) == -1 ) + Py_RETURN_NONE; + else + return Py_BuildValue("(ss)", signame, optlibs); +} + %} diff --git a/swig/gdl.i b/swig/gdl.i index 239b2c3..0443aac 100644 --- a/swig/gdl.i +++ b/swig/gdl.i @@ -91,6 +91,14 @@ class FlowChart(object): self._q.refresh() + def _getitem(self, index): + return BasicBlock(index, self._q[index], self) + + + def __iter__(self): + return (self._getitem(index) for index in xrange(0, self.size)) + + def __getitem__(self, index): """ Returns a basic block @@ -98,9 +106,9 @@ class FlowChart(object): @return: BasicBlock """ if index >= self.size: - raise StopIteration + raise KeyError else: - return BasicBlock(index, self._q[index], self) + return self._getitem(index) # %} diff --git a/swig/graph.i b/swig/graph.i index 3faeefc..9367237 100644 --- a/swig/graph.i +++ b/swig/graph.i @@ -2,26 +2,6 @@ %ignore graph_visitor_t; %ignore abstract_graph_t; -%{ -#ifdef __GNUC__ -// for some reason GCC insists on putting the vtable into the object file, -// even though we only use mutable_graph_t by pointer -// so we define these methods here to make the linker happy - -edge_info_t *idaapi mutable_graph_t::get_edge(edge_t e) { INTERR(); }; -mutable_graph_t *idaapi mutable_graph_t::clone(void) const { INTERR(); }; -bool idaapi mutable_graph_t::redo_layout(void) { INTERR(); }; -void idaapi mutable_graph_t::resize(int n) { INTERR(); }; -int idaapi mutable_graph_t::add_node(const rect_t *r) { INTERR(); }; -ssize_t idaapi mutable_graph_t::del_node(int n) { INTERR(); }; -bool idaapi mutable_graph_t::add_edge(int i, int j, const edge_info_t *ei) { INTERR(); }; -bool idaapi mutable_graph_t::del_edge(int i, int j) { INTERR(); }; -bool idaapi mutable_graph_t::replace_edge(int i, int j, int x, int y) { INTERR(); }; -bool idaapi mutable_graph_t::refresh(void) { INTERR(); }; -bool idaapi mutable_graph_t::set_nrect(int n, const rect_t &r) { INTERR(); }; -#endif -%} - %{ // class py_graph_t @@ -46,6 +26,7 @@ private: nodetext_cache_t(const char *t, bgcolor_t c): text(t), bgcolor(c) { } nodetext_cache_t() { } }; + class nodetext_cache_map_t: public std::map { public: @@ -58,7 +39,7 @@ private: } nodetext_cache_t *add(const int node_id, const char *text, bgcolor_t bgcolor = DEFCOLOR) { - return &(insert(std::make_pair(node_id, nodetext_cache_t(text, bgcolor))).first->second); + return &(insert(std::make_pair(node_id, nodetext_cache_t(text, bgcolor))).first->second); } }; @@ -75,6 +56,7 @@ private: (*this)[form] = py; } }; + class cmdid_map_t: public std::map { private: @@ -83,7 +65,8 @@ private: cmdid_map_t() { - uid = 1; // we start by one and keep zero for error id + // We start by one and keep zero for error id + uid = 1; } void add(py_graph_t *pyg) @@ -92,7 +75,10 @@ private: ++uid; } - const Py_ssize_t id() const { return uid; } + const Py_ssize_t id() const + { + return uid; + } void clear(py_graph_t *pyg) { @@ -108,6 +94,7 @@ private: ++it; } } + py_graph_t *get(Py_ssize_t id) { iterator it = find(id); @@ -136,13 +123,16 @@ private: py_graph_t *_this = cmdid_pyg.get(id); if ( _this != NULL ) _this->on_command(id); + return true; } void on_command(Py_ssize_t id) { // Check return value to OnRefresh() call + PYW_GIL_ENSURE; PyObject *ret = PyObject_CallMethod(self, (char *)S_ON_COMMAND, "n", id); + PYW_GIL_RELEASE; Py_XDECREF(ret); } @@ -155,7 +145,9 @@ private: return; // Check return value to OnRefresh() call + PYW_GIL_ENSURE; PyObject *ret = PyObject_CallMethod(self, (char *)S_ON_REFRESH, NULL); + PYW_GIL_RELEASE; if ( ret == NULL || !PyObject_IsTrue(ret) ) { Py_XDECREF(ret); @@ -213,11 +205,14 @@ private: Py_DECREF(id); if ( v > max_nodes ) break; + edge_ids[j] = v; } + // Incomplete? if ( j != qnumber(edge_ids) ) break; + // Add the edge g->add_edge(edge_ids[0], edge_ids[1], NULL); } @@ -240,7 +235,9 @@ private: } // Not cached, call Python + PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod(self, (char *)S_ON_GETTEXT, "i", node); + PYW_GIL_RELEASE; if ( result == NULL ) return false; @@ -276,6 +273,7 @@ private: *str = c->text.c_str(); if ( bg_color != NULL ) *bg_color = c->bgcolor; + return true; } @@ -290,7 +288,9 @@ private: if ( mousenode == -1 ) return 0; + PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod(self, (char *)S_ON_HINT, "i", mousenode); + PYW_GIL_RELEASE; bool ok = result != NULL && PyString_Check(result); if ( !ok ) { @@ -307,24 +307,33 @@ private: { if ( self != NULL ) { - if ( cb_flags & GR_HAVE_CLOSE ) + if ( (cb_flags & GR_HAVE_CLOSE) != 0 ) { + PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod(self, (char *)S_ON_CLOSE, NULL); + PYW_GIL_RELEASE; + Py_XDECREF(result); } unbind(); } + // Remove the TForm from list if ( form != NULL ) tform_pyg.erase(form); - // remove all associated commands from the list + + // Remove all associated commands from the list cmdid_pyg.clear(this); + // Delete this instance delete this; } // graph is being clicked - int on_clicked(graph_viewer_t * /*gv*/, selection_item_t * /*item1*/, graph_item_t *item2) + int on_clicked( + graph_viewer_t * /*gv*/, + selection_item_t * /*item1*/, + graph_item_t *item2) { // in: graph_viewer_t *gv // selection_item_t *current_item1 @@ -338,7 +347,14 @@ private: if ( item2->n == -1 ) return 1; - PyObject *result = PyObject_CallMethod(self, (char *)S_ON_CLICK, "i", item2->n); + PYW_GIL_ENSURE; + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_CLICK, + "i", + item2->n); + PYW_GIL_RELEASE; + if ( result == NULL || !PyObject_IsTrue(result) ) { Py_XDECREF(result); @@ -360,7 +376,15 @@ private: //selection_item_t *s = va_arg(va, selection_item_t *); if ( item == NULL || !item->is_node ) return 1; - PyObject *result = PyObject_CallMethod(self, (char *)S_ON_DBL_CLICK, "i", item->node); + + PYW_GIL_ENSURE; + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_DBL_CLICK, + "i", + item->node); + PYW_GIL_RELEASE; + if ( result == NULL || !PyObject_IsTrue(result) ) { Py_XDECREF(result); @@ -373,14 +397,25 @@ private: // a graph viewer got focus void on_gotfocus(graph_viewer_t * /*gv*/) { - PyObject *result = PyObject_CallMethod(self, (char *)S_ON_ACTIVATE, NULL); + PYW_GIL_ENSURE; + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_ACTIVATE, + NULL); + PYW_GIL_RELEASE; Py_XDECREF(result); } // a graph viewer lost focus void on_lostfocus(graph_viewer_t *gv) { - PyObject *result = PyObject_CallMethod(self, (char *)S_ON_DEACTIVATE, NULL); + PYW_GIL_ENSURE; + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_DEACTIVATE, + NULL); + PYW_GIL_RELEASE; + Py_XDECREF(result); } @@ -392,7 +427,15 @@ private: // out: 0-ok, 1-forbid to change the current node if ( curnode < 0 ) return 0; - PyObject *result = PyObject_CallMethod(self, (char *)S_ON_SELECT, "i", curnode); + + PYW_GIL_ENSURE; + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_SELECT, + "i", + curnode); + PYW_GIL_RELEASE; + bool allow = (result != NULL && PyObject_IsTrue(result)); Py_XDECREF(result); return allow ? 0 : 1; @@ -428,7 +471,10 @@ private: ret = on_clicked(gv, item, gitem); } else - ret = 1; // ignore click + { + // Ignore the click + ret = 1; + } break; // case grcode_dblclicked: @@ -445,17 +491,20 @@ private: case grcode_gotfocus: if ( cb_flags & GR_HAVE_GOTFOCUS ) on_gotfocus(va_arg(va, graph_viewer_t *)); + ret = 0; break; // case grcode_lostfocus: if ( cb_flags & GR_HAVE_LOSTFOCUS ) on_lostfocus(va_arg(va, graph_viewer_t *)); + ret = 0; break; // case grcode_user_refresh: on_user_refresh(va_arg(va, mutable_graph_t *)); + ret = 1; break; // @@ -617,7 +666,7 @@ private: netnode id; id.create(); gv = create_graph_viewer(form, id, s_callback, this, 0); - open_tform(form, FORM_MDI|FORM_TAB|FORM_MENU); + open_tform(form, FORM_MDI | FORM_TAB | FORM_MENU); if ( gv != NULL ) viewer_fit_window(gv); } @@ -625,6 +674,7 @@ private: { show(); } + viewer_fit_window(gv); return 0; } @@ -655,6 +705,7 @@ private: py_graph_t *_this = extract_this(self); if ( _this == NULL || _this->form == NULL ) return; + _this->jump_to_node(0); } @@ -663,6 +714,7 @@ private: py_graph_t *_this = extract_this(self); if ( _this == NULL || _this->form == NULL ) return 0; + return _this->add_command(title, hotkey); } @@ -671,6 +723,7 @@ private: py_graph_t *_this = extract_this(self); if ( _this == NULL || _this->form == NULL ) return; + close_tform(_this->form, 0); } @@ -679,6 +732,7 @@ private: py_graph_t *_this = extract_this(self); if ( _this == NULL ) return; + _this->refresh(); } @@ -763,7 +817,7 @@ bool pyg_show(PyObject *self); %pythoncode %{ # -class GraphViewer: +class GraphViewer(object): """This class wraps the user graphing facility provided by the graph.hpp file""" def __init__(self, title, close_open = False): """ @@ -793,11 +847,17 @@ class GraphViewer: self._nodes = [] self._edges = [] + + def __iter__(self): + return (self._nodes[index] for index in xrange(0, len(self._nodes))) + + def __getitem__(self, idx): """Returns a reference to the object associated with this node id""" - if idx > len(self._nodes): - raise StopIteration - return self._nodes[idx] + if idx >= len(self._nodes): + raise KeyError + else: + return self._nodes[idx] def Count(self): """Returns the node count""" diff --git a/swig/idaapi.i b/swig/idaapi.i index 28ad909..1cc193e 100644 --- a/swig/idaapi.i +++ b/swig/idaapi.i @@ -77,1175 +77,17 @@ #include "fpro.h" #include #include "graph.hpp" +#include "pywraps.hpp" // -#include "pywraps.hpp" - -//------------------------------------------------------------------------ -// String constants used -static const char S_PY_SWIEX_CLSNAME[] = "switch_info_ex_t"; -static const char S_PY_OP_T_CLSNAME[] = "op_t"; -static const char S_PY_IDC_GLOBAL_VAR_FMT[] = "__py_cvt_gvar_%d"; -static const char S_PY_IDCCVT_ID_ATTR[] = "__idc_cvt_id__"; -static const char S_PY_IDCCVT_VALUE_ATTR[] = "__idc_cvt_value__"; -static const char S_PY_IDC_OPAQUE_T[] = "py_idc_cvt_helper_t"; -static const char S_PROPS[] = "props"; -static const char S_NAME[] = "name"; -static const char S_TITLE[] = "title"; -static const char S_ASM_KEYWORD[] = "asm_keyword"; -static const char S_MENU_NAME[] = "menu_name"; -static const char S_HOTKEY[] = "hotkey"; -static const char S_FLAGS[] = "flags"; -static const char S_VALUE_SIZE[] = "value_size"; -static const char S_MAY_CREATE_AT[] = "may_create_at"; -static const char S_CALC_ITEM_SIZE[] = "calc_item_size"; -static const char S_ID[] = "id"; -static const char S_PRINTF[] = "printf"; -static const char S_TEXT_WIDTH[] = "text_width"; -static const char S_SCAN[] = "scan"; -static const char S_ANALYZE[] = "analyze"; -static const char S_CBSIZE[] = "cbsize"; -static const char S_ON_CLICK[] = "OnClick"; -static const char S_ON_CLOSE[] = "OnClose"; -static const char S_ON_DBL_CLICK[] = "OnDblClick"; -static const char S_ON_CURSOR_POS_CHANGED[] = "OnCursorPosChanged"; -static const char S_ON_KEYDOWN[] = "OnKeydown"; -static const char S_ON_COMPLETE_LINE[] = "OnCompleteLine"; -static const char S_ON_CREATE[] = "OnCreate"; -static const char S_ON_POPUP[] = "OnPopup"; -static const char S_ON_HINT[] = "OnHint"; -static const char S_ON_POPUP_MENU[] = "OnPopupMenu"; -static const char S_ON_EDIT_LINE[] = "OnEditLine"; -static const char S_ON_INSERT_LINE[] = "OnInsertLine"; -static const char S_ON_GET_LINE[] = "OnGetLine"; -static const char S_ON_DELETE_LINE[] = "OnDeleteLine"; -static const char S_ON_REFRESH[] = "OnRefresh"; -static const char S_ON_EXECUTE_LINE[] = "OnExecuteLine"; -static const char S_ON_SELECT_LINE[] = "OnSelectLine"; -static const char S_ON_COMMAND[] = "OnCommand"; -static const char S_ON_GET_ICON[] = "OnGetIcon"; -static const char S_ON_GET_LINE_ATTR[] = "OnGetLineAttr"; -static const char S_ON_GET_SIZE[] = "OnGetSize"; -static const char S_ON_GETTEXT[] = "OnGetText"; -static const char S_ON_ACTIVATE[] = "OnActivate"; -static const char S_ON_DEACTIVATE[] = "OnDeactivate"; -static const char S_ON_SELECT[] = "OnSelect"; -static const char S_M_EDGES[] = "_edges"; -static const char S_M_NODES[] = "_nodes"; -static const char S_M_THIS[] = "_this"; -static const char S_M_TITLE[] = "_title"; -static const char S_CLINK_NAME[] = "__clink__"; - -#ifdef __PYWRAPS__ -static const char S_PY_IDAAPI_MODNAME[] = "__main__"; -#else -static const char S_PY_IDAAPI_MODNAME[] = S_IDAAPI_MODNAME; -#endif - -//------------------------------------------------------------------------ -// Constants used by get_idaapi_class_reference() -#define PY_CLSID_CVT_INT64 0 -#define PY_CLSID_APPCALL_SKEL_OBJ 1 -#define PY_CLSID_CVT_BYREF 2 -#define PY_CLSID_LAST 3 - -//------------------------------------------------------------------------ -static PyObject *py_cvt_helper_module = NULL; -static bool pywraps_initialized = false; - -//------------------------------------------------------------------------ -static idc_class_t *get_py_idc_cvt_opaque() -{ - return find_idc_class(S_PY_IDC_OPAQUE_T); -} - -//------------------------------------------------------------------------- -// Utility function to convert a python object to an IDC object -// and sets a python exception on failure. -bool convert_pyobj_to_idc_exc(PyObject *py_obj, idc_value_t *idc_obj) -{ - int sn = 0; - if ( pyvar_to_idcvar(py_obj, idc_obj, &sn) < CIP_OK ) - { - PyErr_SetString(PyExc_ValueError, "Could not convert Python object to IDC object!"); - return false; - } - return true; -} - -//------------------------------------------------------------------------ -// IDC Opaque object destructor: when the IDC object dies we kill the -// opaque Python object along with it -static const char py_idc_cvt_helper_dtor_args[] = { VT_OBJ, 0 }; -static error_t idaapi py_idc_opaque_dtor( - idc_value_t *argv, - idc_value_t *res) -{ - // Get the value from the object - idc_value_t idc_val; - VarGetAttr(&argv[0], S_PY_IDCCVT_VALUE_ATTR, &idc_val); - - // Extract the Python object reference - PyObject *py_obj = (PyObject *)idc_val.pvoid; - // Decrease its reference (and eventually destroy it) - Py_DECREF(py_obj); - return eOk; -} - -//------------------------------------------------------------------------ -// This function must be called on initialization -bool init_pywraps() -{ - if ( pywraps_initialized ) - return true; - - // Take a reference to the idaapi python module - // (We need it to create instances of certain classes) - if ( py_cvt_helper_module == NULL ) - { - // Take a reference to the module so we can create the needed class instances - py_cvt_helper_module = PyW_TryImportModule(S_PY_IDAAPI_MODNAME); - if ( py_cvt_helper_module == NULL ) - return false; - } - - if ( get_py_idc_cvt_opaque() == NULL ) - { - // Add the class - idc_class_t *idc_cvt_opaque = add_idc_class(S_PY_IDC_OPAQUE_T); - if ( idc_cvt_opaque == NULL ) - return false; - - // Form the dtor name - char dtor_name[MAXSTR]; - qsnprintf(dtor_name, sizeof(dtor_name), "%s.dtor", S_PY_IDC_OPAQUE_T); - - // register the dtor function - if ( !set_idc_func_ex(dtor_name, py_idc_opaque_dtor, py_idc_cvt_helper_dtor_args, 0) ) - return false; - - // Link the dtor function to the class - set_idc_dtor(idc_cvt_opaque, dtor_name); - } - pywraps_initialized = true; - return true; -} - -//------------------------------------------------------------------------ -// This function must be called on de-initialization -void deinit_pywraps() -{ - if ( !pywraps_initialized ) - return; - pywraps_initialized = false; - Py_XDECREF(py_cvt_helper_module); - py_cvt_helper_module = NULL; -} - -//------------------------------------------------------------------------ -// Utility function to create a class instance whose constructor takes zero arguments -PyObject *create_idaapi_class_instance0(const char *clsname) -{ - PyObject *py_cls = get_idaapi_attr(clsname); - if ( py_cls == NULL ) - return NULL; - PyObject *py_obj = PyObject_CallFunctionObjArgs(py_cls, NULL); - Py_DECREF(py_cls); - if ( PyW_GetError() || py_obj == NULL ) - { - Py_XDECREF(py_obj); - Py_RETURN_NONE; - } - return py_obj; -} - -//------------------------------------------------------------------------ -// Utility function to create linked class instances -PyObject *create_idaapi_linked_class_instance(const char *clsname, void *lnk) -{ - PyObject *py_cls = get_idaapi_attr(clsname); - if ( py_cls == NULL ) - return NULL; - - PyObject *py_lnk = PyCObject_FromVoidPtr(lnk, NULL); - PyObject *py_obj = PyObject_CallFunctionObjArgs(py_cls, py_lnk, NULL); - Py_DECREF(py_cls); - Py_DECREF(py_lnk); - - if ( PyW_GetError() || py_obj == NULL ) - { - Py_XDECREF(py_obj); - py_obj = NULL; - } - return py_obj; -} - -//------------------------------------------------------------------------ -// Gets a class type reference in idaapi -// With the class type reference we can create a new instance of that type -// This function takes a reference to the idaapi module and keeps the reference -static PyObject *get_idaapi_attr(const int class_id) -{ - if ( class_id >= PY_CLSID_LAST ) - return NULL; - - // Some class names. The array is parallel with the PY_CLSID_xxx consts - static const char *class_names[]= - { - "PyIdc_cvt_int64__", - "object_t", - "PyIdc_cvt_refclass__" - }; - return PyObject_GetAttrString(py_cvt_helper_module, class_names[class_id]); -} - -//------------------------------------------------------------------------ -// Gets a class reference by name -PyObject *get_idaapi_attr(const char *attrname) -{ - return PyW_TryGetAttrString(py_cvt_helper_module, attrname); -} - -//------------------------------------------------------------------------ -// Returns a qstring from an object attribute -bool PyW_GetStringAttr( - PyObject *py_obj, - const char *attr_name, - qstring *str) -{ - PyObject *py_attr = PyW_TryGetAttrString(py_obj, attr_name); - if ( py_attr == NULL ) - return false; - - bool ok = PyString_Check(py_attr) != 0; - if ( ok ) - *str = PyString_AsString(py_attr); - - Py_DECREF(py_attr); - return ok; -} - -//------------------------------------------------------------------------ -// Returns an attribute or NULL -// No errors will be set if the attribute did not exist -PyObject *PyW_TryGetAttrString(PyObject *py_obj, const char *attr) -{ - if ( !PyObject_HasAttrString(py_obj, attr) ) - return NULL; - return PyObject_GetAttrString(py_obj, attr); -} - -//------------------------------------------------------------------------ -// Tries to import a module and clears the exception on failure -PyObject *PyW_TryImportModule(const char *name) -{ - PyObject *result = PyImport_ImportModule(name); - if ( result != NULL ) - return result; - if ( PyErr_Occurred() ) - PyErr_Clear(); - return NULL; -} - -//------------------------------------------------------------------------- -// Converts a Python number into an IDC value (32 or 64bits) -// The function will first try to convert the number into a 32bit value -// If the number does not fit then VT_INT64 will be used -// NB: This function cannot properly detect if the Python value should be -// converted to a VT_INT64 or not. For example: 2**32-1 = 0xffffffff which -// can fit in a C long but Python creates a PyLong object for it. -// And because of that we are confused as to whether to convert to 32 or 64 -bool PyW_GetNumberAsIDC(PyObject *py_var, idc_value_t *idc_var) -{ - if ( !(PyInt_CheckExact(py_var) || PyLong_CheckExact(py_var)) ) - return false; - - // Can we convert to C long? - long l = PyInt_AsLong(py_var); - if ( !PyErr_Occurred() ) - { - idc_var->set_long(l); - return true; - } - // Clear last error - PyErr_Clear(); - // Can be fit into a C unsigned long? - l = (long) PyLong_AsUnsignedLong(py_var); - if ( !PyErr_Occurred() ) - { - idc_var->set_long(l); - return true; - } - PyErr_Clear(); - idc_var->set_int64(PyLong_AsLongLong(py_var)); - return true; -} - -//------------------------------------------------------------------------- -// Parses a Python object as a long or long long -bool PyW_GetNumber(PyObject *py_var, uint64 *num, bool *is_64) -{ - if ( !(PyInt_CheckExact(py_var) || PyLong_CheckExact(py_var)) ) - return false; - - // Can we convert to C long? - long l = PyInt_AsLong(py_var); - if ( !PyErr_Occurred() ) - { - if ( num != NULL ) - *num = uint64(l); - if ( is_64 != NULL ) - *is_64 = false; - return true; - } - // Clear last error - PyErr_Clear(); - // Can be fit into a C unsigned long? - unsigned long ul = PyLong_AsUnsignedLong(py_var); - if ( !PyErr_Occurred() ) - { - if ( num != NULL ) - *num = uint64(ul); - if ( is_64 != NULL ) - *is_64 = false; - return true; - } - PyErr_Clear(); - PY_LONG_LONG ll = PyLong_AsLongLong(py_var); - if ( !PyErr_Occurred() ) - { - if ( num != NULL ) - *num = uint64(ll); - if ( is_64 != NULL ) - *is_64 = true; - return true; - } - PyErr_Clear(); - return false; -} - -//------------------------------------------------------------------------- -// Checks if a given object is of sequence type -bool PyW_IsSequenceType(PyObject *obj) -{ - if ( !PySequence_Check(obj) ) - return false; - Py_ssize_t sz = PySequence_Size(obj); - if ( sz == -1 || PyErr_Occurred() != NULL ) - { - PyErr_Clear(); - return false; - } - return true; -} - -//------------------------------------------------------------------------- -// Returns the string representation of an object -bool PyW_ObjectToString(PyObject *obj, qstring *out) -{ - PyObject *py_str = PyObject_Str(obj); - if ( py_str != NULL ) - { - *out = PyString_AsString(py_str); - Py_DECREF(py_str); - return true; - } - else - { - out->qclear(); - return false; - } -} - -//-------------------------------------------------------------------------- -// Checks if a Python error occured and fills the out parameter with the -// exception string -bool PyW_GetError(qstring *out) -{ - if ( !PyErr_Occurred() ) - return false; - - PyObject *err_type, *err_value, *err_traceback; - PyErr_Fetch(&err_type, &err_value, &err_traceback); - if ( out != NULL ) - PyW_ObjectToString(err_value, out); - return true; -} - -//------------------------------------------------------------------------- -// A loud version of PyGetError() which gets the error and displays it -bool PyW_ShowErr(const char *cb_name) -{ - static qstring err_str; - if ( !PyW_GetError(&err_str) ) - return false; - warning("IDAPython: Error while calling Python callback <%s>:\n%s", cb_name, err_str.c_str()); - return true; -} - -//------------------------------------------------------------------------- -// Checks if the given py_var is a special PyIdc_cvt_helper object. -// It does that by examining the magic attribute and returns its numeric value. -// It returns -1 if the object is not a recognized helper object. -// Any Python object can be treated as an cvt object if this attribute is created. -static int get_pyidc_cvt_type(PyObject *py_var) -{ - // Check if this our special by reference object - PyObject *attr = PyW_TryGetAttrString(py_var, S_PY_IDCCVT_ID_ATTR); - if ( attr == NULL ) - return -1; - if ( !(PyInt_Check(attr) || PyLong_Check(attr)) ) - { - Py_DECREF(attr); - return -1; - } - int r = (int)PyInt_AsLong(attr); - Py_DECREF(attr); - return r; -} - -//------------------------------------------------------------------------- -// Utility function to create opaque / convertible Python <-> IDC variables -// The referred Python variable will have its reference increased -static bool create_py_idc_opaque_obj(PyObject *py_var, idc_value_t *idc_var) -{ - // Create an IDC object of this special helper class - if ( VarObject(idc_var, get_py_idc_cvt_opaque()) != eOk ) - return false; - - // Store the CVT id - idc_value_t idc_val; - idc_val.set_long(PY_ICID_OPAQUE); - VarSetAttr(idc_var, S_PY_IDCCVT_ID_ATTR, &idc_val); - - // Store the value as a PVOID referencing the given Python object - idc_val.set_pvoid(py_var); - VarSetAttr(idc_var, S_PY_IDCCVT_VALUE_ATTR, &idc_val); - - return true; -} - -//------------------------------------------------------------------------- -Py_ssize_t pyvar_walk_list( - PyObject *py_list, - int (idaapi *cb)(PyObject *py_item, Py_ssize_t index, void *ud), - void *ud) -{ - if ( !PyList_CheckExact(py_list) && !PyW_IsSequenceType(py_list) ) - return CIP_FAILED; - - bool is_seq = !PyList_CheckExact(py_list); - Py_ssize_t size = is_seq ? PySequence_Size(py_list) : PyList_Size(py_list); - - if ( cb == NULL ) - return size; - - Py_ssize_t i; - for ( i=0; iset_long(0); - // Numbers? - else if ( PyW_GetNumberAsIDC(py_var, idc_var) ) - return CIP_OK; - // String - else if ( PyString_Check(py_var) ) - idc_var->_set_string(PyString_AsString(py_var), PyString_Size(py_var)); - // Float - else if ( PyBool_Check(py_var) ) - idc_var->set_long(py_var == Py_True ? 1 : 0); - // Boolean - else if ( PyFloat_Check(py_var) ) - { - double dresult = PyFloat_AsDouble(py_var); - ieee_realcvt((void *)&dresult, idc_var->e, 3); - idc_var->vtype = VT_FLOAT; - } - // void* - else if ( PyCObject_Check(py_var) ) - idc_var->set_pvoid(PyCObject_AsVoidPtr(py_var)); - // Is it a Python list? - else if ( PyList_CheckExact(py_var) || PyW_IsSequenceType(py_var) ) - { - // Create the object - VarObject(idc_var); - - // Determine list size and type - bool is_seq = !PyList_CheckExact(py_var); - Py_ssize_t size = is_seq ? PySequence_Size(py_var) : PyList_Size(py_var); - bool ok = true; - qstring attr_name; - - // Convert each item - for ( Py_ssize_t i=0; i= CIP_OK; - if ( ok ) - { - // Form the attribute name - PyObject *py_int = PyInt_FromSsize_t(i); - ok = PyW_ObjectToString(py_int, &attr_name); - if ( !ok ) - break; - Py_DECREF(py_int); - // Store the attribute - VarSetAttr(idc_var, attr_name.c_str(), &v); - } - // Sequences return a new reference for GetItem() - if ( is_seq ) - Py_DECREF(py_var); - if ( !ok ) - break; - } - return ok ? CIP_OK : CIP_FAILED; - } - // Dictionary: we convert to an IDC object - else if ( PyDict_Check(py_var) ) - { - // Create an empty IDC object - VarObject(idc_var); - - // Get the dict.items() list - PyObject *py_items = PyDict_Items(py_var); - - // Get the size of the list - qstring key_name; - bool ok = true; - Py_ssize_t size = PySequence_Size(py_items); - for ( Py_ssize_t i=0; i (key, value) - PyObject *py_item = PyList_GetItem(py_items, i); - - // Extract key/value - PyObject *key = PySequence_GetItem(py_item, 0); - PyObject *val = PySequence_GetItem(py_item, 1); - - // Get key's string representation - PyW_ObjectToString(key, &key_name); - - // Convert the attribute into an IDC value - idc_value_t v; - ok = pyvar_to_idcvar(val, &v, gvar_sn) >= CIP_OK; - if ( ok ) - { - // Store the attribute - VarSetAttr(idc_var, key_name.c_str(), &v); - } - Py_XDECREF(key); - Py_XDECREF(val); - if ( !ok ) - break; - } - // Decrement attribute reference - Py_DECREF(py_items); - return ok ? CIP_OK : CIP_FAILED; - } - // Possible function? - else if ( PyCallable_Check(py_var) ) - { - idc_var->clear(); - idc_var->vtype = VT_FUNC; - idc_var->funcidx = -1; // Does not apply - return CIP_OK; - } - // Objects: - // - pyidc_cvt objects: int64, byref, opaque - // - other python objects - else - { - // Get the type - int cvt_id = get_pyidc_cvt_type(py_var); - switch ( cvt_id ) - { - // - // INT64 - // - case PY_ICID_INT64: - // Get the value attribute - attr = PyW_TryGetAttrString(py_var, S_PY_IDCCVT_VALUE_ATTR); - if ( attr == NULL ) - return false; - idc_var->set_int64(PyLong_AsLongLong(attr)); - Py_DECREF(attr); - return CIP_OK; - // - // BYREF - // - case PY_ICID_BYREF: - { - // BYREF always require this parameter - if ( gvar_sn == NULL ) - return CIP_FAILED; - - // Get the value attribute - attr = PyW_TryGetAttrString(py_var, S_PY_IDCCVT_VALUE_ATTR); - if ( attr == NULL ) - return CIP_FAILED; - - // Create a global variable - char buf[MAXSTR]; - qsnprintf(buf, sizeof(buf), S_PY_IDC_GLOBAL_VAR_FMT, *gvar_sn); - idc_value_t *gvar = add_idc_gvar(buf); - // Convert the python value into the IDC global variable - bool ok = pyvar_to_idcvar(attr, gvar, gvar_sn) >= CIP_OK; - if ( ok ) - { - (*gvar_sn)++; - // Create a reference to this global variable - VarRef(idc_var, gvar); - } - Py_DECREF(attr); - return ok ? CIP_OK : CIP_FAILED; - } - // - // OPAQUE - // - case PY_ICID_OPAQUE: - { - if ( !create_py_idc_opaque_obj(py_var, idc_var) ) - return CIP_FAILED; - return CIP_OK_NODECREF; - } - // - // Other objects - // - default: - // A normal object? - PyObject *py_dir = PyObject_Dir(py_var); - Py_ssize_t size = PyList_Size(py_dir); - if ( py_dir == NULL || !PyList_Check(py_dir) || size == 0 ) - { - Py_XDECREF(py_dir); - return CIP_FAILED; - } - // Create the IDC object - VarObject(idc_var); - for ( Py_ssize_t i=0; i 2 ) - && (strncmp(field_name, "__", 2) == 0 ) - && (strncmp(field_name+len-2, "__", 2) == 0) ) - { - continue; - } - idc_value_t v; - // Get the non-private attribute from the object - attr = PyObject_GetAttrString(py_var, field_name); - if (attr == NULL - // Convert the attribute into an IDC value - || pyvar_to_idcvar(attr, &v, gvar_sn) < CIP_OK) - { - Py_XDECREF(attr); - return CIP_FAILED; - } - // Store the attribute - VarSetAttr(idc_var, field_name, &v); - // Decrement attribute reference - Py_DECREF(attr); - } - } - } - return CIP_OK; -} - -//------------------------------------------------------------------------- -// Converts an IDC variable to a Python variable -// If py_var points to an existing object then the object will be updated -// If py_var points to an existing immutable object then ZERO is returned -// Returns one of CIP_xxxx. Check pywraps.hpp -int idcvar_to_pyvar( - const idc_value_t &idc_var, - PyObject **py_var) -{ - switch ( idc_var.vtype ) - { - case VT_PVOID: - if ( *py_var == NULL ) - *py_var = PyCObject_FromVoidPtr(idc_var.pvoid, NULL); - else - return CIP_IMMUTABLE; - break; - case VT_INT64: - { - // Recycle? - if ( *py_var != NULL ) - { - // Recycling an int64 object? - int t = get_pyidc_cvt_type(*py_var); - if ( t != PY_ICID_INT64 ) - return CIP_IMMUTABLE; // Cannot recycle immutable object - // Update the attribute - PyObject_SetAttrString(*py_var, S_PY_IDCCVT_VALUE_ATTR, PyLong_FromLongLong(idc_var.i64)); - return CIP_OK; - } - PyObject *py_cls = get_idaapi_attr(PY_CLSID_CVT_INT64); - if ( py_cls == NULL ) - return CIP_FAILED; - *py_var = PyObject_CallFunctionObjArgs(py_cls, PyLong_FromLongLong(idc_var.i64), NULL); - Py_DECREF(py_cls); - if ( PyW_GetError() || *py_var == NULL ) - return CIP_FAILED; - break; - } -#if !defined(NO_OBSOLETE_FUNCS) || defined(__EXPR_SRC) - case VT_STR: - *py_var = PyString_FromString(idc_var.str); - break; -#endif - case VT_STR2: - if ( *py_var == NULL ) - { - const qstring &s = idc_var.qstr(); - *py_var = PyString_FromStringAndSize(s.begin(), s.length()); - break; - } - else - return CIP_IMMUTABLE; // Cannot recycle immutable object - case VT_LONG: - // Cannot recycle immutable objects - if ( *py_var != NULL ) - return CIP_IMMUTABLE; -#ifdef __EA64__ - *py_var = PyLong_FromLongLong(idc_var.num); -#else - *py_var = PyLong_FromLong(idc_var.num); -#endif - break; - case VT_FLOAT: - if ( *py_var == NULL ) - { - double x; - if ( ph.realcvt(&x, (uint16 *)idc_var.e, (sizeof(x)/2-1)|010) != 0 ) - INTERR(); - *py_var = PyFloat_FromDouble(x); - break; - } - else - return CIP_IMMUTABLE; - case VT_REF: - { - if ( *py_var == NULL ) - { - PyObject *py_cls = get_idaapi_attr(PY_CLSID_CVT_BYREF); - if ( py_cls == NULL ) - return CIP_FAILED; - // Create a byref object with None value. We populate it later - *py_var = PyObject_CallFunctionObjArgs(py_cls, Py_None, NULL); - Py_DECREF(py_cls); - if ( PyW_GetError() || *py_var == NULL ) - return CIP_FAILED; - } - int t = *py_var == NULL ? -1 : get_pyidc_cvt_type(*py_var); - if ( t != PY_ICID_BYREF ) - return CIP_FAILED; - - // Dereference - // (Since we are not using VREF_COPY flag, we can safely const_cast) - idc_value_t *dref_v = VarDeref(const_cast(&idc_var), VREF_LOOP); - if ( dref_v == NULL ) - return CIP_FAILED; - - // Can we recycle the object? - PyObject *new_py_val = PyW_TryGetAttrString(*py_var, S_PY_IDCCVT_VALUE_ATTR); - if ( new_py_val != NULL ) - { - // Recycle - t = idcvar_to_pyvar(*dref_v, &new_py_val); - Py_XDECREF(new_py_val); // DECREF because of GetAttrStr - // Success? Nothing more to be done - if ( t == CIP_OK ) - return CIP_OK; - // Clear it so we don't recycle it - new_py_val = NULL; - } - // Try to convert (not recycle) - if ( idcvar_to_pyvar(*dref_v, &new_py_val) != CIP_OK ) - return CIP_FAILED; - // Update the attribute - PyObject_SetAttrString(*py_var, S_PY_IDCCVT_VALUE_ATTR, new_py_val); - Py_DECREF(new_py_val); - break; - } - // Can convert back into a Python object or Python dictionary - // (Depending if py_var will be recycled and it was a dictionary) - case VT_OBJ: - { - // Check if this IDC object has __cvt_id__ and the __idc_cvt_value__ fields - idc_value_t idc_val; - if ( VarGetAttr(&idc_var, S_PY_IDCCVT_ID_ATTR, &idc_val) == eOk - && VarGetAttr(&idc_var, S_PY_IDCCVT_VALUE_ATTR, &idc_val) == eOk ) - { - // Extract the object - *py_var = (PyObject *) idc_val.pvoid; - return CIP_OK_NODECREF; - } - PyObject *obj; - bool is_dict = false; - // Need to create a new object? - if ( *py_var == NULL ) - { - PyObject *py_cls = get_idaapi_attr(PY_CLSID_APPCALL_SKEL_OBJ); - if ( py_cls == NULL ) - return CIP_FAILED; - obj = PyObject_CallFunctionObjArgs(py_cls, NULL); - Py_DECREF(py_cls); - if ( PyW_GetError() || obj == NULL ) - return CIP_FAILED; - } - else - { - // Recycle existing variable - obj = *py_var; - if ( PyDict_Check(obj) ) - is_dict = true; - } - // Walk the IDC attributes and store into python - for (const char *attr_name = VarFirstAttr(&idc_var); - attr_name != NULL; - attr_name=VarNextAttr(&idc_var, attr_name)) - { - // Get the attribute - idc_value_t v; - VarGetAttr(&idc_var, attr_name, &v, true); - // Convert attribute to a python value - PyObject *py_attr(NULL); - int cvt = idcvar_to_pyvar(v, &py_attr); - if ( cvt <= CIP_IMMUTABLE ) - { - // Delete the object (if we created it) - if ( *py_var == NULL ) - Py_DECREF(obj); - return CIP_FAILED; - } - if ( is_dict ) - PyDict_SetItemString(obj, attr_name, py_attr); - else - PyObject_SetAttrString(obj, attr_name, py_attr); - if ( cvt == CIP_OK ) - Py_XDECREF(py_attr); - } - *py_var = obj; - break; - } - // Unhandled type - default: - *py_var = NULL; - return CIP_FAILED; - } - return CIP_OK; -} - - - -//------------------------------------------------------------------------ - -//------------------------------------------------------------------------ -class pywraps_notify_when_t -{ - ppyobject_vec_t table[NW_EVENTSCNT]; - qstring err; - bool in_notify; - struct notify_when_args_t - { - int when; - PyObject *py_callable; - }; - typedef qvector notify_when_args_vec_t; - notify_when_args_vec_t delayed_notify_when_list; - - //------------------------------------------------------------------------ - static int idaapi idp_callback(void *ud, int event_id, va_list va) - { - pywraps_notify_when_t *_this = (pywraps_notify_when_t *)ud; - switch ( event_id ) - { - case processor_t::newfile: - case processor_t::oldfile: - { - int old = event_id == processor_t::oldfile ? 1 : 0; - char *dbname = va_arg(va, char *); - _this->notify(NW_OPENIDB_SLOT, old); - } - break; - case processor_t::closebase: - _this->notify(NW_CLOSEIDB_SLOT); - break; - } - // event not processed, let other plugins or the processor module handle it - return 0; - } - - //------------------------------------------------------------------------ - bool unnotify_when(int when, PyObject *py_callable) - { - int cnt = 0; - for ( int slot=0; slot 0; - } - - //------------------------------------------------------------------------ - void register_callback(int slot, PyObject *py_callable) - { - ppyobject_vec_t &tbl = table[slot]; - ppyobject_vec_t::iterator it_end = tbl.end(), it = std::find(tbl.begin(), it_end, py_callable); - // Already added - if ( it != it_end ) - return; - - // Increment reference - Py_INCREF(py_callable); - - // Insert the element - tbl.push_back(py_callable); - } - - //------------------------------------------------------------------------ - void unregister_callback(int slot, PyObject *py_callable) - { - ppyobject_vec_t &tbl = table[slot]; - ppyobject_vec_t::iterator it_end = tbl.end(), it = std::find(tbl.begin(), it_end, py_callable); - // Not found? - if ( it == it_end ) - return; - - // Decrement reference - Py_DECREF(py_callable); - - // Delete the element - tbl.erase(it); - } - -public: - //------------------------------------------------------------------------ - bool init() - { - return hook_to_notification_point(HT_IDP, idp_callback, this); - } - - //------------------------------------------------------------------------ - bool deinit() - { - // Uninstall all objects - ppyobject_vec_t::iterator it, it_end; - for ( int slot=0; slot 0; - } - - //------------------------------------------------------------------------ - bool notify(int slot, ...) - { - va_list va; - va_start(va, slot); - bool ok = notify_va(slot, va); - va_end(va); - return ok; - } - - //------------------------------------------------------------------------ - bool notify_va(int slot, va_list va) - { - // Sanity bounds check! - if ( slot < 0 || slot >= NW_EVENTSCNT ) - return false; - - bool ok = true; - in_notify = true; - int old = slot == NW_OPENIDB_SLOT ? va_arg(va, int) : 0; - for (ppyobject_vec_t::iterator it = table[slot].begin(), it_end = table[slot].end(); - it != it_end; - ++it) - { - // Form the notification code - PyObject *py_code = PyInt_FromLong(1 << slot); - PyObject *py_result(NULL); - switch ( slot ) - { - case NW_CLOSEIDB_SLOT: - case NW_INITIDA_SLOT: - case NW_TERMIDA_SLOT: - py_result = PyObject_CallFunctionObjArgs(*it, py_code, NULL); - break; - case NW_OPENIDB_SLOT: - { - PyObject *py_old = PyInt_FromLong(old); - py_result = PyObject_CallFunctionObjArgs(*it, py_code, py_old, NULL); - Py_DECREF(py_old); - } - break; - } - Py_DECREF(py_code); - if ( PyW_GetError(&err) || py_result == NULL ) - { - PyErr_Clear(); - warning("notify_when(): Error occured while notifying object.\n%s", err.c_str()); - ok = false; - } - Py_XDECREF(py_result); - } - in_notify = false; - - // Process any delayed notify_when() calls that - if ( !delayed_notify_when_list.empty() ) - { - for (notify_when_args_vec_t::iterator it = delayed_notify_when_list.begin(), it_end=delayed_notify_when_list.end(); - it != it_end; - ++it) - { - notify_when(it->when, it->py_callable); - } - delayed_notify_when_list.qclear(); - } - return ok; - } - - //------------------------------------------------------------------------ - pywraps_notify_when_t() - { - in_notify = false; - } -}; - -static pywraps_notify_when_t *g_nw = NULL; - -//------------------------------------------------------------------------ -// Initializes the notify_when mechanism -// (Normally called by IDAPython plugin.init()) -bool pywraps_nw_init() -{ - if ( g_nw != NULL ) - return true; - - g_nw = new pywraps_notify_when_t(); - if ( g_nw->init() ) - return true; - - // Things went bad, undo! - delete g_nw; - g_nw = NULL; - return false; -} - -//------------------------------------------------------------------------ -bool pywraps_nw_notify(int slot, ...) -{ - if ( g_nw == NULL ) - return false; - - va_list va; - va_start(va, slot); - bool ok = g_nw->notify_va(slot, va); - va_end(va); - - return ok; -} - -//------------------------------------------------------------------------ -// Deinitializes the notify_when mechanism -bool pywraps_nw_term() -{ - if ( g_nw == NULL ) - return true; - - // If could not deinitialize then return w/o stopping nw - if ( !g_nw->deinit() ) - return false; - - // Cleanup - delete g_nw; - g_nw = NULL; - return true; -} - - - - +//--------------------------------------------------------------------------- // Use these macros to define script<->C fields #define DEFINE_SCFIELDX(name, type, is_opt) { #name, type, qoffsetof(CUR_STRUC, name), is_opt } #define DEFINE_SCFIELD(name, type) DEFINE_SCFIELDX(name, type, 0) #define DEFINE_SCFIELD_OPT(name, type) DEFINE_SCFIELDX(name, type, 1) +//--------------------------------------------------------------------------- enum scfield_types_t { // Numeric fields @@ -1367,7 +209,7 @@ class pycvt_t typedef qvector uint64vec_t; static int idaapi make_int_list( PyObject *py_item, - Py_ssize_t index, + Py_ssize_t /*index*/, void *ud) { uint64 val; @@ -1601,6 +443,1336 @@ public: } }; +//------------------------------------------------------------------------- +Py_ssize_t pyvar_walk_list( + PyObject *py_list, + int (idaapi *cb)(PyObject *py_item, Py_ssize_t index, void *ud), + void *ud) +{ + if ( !PyList_CheckExact(py_list) && !PyW_IsSequenceType(py_list) ) + return CIP_FAILED; + + bool is_seq = !PyList_CheckExact(py_list); + Py_ssize_t size = is_seq ? PySequence_Size(py_list) : PyList_Size(py_list); + + if ( cb == NULL ) + return size; + + Py_ssize_t i; + for ( i=0; i= PY_CLSID_LAST ) + return NULL; + + // Some class names. The array is parallel with the PY_CLSID_xxx consts + static const char *class_names[]= + { + "PyIdc_cvt_int64__", + "object_t", + "PyIdc_cvt_refclass__" + }; + return PyObject_GetAttrString(py_cvt_helper_module, class_names[class_id]); +} + +//------------------------------------------------------------------------ +// Gets a class reference by name +PyObject *get_idaapi_attr(const char *attrname) +{ + return PyW_TryGetAttrString(py_cvt_helper_module, attrname); +} + +//------------------------------------------------------------------------ +// Returns a qstring from an object attribute +bool PyW_GetStringAttr( + PyObject *py_obj, + const char *attr_name, + qstring *str) +{ + PyObject *py_attr = PyW_TryGetAttrString(py_obj, attr_name); + if ( py_attr == NULL ) + return false; + + bool ok = PyString_Check(py_attr) != 0; + if ( ok ) + *str = PyString_AsString(py_attr); + + Py_DECREF(py_attr); + return ok; +} + +//------------------------------------------------------------------------ +// Returns an attribute or NULL +// No errors will be set if the attribute did not exist +PyObject *PyW_TryGetAttrString(PyObject *py_obj, const char *attr) +{ + if ( !PyObject_HasAttrString(py_obj, attr) ) + return NULL; + else + return PyObject_GetAttrString(py_obj, attr); +} + +//------------------------------------------------------------------------ +// Tries to import a module and clears the exception on failure +PyObject *PyW_TryImportModule(const char *name) +{ + PYW_GIL_ENSURE; + PyObject *result = PyImport_ImportModule(name); + PYW_GIL_RELEASE; + if ( result != NULL ) + return result; + if ( PyErr_Occurred() ) + PyErr_Clear(); + return NULL; +} + +//------------------------------------------------------------------------- +// Converts a Python number into an IDC value (32 or 64bits) +// The function will first try to convert the number into a 32bit value +// If the number does not fit then VT_INT64 will be used +// NB: This function cannot properly detect if the Python value should be +// converted to a VT_INT64 or not. For example: 2**32-1 = 0xffffffff which +// can fit in a C long but Python creates a PyLong object for it. +// And because of that we are confused as to whether to convert to 32 or 64 +bool PyW_GetNumberAsIDC(PyObject *py_var, idc_value_t *idc_var) +{ + if ( !(PyInt_CheckExact(py_var) || PyLong_CheckExact(py_var)) ) + return false; + + // Can we convert to C long? + long l = PyInt_AsLong(py_var); + if ( !PyErr_Occurred() ) + { + idc_var->set_long(l); + return true; + } + // Clear last error + PyErr_Clear(); + // Can be fit into a C unsigned long? + l = (long) PyLong_AsUnsignedLong(py_var); + if ( !PyErr_Occurred() ) + { + idc_var->set_long(l); + return true; + } + PyErr_Clear(); + idc_var->set_int64(PyLong_AsLongLong(py_var)); + return true; +} + +//------------------------------------------------------------------------- +// Parses a Python object as a long or long long +bool PyW_GetNumber(PyObject *py_var, uint64 *num, bool *is_64) +{ + if ( !(PyInt_CheckExact(py_var) || PyLong_CheckExact(py_var)) ) + return false; + + // Can we convert to C long? + long l = PyInt_AsLong(py_var); + if ( !PyErr_Occurred() ) + { + if ( num != NULL ) + *num = uint64(l); + if ( is_64 != NULL ) + *is_64 = false; + return true; + } + + // Clear last error + PyErr_Clear(); + + // Can be fit into a C unsigned long? + unsigned long ul = PyLong_AsUnsignedLong(py_var); + if ( !PyErr_Occurred() ) + { + if ( num != NULL ) + *num = uint64(ul); + if ( is_64 != NULL ) + *is_64 = false; + return true; + } + PyErr_Clear(); + + // Try to parse as int64 + PY_LONG_LONG ll = PyLong_AsLongLong(py_var); + if ( !PyErr_Occurred() ) + { + if ( num != NULL ) + *num = uint64(ll); + if ( is_64 != NULL ) + *is_64 = true; + return true; + } + + // Try to parse as uint64 + unsigned PY_LONG_LONG ull = PyLong_AsUnsignedLongLong(py_var); + PyObject *err = PyErr_Occurred(); + if ( err == NULL ) + { + if ( num != NULL ) + *num = uint64(ull); + if ( is_64 != NULL ) + *is_64 = true; + return true; + } + // Negative number? _And_ it with uint64(-1) + bool ok = false; + if ( err == PyExc_TypeError ) + { + PyObject *py_mask = Py_BuildValue("K", 0xFFFFFFFFFFFFFFFFull); + PyObject *py_num = PyNumber_And(py_var, py_mask); + if ( py_num != NULL && py_mask != NULL ) + { + PyErr_Clear(); + ull = PyLong_AsUnsignedLongLong(py_num); + if ( !PyErr_Occurred() ) + { + if ( num != NULL ) + *num = uint64(ull); + if ( is_64 != NULL ) + *is_64 = true; + ok = true; + } + } + Py_XDECREF(py_num); + Py_XDECREF(py_mask); + } + PyErr_Clear(); + return ok; +} + +//------------------------------------------------------------------------- +// Checks if a given object is of sequence type +bool PyW_IsSequenceType(PyObject *obj) +{ + if ( !PySequence_Check(obj) ) + return false; + + Py_ssize_t sz = PySequence_Size(obj); + if ( sz == -1 || PyErr_Occurred() != NULL ) + { + PyErr_Clear(); + return false; + } + return true; +} + +//------------------------------------------------------------------------- +// Returns the string representation of an object +bool PyW_ObjectToString(PyObject *obj, qstring *out) +{ + PyObject *py_str = PyObject_Str(obj); + if ( py_str != NULL ) + { + *out = PyString_AsString(py_str); + Py_DECREF(py_str); + return true; + } + else + { + out->qclear(); + return false; + } +} + +//-------------------------------------------------------------------------- +// Checks if a Python error occured and fills the out parameter with the +// exception string +bool PyW_GetError(qstring *out) +{ + if ( PyErr_Occurred() == NULL ) + return false; + + // Error occurred but details not needed? + if ( out == NULL ) + { + // Just clear the error + PyErr_Clear(); + } + else + { + PyObject *err_type, *err_value, *err_traceback; + PyErr_Fetch(&err_type, &err_value, &err_traceback); + PyW_ObjectToString(err_value, out); + } + return true; +} + +//------------------------------------------------------------------------- +// A loud version of PyGetError() which gets the error and displays it +// This method is used to display errors that occurred in a callback +bool PyW_ShowCbErr(const char *cb_name) +{ + static qstring err_str; + if ( !PyW_GetError(&err_str) ) + return false; + + warning("IDAPython: Error while calling Python callback <%s>:\n%s", cb_name, err_str.c_str()); + return true; +} + +//------------------------------------------------------------------------- +// Checks if the given py_var is a special PyIdc_cvt_helper object. +// It does that by examining the magic attribute and returns its numeric value. +// It returns -1 if the object is not a recognized helper object. +// Any Python object can be treated as an cvt object if this attribute is created. +static int get_pyidc_cvt_type(PyObject *py_var) +{ + // Check if this our special by reference object + PyObject *attr = PyW_TryGetAttrString(py_var, S_PY_IDCCVT_ID_ATTR); + if ( attr == NULL ) + return -1; + + if ( !(PyInt_Check(attr) || PyLong_Check(attr)) ) + { + Py_DECREF(attr); + return -1; + } + int r = (int)PyInt_AsLong(attr); + Py_DECREF(attr); + return r; +} + +//------------------------------------------------------------------------- +// Utility function to create opaque / convertible Python <-> IDC variables +// The referred Python variable will have its reference increased +static bool create_py_idc_opaque_obj(PyObject *py_var, idc_value_t *idc_var) +{ + // Create an IDC object of this special helper class + if ( VarObject(idc_var, get_py_idc_cvt_opaque()) != eOk ) + return false; + + // Store the CVT id + idc_value_t idc_val; + idc_val.set_long(PY_ICID_OPAQUE); + VarSetAttr(idc_var, S_PY_IDCCVT_ID_ATTR, &idc_val); + + // Store the value as a PVOID referencing the given Python object + idc_val.set_pvoid(py_var); + VarSetAttr(idc_var, S_PY_IDCCVT_VALUE_ATTR, &idc_val); + + return true; +} + +//------------------------------------------------------------------------- +// Converts a Python variable into an IDC variable +// This function returns on one CIP_XXXX +int pyvar_to_idcvar( + PyObject *py_var, + idc_value_t *idc_var, + int *gvar_sn) +{ + PyObject *attr; + // None / NULL + if ( py_var == NULL || py_var == Py_None ) + idc_var->set_long(0); + // Numbers? + else if ( PyW_GetNumberAsIDC(py_var, idc_var) ) + return CIP_OK; + // String + else if ( PyString_Check(py_var) ) + idc_var->_set_string(PyString_AsString(py_var), PyString_Size(py_var)); + // Float + else if ( PyBool_Check(py_var) ) + idc_var->set_long(py_var == Py_True ? 1 : 0); + // Boolean + else if ( PyFloat_Check(py_var) ) + { + double dresult = PyFloat_AsDouble(py_var); + ieee_realcvt((void *)&dresult, idc_var->e, 3); + idc_var->vtype = VT_FLOAT; + } + // void* + else if ( PyCObject_Check(py_var) ) + idc_var->set_pvoid(PyCObject_AsVoidPtr(py_var)); + // Is it a Python list? + else if ( PyList_CheckExact(py_var) || PyW_IsSequenceType(py_var) ) + { + // Create the object + VarObject(idc_var); + + // Determine list size and type + bool is_seq = !PyList_CheckExact(py_var); + Py_ssize_t size = is_seq ? PySequence_Size(py_var) : PyList_Size(py_var); + bool ok = true; + qstring attr_name; + + // Convert each item + for ( Py_ssize_t i=0; i= CIP_OK; + if ( ok ) + { + // Form the attribute name + PyObject *py_int = PyInt_FromSsize_t(i); + ok = PyW_ObjectToString(py_int, &attr_name); + if ( !ok ) + break; + Py_DECREF(py_int); + // Store the attribute + VarSetAttr(idc_var, attr_name.c_str(), &v); + } + // Sequences return a new reference for GetItem() + if ( is_seq ) + Py_DECREF(py_var); + if ( !ok ) + break; + } + return ok ? CIP_OK : CIP_FAILED; + } + // Dictionary: we convert to an IDC object + else if ( PyDict_Check(py_var) ) + { + // Create an empty IDC object + VarObject(idc_var); + + // Get the dict.items() list + PyObject *py_items = PyDict_Items(py_var); + + // Get the size of the list + qstring key_name; + bool ok = true; + Py_ssize_t size = PySequence_Size(py_items); + for ( Py_ssize_t i=0; i (key, value) + PyObject *py_item = PyList_GetItem(py_items, i); + + // Extract key/value + PyObject *key = PySequence_GetItem(py_item, 0); + PyObject *val = PySequence_GetItem(py_item, 1); + + // Get key's string representation + PyW_ObjectToString(key, &key_name); + + // Convert the attribute into an IDC value + idc_value_t v; + ok = pyvar_to_idcvar(val, &v, gvar_sn) >= CIP_OK; + if ( ok ) + { + // Store the attribute + VarSetAttr(idc_var, key_name.c_str(), &v); + } + Py_XDECREF(key); + Py_XDECREF(val); + if ( !ok ) + break; + } + // Decrement attribute reference + Py_DECREF(py_items); + return ok ? CIP_OK : CIP_FAILED; + } + // Possible function? + else if ( PyCallable_Check(py_var) ) + { + idc_var->clear(); + idc_var->vtype = VT_FUNC; + idc_var->funcidx = -1; // Does not apply + return CIP_OK; + } + // Objects: + // - pyidc_cvt objects: int64, byref, opaque + // - other python objects + else + { + // Get the type + int cvt_id = get_pyidc_cvt_type(py_var); + switch ( cvt_id ) + { + // + // INT64 + // + case PY_ICID_INT64: + // Get the value attribute + attr = PyW_TryGetAttrString(py_var, S_PY_IDCCVT_VALUE_ATTR); + if ( attr == NULL ) + return false; + idc_var->set_int64(PyLong_AsLongLong(attr)); + Py_DECREF(attr); + return CIP_OK; + // + // BYREF + // + case PY_ICID_BYREF: + { + // BYREF always require this parameter + if ( gvar_sn == NULL ) + return CIP_FAILED; + + // Get the value attribute + attr = PyW_TryGetAttrString(py_var, S_PY_IDCCVT_VALUE_ATTR); + if ( attr == NULL ) + return CIP_FAILED; + + // Create a global variable + char buf[MAXSTR]; + qsnprintf(buf, sizeof(buf), S_PY_IDC_GLOBAL_VAR_FMT, *gvar_sn); + idc_value_t *gvar = add_idc_gvar(buf); + // Convert the python value into the IDC global variable + bool ok = pyvar_to_idcvar(attr, gvar, gvar_sn) >= CIP_OK; + if ( ok ) + { + (*gvar_sn)++; + // Create a reference to this global variable + VarRef(idc_var, gvar); + } + Py_DECREF(attr); + return ok ? CIP_OK : CIP_FAILED; + } + // + // OPAQUE + // + case PY_ICID_OPAQUE: + { + if ( !create_py_idc_opaque_obj(py_var, idc_var) ) + return CIP_FAILED; + return CIP_OK_NODECREF; + } + // + // Other objects + // + default: + // A normal object? + PyObject *py_dir = PyObject_Dir(py_var); + Py_ssize_t size = PyList_Size(py_dir); + if ( py_dir == NULL || !PyList_Check(py_dir) || size == 0 ) + { + Py_XDECREF(py_dir); + return CIP_FAILED; + } + // Create the IDC object + VarObject(idc_var); + for ( Py_ssize_t i=0; i 2 ) + && (strncmp(field_name, "__", 2) == 0 ) + && (strncmp(field_name+len-2, "__", 2) == 0) ) + { + continue; + } + + idc_value_t v; + // Get the non-private attribute from the object + attr = PyObject_GetAttrString(py_var, field_name); + if (attr == NULL + // Convert the attribute into an IDC value + || pyvar_to_idcvar(attr, &v, gvar_sn) < CIP_OK) + { + Py_XDECREF(attr); + return CIP_FAILED; + } + + // Store the attribute + VarSetAttr(idc_var, field_name, &v); + + // Decrement attribute reference + Py_DECREF(attr); + } + } + } + return CIP_OK; +} + +//------------------------------------------------------------------------- +// Converts an IDC variable to a Python variable +// If py_var points to an existing object then the object will be updated +// If py_var points to an existing immutable object then ZERO is returned +// Returns one of CIP_xxxx. Check pywraps.hpp +int idcvar_to_pyvar( + const idc_value_t &idc_var, + PyObject **py_var) +{ + switch ( idc_var.vtype ) + { + case VT_PVOID: + if ( *py_var == NULL ) + *py_var = PyCObject_FromVoidPtr(idc_var.pvoid, NULL); + else + return CIP_IMMUTABLE; + break; + + case VT_INT64: + { + // Recycle? + if ( *py_var != NULL ) + { + // Recycling an int64 object? + int t = get_pyidc_cvt_type(*py_var); + if ( t != PY_ICID_INT64 ) + return CIP_IMMUTABLE; // Cannot recycle immutable object + // Update the attribute + PyObject_SetAttrString(*py_var, S_PY_IDCCVT_VALUE_ATTR, PyLong_FromLongLong(idc_var.i64)); + return CIP_OK; + } + PyObject *py_cls = get_idaapi_attr(PY_CLSID_CVT_INT64); + if ( py_cls == NULL ) + return CIP_FAILED; + *py_var = PyObject_CallFunctionObjArgs(py_cls, PyLong_FromLongLong(idc_var.i64), NULL); + Py_DECREF(py_cls); + if ( PyW_GetError() || *py_var == NULL ) + return CIP_FAILED; + break; + } + +#if !defined(NO_OBSOLETE_FUNCS) || defined(__EXPR_SRC) + case VT_STR: + *py_var = PyString_FromString(idc_var.str); + break; + +#endif + case VT_STR2: + if ( *py_var == NULL ) + { + const qstring &s = idc_var.qstr(); + *py_var = PyString_FromStringAndSize(s.begin(), s.length()); + break; + } + else + return CIP_IMMUTABLE; // Cannot recycle immutable object + case VT_LONG: + // Cannot recycle immutable objects + if ( *py_var != NULL ) + return CIP_IMMUTABLE; +#ifdef __EA64__ + *py_var = PyLong_FromLongLong(idc_var.num); +#else + *py_var = PyLong_FromLong(idc_var.num); +#endif + break; + case VT_FLOAT: + if ( *py_var == NULL ) + { + double x; + if ( ph.realcvt(&x, (uint16 *)idc_var.e, (sizeof(x)/2-1)|010) != 0 ) + INTERR(30160); + + *py_var = PyFloat_FromDouble(x); + break; + } + else + return CIP_IMMUTABLE; + + case VT_REF: + { + if ( *py_var == NULL ) + { + PyObject *py_cls = get_idaapi_attr(PY_CLSID_CVT_BYREF); + if ( py_cls == NULL ) + return CIP_FAILED; + + // Create a byref object with None value. We populate it later + *py_var = PyObject_CallFunctionObjArgs(py_cls, Py_None, NULL); + Py_DECREF(py_cls); + if ( PyW_GetError() || *py_var == NULL ) + return CIP_FAILED; + } + int t = *py_var == NULL ? -1 : get_pyidc_cvt_type(*py_var); + if ( t != PY_ICID_BYREF ) + return CIP_FAILED; + + // Dereference + // (Since we are not using VREF_COPY flag, we can safely const_cast) + idc_value_t *dref_v = VarDeref(const_cast(&idc_var), VREF_LOOP); + if ( dref_v == NULL ) + return CIP_FAILED; + + // Can we recycle the object? + PyObject *new_py_val = PyW_TryGetAttrString(*py_var, S_PY_IDCCVT_VALUE_ATTR); + if ( new_py_val != NULL ) + { + // Recycle + t = idcvar_to_pyvar(*dref_v, &new_py_val); + Py_XDECREF(new_py_val); // DECREF because of GetAttrStr + + // Success? Nothing more to be done + if ( t == CIP_OK ) + return CIP_OK; + + // Clear it so we don't recycle it + new_py_val = NULL; + } + // Try to convert (not recycle) + if ( idcvar_to_pyvar(*dref_v, &new_py_val) != CIP_OK ) + return CIP_FAILED; + + // Update the attribute + PyObject_SetAttrString(*py_var, S_PY_IDCCVT_VALUE_ATTR, new_py_val); + Py_DECREF(new_py_val); + break; + } + + // Can convert back into a Python object or Python dictionary + // (Depending if py_var will be recycled and it was a dictionary) + case VT_OBJ: + { + // Check if this IDC object has __cvt_id__ and the __idc_cvt_value__ fields + idc_value_t idc_val; + if ( VarGetAttr(&idc_var, S_PY_IDCCVT_ID_ATTR, &idc_val) == eOk + && VarGetAttr(&idc_var, S_PY_IDCCVT_VALUE_ATTR, &idc_val) == eOk ) + { + // Extract the object + *py_var = (PyObject *) idc_val.pvoid; + return CIP_OK_NODECREF; + } + PyObject *obj; + bool is_dict = false; + + // Need to create a new object? + if ( *py_var == NULL ) + { + // Get skeleton class reference + PyObject *py_cls = get_idaapi_attr(PY_CLSID_APPCALL_SKEL_OBJ); + if ( py_cls == NULL ) + return CIP_FAILED; + + // Call constructor + obj = PyObject_CallFunctionObjArgs(py_cls, NULL); + Py_DECREF(py_cls); + if ( PyW_GetError() || obj == NULL ) + return CIP_FAILED; + } + else + { + // Recycle existing variable + obj = *py_var; + if ( PyDict_Check(obj) ) + is_dict = true; + } + + // Walk the IDC attributes and store into python + for (const char *attr_name = VarFirstAttr(&idc_var); + attr_name != NULL; + attr_name = VarNextAttr(&idc_var, attr_name) ) + { + // Get the attribute + idc_value_t v; + VarGetAttr(&idc_var, attr_name, &v, true); + + // Convert attribute to a python value (recursively) + PyObject *py_attr(NULL); + int cvt = idcvar_to_pyvar(v, &py_attr); + if ( cvt <= CIP_IMMUTABLE ) + { + // Delete the object (if we created it) + if ( *py_var == NULL ) + Py_DECREF(obj); + + return CIP_FAILED; + } + if ( is_dict ) + PyDict_SetItemString(obj, attr_name, py_attr); + else + PyObject_SetAttrString(obj, attr_name, py_attr); + + if ( cvt == CIP_OK ) + Py_XDECREF(py_attr); + } + *py_var = obj; + break; + } + // Unhandled type + default: + *py_var = NULL; + return CIP_FAILED; + } + return CIP_OK; +} + + + +//------------------------------------------------------------------------ + +//------------------------------------------------------------------------ +class pywraps_notify_when_t +{ + ppyobject_vec_t table[NW_EVENTSCNT]; + qstring err; + bool in_notify; + struct notify_when_args_t + { + int when; + PyObject *py_callable; + }; + typedef qvector notify_when_args_vec_t; + notify_when_args_vec_t delayed_notify_when_list; + + //------------------------------------------------------------------------ + static int idaapi idp_callback(void *ud, int event_id, va_list va) + { + pywraps_notify_when_t *_this = (pywraps_notify_when_t *)ud; + switch ( event_id ) + { + case processor_t::newfile: + case processor_t::oldfile: + { + int old = event_id == processor_t::oldfile ? 1 : 0; + char *dbname = va_arg(va, char *); + _this->notify(NW_OPENIDB_SLOT, old); + } + break; + case processor_t::closebase: + _this->notify(NW_CLOSEIDB_SLOT); + break; + } + // event not processed, let other plugins or the processor module handle it + return 0; + } + + //------------------------------------------------------------------------ + bool unnotify_when(int when, PyObject *py_callable) + { + int cnt = 0; + for ( int slot=0; slot 0; + } + + //------------------------------------------------------------------------ + void register_callback(int slot, PyObject *py_callable) + { + ppyobject_vec_t &tbl = table[slot]; + ppyobject_vec_t::iterator it_end = tbl.end(), it = std::find(tbl.begin(), it_end, py_callable); + + // Already added + if ( it != it_end ) + return; + + // Increment reference + Py_INCREF(py_callable); + + // Insert the element + tbl.push_back(py_callable); + } + + //------------------------------------------------------------------------ + void unregister_callback(int slot, PyObject *py_callable) + { + ppyobject_vec_t &tbl = table[slot]; + ppyobject_vec_t::iterator it_end = tbl.end(), it = std::find(tbl.begin(), it_end, py_callable); + // Not found? + if ( it == it_end ) + return; + + // Decrement reference + Py_DECREF(py_callable); + + // Delete the element + tbl.erase(it); + } + +public: + //------------------------------------------------------------------------ + bool init() + { + return hook_to_notification_point(HT_IDP, idp_callback, this); + } + + //------------------------------------------------------------------------ + bool deinit() + { + // Uninstall all objects + ppyobject_vec_t::iterator it, it_end; + for ( int slot=0; slot 0; + } + + //------------------------------------------------------------------------ + bool notify(int slot, ...) + { + va_list va; + va_start(va, slot); + bool ok = notify_va(slot, va); + va_end(va); + return ok; + } + + //------------------------------------------------------------------------ + bool notify_va(int slot, va_list va) + { + // Sanity bounds check! + if ( slot < 0 || slot >= NW_EVENTSCNT ) + return false; + + bool ok = true; + in_notify = true; + int old = slot == NW_OPENIDB_SLOT ? va_arg(va, int) : 0; + for (ppyobject_vec_t::iterator it = table[slot].begin(), it_end = table[slot].end(); + it != it_end; + ++it) + { + // Form the notification code + PyObject *py_code = PyInt_FromLong(1 << slot); + PyObject *py_result(NULL); + switch ( slot ) + { + case NW_CLOSEIDB_SLOT: + case NW_INITIDA_SLOT: + case NW_TERMIDA_SLOT: + { + PYW_GIL_ENSURE; + py_result = PyObject_CallFunctionObjArgs(*it, py_code, NULL); + PYW_GIL_RELEASE; + break; + } + case NW_OPENIDB_SLOT: + { + PyObject *py_old = PyInt_FromLong(old); + PYW_GIL_ENSURE; + py_result = PyObject_CallFunctionObjArgs(*it, py_code, py_old, NULL); + PYW_GIL_RELEASE; + Py_DECREF(py_old); + } + break; + } + Py_DECREF(py_code); + if ( PyW_GetError(&err) || py_result == NULL ) + { + PyErr_Clear(); + warning("notify_when(): Error occured while notifying object.\n%s", err.c_str()); + ok = false; + } + Py_XDECREF(py_result); + } + in_notify = false; + + // Process any delayed notify_when() calls that + if ( !delayed_notify_when_list.empty() ) + { + for (notify_when_args_vec_t::iterator it = delayed_notify_when_list.begin(), it_end=delayed_notify_when_list.end(); + it != it_end; + ++it) + { + notify_when(it->when, it->py_callable); + } + delayed_notify_when_list.qclear(); + } + return ok; + } + + //------------------------------------------------------------------------ + pywraps_notify_when_t() + { + in_notify = false; + } +}; + +static pywraps_notify_when_t *g_nw = NULL; + +//------------------------------------------------------------------------ +// Initializes the notify_when mechanism +// (Normally called by IDAPython plugin.init()) +bool pywraps_nw_init() +{ + if ( g_nw != NULL ) + return true; + + g_nw = new pywraps_notify_when_t(); + if ( g_nw->init() ) + return true; + + // Things went bad, undo! + delete g_nw; + g_nw = NULL; + return false; +} + +//------------------------------------------------------------------------ +bool pywraps_nw_notify(int slot, ...) +{ + if ( g_nw == NULL ) + return false; + + va_list va; + va_start(va, slot); + bool ok = g_nw->notify_va(slot, va); + va_end(va); + + return ok; +} + +//------------------------------------------------------------------------ +// Deinitializes the notify_when mechanism +bool pywraps_nw_term() +{ + if ( g_nw == NULL ) + return true; + + // If could not deinitialize then return w/o stopping nw + if ( !g_nw->deinit() ) + return false; + + // Cleanup + delete g_nw; + g_nw = NULL; + return true; +} + // %} @@ -1827,7 +1999,7 @@ def as_cstr(val): """ if isinstance(val, PyIdc_cvt_refclass__): val = val.value - + n = val.find('\x00') return val if n == -1 else val[:n] @@ -1860,7 +2032,7 @@ def copy_bits(v, s, e=-1): """ Copy bits from a value @param v: the value - @param s: starting bit + @param s: starting bit (0-based) @param e: ending bit """ # end-bit not specified? use start bit (thus extract one bit) @@ -1870,9 +2042,7 @@ def copy_bits(v, s, e=-1): if s > e: e, s = s, e - mask = 0 - for i in xrange(s, e+1): - mask |= 1 << i + mask = ~(((1 << (e-s+1))-1) << s) return (v & mask) >> s @@ -1893,7 +2063,7 @@ def struct_unpack(buffer, signed = False, offs = 0): """ # Supported length? n = len(buffer) - if not n in __struct_unpack_table: + if n not in __struct_unpack_table: return None # Conver to number signed = 1 if signed else 0 @@ -2015,6 +2185,7 @@ class __IDAPython_Completion_Util(object): return s +# Instantiate a completion object IDAPython_Completion = __IDAPython_Completion_Util() diff --git a/swig/idd.i b/swig/idd.i index 3835172..9b7a7d7 100644 --- a/swig/idd.i +++ b/swig/idd.i @@ -85,7 +85,9 @@ PyObject *py_appcall( // Set exception message if ( !ok ) { - PyErr_SetString(PyExc_ValueError, "PyAppCall: Failed to convert Python values to IDC values"); + PyErr_SetString( + PyExc_ValueError, + "PyAppCall: Failed to convert Python values to IDC values"); return NULL; } @@ -93,6 +95,7 @@ PyObject *py_appcall( { msg("input variables:\n" "----------------\n"); + qstring s; for ( Py_ssize_t i=0; ithread_get_sreg_base(tid, sreg_value, &answer) != 1 ) Py_RETURN_NONE; + return Py_BuildValue(PY_FMT64, pyul_t(answer)); } @@ -620,6 +626,7 @@ class Appcall_callable__(object): fields = property(__get_fields) """Returns the field names""" + def retrieve(self, src=None, flags=0): """ Unpacks a typed object from the database if an ea is given or from a string if a string was passed @@ -628,7 +635,7 @@ class Appcall_callable__(object): """ # Nothing passed? Take the address and unpack from the database - if not src: + if src is None: src = self.ea if type(src) == types.StringType: @@ -642,7 +649,7 @@ class Appcall_callable__(object): @param obj: The object to pack @param dest_ea: If packing to idb this will be the store location @param base_ea: If packing to a buffer, this will be the base that will be used to relocate the pointers - + @return: - If packing to a string then a Tuple(Boolean, packed_string or error code) - If packing to the database then a return code is returned (0 is success) @@ -740,11 +747,11 @@ class Appcall__(object): # resolve and raise exception on error ea = Appcall__.__name_or_ea(name_or_ea) # parse the type - if not flags: + if flags is None: flags = 1 | 2 | 4 # PT_SIL | PT_NDC | PT_TYP result = _idaapi.idc_parse_decl(_idaapi.cvar.idati, prototype, flags) - if not result: + if result is None: raise ValueError, "Could not parse type: " + prototype # Return the callable method with type info @@ -797,7 +804,7 @@ class Appcall__(object): Creates a string buffer. The returned value (r) will be a byref object. Use r.value to get the contents and r.size to get the buffer's size """ - if not str: + if str is None: str = "" left = size - len(str) if left > 0: @@ -834,7 +841,7 @@ class Appcall__(object): """ # parse the type result = _idaapi.idc_parse_decl(_idaapi.cvar.idati, typestr, 1 | 2 | 4) # PT_SIL | PT_NDC | PT_TYP - if not result: + if result is None: raise ValueError, "Could not parse type: " + typestr # Return the callable method with type info return Appcall_callable__(ea, result[1], result[2]) diff --git a/swig/idp.i b/swig/idp.i index 0310eb9..c2ca7a4 100644 --- a/swig/idp.i +++ b/swig/idp.i @@ -42,72 +42,6 @@ %feature("director") IDB_Hooks; %feature("director") IDP_Hooks; %inline %{ -int idaapi IDB_Callback(void *ud, int notification_code, va_list va); -class IDB_Hooks -{ -public: - virtual ~IDB_Hooks() {}; - - bool hook() { return hook_to_notification_point(HT_IDB, IDB_Callback, this); } - bool unhook() { return unhook_from_notification_point(HT_IDB, IDB_Callback, this); } - /* Hook functions to override in Python */ - virtual int byte_patched(ea_t ea) { return 0; }; - virtual int cmt_changed(ea_t, bool repeatable_cmt) { return 0; }; - virtual int ti_changed(ea_t ea, const type_t *type, const p_list *fnames) { msg("ti_changed hook not supported yet\n"); return 0; }; - virtual int op_ti_changed(ea_t ea, int n, const type_t *type, const p_list *fnames) { msg("op_ti_changed hook not supported yet\n"); return 0; }; - virtual int op_type_changed(ea_t ea, int n) { return 0; }; - virtual int enum_created(enum_t id) { return 0; }; - virtual int enum_deleted(enum_t id) { return 0; }; - virtual int enum_bf_changed(enum_t id) { return 0; }; - virtual int enum_renamed(enum_t id) { return 0; }; - virtual int enum_cmt_changed(enum_t id) { return 0; }; - virtual int enum_member_created(enum_t id, const_t cid) { return 0; }; - virtual int enum_member_deleted(enum_t id, const_t cid) { return 0; }; - virtual int struc_created(tid_t struc_id) { return 0; }; - virtual int struc_deleted(tid_t struc_id) { return 0; }; - virtual int struc_renamed(struc_t *sptr) { return 0; }; - virtual int struc_expanded(struc_t *sptr) { return 0; }; - virtual int struc_cmt_changed(tid_t struc_id) { return 0; }; - virtual int struc_member_created(struc_t *sptr, member_t *mptr) { return 0; }; - virtual int struc_member_deleted(struc_t *sptr, tid_t member_id) { return 0; }; - virtual int struc_member_renamed(struc_t *sptr, member_t *mptr) { return 0; }; - virtual int struc_member_changed(struc_t *sptr, member_t *mptr) { return 0; }; - virtual int thunk_func_created(func_t *pfn) { return 0; }; - virtual int func_tail_appended(func_t *pfn, func_t *tail) { return 0; }; - virtual int func_tail_removed(func_t *pfn, ea_t tail_ea) { return 0; }; - virtual int tail_owner_changed(func_t *tail, ea_t owner_func) { return 0; }; - virtual int func_noret_changed(func_t *pfn) { return 0; }; - virtual int segm_added(segment_t *s) { return 0; }; - virtual int segm_deleted(ea_t startEA) { return 0; }; - virtual int segm_start_changed(segment_t *s) { return 0; }; - virtual int segm_end_changed(segment_t *s) { return 0; }; - virtual int segm_moved(ea_t from, ea_t to, asize_t size) { return 0; }; -}; - -// Assemble an instruction into the database (display a warning if an error is found) -// args: -// ea_t ea - linear address of instruction -// ea_t cs - cs of instruction -// ea_t ip - ip of instruction -// bool use32 - is 32bit segment? -// const char *line - line to assemble -// returns: 1: success, 0: failure -inline const int assemble(ea_t ea, ea_t cs, ea_t ip, bool use32, const char *line) -{ - int inslen; - char buf[MAXSTR]; - - if (ph.notify != NULL) - { - inslen = ph.notify(ph.assemble, ea, cs, ip, use32, line, buf); - if (inslen > 0) - { - patch_many_bytes(ea, buf, inslen); - return 1; - } - } - return 0; -} // //------------------------------------------------------------------------- @@ -117,8 +51,8 @@ inline const int assemble(ea_t ea, ea_t cs, ea_t ip, bool use32, const char *lin # def AssembleLine(ea, cs, ip, use32, line): """ - Assemble an instruction to a buffer (display a warning if an error is found) - + Assemble an instruction to a string (display a warning if an error is found) + @param ea: linear address of instruction @param cs: cs of instruction @param ip: ip of instruction @@ -143,6 +77,39 @@ static PyObject *AssembleLine(ea_t ea, ea_t cs, ea_t ip, bool use32, const char Py_RETURN_NONE; } +//--------------------------------------------------------------------------- +/* +# +def assemble(ea, cs, ip, use32, line): + """ + Assemble an instruction into the database (display a warning if an error is found) + @param ea: linear address of instruction + @param cs: cs of instruction + @param ip: ip of instruction + @param use32: is 32bit segment? + @param line: line to assemble + + @return: Boolean. True on success. + """ +# +*/ +bool assemble(ea_t ea, ea_t cs, ea_t ip, bool use32, const char *line) +{ + int inslen; + char buf[MAXSTR]; + + if (ph.notify != NULL) + { + inslen = ph.notify(ph.assemble, ea, cs, ip, use32, line, buf); + if (inslen > 0) + { + patch_many_bytes(ea, buf, inslen); + return true; + } + } + return false; +} + //------------------------------------------------------------------------- /* # @@ -408,6 +375,7 @@ static PyObject *ph_get_regnames() PyObject *py_result = PyList_New(ph.regsNum); for ( Py_ssize_t i=0; i class IDP_Hooks(object): + def hook(self): + """ + Creates an IDP hook + + @return: Boolean true on success + """ + pass + + def unhook(self): + """ + Removes the IDP hook + @return: Boolean true on success + """ + pass + def custom_ana(self): """ Analyzes and decodes an instruction at idaapi.cmd.ea @@ -476,63 +459,179 @@ class IDP_Hooks(object): - 0: the instruction is created because of some coderef, user request or another weighty reason. - @return: 1-ok, <=0-no, the instruction isn't likely to appear in the program + @return: 1-ok, <=0-no, the instruction isn't likely to appear in the program """ pass - def is_sane_insn(self, no_crefs): + def may_be_func(self, no_crefs): """ - can a function start here? + Can a function start here? @param state: autoanalysis phase 0: creating functions 1: creating chunks - + @return: integer (probability 0..100) """ pass + + def closebase(self): + """ + The database will be closed now + """ + pass + + def savebase(self): + """ + The database is being saved. Processor module should + """ + pass + + def rename(self, ea, new_name): + """ + The kernel is going to rename a byte. + + @param ea: Address + @param new_name: The new name + + @return: + - If returns value <=0, then the kernel should + not rename it. See also the 'renamed' event + """ + + def renamed(self, ea, new_name, local_name): + """ + The kernel has renamed a byte + + @param ea: Address + @param new_name: The new name + @param local_name: Is local name + + @return: Ignored + """ + + def undefine(self, ea): + """ + An item in the database (insn or data) is being deleted + @param ea: Address + @return: + - returns: >0-ok, <=0-the kernel should stop + - if the return value is positive: + bit0 - ignored + bit1 - do not delete srareas at the item end + """ + + def make_code(self, ea, size): + """ + An instruction is being created + @param ea: Address + @param size: Instruction size + @return: 1-ok, <=0-the kernel should stop + """ + + def make_code(self, ea, size): + """ + An instruction is being created + @param ea: Address + @param size: Instruction size + @return: 1-ok, <=0-the kernel should stop + """ + + def make_data(self, ea, flags, tid, len): + """ + A data item is being created + @param ea: Address + @param tid: type id + @param flags: item flags + @param len: data item size + @return: 1-ok, <=0-the kernel should stop + """ + + def load_idasgn(self, short_sig_name): + """ + FLIRT signature have been loaded for normal processing + (not for recognition of startup sequences) + @param short_sig_name: signature name + @return: Ignored + """ + + def add_func(self, func): + """ + The kernel has added a function + @param func: the func_t instance + @return: Ignored + """ + + def del_func(self, func): + """ + The kernel is about to delete a function + @param func: the func_t instance + @return: 1-ok,<=0-do not delete + """ + + def is_call_insn(self, ea, func_name): + """ + Is the instruction a "call"? + + @param ea: instruction address + @return: 1-unknown, 0-no, 2-yes + """ + + def is_ret_insn(self, ea, func_name): + """ + Is the instruction a "return"? + + @param ea: instruction address + @param strict: - True: report only ret instructions + False: include instructions like "leave" which begins the function epilog + @return: 1-unknown, 0-no, 2-yes + """ + # */ +//--------------------------------------------------------------------------- +// IDP hooks +//--------------------------------------------------------------------------- int idaapi IDP_Callback(void *ud, int notification_code, va_list va); -class IDP_Hooks +class IDP_Hooks { public: - virtual ~IDP_Hooks() + virtual ~IDP_Hooks() { } - bool hook() - { - return hook_to_notification_point(HT_IDP, IDP_Callback, this); + bool hook() + { + return hook_to_notification_point(HT_IDP, IDP_Callback, this); } - bool unhook() - { - return unhook_from_notification_point(HT_IDP, IDP_Callback, this); + bool unhook() + { + return unhook_from_notification_point(HT_IDP, IDP_Callback, this); } - virtual bool custom_ana() - { - return false; + virtual bool custom_ana() + { + return false; } - virtual bool custom_out() - { - return false; + virtual bool custom_out() + { + return false; } - + virtual bool custom_emu() - { - return false; + { + return false; } - - virtual bool custom_outop(PyObject *py_op) - { - return false; + + virtual bool custom_outop(PyObject *py_op) + { + return false; } - - virtual PyObject *custom_mnem() - { - return NULL; + + virtual PyObject *custom_mnem() + { + return NULL; } virtual int is_sane_insn(int no_crefs) @@ -544,12 +643,287 @@ public: { return 0; } + + virtual int closebase() + { + return 0; + } + + virtual void savebase() + { + } + + virtual int rename(ea_t ea, const char *new_name) + { + return 0; + } + + virtual void renamed(ea_t ea, const char *new_name, bool local_name) + { + } + + virtual int undefine(ea_t ea) + { + return 0; + } + + virtual int make_code(ea_t ea, asize_t size) + { + return 0; + } + + virtual int make_data(ea_t ea, flags_t flags, tid_t tid, asize_t len) + { + return 0; + } + + virtual void load_idasgn(const char *short_sig_name) + { + } + + virtual void add_func(func_t *func) + { + } + + virtual int del_func(func_t *func) + { + return 0; + } + + virtual int is_call_insn(ea_t ea) + { + return 0; + } + + virtual int is_ret_insn(ea_t ea, bool strict) + { + return 0; + } + +}; + +//--------------------------------------------------------------------------- +// IDB hooks +//--------------------------------------------------------------------------- +int idaapi IDB_Callback(void *ud, int notification_code, va_list va); +class IDB_Hooks +{ +public: + virtual ~IDB_Hooks() {}; + + bool hook() + { + return hook_to_notification_point(HT_IDB, IDB_Callback, this); + } + bool unhook() + { + return unhook_from_notification_point(HT_IDB, IDB_Callback, this); + } + // Hook functions to override in Python + virtual int byte_patched(ea_t ea) { return 0; }; + virtual int cmt_changed(ea_t, bool repeatable_cmt) { return 0; }; + virtual int ti_changed(ea_t ea, const type_t *type, const p_list *fnames) { msg("ti_changed hook not supported yet\n"); return 0; }; + virtual int op_ti_changed(ea_t ea, int n, const type_t *type, const p_list *fnames) { msg("op_ti_changed hook not supported yet\n"); return 0; }; + virtual int op_type_changed(ea_t ea, int n) { return 0; }; + virtual int enum_created(enum_t id) { return 0; }; + virtual int enum_deleted(enum_t id) { return 0; }; + virtual int enum_bf_changed(enum_t id) { return 0; }; + virtual int enum_renamed(enum_t id) { return 0; }; + virtual int enum_cmt_changed(enum_t id) { return 0; }; + virtual int enum_member_created(enum_t id, const_t cid) { return 0; }; + virtual int enum_member_deleted(enum_t id, const_t cid) { return 0; }; + virtual int struc_created(tid_t struc_id) { return 0; }; + virtual int struc_deleted(tid_t struc_id) { return 0; }; + virtual int struc_renamed(struc_t *sptr) { return 0; }; + virtual int struc_expanded(struc_t *sptr) { return 0; }; + virtual int struc_cmt_changed(tid_t struc_id) { return 0; }; + virtual int struc_member_created(struc_t *sptr, member_t *mptr) { return 0; }; + virtual int struc_member_deleted(struc_t *sptr, tid_t member_id) { return 0; }; + virtual int struc_member_renamed(struc_t *sptr, member_t *mptr) { return 0; }; + virtual int struc_member_changed(struc_t *sptr, member_t *mptr) { return 0; }; + virtual int thunk_func_created(func_t *pfn) { return 0; }; + virtual int func_tail_appended(func_t *pfn, func_t *tail) { return 0; }; + virtual int func_tail_removed(func_t *pfn, ea_t tail_ea) { return 0; }; + virtual int tail_owner_changed(func_t *tail, ea_t owner_func) { return 0; }; + virtual int func_noret_changed(func_t *pfn) { return 0; }; + virtual int segm_added(segment_t *s) { return 0; }; + virtual int segm_deleted(ea_t startEA) { return 0; }; + virtual int segm_start_changed(segment_t *s) { return 0; }; + virtual int segm_end_changed(segment_t *s) { return 0; }; + virtual int segm_moved(ea_t from, ea_t to, asize_t size) { return 0; }; }; // %} %{ +// +//------------------------------------------------------------------------- +int idaapi IDP_Callback(void *ud, int notification_code, va_list va) +{ + IDP_Hooks *proxy = (IDP_Hooks *)ud; + int ret = 0; + try + { + switch ( notification_code ) + { + case processor_t::custom_ana: + ret = proxy->custom_ana() ? 1 + cmd.size : 0; + break; + + case processor_t::custom_out: + ret = proxy->custom_out() ? 2 : 0; + break; + + case processor_t::custom_emu: + ret = proxy->custom_emu() ? 2 : 0; + break; + + case processor_t::custom_outop: + { + op_t *op = va_arg(va, op_t *); + PyObject *py_obj = create_idaapi_linked_class_instance(S_PY_OP_T_CLSNAME, op); + if ( py_obj == NULL ) + break; + ret = proxy->custom_outop(py_obj) ? 2 : 0; + Py_XDECREF(py_obj); + break; + } + + case processor_t::custom_mnem: + { + PyObject *py_ret = proxy->custom_mnem(); + if ( py_ret != NULL && PyString_Check(py_ret) ) + { + char *outbuffer = va_arg(va, char *); + size_t bufsize = va_arg(va, size_t); + + qstrncpy(outbuffer, PyString_AS_STRING(py_ret), bufsize); + ret = 2; + } + else + { + ret = 0; + } + Py_XDECREF(py_ret); + break; + } + + case processor_t::is_sane_insn: + { + int no_crefs = va_arg(va, int); + ret = proxy->is_sane_insn(no_crefs); + break; + } + + case processor_t::may_be_func: + { + int state = va_arg(va, int); + ret = proxy->may_be_func(state); + break; + } + + case processor_t::closebase: + { + proxy->closebase(); + break; + } + + case processor_t::savebase: + { + proxy->savebase(); + break; + } + + case processor_t::rename: + { + ea_t ea = va_arg(va, ea_t); + const char *new_name = va_arg(va, const char *); + ret = proxy->rename(ea, new_name); + break; + } + + case processor_t::renamed: + { + ea_t ea = va_arg(va, ea_t); + const char *new_name = va_arg(va, const char *); + bool local_name = va_argi(va, bool); + proxy->renamed(ea, new_name, local_name); + break; + } + + case processor_t::undefine: + { + ea_t ea = va_arg(va, ea_t); + ret = proxy->undefine(ea); + break; + } + + case processor_t::make_code: + { + ea_t ea = va_arg(va, ea_t); + asize_t size = va_arg(va, asize_t); + ret = proxy->make_code(ea, size); + break; + } + + case processor_t::make_data: + { + ea_t ea = va_arg(va, ea_t); + flags_t flags = va_arg(va, flags_t); + tid_t tid = va_arg(va, tid_t); + asize_t len = va_arg(va, asize_t); + ret = proxy->make_data(ea, flags, tid, len); + break; + } + + case processor_t::load_idasgn: + { + const char *short_sig_name = va_arg(va, const char *); + proxy->load_idasgn(short_sig_name); + break; + } + + case processor_t::add_func: + { + func_t *func = va_arg(va, func_t *); + proxy->add_func(func); + break; + } + + case processor_t::del_func: + { + func_t *func = va_arg(va, func_t *); + ret = proxy->del_func(func); + break; + } + + case processor_t::is_call_insn: + { + ea_t ea = va_arg(va, ea_t); + ret = proxy->is_call_insn(ea); + break; + } + + case processor_t::is_ret_insn: + { + ea_t ea = va_arg(va, ea_t); + bool strict = va_argi(va, bool); + ret = proxy->is_ret_insn(ea, strict); + break; + } + } + } + catch (Swig::DirectorException &) + { + msg("Exception in IDP Hook function:\n"); + if ( PyErr_Occurred() ) + PyErr_Print(); + } + return ret; +} + +//--------------------------------------------------------------------------- int idaapi IDB_Callback(void *ud, int notification_code, va_list va) { class IDB_Hooks *proxy = (class IDB_Hooks *)ud; @@ -615,12 +989,20 @@ int idaapi IDB_Callback(void *ud, int notification_code, va_list va) id = va_arg(va, enum_t); return proxy->enum_cmt_changed(id); +#ifdef NO_OBSOLETE_FUNCS case idb_event::enum_member_created: +#else + case idb_event::enum_const_created: +#endif id = va_arg(va, enum_t); cid = va_arg(va, const_t); return proxy->enum_member_created(id, cid); +#ifdef NO_OBSOLETE_FUNCS case idb_event::enum_member_deleted: +#else + case idb_event::enum_const_deleted: +#endif id = va_arg(va, enum_t); cid = va_arg(va, const_t); return proxy->enum_member_deleted(id, cid); @@ -711,9 +1093,9 @@ int idaapi IDB_Callback(void *ud, int notification_code, va_list va) return proxy->segm_moved(ea, ea2, size); } } - catch (Swig::DirectorException &) - { - msg("Exception in IDB Hook function:\n"); + catch (Swig::DirectorException &) + { + msg("Exception in IDB Hook function:\n"); if (PyErr_Occurred()) { PyErr_Print(); @@ -722,86 +1104,6 @@ int idaapi IDB_Callback(void *ud, int notification_code, va_list va) return 0; } -// -//------------------------------------------------------------------------- -int idaapi IDP_Callback(void *ud, int notification_code, va_list va) -{ - IDP_Hooks *proxy = (IDP_Hooks *)ud; - int ret; - try - { - switch ( notification_code ) - { - default: - ret = 0; - break; - - case processor_t::custom_ana: - ret = proxy->custom_ana() ? 1 + cmd.size : 0; - break; - - case processor_t::custom_out: - ret = proxy->custom_out() ? 2 : 0; - break; - - case processor_t::custom_emu: - ret = proxy->custom_emu() ? 2 : 0; - break; - - case processor_t::custom_outop: - { - op_t *op = va_arg(va, op_t *); - PyObject *py_obj = create_idaapi_linked_class_instance(S_PY_OP_T_CLSNAME, op); - if ( py_obj == NULL ) - break; - ret = proxy->custom_outop(py_obj) ? 2 : 0; - Py_XDECREF(py_obj); - break; - } - - case processor_t::custom_mnem: - { - PyObject *py_ret = proxy->custom_mnem(); - if ( py_ret != NULL && PyString_Check(py_ret) ) - { - char *outbuffer = va_arg(va, char *); - size_t bufsize = va_arg(va, size_t); - - qstrncpy(outbuffer, PyString_AS_STRING(py_ret), bufsize); - ret = 2; - } - else - { - ret = 0; - } - Py_XDECREF(py_ret); - break; - } - - case processor_t::is_sane_insn: - { - int no_crefs = va_arg(va, int); - ret = proxy->is_sane_insn(no_crefs); - break; - } - - case processor_t::may_be_func: - { - int state = va_arg(va, int); - ret = proxy->may_be_func(state); - break; - } - } - } - catch (Swig::DirectorException &) - { - msg("Exception in IDP Hook function:\n"); - if ( PyErr_Occurred() ) - PyErr_Print(); - } - return ret; -} - //------------------------------------------------------------------------- // %} \ No newline at end of file diff --git a/swig/kernwin.i b/swig/kernwin.i index 106cc0b..d26aad4 100644 --- a/swig/kernwin.i +++ b/swig/kernwin.i @@ -1,7 +1,9 @@ // Ignore the va_list functions %ignore AskUsingForm_cv; +%ignore AskUsingForm_c; %ignore close_form; %ignore vaskstr; +%ignore load_custom_icon; %ignore vasktext; %ignore add_menu_item; %rename (add_menu_item) py_add_menu_item; @@ -19,6 +21,7 @@ %rename (error) py_error; %ignore vinfo; +%ignore UI_Callback; %ignore vnomem; %ignore vmsg; %ignore show_wait_box_v; @@ -57,9 +60,10 @@ %rename (asktext) py_asktext; %rename (str2ea) py_str2ea; +%rename (process_ui_action) py_process_ui_action; %ignore execute_sync; %ignore exec_request_t; -%rename (execute_sync) py_execute_sync; + // Make askaddr(), askseg(), and asklong() return a // tuple: (result, value) @@ -70,6 +74,7 @@ %apply unsigned long *INOUT { sel_t *sel }; %rename (_askseg) askseg; +%feature("director") UI_Hooks; %inline %{ int py_msg(const char *format) { @@ -92,49 +97,645 @@ void refresh_lists(void) } %} -%pythoncode %{ -def asklong(defval, format): - res, val = _idaapi._asklong(defval, format) - - if res == 1: - return val - else: - return None - -def askaddr(defval, format): - res, ea = _idaapi._askaddr(defval, format) - - if res == 1: - return ea - else: - return None - -def askseg(defval, format): - res, sel = _idaapi._askseg(defval, format) - - if res == 1: - return sel - else: - return None - -%} - # This is for get_cursor() %apply int *OUTPUT {int *x, int *y}; # This is for read_selection() %apply unsigned long *OUTPUT { ea_t *ea1, ea_t *ea2 }; -%{ -// +%inline %{ +// +//------------------------------------------------------------------------ //------------------------------------------------------------------------ -struct py_add_del_menu_item_ctx +/* +# +def get_highlighted_identifier(flags = 0): + """ + Returns the currently highlighted identifier + + @param flags: reserved (pass 0) + @return: None or the highlighted identifier + """ + pass +# +*/ +static PyObject *py_get_highlighted_identifier(int flags = 0) { - qstring menupath; - PyObject *cb_data; + char buf[MAXSTR]; + bool ok = get_highlighted_identifier(buf, sizeof(buf), flags); + if ( !ok ) + Py_RETURN_NONE; + else + return PyString_FromString(buf); +} + +//------------------------------------------------------------------------ +static int py_load_custom_icon_fn(const char *filename) +{ + return load_custom_icon(filename); +} + +//------------------------------------------------------------------------ +static int py_load_custom_icon_data(PyObject *data, const char *format) +{ + Py_ssize_t len; + char *s; + if ( PyString_AsStringAndSize(data, &s, &len) == -1 ) + return 0; + else + return load_custom_icon(s, len, format); +} + +//------------------------------------------------------------------------ +/* +# +def free_custom_icon(icon_id): + """ + Frees an icon loaded with load_custom_icon() + """ + pass +# +*/ + +//------------------------------------------------------------------------ +/* +# +def asktext(max_text, defval, prompt): + """ + Asks for a long text + + @param max_text: Maximum text length + @param defval: The default value + @param prompt: The prompt value + @return: None or the entered string + """ + pass +# +*/ +PyObject *py_asktext(int max_text, const char *defval, const char *prompt) +{ + if ( max_text <= 0 ) + Py_RETURN_NONE; + + char *buf = new char[max_text]; + if ( buf == NULL ) + Py_RETURN_NONE; + + PyObject *py_ret; + if ( asktext(size_t(max_text), buf, defval, prompt) != NULL ) + { + py_ret = PyString_FromString(buf); + } + else + { + py_ret = Py_None; + Py_INCREF(py_ret); + } + delete [] buf; + return py_ret; +} + +//------------------------------------------------------------------------ +/* +# +def str2ea(addr): + """ + Converts a string express to EA. The expression evaluator may be called as well. + + @return: BADADDR or address value + """ + pass +# +*/ +ea_t py_str2ea(const char *str, ea_t screenEA = BADADDR) +{ + ea_t ea; + bool ok = str2ea(str, &ea, screenEA); + return ok ? ea : BADADDR; +} + +//------------------------------------------------------------------------ +/* +# +def process_ui_action(name, flags): + """ + Invokes an IDA Pro UI action by name + + @param name: action name + @param flags: Reserved. Must be zero + @return: Boolean + """ + pass +# +*/ +static bool py_process_ui_action(const char *name, int flags) +{ + return process_ui_action(name, flags, NULL); +} + +//------------------------------------------------------------------------ +/* +# +def del_menu_item(menu_ctx): + """ + Deletes a menu item previously added with add_menu_item() + + @param menu_ctx: value returned by add_menu_item() + @return: Boolean + """ + pass +# +*/ +static bool py_del_menu_item(PyObject *py_ctx) +{ + if ( !PyCObject_Check(py_ctx) ) + return false; + + py_add_del_menu_item_ctx *ctx = (py_add_del_menu_item_ctx *)PyCObject_AsVoidPtr(py_ctx); + + bool ok = del_menu_item(ctx->menupath.c_str()); + + if ( ok ) + { + Py_DECREF(ctx->cb_data); + delete ctx; + } + + return ok; +} + +//------------------------------------------------------------------------ +/* +# +def add_menu_item(menupath, name, hotkey, flags, callback, args): + """ + Adds a menu item + + @param menupath: path to the menu item after or before which the insertion will take place + @param name: name of the menu item (~x~ is used to denote Alt-x hot letter) + @param hotkey: hotkey for the menu item (may be empty) + @param flags: one of SETMENU_... consts + @param callback: function which gets called when the user selects the menu item. + The function callback is of the form: + def callback(*args): + pass + @param args: tuple containing the arguments + + @return: None or a menu context (to be used by del_menu_item()) + """ + pass +# +*/ +bool idaapi py_menu_item_callback(void *userdata); +static PyObject *py_add_menu_item( + const char *menupath, + const char *name, + const char *hotkey, + int flags, + PyObject *pyfunc, + PyObject *args) +{ + bool no_args; + + if ( args == Py_None ) + { + no_args = true; + args = PyTuple_New(0); + if ( args == NULL ) + return NULL; + } + else if ( !PyTuple_Check(args) ) + { + PyErr_SetString(PyExc_TypeError, "args must be a tuple or None"); + return NULL; + } + else + { + no_args = false; + } + + // Form a tuple holding the function to be called and its arguments + PyObject *cb_data = Py_BuildValue("(OO)", pyfunc, args); + + // If we created an empty tuple, then we must free it + if ( no_args ) + Py_DECREF(args); + + // Add the menu item + bool b = add_menu_item(menupath, name, hotkey, flags, py_menu_item_callback, (void *)cb_data); + + if ( !b ) + { + Py_XDECREF(cb_data); + Py_RETURN_NONE; + } + // Create a context (for the delete_menu_item()) + py_add_del_menu_item_ctx *ctx = new py_add_del_menu_item_ctx(); + + // Form the complete menu path + ctx->menupath = menupath; + ctx->menupath.append(name); + // Save callback data + ctx->cb_data = cb_data; + + // Return context to user + return PyCObject_FromVoidPtr(ctx, NULL); +} + +//------------------------------------------------------------------------ +/* +# +def set_dock_pos(src, dest, orient, left = 0, top = 0, right = 0, bottom = 0): + """ + Sets the dock orientation of a window relatively to another window. + + @param src: Source docking control + @param dest: Destination docking control + @param orient: One of DOR_XXXX constants + @param left, top, right, bottom: These parameter if DOR_FLOATING is used, or if you want to specify the width of docked windows + @return: Boolean + + Example: + set_dock_pos('Structures', 'Enums', DOR_RIGHT) <- docks the Structures window to the right of Enums window + """ + pass +# +*/ + +//------------------------------------------------------------------------ +/* +# +def is_idaq(): + """ + Returns True or False depending if IDAPython is hosted by IDAQ + """ +# +*/ + +//--------------------------------------------------------------------------- +// UI hooks +//--------------------------------------------------------------------------- +int idaapi UI_Callback(void *ud, int notification_code, va_list va); +/* +# +class UI_Hooks(object): + def hook(self): + """ + Creates an UI hook + + @return: Boolean true on success + """ + pass + + def unhook(self): + """ + Removes the UI hook + @return: Boolean true on success + """ + pass + + def preprocess(self, name): + """ + IDA ui is about to handle a user command + + @param name: ui command name + (these names can be looked up in ida[tg]ui.cfg) + @return: 0-ok, nonzero - a plugin has handled the command + """ + + def postprocess(self): + """ + An ida ui command has been handled + + @return: Ignored + """ + +# +*/ +class UI_Hooks +{ +public: + virtual ~UI_Hooks() + { + } + + bool hook() + { + return hook_to_notification_point(HT_UI, UI_Callback, this); + } + + bool unhook() + { + return unhook_from_notification_point(HT_UI, UI_Callback, this); + } + + virtual int preprocess(const char *name) + { + return 0; + } + + virtual void postprocess() + { + } }; + +//--------------------------------------------------------------------------- +uint32 idaapi choose_sizer(void *self) +{ + PyObject *pyres; + uint32 res; + + PYW_GIL_ENSURE; + pyres = PyObject_CallMethod((PyObject *)self, "sizer", ""); + PYW_GIL_RELEASE; + + res = PyInt_AsLong(pyres); + Py_DECREF(pyres); + return res; +} + +//--------------------------------------------------------------------------- +char *idaapi choose_getl(void *self, uint32 n, char *buf) +{ + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod( + (PyObject *)self, + "getl", + "l", + n); + PYW_GIL_RELEASE; + + const char *res; + if (pyres == NULL || (res = PyString_AsString(pyres)) == NULL ) + qstrncpy(buf, "", MAXSTR); + else + qstrncpy(buf, res, MAXSTR); + + Py_XDECREF(pyres); + return buf; +} + +//--------------------------------------------------------------------------- +void idaapi choose_enter(void *self, uint32 n) +{ + PYW_GIL_ENSURE; + Py_XDECREF(PyObject_CallMethod((PyObject *)self, "enter", "l", n)); + PYW_GIL_RELEASE; +} + +//--------------------------------------------------------------------------- +uint32 choose_choose( + void *self, + int flags, + int x0,int y0, + int x1,int y1, + int width) +{ + PyObject *pytitle = PyObject_GetAttrString((PyObject *)self, "title"); + const char *title = pytitle != NULL ? PyString_AsString(pytitle) : "Choose"; + + int r = choose( + flags, + x0, y0, + x1, y1, + self, + width, + choose_sizer, + choose_getl, + title, + 1, + 1, + NULL, /* del */ + NULL, /* inst */ + NULL, /* update */ + NULL, /* edit */ + choose_enter, + NULL, /* destroy */ + NULL, /* popup_names */ + NULL);/* get_icon */ + Py_XDECREF(pytitle); + return r; +} + + +PyObject *choose2_find(const char *title); +int choose2_add_command(PyObject *self, const char *caption, int flags, int menu_index, int icon); +void choose2_refresh(PyObject *self); +void choose2_close(PyObject *self); +int choose2_create(PyObject *self, bool embedded); +void choose2_activate(PyObject *self); +PyObject *choose2_get_embedded(PyObject *self); +PyObject *choose2_get_embedded_selection(PyObject *self); + + +#define DECLARE_FORM_ACTIONS form_actions_t *fa = (form_actions_t *)p_fa; + +//--------------------------------------------------------------------------- +static bool formchgcbfa_enable_field(size_t p_fa, int fid, bool enable) +{ + DECLARE_FORM_ACTIONS; + return fa->enable_field(fid, enable); +} + +//--------------------------------------------------------------------------- +static bool formchgcbfa_show_field(size_t p_fa, int fid, bool show) +{ + DECLARE_FORM_ACTIONS; + return fa->show_field(fid, show); +} + +//--------------------------------------------------------------------------- +static bool formchgcbfa_move_field( + size_t p_fa, + int fid, + int x, + int y, + int w, + int h) +{ + DECLARE_FORM_ACTIONS; + return fa->move_field(fid, x, y, w, h); +} + +//--------------------------------------------------------------------------- +static int formchgcbfa_get_focused_field(size_t p_fa) +{ + DECLARE_FORM_ACTIONS; + return fa->get_focused_field(); +} + +//--------------------------------------------------------------------------- +static bool formchgcbfa_set_focused_field(size_t p_fa, int fid) +{ + DECLARE_FORM_ACTIONS; + return fa->set_focused_field(fid); +} + +//--------------------------------------------------------------------------- +static void formchgcbfa_refresh_field(size_t p_fa, int fid) +{ + DECLARE_FORM_ACTIONS; + return fa->refresh_field(fid); +} + +//--------------------------------------------------------------------------- +static void formchgcbfa_set_field_value( + size_t p_fa, + int fid, + int ft, + PyObject *py_val, + size_t sz) +{ + DECLARE_FORM_ACTIONS; + return fa->refresh_field(fid); +} + +//--------------------------------------------------------------------------- +static PyObject *formchgcbfa_get_field_value( + size_t p_fa, + int fid, + int ft, + size_t sz) +{ + DECLARE_FORM_ACTIONS; + switch ( ft ) + { + // button - uint32 + case 4: + { + uint32 val; + if ( fa->get_field_value(fid, &val) ) + return PyLong_FromUnsignedLong(val); + break; + } + // ushort + case 2: + { + ushort val; + if ( fa->get_field_value(fid, &val) ) + return PyLong_FromUnsignedLong(val); + break; + } + // string label + case 1: + { + char val[MAXSTR]; + if ( fa->get_field_value(fid, val) ) + return PyString_FromString(val); + break; + } + // string input + case 3: + { + qstring val; + val.resize(sz + 1); + if ( fa->get_field_value(fid, val.begin()) ) + return PyString_FromString(val.begin()); + break; + } + case 5: + { + intvec_t intvec; + // Returned as 1-base + if (fa->get_field_value(fid, &intvec)) + { + // Make 0-based + for ( intvec_t::iterator it=intvec.begin(); it != intvec.end(); ++it) + (*it)--; + + return PyW_IntVecToPyList(intvec); + } + } + } + Py_RETURN_NONE; +} + +//--------------------------------------------------------------------------- +static bool formchgcbfa_set_field_value( + size_t p_fa, + int fid, + int ft, + PyObject *py_val) +{ + DECLARE_FORM_ACTIONS; + + switch ( ft ) + { + // button - uint32 + case 4: + { + uint32 val = PyLong_AsUnsignedLong(py_val); + return fa->set_field_value(fid, &val); + } + // ushort + case 2: + { + ushort val = PyLong_AsUnsignedLong(py_val) & 0xffff; + return fa->set_field_value(fid, &val); + } + // strings + case 3: + case 1: + return fa->set_field_value(fid, PyString_AsString(py_val)); + // intvec_t + case 5: + { + intvec_t intvec; + // Passed as 0-based + PyW_PyListToIntVec(py_val, intvec); + + // Make 1-based + for ( intvec_t::iterator it=intvec.begin(); it != intvec.end(); ++it) + (*it)++; + + bool ok = fa->set_field_value(fid, &intvec); + return ok; + } + // unknown + default: + return false; + } +} + +#undef DECLARE_FORM_ACTIONS +// +%} + +%{ +// +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +int idaapi UI_Callback(void *ud, int notification_code, va_list va) +{ + UI_Hooks *proxy = (UI_Hooks *)ud; + int ret = 0; + try + { + switch (notification_code) + { + case ui_preprocess: + { + const char *name = va_arg(va, const char *); + return proxy->preprocess(name); + } + + case ui_postprocess: + proxy->postprocess(); + break; + } + } + catch (Swig::DirectorException &) + { + msg("Exception in UI Hook function:\n"); + if ( PyErr_Occurred() ) + PyErr_Print(); + } + return ret; +} + //------------------------------------------------------------------------ bool idaapi py_menu_item_callback(void *userdata) { @@ -143,11 +744,13 @@ bool idaapi py_menu_item_callback(void *userdata) PyObject *func = PyTuple_GET_ITEM(userdata, 0); PyObject *args = PyTuple_GET_ITEM(userdata, 1); - // call the python function + // Call the python function + PYW_GIL_ENSURE; PyObject *result = PyEval_CallObject(func, args); + PYW_GIL_RELEASE; - // we cannot raise an exception in the callback, just print it. - if ( result == NULL ) + // We cannot raise an exception in the callback, just print it. + if ( result == NULL ) { PyErr_Print(); return false; @@ -160,14 +763,17 @@ bool idaapi py_menu_item_callback(void *userdata) - //------------------------------------------------------------------------ // Some defines #define POPUP_NAMES_COUNT 4 -#define MAX_CHOOSER_MENU_COMMANDS 12 +#define MAX_CHOOSER_MENU_COMMANDS 20 #define thisobj ((py_choose2_t *) obj) #define thisdecl py_choose2_t *_this = thisobj -#define MENU_COMMAND_CB(id) static uint32 idaapi s_menu_command_##id(void *obj, uint32 n) { return thisobj->on_command(id, int(n)); } +#define MENU_COMMAND_CB(id) \ + static uint32 idaapi s_menu_command_##id(void *obj, uint32 n) \ + { \ + return thisobj->on_command(id, int(n)); \ + } //------------------------------------------------------------------------ // Helper functions @@ -178,9 +784,7 @@ static pychoose2_to_choose2_map_t choosers; py_choose2_t *choose2_find_instance(PyObject *self) { pychoose2_to_choose2_map_t::iterator it = choosers.find(self); - if ( it == choosers.end() ) - return NULL; - return it->second; + return it == choosers.end() ? NULL : it->second; } void choose2_add_instance(PyObject *self, py_choose2_t *c2) @@ -201,88 +805,171 @@ class py_choose2_t private: enum { - CHOOSE2_HAVE_DEL = 0x0001, - CHOOSE2_HAVE_INS = 0x0002, - CHOOSE2_HAVE_UPDATE = 0x0004, - CHOOSE2_HAVE_EDIT = 0x0008, - CHOOSE2_HAVE_ENTER = 0x0010, - CHOOSE2_HAVE_GETICON = 0x0020, - CHOOSE2_HAVE_GETATTR = 0x0040, - CHOOSE2_HAVE_COMMAND = 0x0080, - CHOOSE2_HAVE_ONCLOSE = 0x0100 + CHOOSE2_HAVE_DEL = 0x0001, + CHOOSE2_HAVE_INS = 0x0002, + CHOOSE2_HAVE_UPDATE = 0x0004, + CHOOSE2_HAVE_EDIT = 0x0008, + CHOOSE2_HAVE_ENTER = 0x0010, + CHOOSE2_HAVE_GETICON = 0x0020, + CHOOSE2_HAVE_GETATTR = 0x0040, + CHOOSE2_HAVE_COMMAND = 0x0080, + CHOOSE2_HAVE_ONCLOSE = 0x0100, + CHOOSE2_HAVE_SELECT = 0x0200, + CHOOSE2_HAVE_REFRESHED = 0x0400, }; + // Chooser flags int flags; - int cb_flags; + + // Callback flags (to tell which callback exists and which not) + // One of CHOOSE2_HAVE_xxxx + unsigned int cb_flags; + chooser_info_t *embedded; + intvec_t embedded_sel; + + // Menu callback index (in the menu_cbs array) + int menu_cb_idx; + + // Chooser title qstring title; + + // Column widths + intvec_t widths; + + // Python object link PyObject *self; + // Chooser columns qstrvec_t cols; - // the number of declarations should follow the MAX_CHOOSER_MENU_COMMANDS value + const char **popup_names; + bool ui_cb_hooked; + + // The number of declarations should follow the MAX_CHOOSER_MENU_COMMANDS value MENU_COMMAND_CB(0) MENU_COMMAND_CB(1) MENU_COMMAND_CB(2) MENU_COMMAND_CB(3) MENU_COMMAND_CB(4) MENU_COMMAND_CB(5) MENU_COMMAND_CB(6) MENU_COMMAND_CB(7) MENU_COMMAND_CB(8) MENU_COMMAND_CB(9) MENU_COMMAND_CB(10) MENU_COMMAND_CB(11) + MENU_COMMAND_CB(12) MENU_COMMAND_CB(13) + MENU_COMMAND_CB(14) MENU_COMMAND_CB(15) + MENU_COMMAND_CB(16) MENU_COMMAND_CB(17) + MENU_COMMAND_CB(18) MENU_COMMAND_CB(19) static chooser_cb_t *menu_cbs[MAX_CHOOSER_MENU_COMMANDS]; - int menu_cb_idx; + //------------------------------------------------------------------------ // Static methods to dispatch to member functions //------------------------------------------------------------------------ static int idaapi ui_cb(void *obj, int notification_code, va_list va) { + // UI callback to handle chooser items with attributes if ( notification_code != ui_get_chooser_item_attrs ) return 0; + va_arg(va, void *); int n = int(va_arg(va, uint32)); chooser_item_attrs_t *attr = va_arg(va, chooser_item_attrs_t *); thisobj->on_get_line_attr(n, attr); return 1; } + + static void idaapi s_select(void *obj, const intvec_t &sel) + { + thisobj->on_select(sel); + } + + static void idaapi s_refreshed(void *obj) + { + thisobj->on_refreshed(); + } + static uint32 idaapi s_sizer(void *obj) { return (uint32)thisobj->on_get_size(); } + static void idaapi s_getl(void *obj, uint32 n, char * const *arrptr) { thisobj->on_get_line(int(n), arrptr); } + static uint32 idaapi s_del(void *obj, uint32 n) { return uint32(thisobj->on_delete_line(int(n))); } + static void idaapi s_ins(void *obj) { thisobj->on_insert_line(); } + static uint32 idaapi s_update(void *obj, uint32 n) { return uint32(thisobj->on_refresh(int(n))); } + static void idaapi s_edit(void *obj, uint32 n) { thisobj->on_edit_line(int(n)); } + static void idaapi s_enter(void * obj, uint32 n) { - thisobj->on_select_line(int(n)); + thisobj->on_enter(int(n)); } + static int idaapi s_get_icon(void *obj, uint32 n) { return thisobj->on_get_icon(int(n)); } + static void idaapi s_destroy(void *obj) { thisobj->on_close(); } -private: + //------------------------------------------------------------------------ // Member functions corresponding to each chooser2() callback //------------------------------------------------------------------------ + void clear_popup_names() + { + if ( popup_names == NULL ) + return; + + for ( int i=0; i=0; i-- ) { PyObject *item = PyList_GetItem(list, Py_ssize_t(i)); if ( item == NULL ) continue; + const char *str = PyString_AsString(item); if ( str != NULL ) qstrncpy(line_arr[i], str, MAXSTR); @@ -310,38 +1002,61 @@ private: size_t on_get_size() { + PYW_GIL_ENSURE; PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_GET_SIZE, NULL); + PYW_GIL_RELEASE; if ( pyres == NULL ) return 0; + size_t res = PyInt_AsLong(pyres); Py_DECREF(pyres); return res; } + void on_refreshed() + { + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_REFRESHED, NULL); + PYW_GIL_RELEASE; + Py_XDECREF(pyres); + } + + void on_select(const intvec_t &intvec) + { + PYW_GIL_ENSURE; + PyObject *py_list = PyW_IntVecToPyList(intvec); + PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_SELECT, "O", py_list); + PYW_GIL_RELEASE; + Py_XDECREF(pyres); + Py_XDECREF(py_list); + } + void on_close() { -#ifdef CH_ATTRS - if ( (flags & CH_ATTRS) != 0 ) - unhook_from_notification_point(HT_UI, ui_cb, this); -#endif // Call Python + PYW_GIL_ENSURE; PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_CLOSE, NULL); + PYW_GIL_RELEASE; Py_XDECREF(pyres); - Py_XDECREF(self); - // Remove from list - choose2_del_instance(self); - - // delete this instance if none modal - if ( (flags & CH_MODAL) == 0 ) + // Delete this instance if none modal and not embedded + if ( !is_modal() && get_embedded() == NULL ) delete this; } int on_delete_line(int lineno) { - PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_DELETE_LINE, "i", lineno - 1); + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod( + self, + (char *)S_ON_DELETE_LINE, + "i", + lineno - 1); + PYW_GIL_RELEASE; + if ( pyres == NULL ) return lineno; + size_t res = PyInt_AsLong(pyres); Py_DECREF(pyres); return res + 1; @@ -349,9 +1064,16 @@ private: int on_refresh(int lineno) { - PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_REFRESH, "i", lineno - 1); + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod( + self, + (char *)S_ON_REFRESH, + "i", + lineno - 1); + PYW_GIL_RELEASE; if ( pyres == NULL ) return lineno; + size_t res = PyInt_AsLong(pyres); Py_DECREF(pyres); return res + 1; @@ -359,27 +1081,50 @@ private: void on_insert_line() { + PYW_GIL_ENSURE; PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_INSERT_LINE, NULL); + PYW_GIL_RELEASE; Py_XDECREF(pyres); } - void on_select_line(int lineno) + void on_enter(int lineno) { - PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_SELECT_LINE, "i", lineno - 1); + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod( + self, + (char *)S_ON_SELECT_LINE, + "i", + lineno - 1); + PYW_GIL_RELEASE; Py_XDECREF(pyres); } void on_edit_line(int lineno) { - PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_EDIT_LINE, "i", lineno - 1); + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod( + self, + (char *)S_ON_EDIT_LINE, + "i", + lineno - 1); + PYW_GIL_RELEASE; Py_XDECREF(pyres); } int on_command(int cmd_id, int lineno) { - PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_COMMAND, "ii", lineno - 1, cmd_id); + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod( + self, + (char *)S_ON_COMMAND, + "ii", + lineno - 1, + cmd_id); + PYW_GIL_RELEASE; + if ( pyres==NULL ) return lineno; + size_t res = PyInt_AsLong(pyres); Py_XDECREF(pyres); return res; @@ -387,7 +1132,14 @@ private: int on_get_icon(int lineno) { - PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_GET_ICON, "i", lineno - 1); + PYW_GIL_ENSURE; + PyObject *pyres = PyObject_CallMethod( + self, + (char *)S_ON_GET_ICON, + "i", + lineno - 1); + PYW_GIL_RELEASE; + size_t res = PyInt_AsLong(pyres); Py_XDECREF(pyres); return res; @@ -395,7 +1147,10 @@ private: void on_get_line_attr(int lineno, chooser_item_attrs_t *attr) { + PYW_GIL_ENSURE; PyObject *pyres = PyObject_CallMethod(self, (char *)S_ON_GET_LINE_ATTR, "i", lineno - 1); + PYW_GIL_RELEASE; + if ( pyres == NULL ) return; @@ -414,12 +1169,23 @@ public: //------------------------------------------------------------------------ // Public methods //------------------------------------------------------------------------ - py_choose2_t() + py_choose2_t(): flags(0), cb_flags(0), + embedded(NULL), menu_cb_idx(0), + self(NULL), popup_names(NULL), ui_cb_hooked(false) { - flags = 0; - cb_flags = 0; - menu_cb_idx = 0; - self = NULL; + } + + ~py_choose2_t() + { + // Remove from list + choose2_del_instance(self); + + // Uninstall hooks + install_hooks(false); + + delete embedded; + Py_XDECREF(self); + clear_popup_names(); } static py_choose2_t *find_chooser(const char *title) @@ -429,6 +1195,7 @@ public: void close() { + // Will trigger on_close() close_chooser(title.c_str()); } @@ -437,83 +1204,106 @@ public: TForm *frm = find_tform(title.c_str()); if ( frm == NULL ) return false; + switchto_tform(frm, true); return true; } - int choose2( - int fl, - int ncols, - const int *widths, - const char *title, - int deflt = -1, - // An array of 4 strings: ("Insert", "Delete", "Edit", "Refresh" - const char * const *popup_names = NULL, - int icon = -1, - int x1 = -1, int y1 = -1, int x2 = -1, int y2 = -1) - { - flags = fl; - if ( (flags & CH_ATTRS) != 0 ) - { - if ( !hook_to_notification_point(HT_UI, ui_cb, this) ) - flags &= ~CH_ATTRS; - } - this->title = title; - return ::choose2( - flags, - x1, y1, x2, y2, - this, - ncols, widths, - s_sizer, - s_getl, - title, - icon, - deflt, - cb_flags & CHOOSE2_HAVE_DEL ? s_del : NULL, - cb_flags & CHOOSE2_HAVE_INS ? s_ins : NULL, - cb_flags & CHOOSE2_HAVE_UPDATE ? s_update : NULL, - cb_flags & CHOOSE2_HAVE_EDIT ? s_edit : NULL, - cb_flags & CHOOSE2_HAVE_ENTER ? s_enter : NULL, - s_destroy, - popup_names, - cb_flags & CHOOSE2_HAVE_GETICON ? s_get_icon : NULL); - } - - int add_command(const char *caption, int flags=0, int menu_index=-1, int icon=-1) + int add_command( + const char *caption, + int flags=0, + int menu_index=-1, + int icon=-1) { if ( menu_cb_idx >= MAX_CHOOSER_MENU_COMMANDS ) return -1; - bool ret = add_chooser_command(title.c_str(), caption, menu_cbs[menu_cb_idx], menu_index, icon, flags); - if ( !ret ) + // For embedded chooser, the "caption" will be overloaded to encode + // the AskUsingForm's title, caption and embedded chooser id + // Title:EmbeddedChooserID:Caption + char title_buf[MAXSTR]; + const char *ptitle; + + // Embedded chooser? + if ( get_embedded() != NULL ) + { + static const char delimiter[] = ":"; + char temp[MAXSTR]; + qstrncpy(temp, caption, sizeof(temp)); + + char *p = strtok(temp, delimiter); + if ( p == NULL ) + return -1; + + // Copy the title + char title_str[MAXSTR]; + qstrncpy(title_str, p, sizeof(title_str)); + + // Copy the echooser ID + p = strtok(NULL, delimiter); + if ( p == NULL ) + return -1; + + char id_str[10]; + qstrncpy(id_str, p, sizeof(id_str)); + + // Form the new title of the form: "AskUsingFormTitle:EchooserId" + qsnprintf(title_buf, sizeof(title_buf), "%s:%s", title_str, id_str); + + // Adjust the title + ptitle = title_buf; + + // Adjust the caption + p = strtok(NULL, delimiter); + caption += (p - temp); + } + else + { + ptitle = title.c_str(); + } + + if ( !add_chooser_command( + ptitle, + caption, + menu_cbs[menu_cb_idx], + menu_index, + icon, + flags)) + { return -1; + } return menu_cb_idx++; } - int show(PyObject *self) + // Create a chooser. + // If it detects the "embedded" attribute, then it will create a chooser_info_t structure + // Otherwise the chooser window is created and displayed + int create(PyObject *self) { PyObject *attr; - // get flags - if ( (attr = PyW_TryGetAttrString(self, S_FLAGS)) == NULL ) + + // Get flags + attr = PyW_TryGetAttrString(self, S_FLAGS); + if ( attr == NULL ) return -1; - int flags = PyInt_Check(attr) != 0 ? PyInt_AsLong(attr) : 0; + + flags = PyInt_Check(attr) != 0 ? PyInt_AsLong(attr) : 0; Py_DECREF(attr); - qstring title; - // get the title + // Get the title if ( !PyW_GetStringAttr(self, S_TITLE, &title) ) return -1; - // get columns - if ( (attr = PyW_TryGetAttrString(self, "cols")) == NULL ) + // Get columns + attr = PyW_TryGetAttrString(self, "cols"); + if ( attr == NULL ) return -1; - // get col count - int ncols = PyList_Size(attr); + // Get col count + int ncols = int(PyList_Size(attr)); - // get cols caption and widthes - intvec_t widths; + // Get cols caption and widthes cols.qclear(); for ( int i=0; iself = self; - // Create chooser - int r = this->choose2( - flags, - ncols, - &widths[0], - title.c_str(), - deflt, - popup_names, - icon, - pts[0], - pts[1], - pts[2], - pts[3]); + // Hook to notification point (to handle chooser item attributes) + install_hooks(true); - // Clear temporary popup_names - if ( popup_names != NULL ) + // Check if *embedded + attr = PyW_TryGetAttrString(self, S_EMBEDDED); + if ( attr != NULL && PyObject_IsTrue(attr) == 1 ) { - for ( int i=0; iobj = this; + embedded->cb = sizeof(chooser_info_t); + embedded->title = title.c_str(); + embedded->columns = ncols; + embedded->deflt = deflt; + embedded->flags = flags; + embedded->width = pts[0]; // Take x1 + embedded->height = pts[1]; // Take y1 + embedded->icon = icon; + embedded->popup_names = popup_names; + embedded->widths = widths.begin(); + embedded->destroyer = s_destroy; + embedded->getl = s_getl; + embedded->sizer = s_sizer; + embedded->del = (cb_flags & CHOOSE2_HAVE_DEL) != 0 ? s_del : NULL; + embedded->edit = (cb_flags & CHOOSE2_HAVE_EDIT) != 0 ? s_edit : NULL; + embedded->enter = (cb_flags & CHOOSE2_HAVE_ENTER) != 0 ? s_enter : NULL; + embedded->get_icon = (cb_flags & CHOOSE2_HAVE_GETICON) != 0 ? s_get_icon : NULL; + embedded->ins = (cb_flags & CHOOSE2_HAVE_INS) != 0 ? s_ins : NULL; + embedded->update = (cb_flags & CHOOSE2_HAVE_UPDATE) != 0 ? s_update : NULL; + // Fill callbacks that are only present in idaq + if ( is_idaq() ) + { + embedded->select = (cb_flags & CHOOSE2_HAVE_SELECT) != 0 ? s_select : NULL; + embedded->refresh = (cb_flags & CHOOSE2_HAVE_REFRESHED)!= 0 ? s_refreshed : NULL; + } + else + { + embedded->select = NULL; + embedded->refresh = NULL; + } } + Py_XDECREF(attr); - // Modal chooser return the index of the selected item - if ( (flags & CH_MODAL) != 0 ) - r--; + // Create the chooser (if not embedded) + int r; + if ( embedded == NULL ) + { + r = ::choose2( + flags, + pts[0], pts[1], pts[2], pts[3], + this, + ncols, + &widths[0], + s_sizer, + s_getl, + title.c_str(), + icon, + deflt, + (cb_flags & CHOOSE2_HAVE_DEL) != 0 ? s_del : NULL, + (cb_flags & CHOOSE2_HAVE_INS) != 0 ? s_ins : NULL, + (cb_flags & CHOOSE2_HAVE_UPDATE) != 0 ? s_update : NULL, + (cb_flags & CHOOSE2_HAVE_EDIT) != 0 ? s_edit : NULL, + (cb_flags & CHOOSE2_HAVE_ENTER) != 0 ? s_enter : NULL, + s_destroy, + popup_names, + (cb_flags & CHOOSE2_HAVE_GETICON) != 0 ? s_get_icon : NULL); + + clear_popup_names(); + + // Modal chooser return the index of the selected item + if ( is_modal() ) + r--; + } + // Embedded chooser? + else + { + // Return success + r = 1; + } return r; } @@ -671,6 +1516,21 @@ public: { refresh_chooser(title.c_str()); } + + bool is_modal() + { + return (flags & CH_MODAL) != 0; + } + + intvec_t *get_sel_vec() + { + return &embedded_sel; + } + + chooser_info_t *get_embedded() const + { + return embedded; + } }; //------------------------------------------------------------------------ @@ -683,7 +1543,11 @@ chooser_cb_t *py_choose2_t::menu_cbs[MAX_CHOOSER_MENU_COMMANDS] = DECL_MENU_COMMAND_CB(4), DECL_MENU_COMMAND_CB(5), DECL_MENU_COMMAND_CB(6), DECL_MENU_COMMAND_CB(7), DECL_MENU_COMMAND_CB(8), DECL_MENU_COMMAND_CB(9), - DECL_MENU_COMMAND_CB(10), DECL_MENU_COMMAND_CB(11) + DECL_MENU_COMMAND_CB(10), DECL_MENU_COMMAND_CB(11), + DECL_MENU_COMMAND_CB(12), DECL_MENU_COMMAND_CB(13), + DECL_MENU_COMMAND_CB(14), DECL_MENU_COMMAND_CB(15), + DECL_MENU_COMMAND_CB(16), DECL_MENU_COMMAND_CB(17), + DECL_MENU_COMMAND_CB(18), DECL_MENU_COMMAND_CB(19) }; #undef DECL_MENU_COMMAND_CB @@ -694,25 +1558,55 @@ chooser_cb_t *py_choose2_t::menu_cbs[MAX_CHOOSER_MENU_COMMANDS] = #undef MENU_COMMAND_CB //------------------------------------------------------------------------ -int choose2_show(PyObject *self) +int choose2_create(PyObject *self, bool embedded) { - py_choose2_t *c2 = choose2_find_instance(self); + py_choose2_t *c2; + + c2 = choose2_find_instance(self); if ( c2 != NULL ) { - c2->activate(); + if ( !embedded ) + c2->activate(); return 1; } + c2 = new py_choose2_t(); + choose2_add_instance(self, c2); - return c2->show(self); + + int r = c2->create(self); + // Non embedded chooser? Return immediately + if ( !embedded ) + return r; + + // Embedded chooser was not created? + if ( c2->get_embedded() == NULL || r != 1 ) + { + delete c2; + r = 0; + } + return r; } //------------------------------------------------------------------------ void choose2_close(PyObject *self) { py_choose2_t *c2 = choose2_find_instance(self); - if ( c2 != NULL ) + if ( c2 == NULL ) + return; + + // Modal or embedded chooser? + if ( c2->get_embedded() != NULL || c2->is_modal() ) + { + // Then simply delete the instance + delete c2; + } + else + { + // Close the chooser. + // In turn this will lead to the deletion of the object c2->close(); + } } //------------------------------------------------------------------------ @@ -732,7 +1626,45 @@ void choose2_activate(PyObject *self) } //------------------------------------------------------------------------ -int choose2_add_command(PyObject *self, const char *caption, int flags=0, int menu_index=-1, int icon=-1) +PyObject *choose2_get_embedded_selection(PyObject *self) +{ + py_choose2_t *c2 = choose2_find_instance(self); + chooser_info_t *embedded; + + if ( c2 == NULL || (embedded = c2->get_embedded()) == NULL ) + Py_RETURN_NONE; + + // Returned as 1-based + intvec_t &intvec = *c2->get_sel_vec(); + + // Make 0-based + for ( intvec_t::iterator it=intvec.begin(); it != intvec.end(); ++it) + (*it)--; + + return PyW_IntVecToPyList(intvec); +} + +//------------------------------------------------------------------------ +PyObject *choose2_get_embedded(PyObject *self) +{ + py_choose2_t *c2 = choose2_find_instance(self); + chooser_info_t *embedded; + + if ( c2 == NULL || (embedded = c2->get_embedded()) == NULL ) + Py_RETURN_NONE; + else + return Py_BuildValue("(KK)", + PY_ULONG_LONG(embedded), + PY_ULONG_LONG(c2->get_sel_vec())); +} + +//------------------------------------------------------------------------ +int choose2_add_command( + PyObject *self, + const char *caption, + int flags=0, + int menu_index=-1, + int icon=-1) { py_choose2_t *c2 = choose2_find_instance(self); if ( c2 != NULL ) @@ -745,125 +1677,14 @@ int choose2_add_command(PyObject *self, const char *caption, int flags=0, int me PyObject *choose2_find(const char *title) { py_choose2_t *c2 = py_choose2_t::find_chooser(title); - if ( c2 == NULL ) - return NULL; - return c2->get_self(); + return c2 == NULL ? NULL : c2->get_self(); } -class plgform_t +static size_t py_get_AskUsingForm() { -private: - PyObject *py_obj; - TForm *form; - - static int idaapi s_callback(void *ud, int notification_code, va_list va) - { - plgform_t *_this = (plgform_t *)ud; - if ( notification_code == ui_tform_visible ) - { - TForm *form = va_arg(va, TForm *); - if ( form == _this->form ) - { - PyObject *py_result = PyObject_CallMethod( - _this->py_obj, - (char *)S_ON_CREATE, "O", - PyCObject_FromVoidPtr(form, NULL)); - - PyW_ShowErr(S_ON_CREATE); - Py_XDECREF(py_result); - } - } - else if ( notification_code == ui_tform_invisible ) - { - TForm *form = va_arg(va, TForm *); - if ( form == _this->form ) - { - PyObject *py_result = PyObject_CallMethod( - _this->py_obj, - (char *)S_ON_CLOSE, "O", - PyCObject_FromVoidPtr(form, NULL)); - - PyW_ShowErr(S_ON_CLOSE); - Py_XDECREF(py_result); - - _this->unhook(); - } - } - return 0; - } - - void unhook() - { - unhook_from_notification_point(HT_UI, s_callback, this); - form = NULL; - - // Call DECREF at last, since it may trigger __del__ - Py_XDECREF(py_obj); - } - -public: - plgform_t(): py_obj(NULL), form(NULL) - { - } - - bool show( - PyObject *obj, - const char *caption, - int options) - { - // Already displayed? - TForm *f = find_tform(caption); - if ( f != NULL ) - { - // Our form? - if ( f == form ) - { - // Switch to it - switchto_tform(form, true); - return true; - } - // Fail to create - return false; - } - // Create a form - form = create_tform(caption, NULL); - if ( form == NULL ) - return false; - - if ( !hook_to_notification_point(HT_UI, s_callback, this) ) - { - form = NULL; - return false; - } - - py_obj = obj; - Py_INCREF(obj); - - if ( is_idaq() ) - options |= FORM_QWIDGET; - - this->form = form; - open_tform(form, options); - return true; - } - - void close(int options = 0) - { - if ( form != NULL ) - close_tform(form, options); - } - - static PyObject *__new__() - { - return PyCObject_FromVoidPtr(new plgform_t(), __del__); - } - - static void __del__(void *obj) - { - delete (plgform_t *)obj; - } -}; + return (size_t)AskUsingForm_c; +} // %} @@ -930,9 +1751,16 @@ private: // Returns: true-executed line, false-ask for more lines bool on_execute_line(const char *line) { - PyObject *result = PyObject_CallMethod(self, (char *)S_ON_EXECUTE_LINE, "s", line); + PYW_GIL_ENSURE; + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_EXECUTE_LINE, + "s", + line); + PYW_GIL_RELEASE; + bool ok = result != NULL && PyObject_IsTrue(result); - PyW_ShowErr(S_ON_EXECUTE_LINE); + PyW_ShowCbErr(S_ON_EXECUTE_LINE); Py_XDECREF(result); return ok; } @@ -956,6 +1784,7 @@ private: int *vk_key, int shift) { + PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod( self, (char *)S_ON_KEYDOWN, @@ -965,10 +1794,11 @@ private: *p_sellen, *vk_key, shift); + PYW_GIL_RELEASE; bool ok = result != NULL && PyTuple_Check(result); - PyW_ShowErr(S_ON_KEYDOWN); + PyW_ShowCbErr(S_ON_KEYDOWN); if ( ok ) { @@ -1005,9 +1835,19 @@ private: const char *line, int x) { - PyObject *result = PyObject_CallMethod(self, (char *)S_ON_COMPLETE_LINE, "sisi", prefix, n, line, x); + PYW_GIL_ENSURE; + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_COMPLETE_LINE, + "sisi", + prefix, + n, + line, + x); + PYW_GIL_RELEASE; + bool ok = result != NULL && PyString_Check(result); - PyW_ShowErr(S_ON_COMPLETE_LINE); + PyW_ShowCbErr(S_ON_COMPLETE_LINE); if ( ok ) *completion = PyString_AsString(result); @@ -1125,6 +1965,164 @@ const py_cli_cbs_t py_cli_t::py_cli_cbs[MAX_PY_CLI] = #undef DECL_PY_CLI_CB // +// +//--------------------------------------------------------------------------- +class plgform_t +{ +private: + PyObject *py_obj; + TForm *form; + + static int idaapi s_callback(void *ud, int notification_code, va_list va) + { + plgform_t *_this = (plgform_t *)ud; + if ( notification_code == ui_tform_visible ) + { + TForm *form = va_arg(va, TForm *); + if ( form == _this->form ) + { + // Qt: QWidget* + // G: HWND + // We wrap and pass as a CObject in the hope that a Python UI framework + // can unwrap a CObject and get the hwnd/widget back + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + _this->py_obj, + (char *)S_ON_CREATE, "O", + PyCObject_FromVoidPtr(form, NULL)); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ON_CREATE); + Py_XDECREF(py_result); + } + } + else if ( notification_code == ui_tform_invisible ) + { + TForm *form = va_arg(va, TForm *); + if ( form == _this->form ) + { + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + _this->py_obj, + (char *)S_ON_CLOSE, "O", + PyCObject_FromVoidPtr(form, NULL)); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ON_CLOSE); + Py_XDECREF(py_result); + + _this->unhook(); + } + } + return 0; + } + + void unhook() + { + unhook_from_notification_point(HT_UI, s_callback, this); + form = NULL; + + // Call DECREF at last, since it may trigger __del__ + Py_XDECREF(py_obj); + } + +public: + plgform_t(): py_obj(NULL), form(NULL) + { + } + + bool show( + PyObject *obj, + const char *caption, + int options) + { + // Already displayed? + TForm *f = find_tform(caption); + if ( f != NULL ) + { + // Our form? + if ( f == form ) + { + // Switch to it + switchto_tform(form, true); + return true; + } + // Fail to create + return false; + } + + // Create a form + form = create_tform(caption, NULL); + if ( form == NULL ) + return false; + + if ( !hook_to_notification_point(HT_UI, s_callback, this) ) + { + form = NULL; + return false; + } + + py_obj = obj; + Py_INCREF(obj); + + if ( is_idaq() ) + options |= FORM_QWIDGET; + + this->form = form; + open_tform(form, options); + return true; + } + + void close(int options = 0) + { + if ( form != NULL ) + close_tform(form, options); + } + + static PyObject *create() + { + return PyCObject_FromVoidPtr(new plgform_t(), destroy); + } + + static void destroy(void *obj) + { + delete (plgform_t *)obj; + } +}; +// +%} + +%inline %{ +// +//--------------------------------------------------------------------------- +#define DECL_PLGFORM plgform_t *plgform = (plgform_t *) PyCObject_AsVoidPtr(py_link); +static PyObject *plgform_new() +{ + return plgform_t::create(); +} + +static bool plgform_show( + PyObject *py_link, + PyObject *py_obj, + const char *caption, + int options = FORM_MDI|FORM_TAB|FORM_MENU|FORM_RESTORE) +{ + DECL_PLGFORM; + return plgform->show(py_obj, caption, options); +} + +static void plgform_close( + PyObject *py_link, + int options) +{ + DECL_PLGFORM; + plgform->close(options); +} +#undef DECL_PLGFORM +// +%} + +%{ // //--------------------------------------------------------------------------- // Base class for all custviewer place_t providers @@ -1143,27 +2141,27 @@ private: strvec_t lines; simpleline_place_t pl_min, pl_max; public: - + void *get_ud() { return &lines; } - + place_t *get_min() { return &pl_min; } - + place_t *get_max() { return &pl_max; } - + strvec_t &get_lines() { return lines; } - + void set_minmax(size_t start=0, size_t end=size_t(-1)) { if ( start == 0 && end == size_t(-1) ) @@ -1178,7 +2176,7 @@ public: pl_max.n = end; } } - + bool set_line(size_t nline, simpleline_t &sl) { if ( nline >= lines.size() ) @@ -1186,7 +2184,7 @@ public: lines[nline] = sl; return true; } - + bool del_line(size_t nline) { if ( nline >= lines.size() ) @@ -1194,17 +2192,17 @@ public: lines.erase(lines.begin()+nline); return true; } - + void add_line(simpleline_t &line) { lines.push_back(line); } - + void add_line(const char *str) { lines.push_back(simpleline_t(str)); } - + bool insert_line(size_t nline, simpleline_t &line) { if ( nline >= lines.size() ) @@ -1212,7 +2210,7 @@ public: lines.insert(lines.begin()+nline, line); return true; } - + bool patch_line(size_t nline, size_t offs, int value) { if ( nline >= lines.size() ) @@ -1221,12 +2219,12 @@ public: L[offs] = (uchar) value & 0xFF; return true; } - + const size_t to_lineno(place_t *pl) const { return ((simpleline_place_t *)pl)->n; } - + bool curline(place_t *pl, size_t *n) { if ( pl == NULL ) @@ -1235,22 +2233,22 @@ public: *n = to_lineno(pl); return true; } - + simpleline_t *get_line(size_t nline) { return nline >= lines.size() ? NULL : &lines[nline]; } - + simpleline_t *get_line(place_t *pl) { return pl == NULL ? NULL : get_line(((simpleline_place_t *)pl)->n); } - + const size_t count() const { return lines.size(); } - + void clear_lines() { lines.clear(); @@ -1303,10 +2301,15 @@ private: cvw_popupmap_t::iterator it = _global_popup_map.find(mid); if ( it == _global_popup_map.end() ) return false; + return it->second.cv->on_popup_menu(it->second.menu_id); } - static bool idaapi s_cv_keydown(TCustomControl * /*cv*/, int vk_key, int shift, void *ud) + static bool idaapi s_cv_keydown( + TCustomControl * /*cv*/, + int vk_key, + int shift, + void *ud) { customviewer_t *_this = (customviewer_t *)ud; return _this->on_keydown(vk_key, shift); @@ -1352,7 +2355,10 @@ private: place_t *place = va_arg(va, place_t *); int *important_lines = va_arg(va, int *); qstring &hint = *va_arg(va, qstring *); - return ((_this->_features & HAVE_HINT) == 0 || place == NULL || _this->_cv != viewer) ? 0 : (_this->on_hint(place, important_lines, hint) ? 1 : 0); + if ( (_this->_features & HAVE_HINT) == 0 || place == NULL || _this->_cv != viewer ) + return 0; + else + return _this->on_hint(place, important_lines, hint) ? 1 : 0; } case ui_tform_invisible: @@ -1492,13 +2498,13 @@ public: // find the beginning of the word const char *ptr = line + x; - while ( ptr > line && !isspace(ptr[-1]) ) + while ( ptr > line && !qisspace(ptr[-1]) ) ptr--; // find the end of the word const char *begin = ptr; ptr = line + x; - while ( !isspace(*ptr) && *ptr != '\0' ) + while ( !qisspace(*ptr) && *ptr != '\0' ) ptr++; word.qclear(); @@ -1634,14 +2640,11 @@ public: //-------------------------------------------------------------------------- bool show() { - // closed already? + // Closed already? if ( _form == NULL ) return false; open_tform(_form, FORM_TAB|FORM_MENU|FORM_RESTORE); - int x, y; - get_place(false, &x, &y); - msg("curplace after open: %d %d\n", x, y); return true; } }; @@ -1672,6 +2675,7 @@ private: PyObject *py_val = PyTuple_GetItem(py, 0); if ( !PyString_Check(py_val) ) return false; + sl.line = PyString_AsString(py_val); if ( (sz > 1) && (py_val = PyTuple_GetItem(py, 1)) && PyLong_Check(py_val) ) @@ -1679,6 +2683,7 @@ private: if ( (sz > 2) && (py_val = PyTuple_GetItem(py, 2)) && PyLong_Check(py_val) ) sl.bgcolor = PyLong_AsUnsignedLong(py_val); + return true; } @@ -1687,8 +2692,10 @@ private: // virtual bool on_click(int shift) { + PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_CLICK, "i", shift); - PyW_ShowErr(S_ON_CLICK); + PYW_GIL_RELEASE; + PyW_ShowCbErr(S_ON_CLICK); bool ok = py_result != NULL && PyObject_IsTrue(py_result); Py_XDECREF(py_result); return ok; @@ -1698,8 +2705,10 @@ private: // OnDblClick virtual bool on_dblclick(int shift) { + PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_DBL_CLICK, "i", shift); - PyW_ShowErr(S_ON_DBL_CLICK); + PYW_GIL_RELEASE; + PyW_ShowCbErr(S_ON_DBL_CLICK); bool ok = py_result != NULL && PyObject_IsTrue(py_result); Py_XDECREF(py_result); return ok; @@ -1709,8 +2718,10 @@ private: // OnCurorPositionChanged virtual void on_curpos_changed() { + PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_CURSOR_POS_CHANGED, NULL); - PyW_ShowErr(S_ON_CURSOR_POS_CHANGED); + PYW_GIL_RELEASE; + PyW_ShowCbErr(S_ON_CURSOR_POS_CHANGED); Py_XDECREF(py_result); } @@ -1721,8 +2732,11 @@ private: // Call the close method if it is there and the object is still bound if ( (features & HAVE_CLOSE) != 0 && py_self != NULL ) { + PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_CLOSE, NULL); - PyW_ShowErr(S_ON_CLOSE); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ON_CLOSE); Py_XDECREF(py_result); // Cleanup @@ -1735,8 +2749,16 @@ private: // OnKeyDown virtual bool on_keydown(int vk_key, int shift) { - PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_KEYDOWN, "ii", vk_key, shift); - PyW_ShowErr(S_ON_KEYDOWN); + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + py_self, + (char *)S_ON_KEYDOWN, + "ii", + vk_key, + shift); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ON_KEYDOWN); bool ok = py_result != NULL && PyObject_IsTrue(py_result); Py_XDECREF(py_result); return ok; @@ -1746,8 +2768,14 @@ private: // OnPopupShow virtual bool on_popup() { - PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_POPUP, NULL); - PyW_ShowErr(S_ON_POPUP); + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + py_self, + (char *)S_ON_POPUP, + NULL); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ON_POPUP); bool ok = py_result != NULL && PyObject_IsTrue(py_result); Py_XDECREF(py_result); return ok; @@ -1758,8 +2786,15 @@ private: virtual bool on_hint(place_t *place, int *important_lines, qstring &hint) { size_t ln = data.to_lineno(place); - PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_HINT, PY_FMT64, pyul_t(ln)); - PyW_ShowErr(S_ON_HINT); + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + py_self, + (char *)S_ON_HINT, + PY_FMT64, + pyul_t(ln)); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ON_HINT); bool ok = py_result != NULL && PyTuple_Check(py_result) && PyTuple_Size(py_result) == 2; if ( ok ) { @@ -1769,6 +2804,7 @@ private: if ( important_lines != NULL ) *important_lines = PyInt_AsLong(py_nlines); + hint = PyString_AsString(py_hint); } Py_XDECREF(py_result); @@ -1779,8 +2815,15 @@ private: // OnPopupMenuClick virtual bool on_popup_menu(size_t menu_id) { - PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_POPUP_MENU, PY_FMT64, pyul_t(menu_id)); - PyW_ShowErr(S_ON_POPUP_MENU); + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallMethod( + py_self, + (char *)S_ON_POPUP_MENU, + PY_FMT64, + pyul_t(menu_id)); + PYW_GIL_RELEASE; + + PyW_ShowCbErr(S_ON_POPUP_MENU); bool ok = py_result != NULL && PyObject_IsTrue(py_result); Py_XDECREF(py_result); return ok; @@ -1809,6 +2852,7 @@ public: simpleline_t sl; if ( !py_to_simpleline(py_sl, sl) ) return false; + return data.set_line(nline, sl); } @@ -1928,6 +2972,7 @@ public: // Return a reference to the C++ instance (only once) if ( py_this == NULL ) py_this = PyCObject_FromVoidPtr(this, NULL); + return true; } @@ -1972,6 +3017,7 @@ public: Py_RETURN_NONE; return Py_BuildValue("(" PY_FMT64 PY_FMT64 PY_FMT64 PY_FMT64 ")", pyul_t(x1), pyul_t(y1), pyul_t(x2), pyul_t(y2)); } + static py_simplecustview_t *get_this(PyObject *py_this) { return PyCObject_Check(py_this) ? (py_simplecustview_t *) PyCObject_AsVoidPtr(py_this) : NULL; @@ -2194,408 +3240,6 @@ bool pyscv_edit_line(PyObject *py_this, size_t nline, PyObject *py_sl) // %} -%inline %{ -uint32 idaapi choose_sizer(void *self) -{ - PyObject *pyres; - uint32 res; - - pyres = PyObject_CallMethod((PyObject *)self, "sizer", ""); - res = PyInt_AsLong(pyres); - Py_DECREF(pyres); - return res; -} - -char * idaapi choose_getl(void *self, uint32 n, char *buf) -{ - PyObject *pyres; - char *res; - - pyres = PyObject_CallMethod((PyObject *)self, "getl", "l", n); - - if (!pyres) - { - strcpy(buf, ""); - return buf; - } - - res = PyString_AsString(pyres); - - if (res) - { - strncpy(buf, res, MAXSTR); - res = buf; - } - else - { - strcpy(buf, ""); - res = buf; - } - - Py_DECREF(pyres); - return res; -} - -void idaapi choose_enter(void *self, uint32 n) -{ - PyObject_CallMethod((PyObject *)self, "enter", "l", n); - return; -} - -uint32 choose_choose(void *self, - int flags, - int x0,int y0, - int x1,int y1, - int width) -{ - PyObject *pytitle; - const char *title; - if ((pytitle = PyObject_GetAttrString((PyObject *)self, "title"))) - { - title = PyString_AsString(pytitle); - } - else - { - title = "Choose"; - pytitle = NULL; - } - int r = choose( - flags, - x0, y0, - x1, y1, - self, - width, - &choose_sizer, - &choose_getl, - title, - 1, - 1, - NULL, /* del */ - NULL, /* inst */ - NULL, /* update */ - NULL, /* edit */ - &choose_enter, - NULL, /* destroy */ - NULL, /* popup_names */ - NULL /* get_icon */ - ); - Py_XDECREF(pytitle); - return r; -} - -// -//------------------------------------------------------------------------ - -//------------------------------------------------------------------------ -/* -# -def get_highlighted_identifier(flags = 0): - """ - Returns the currently highlighted identifier - - @param flags: reserved (pass 0) - @return: None or the highlighted identifier - """ - pass -# -*/ -static PyObject *py_get_highlighted_identifier(int flags = 0) -{ - char buf[MAXSTR]; - bool ok = get_highlighted_identifier(buf, sizeof(buf), flags); - if ( !ok ) - Py_RETURN_NONE; - else - return PyString_FromString(buf); -} - -//------------------------------------------------------------------------ -/* -# -def asktext(max_text, defval, prompt): - """ - Asks for a long text - - @param max_text: Maximum text length - @param defval: The default value - @param prompt: The prompt value - @return: None or the entered string - """ - pass -# -*/ -PyObject *py_asktext(int max_text, const char *defval, const char *prompt) -{ - if ( max_text <= 0 ) - Py_RETURN_NONE; - - char *buf = new char[max_text]; - if ( buf == NULL ) - Py_RETURN_NONE; - - PyObject *py_ret; - if ( asktext(size_t(max_text), buf, defval, prompt) != NULL ) - { - py_ret = PyString_FromString(buf); - } - else - { - py_ret = Py_None; - Py_INCREF(py_ret); - } - delete [] buf; - return py_ret; -} - -//------------------------------------------------------------------------ -/* -# -def str2ea(addr): - """ - Converts a string express to EA. The expression evaluator may be called as well. - - @return: BADADDR or address value - """ - pass -# -*/ -ea_t py_str2ea(const char *str, ea_t screenEA = BADADDR) -{ - ea_t ea; - bool ok = str2ea(str, &ea, screenEA); - return ok ? ea : BADADDR; -} - -//------------------------------------------------------------------------ -/* -# -def del_menu_item(menu_ctx): - """ - Deletes a menu item previously added with add_menu_item() - - @param menu_ctx: value returned by add_menu_item() - @return: Boolean - """ - pass -# -*/ -static bool py_del_menu_item(PyObject *py_ctx) -{ - if ( !PyCObject_Check(py_ctx) ) - return false; - - py_add_del_menu_item_ctx *ctx = (py_add_del_menu_item_ctx *)PyCObject_AsVoidPtr(py_ctx); - - bool ok = del_menu_item(ctx->menupath.c_str()); - - if ( ok ) - { - Py_DECREF(ctx->cb_data); - delete ctx; - } - - return ok; -} - -//------------------------------------------------------------------------ -/* -# -def add_menu_item(menupath, name, hotkey, flags, callback, args): - """ - Adds a menu item - - @param menupath: path to the menu item after or before which the insertion will take place - @param name: name of the menu item (~x~ is used to denote Alt-x hot letter) - @param hotkey: hotkey for the menu item (may be empty) - @param flags: one of SETMENU_... consts - @param callback: function which gets called when the user selects the menu item. - The function callback is of the form: - def callback(*args): - pass - @param args: tuple containing the arguments - - @return: None or a menu context (to be used by del_menu_item()) - """ - pass -# -*/ -static PyObject *py_add_menu_item( - const char *menupath, - const char *name, - const char *hotkey, - int flags, - PyObject *pyfunc, - PyObject *args) -{ - bool no_args; - - if ( args == Py_None ) - { - no_args = true; - args = PyTuple_New(0); - if ( args == NULL ) - return NULL; - } - else if ( !PyTuple_Check(args) ) - { - PyErr_SetString(PyExc_TypeError, "args must be a tuple or None"); - return NULL; - } - else - { - no_args = false; - } - - // Form a tuple holding the function to be called and its arguments - PyObject *cb_data = Py_BuildValue("(OO)", pyfunc, args); - - // If we created an empty tuple, then we must free it - if ( no_args ) - Py_DECREF(args); - - // Add the menu item - bool b = add_menu_item(menupath, name, hotkey, flags, py_menu_item_callback, (void *)cb_data); - - if ( !b ) - { - Py_XDECREF(cb_data); - Py_RETURN_NONE; - } - // Create a context (for the delete_menu_item()) - py_add_del_menu_item_ctx *ctx = new py_add_del_menu_item_ctx(); - - // Form the complete menu path - ctx->menupath = menupath; - ctx->menupath.append(name); - // Save callback data - ctx->cb_data = cb_data; - - // Return context to user - return PyCObject_FromVoidPtr(ctx, NULL); -} - -//------------------------------------------------------------------------ -/* -# - -MFF_FAST = 0x0000 -"""execute code as soon as possible -this mode is ok call ui related functions -that do not query the database.""" - -MFF_READ = 0x0001 -"""execute code only when ida is idle and it is safe to query the database. -this mode is recommended only for code that does not modify the database. -(nb: ida may be in the middle of executing another user request, for example it may be waiting for him to enter values into a modal dialog box)""" - -MFF_WRITE = 0x0002 -"""execute code only when ida is idle and it is safe to modify the database. in particular, this flag will suspend execution if there is -a modal dialog box on the screen this mode can be used to call any ida api function. MFF_WRITE implies MFF_READ""" - -def py_execute_sync(callable, reqf) - """ - Converts a string express to EA. The expression evaluator may be called as well. - - @param callable: A python callable object - @param reqf: one of MFF_ flags - @return: BADADDR or address value - """ - pass -# -//------------------------------------------------------------------------ -static int py_execute_sync(PyObject *py_callable, int reqf) -{ - if ( !PyCallable_Check(py_callable) ) - return -1; - - struct py_exec_request_t: exec_request_t - { - PyObject *py_callable; - virtual int idaapi execute(void) - { - PyGILState_STATE state = PyGILState_Ensure(); - PyObject *py_result = PyObject_CallFunctionObjArgs(py_callable, NULL); - int r = py_result == NULL || !PyInt_Check(py_result) ? -1 : PyInt_AsLong(py_result); - Py_XDECREF(py_result); - PyGILState_Release(state); - return r; - } - py_exec_request_t(PyObject *pyc): py_callable(pyc) - { - } - }; - py_exec_request_t req(py_callable); - return execute_sync(req, reqf); -} -*/ - -//------------------------------------------------------------------------ -/* -# -def set_dock_pos(src, dest, orient, left = 0, top = 0, right = 0, bottom = 0): - """ - Sets the dock orientation of a window relatively to another window. - - @param src: Source docking control - @param dest: Destination docking control - @param orient: One of DOR_XXXX constants - @param left, top, right, bottom: These parameter if DOR_FLOATING is used, or if you want to specify the width of docked windows - @return: Boolean - - Example: - set_dock_pos('Structures', 'Enums', DOR_RIGHT) <- docks the Structures window to the right of Enums window - """ - pass -# -*/ - -//------------------------------------------------------------------------ -/* -# -def is_idaq(): - """ - Returns True or False depending if IDAPython is hosted by IDAQ - """ -# -*/ - - - -PyObject *choose2_find(const char *title); -int choose2_add_command(PyObject *self, const char *caption, int flags, int menu_index, int icon); -void choose2_refresh(PyObject *self); -void choose2_close(PyObject *self); -int choose2_show(PyObject *self); -void choose2_activate(PyObject *self); - - -#define DECL_PLGFORM plgform_t *plgform = (plgform_t *) PyCObject_AsVoidPtr(py_link); -static PyObject *plgform_new() -{ - return plgform_t::__new__(); -} - -static bool plgform_show( - PyObject *py_link, - PyObject *py_obj, - const char *caption, - int options = FORM_MDI|FORM_TAB|FORM_MENU|FORM_RESTORE) -{ - DECL_PLGFORM; - return plgform->show(py_obj, caption, options); -} - -static void plgform_close( - PyObject *py_link, - int options) -{ - DECL_PLGFORM; - plgform->close(options); -} -#undef DECL_PLGFORM -// -%} - %include "kernwin.hpp" uint32 choose_choose(PyObject *self, int flags, @@ -2608,69 +3252,6 @@ uint32 choose_choose(PyObject *self, %pythoncode %{ -class Choose: - """ - Choose - class for choose() with callbacks - """ - def __init__(self, list, title, flags=0): - self.list = list - self.title = title - - self.flags = flags - self.x0 = -1 - self.x1 = -1 - self.y0 = -1 - self.y1 = -1 - - self.width = -1 - - # HACK: Add a circular reference for non-modal choosers. This prevents the GC - # from collecting the class object the callbacks need. Unfortunately this means - # that the class will never be collected, unless refhack is set to None explicitly. - if (flags & 1) == 0: - self.refhack = self - - def sizer(self): - """ - Callback: sizer - returns the length of the list - """ - return len(self.list) - - def getl(self, n): - """ - Callback: getl - get one item from the list - """ - if n == 0: - return self.title - if n <= self.sizer(): - return str(self.list[n-1]) - else: - return "" - - def ins(self): - pass - - def update(self, n): - pass - - def edit(self, n): - pass - - def enter(self, n): - print "enter(%d) called" % n - - def destroy(self): - pass - - def get_icon(self, n): - pass - - def choose(self): - """ - choose - Display the choose dialogue - """ - return _idaapi.choose_choose(self, self.flags, self.x0, self.y0, self.x1, self.y1, self.width) - # DP_LEFT = 0x0001 DP_TOP = 0x0002 @@ -2685,6 +3266,54 @@ DP_BEFORE = 0x0020 DP_RAW = 0x0040 DP_FLOATING = 0x0080 +# ---------------------------------------------------------------------- +def load_custom_icon(file_name=None, data=None, format=None): + """ + Loads a custom icon and returns an identifier that can be used with other APIs + + If file_name is passed then the other two arguments are ignored. + + @param file_name: The icon file name + @param data: The icon data + @param format: The icon data format + + @return: Icon id or 0 on failure. + Use free_custom_icon() to free it + """ + if file_name is not None: + return _idaapi.py_load_custom_icon_fn(file_name) + elif not (data is None and format is None): + return _idaapi.py_load_custom_icon_data(data, format) + else: + return 0 + +# ---------------------------------------------------------------------- +def asklong(defval, format): + res, val = _idaapi._asklong(defval, format) + + if res == 1: + return val + else: + return None + +# ---------------------------------------------------------------------- +def askaddr(defval, format): + res, ea = _idaapi._askaddr(defval, format) + + if res == 1: + return ea + else: + return None + +# ---------------------------------------------------------------------- +def askseg(defval, format): + res, sel = _idaapi._askseg(defval, format) + + if res == 1: + return sel + else: + return None + class Choose2(object): @@ -2695,10 +3324,17 @@ class Choose2(object): """ CH_MODAL = 0x01 + """Modal chooser""" + CH_MULTI = 0x02 + """Allow multi selection""" + CH_MULTI_EDIT = 0x04 CH_NOBTNS = 0x08 CH_ATTRS = 0x10 + CH_NOIDB = 0x20 + """use the chooser even without an open database, same as x0=-2""" + CH_BUILTIN_MASK = 0xF80000 # column flags (are specified in the widths array) @@ -2708,16 +3344,23 @@ class Choose2(object): CHCOL_DEC = 0x00030000 CHCOL_FORMAT = 0x00070000 - def __init__(self, title, cols, flags=0, popup_names=None, icon=-1, x1=-1, y1=-1, x2=-1, y2=-1, deflt=-1): - """Constructs a chooser window. + + def __init__(self, title, cols, flags=0, popup_names=None, + icon=-1, x1=-1, y1=-1, x2=-1, y2=-1, deflt=-1, + embedded=False, width=None, height=None): + """ + Constructs a chooser window. @param title: The chooser title @param cols: a list of colums; each list item is a list of two items - example: [ ["Address", 10 | Choose2.CHCOL_HEX], ["Name", 30 | CHCOL_PLAIN] ] + example: [ ["Address", 10 | Choose2.CHCOL_HEX], ["Name", 30 | Choose2.CHCOL_PLAIN] ] @param flags: One of CH_XXXX constants @param deflt: Default starting item @param popup_names: list of new captions to replace this list ["Insert", "Delete", "Edit", "Refresh"] - @param icon: Icon index (the icon should exist in ida resources) + @param icon: Icon index (the icon should exist in ida resources or an index to a custom loaded icon) @param x1, y1, x2, y2: The default location + @param embedded: Create as embedded chooser + @param width: Embedded chooser width + @param height: Embedded chooser height """ self.title = title self.flags = flags @@ -2729,33 +3372,85 @@ class Choose2(object): self.y1 = y1 self.x2 = x2 self.y2 = y2 + self.embedded = embedded + if embedded: + self.x1 = width + self.y1 = height + + + def Embedded(self): + """ + Creates an embedded chooser (as opposed to Show()) + @return: Returns 1 on success + """ + return _idaapi.choose2_create(self, True) + + + def GetEmbSelection(self): + """ + Returns the selection associated with an embedded chooser + + @return: + - None if chooser is not embedded + - A list with selection indices (0-based) + """ + return _idaapi.choose2_get_embedded_selection(self) + def Show(self, modal=False): - """Activates or creates a chooser window""" + """ + Activates or creates a chooser window + @param modal: Display as modal dialog + @return: For modal choosers it will return the selected item index (0-based) + """ if modal: self.flags |= Choose2.CH_MODAL + + # Disable the timeout + old = idaapi.set_script_timeout(0) + n = _idaapi.choose2_create(self, False) + idaapi.set_script_timeout(old) + + # Delete the modal chooser instance + self.Close() + + return n else: self.flags &= ~Choose2.CH_MODAL - return _idaapi.choose2_show(self) + return _idaapi.choose2_create(self, False) + def Activate(self): """Activates a visible chooser""" return _idaapi.choose2_activate(self) + def Refresh(self): """Causes the refresh callback to trigger""" return _idaapi.choose2_refresh(self) + def Close(self): """Closes the chooser""" return _idaapi.choose2_close(self) - def AddCommand(self, caption, flags = _idaapi.CHOOSER_POPUP_MENU, menu_index=-1,icon = -1): - """Adds a new chooser command + + def AddCommand(self, + caption, + flags = _idaapi.CHOOSER_POPUP_MENU, + menu_index = -1, + icon = -1, + emb=None): + """ + Adds a new chooser command Save the returned value and later use it in the OnCommand handler @return: Returns a negative value on failure or the command index """ + + # Use the 'emb' as a sentinel. It will be passed the correct value from the EmbeddedChooserControl + if self.embedded and ((emb is None) or (emb != 2002)): + raise RuntimeError("Please add a command through EmbeddedChooserControl.AddCommand()") return _idaapi.choose2_add_command(self, caption, flags, menu_index, icon) # @@ -2773,6 +3468,7 @@ class Choose2(object): # def OnGetLine(self, n): # """Called when the chooser window requires lines. # This callback is mandatory. +# @param n: Line number (0-based) # @return: The user should return a list with ncols elements. # example: a list [col1, col2, col3, ...] describing the n-th line # """ @@ -2788,27 +3484,52 @@ class Choose2(object): # def OnEditLine(self, n): # """ # Called when an item is being edited. -# @param n: Line number (zero based) +# @param n: Line number (0-based) # @return: Nothing # """ # pass # # def OnInsertLine(self): -# """Called when 'Insert' is selected either via the hotkey or popup menu. +# """ +# Called when 'Insert' is selected either via the hotkey or popup menu. # @return: Nothing # """ # pass # # def OnSelectLine(self, n): -# """Called when the line selection changes""" +# """ +# Called when a line is selected and then Ok or double click was pressed +# @param n: Line number (0-based) +# """ +# pass +# +# def OnSelectionChange(self, sel_list): +# """ +# Called when the selection changes +# @param sel_list: A list of selected item indices +# """ # pass # # def OnDeleteLine(self, n): -# """Called when a line is about to be deleted""" +# """ +# Called when a line is about to be deleted +# @param n: Line number (0-based) +# """ # return self.n # # def OnRefresh(self, n): -# """Called when the 'Refresh' is selected +# """ +# Triggered when the 'Refresh' is called from the popup menu item. +# +# @param n: The currently selected line (0-based) at the time of the refresh call +# @return: Return the number of elements +# """ +# return self.n +# +# def OnRefreshed(self): +# """ +# Triggered when a refresh happens (for example due to column sorting) +# @param n: Line number (0-based) # @return: Return the number of elements # """ # return self.n @@ -2818,15 +3539,1058 @@ class Choose2(object): # return 0 # # def OnGetIcon(self, n): -# """Return icon number for a given item (or -1 if no icon is avail)""" +# """ +# Return icon number for a given item (or -1 if no icon is avail) +# @param n: Line number (0-based) +# """ # return -1 # # def OnGetLineAttr(self, n): -# """Return list [bgcolor, flags=CHITEM_XXXX] or None; check chooser_item_attrs_t""" +# """ +# Return list [bgcolor, flags=CHITEM_XXXX] or None; check chooser_item_attrs_t +# @param n: Line number (0-based) +# """ # return [0x0, CHITEM_BOLD] # +#ICON WARNING|QUESTION|INFO|NONE +#AUTOHIDE NONE|DATABASE|REGISTRY|SESSION +#HIDECANCEL +#BUTTON YES|NO|CANCEL "Value" +#STARTITEM {id:ItemName} +#HELP / ENDHELP +try: + import types + from ctypes import * + # On Windows, we use stdcall + + # Callback for buttons + # typedef void (idaapi *formcb_t)(TView *fields[], int code); + + _FORMCB_T = WINFUNCTYPE(c_void_p, c_void_p, c_int) + + # Callback for form change + # typedef int (idaapi *formchgcb_t)(int field_id, form_actions_t &fa); + _FORMCHGCB_T = WINFUNCTYPE(c_int, c_int, c_void_p) +except: + try: + _FORMCB_T = CFUNCTYPE(c_void_p, c_void_p, c_int) + _FORMCHGCB_T = CFUNCTYPE(c_int, c_int, c_void_p) + except: + _FORMCHGCB_T = _FORMCB_T = None + + +# ----------------------------------------------------------------------- +class Form(object): + + FT_ASCII = 'A' + """Ascii string - char *""" + FT_SEG = 'S' + """Segment - sel_t *""" + FT_HEX = 'N' + """Hex number - uval_t *""" + FT_SHEX = 'n' + """Signed hex number - sval_t *""" + FT_COLOR = 'K' + """Color button - bgcolor_t *""" + FT_ADDR = '$' + """Address - ea_t *""" + FT_UINT64 = 'L' + """default base uint64 - uint64""" + FT_INT64 = 'l' + """default base int64 - int64""" + FT_RAWHEX = 'M' + """Hex number, no 0x prefix - uval_t *""" + FT_FILE = 'f' + """File browse - char * at least QMAXPATH""" + FT_DEC = 'D' + """Decimal number - sval_t *""" + FT_OCT = 'O' + """Octal number, C notation - sval_t *""" + FT_BIN = 'Y' + """Binary number, 0b prefix - sval_t *""" + FT_CHAR = 'H' + """Char value -- sval_t *""" + FT_IDENT = 'I' + """Identifier - char * at least MAXNAMELEN""" + FT_BUTTON = 'B' + """Button - def handler(code)""" + FT_DIR = 'F' + """Path to directory - char * at least QMAXPATH""" + FT_TYPE = 'T' + """Type declaration - char * at least MAXSTR""" + _FT_USHORT = '_US' + """Unsigned short""" + FT_FORMCHG = '%/' + """Form change callback - formchgcb_t""" + FT_ECHOOSER = 'E' + """Embedded chooser - idaapi.Choose2""" + + FT_CHKGRP = 'C' + FT_CHKGRP2= 'c' + FT_RADGRP = 'R' + FT_RADGRP2= 'r' + + @staticmethod + def fieldtype_to_ctype(tp, i64 = False): + """ + Factory method returning a ctype class corresponding to the field type string + """ + if tp in (Form.FT_SEG, Form.FT_HEX, Form.FT_RAWHEX, Form.FT_ADDR): + return c_ulonglong if i64 else c_ulong + elif tp in (Form.FT_SHEX, Form.FT_DEC, Form.FT_OCT, Form.FT_BIN, Form.FT_CHAR): + return c_longlong if i64 else c_long + elif tp == Form.FT_UINT64: + return c_ulonglong + elif tp == Form.FT_INT64: + return c_longlong + elif tp == Form.FT_COLOR: + return c_ulong + elif tp == Form._FT_USHORT: + return c_ushort + elif tp in (Form.FT_FORMCHG, Form.FT_ECHOOSER): + return c_void_p + else: + return None + + + # + # Generic argument helper classes + # + class NumericArgument(object): + """ + Argument representing various integer arguments (ushort, uint32, uint64, etc...) + @param tp: One of Form.FT_XXX + """ + DefI64 = False + def __init__(self, tp, value): + cls = Form.fieldtype_to_ctype(tp, self.DefI64) + if cls is None: + raise TypeError("Invalid field type: %s" % tp) + # Get a pointer type to the ctype type + self.arg = pointer(cls(value)) + + def __set_value(self, v): + self.arg.contents.value = v + value = property(lambda self: self.arg.contents.value, __set_value) + + + class StringArgument(object): + """ + Argument representing a character buffer + """ + def __init__(self, size=None, value=None): + if size is None: + raise SyntaxError("The string size must be passed") + + if value is None: + self.arg = create_string_buffer(size) + else: + self.arg = create_string_buffer(value, size) + self.size = size + + def __set_value(self, v): + self.arg.value = v + value = property(lambda self: self.arg.value, __set_value) + + + # + # Base control class + # + class Control(object): + def __init__(self): + self.id = 0 + """Automatically assigned control ID""" + + self.arg = None + """Control argument value. This could be one element or a list/tuple (for multiple args per control)""" + + self.form = None + """Reference to the parent form. It is filled by Form.Add() + """ + + + def get_tag(self): + """ + Control tag character. One of Form.FT_XXXX. + The form class will expand the {} notation and replace them with the tags + """ + pass + + def get_arg(self): + """ + Control returns the parameter to be pushed on the stack + (Of AskUsingForm()) + """ + return self.arg + + def free(self): + """ + Free the control + """ + # Release parent form reference + self.form = None + + + # + # Label controls + # + class LabelControl(Control): + """ + Base class for static label control + """ + def __init__(self, tp): + Form.Control.__init__(self) + self.tp = tp + + def get_tag(self): + return '%%%d%s' % (self.id, self.tp) + + + class StringLabel(LabelControl): + """ + String label control + """ + def __init__(self, value, tp=None, sz=1024): + """ + Type field can be one of: + A - ascii string + T - type declaration + I - ident + F - folder + f - file + X - command + """ + if tp is None: + tp = Form.FT_ASCII + Form.LabelControl.__init__(self, tp) + self.size = sz + self.arg = create_string_buffer(value, sz) + + + class NumericLabel(LabelControl, NumericArgument): + """ + Numeric label control + """ + def __init__(self, value, tp=None): + if tp is None: + tp = Form.FT_HEX + Form.LabelControl.__init__(self, tp) + Form.NumericArgument.__init__(self, tp, value) + + + # + # Group controls + # + class GroupItemControl(Control): + """ + Base class for group control items + """ + def __init__(self, tag, parent): + Form.Control.__init__(self) + self.tag = tag + self.parent = parent + # Item position (filled when form is compiled) + self.pos = 0 + + def assign_pos(self): + self.pos = self.parent.next_child_pos() + + def get_tag(self): + return "%s%d" % (self.tag, self.id) + + + class ChkGroupItemControl(GroupItemControl): + """ + Checkbox group item control + """ + def __init__(self, tag, parent): + Form.GroupItemControl.__init__(self, tag, parent) + + def __get_value(self): + return (self.parent.value & (1 << self.pos)) != 0 + + def __set_value(self, v): + pv = self.parent.value + if v: + pv = pv | (1 << self.pos) + else: + pv = pv & ~(1 << self.pos) + + self.parent.value = pv + + checked = property(__get_value, __set_value) + """Get/Sets checkbox item check status""" + + + class RadGroupItemControl(GroupItemControl): + """ + Radiobox group item control + """ + def __init__(self, tag, parent): + Form.GroupItemControl.__init__(self, tag, parent) + + def __get_value(self): + return self.parent.value == self.pos + + def __set_value(self, v): + self.parent.value = self.pos + + selected = property(__get_value, __set_value) + """Get/Sets radiobox item selection status""" + + + class GroupControl(Control, NumericArgument): + """ + Base class for group controls + """ + def __init__(self, children_names, tag, value=0): + Form.Control.__init__(self) + self.children_names = children_names + self.tag = tag + self._reset() + Form.NumericArgument.__init__(self, Form._FT_USHORT, value) + + def _reset(self): + self.childpos = 0 + + def next_child_pos(self): + v = self.childpos + self.childpos += 1 + return v + + def get_tag(self): + return "%d" % self.id + + + class ChkGroupControl(GroupControl): + """ + Checkbox group control class. + It holds a set of checkbox controls + """ + ItemClass = None + """ + Group control item factory class instance + We need this because later we won't be treating ChkGroupControl or RadGroupControl + individually, instead we will be working with GroupControl in general. + """ + def __init__(self, children_names, value=0, secondary=False): + # Assign group item factory class + if Form.ChkGroupControl.ItemClass is None: + Form.ChkGroupControl.ItemClass = Form.ChkGroupItemControl + + Form.GroupControl.__init__( + self, + children_names, + Form.FT_CHKGRP2 if secondary else Form.FT_CHKGRP, + value) + + + class RadGroupControl(GroupControl): + """ + Radiobox group control class. + It holds a set of radiobox controls + """ + ItemClass = None + def __init__(self, children_names, value=0, secondary=False): + """ + Creates a radiogroup control. + @param children_names: A tuple containing group item names + @param value: Initial selected radio item + @param secondory: Allows rendering one the same line as the previous group control. + Use this if you have another group control on the same line. + """ + # Assign group item factory class + if Form.RadGroupControl.ItemClass is None: + Form.RadGroupControl.ItemClass = Form.RadGroupItemControl + + Form.GroupControl.__init__( + self, + children_names, + Form.FT_RADGRP2 if secondary else Form.FT_RADGRP, + value) + + + # + # Input controls + # + class InputControl(Control): + """ + Generic form input control. + It could be numeric control, string control, directory/file browsing, etc... + """ + def __init__(self, tp, width, swidth, hlp = None): + """ + @param width: Display width + @param swidth: String width + """ + Form.Control.__init__(self) + self.tp = tp + self.width = width + self.switdh = swidth + self.hlp = hlp + + def get_tag(self): + return "%s%d:%s:%s:%s" % ( + self.tp, self.id, + self.width, + self.switdh, + ":" if self.hlp is None else self.hlp) + + + class NumericInput(InputControl, NumericArgument): + """ + A composite class serving as a base numeric input control class + """ + def __init__(self, tp=None, value=0, width=50, swidth=10, hlp=None): + if tp is None: + tp = Form.FT_HEX + Form.InputControl.__init__(self, tp, width, swidth, hlp) + Form.NumericArgument.__init__(self, self.tp, value) + + + class ColorInput(NumericInput): + """ + Color button input control + """ + def __init__(self, value = 0): + """ + @param value: Initial color value in RGB + """ + Form.NumericInput.__init__(self, tp=Form.FT_COLOR, value=value) + + + class StringInput(InputControl, StringArgument): + """ + Base string input control class. + This class also constructs a StringArgument + """ + def __init__(self, + tp=None, + width=1024, + swidth=40, + hlp=None, + value=None, + size=None): + """ + @param width: String size. But in some cases it has special meaning. For example in FileInput control. + If you want to define the string buffer size then pass the 'size' argument + @param swidth: Control width + @param value: Initial value + @param size: String size + """ + if tp is None: + tp = Form.FT_ASCII + if not size: + size = width + Form.InputControl.__init__(self, tp, width, swidth, hlp) + Form.StringArgument.__init__(self, size=size, value=value) + + + class FileInput(StringInput): + """ + File Open/Save input control + """ + def __init__(self, + width=512, + swidth=80, + save=False, open=False, + hlp=None, value=None): + + if save == open: + raise ValueError("Invalid mode. Choose either open or save") + if width < 512: + raise ValueError("Invalid width. Must be greater than 512.") + + # The width field is overloaded in this control and is used + # to denote the type of the FileInput dialog (save or load) + # On the other hand it is passed as is to the StringArgument part + Form.StringInput.__init__( + self, + tp=Form.FT_FILE, + width="1" if save else "0", + swidth=swidth, + hlp=hlp, + size=width, + value=value) + + + class DirInput(StringInput): + """ + Directory browsing control + """ + def __init__(self, + width=512, + swidth=80, + hlp=None, + value=None): + + if width < 512: + raise ValueError("Invalid width. Must be greater than 512.") + + Form.StringInput.__init__( + self, + tp=Form.FT_DIR, + width=width, + swidth=swidth, + hlp=hlp, + size=width, + value=value) + + + class ButtonInput(InputControl): + """ + Button control. + A handler along with a 'code' (numeric value) can be associated with the button. + This way one handler can handle many buttons based on the button code (or in other terms id or tag) + """ + def __init__(self, handler, code="", swidth="", hlp=None): + """ + @param handler: Button handler. A callback taking one argument which is the code. + @param code: A code associated with the button and that is later passed to the handler. + """ + Form.InputControl.__init__( + self, + Form.FT_BUTTON, + code, + swidth, + hlp) + self.arg = _FORMCB_T(lambda view, code, h=handler: h(code)) + + + class FormChangeCb(Control): + """ + Form change handler. + This can be thought of like a dialog procedure. + Everytime a form action occurs, this handler will be called along with the control id. + The programmer can then call various form actions accordingly: + - EnableField + - ShowField + - MoveField + - GetFieldValue + - etc... + + Special control IDs: -1 (The form is initialized) and -2 (Ok has been clicked) + + """ + def __init__(self, handler): + """ + Constructs the handler. + @param handler: The handler (preferrably a member function of a class derived from the Form class). + """ + Form.Control.__init__(self) + + # Save the handler + self.handler = handler + + # Create a callback stub + # We use this mechanism to create an intermediate step + # where we can create an 'fa' adapter for use by Python + self.arg = _FORMCHGCB_T(self.helper_cb) + + def helper_cb(self, fid, p_fa): + # Remember the pointer to the forms_action + self.form.p_fa = p_fa + + # Call user's handler + r = self.handler(fid) + return 0 if r is None else r + + def get_tag(self): + return Form.FT_FORMCHG + + def free(self): + Form.Control.free(self) + # Remove reference to the handler + # (Normally the handler is a member function in the parent form) + self.handler = None + + + class EmbeddedChooserControl(InputControl): + """ + Embedded chooser control. + This control links to a Chooser2 control created with the 'embedded=True' + """ + def __init__(self, + chooser=None, + swidth=40, + hlp=None): + """ + Embedded chooser control + + @param chooser: A chooser2 instance (must be constructed with 'embedded=True') + """ + + # !! Make sure a chooser instance is passed !! + if chooser is None or not isinstance(chooser, Choose2): + raise ValueError("Invalid chooser passed.") + + # Create an embedded chooser structure from the Choose2 instance + if chooser.Embedded() != 1: + raise ValueError("Failed to create embedded chooser instance.") + + # Construct input control + Form.InputControl.__init__(self, Form.FT_ECHOOSER, "", swidth) + + # Get a pointer to the chooser_info_t and the selection vector + # (These two parameters are the needed arguments for the AskUsingForm()) + emb, sel = _idaapi.choose2_get_embedded(chooser) + + # Get a pointer to a c_void_p constructed from an address + p_embedded = pointer(c_void_p.from_address(emb)) + p_sel = pointer(c_void_p.from_address(sel)) + + # - Create the embedded chooser info on control creation + # - Do not free the embeded chooser because after we get the args + # via Compile() the user can still call Execute() which relies + # on the already computed args + self.arg = (p_embedded, p_sel) + + # Save chooser instance + self.chooser = chooser + + # Add a bogus 'size' attribute + self.size = 0 + + + value = property(lambda self: self.chooser) + """Returns the embedded chooser instance""" + + + def AddCommand(self, + caption, + flags = _idaapi.CHOOSER_POPUP_MENU, + menu_index = -1, + icon = -1): + """ + Adds a new embedded chooser command + Save the returned value and later use it in the OnCommand handler + + @return: Returns a negative value on failure or the command index + """ + if not self.form.title: + raise ValueError("Form title is not set!") + + # Encode all information for the AddCommand() in the 'caption' parameter + caption = "%s:%d:%s" % (self.form.title, self.id, caption) + return self.chooser.AddCommand(caption, flags=flags, menu_index=menu_index, icon=icon, emb=2002) + + + def free(self): + """ + Frees the embedded chooser data + """ + self.chooser.Close() + self.chooser = None + Form.Control.free(self) + + + # + # Class methods + # + def __init__(self, form, controls): + """ + Contruct a Form class. + This class wraps around AskUsingForm() and provides an easier / alternative syntax for describing forms. + The form control names are wrapped inside the opening and closing curly braces and the control themselves are + defined and instantiated via various form controls (subclasses of Form). + + @param form: The form string + @param controls: A dictionary containing the control name as a _key_ and control object as _value_ + """ + self._reset() + self.form = form + self.controls = controls + self.__args = None + + self.title = None + """The Form title. It will be filled when the form is compiled""" + + + def Free(self): + """ + Frees all resources associated with a compiled form. + Make sure you call this function when you finish using the form. + """ + for ctrl in self.__controls.values(): + ctrl.free() + # Reset the controls + # (Note that we are not removing the form control attributes, no need) + self._reset() + + + def _reset(self): + """ + Resets the Form class state variables + """ + self.__controls = {} + self.__ctrl_id = 1 + + + def __getitem__(self, name): + """Returns a control object by name""" + return self.__controls[name] + + + def Add(self, name, ctrl, mkattr = True): + """ + Low level function. Prefer AddControls() to this function. + This function adds one control to the form. + + @param name: Control name + @param ctrl: Control object + @param mkattr: Create control name / control object as a form attribute + """ + # Assign a unique ID + ctrl.id = self.__ctrl_id + self.__ctrl_id += 1 + + # Create attribute with control name + if mkattr: + setattr(self, name, ctrl) + + # Remember the control + self.__controls[name] = ctrl + + # Link the form to the control via its form attribute + ctrl.form = self + + # Is it a group? Add each child + if isinstance(ctrl, Form.GroupControl): + self._AddGroup(ctrl, mkattr) + + + def FindControlById(self, id): + """ + Finds a control instance given its id + """ + for ctrl in self.__controls.values(): + if ctrl.id == id: + return ctrl + return None + + + @staticmethod + def _ParseFormTitle(form): + """ + Parses the form's title from the form text + """ + help_state = 0 + for i, line in enumerate(form.split("\n")): + if line.startswith("STARTITEM ") or line.startswith("BUTTON "): + continue + # Skip "HELP" and remember state + elif help_state == 0 and line == "HELP": + help_state = 1 # Mark inside HELP + continue + elif help_state == 1 and line == "ENDHELP": + help_state = 2 # Mark end of HELP + continue + return line.strip() + + return None + + + def _AddGroup(self, Group, mkattr=True): + """ + Internal function. + This function expands the group item names and creates individual group item controls + + @param Group: The group class (checkbox or radio group class) + """ + + # Create group item controls for each child + for child_name in Group.children_names: + self.Add( + child_name, + # Use the class factory + Group.ItemClass(Group.tag, Group), + mkattr) + + + def AddControls(self, controls, mkattr=True): + """ + Adds controls from a dictionary. + The dictionary key is the control name and the value is a Form.Control object + @param controls: The control dictionary + """ + for name, ctrl in controls.items(): + # Add the control + self.Add(name, ctrl, mkattr) + + + def CompileEx(self, form): + """ + Low level function. + Compiles (parses the form syntax and adds the control) the form string and + returns the argument list to be passed the argument list to AskUsingForm(). + + The form controls are wrapped inside curly braces: {ControlName}. + + A special operator can be used to return the ID of a given control by its name: {id:ControlName}. + This is useful when you use the STARTITEM form keyword to set the initially focused control. + + @param form: Compiles the form and returns the arguments needed to be passed to AskUsingForm() + """ + # First argument is the form string + args = [None] + ctrlcnt = 1 + + # Reset all group control internal flags + for ctrl in self.__controls.values(): + if isinstance(ctrl, Form.GroupControl): + ctrl._reset() + + p = 0 + while True: + i1 = form.find("{", p) + # No more items? + if i1 == -1: + break + + # Check if escaped + if (i1 != 0) and form[i1-1] == "\\": + # Remove escape sequence and restart search + form = form[:i1-1] + form[i1:] + + # Skip current marker + p = i1 + + # Continue search + continue + + i2 = form.find("}", i1) + if i2 == -1: + raise SyntaxError("No matching closing brace '}'") + + # Parse control name + ctrlname = form[i1+1:i2] + if not ctrlname: + raise ValueError("Control %d has an invalid name!" % ctrlcnt) + + # Is it the IDOF operator? + if ctrlname.startswith("id:"): + idfunc = True + # Take actual ctrlname + ctrlname = ctrlname[3:] + else: + idfunc = False + + # Find the control + ctrl = self.__controls.get(ctrlname, None) + if ctrl is None: + raise ValueError("No matching control '%s'" % ctrlname) + + # Replace control name by tag + if idfunc: + tag = str(ctrl.id) + else: + tag = ctrl.get_tag() + taglen = len(tag) + form = form[:i1] + tag + form[i2+1:] + + # Set new position + p = i1 + taglen + + # Was it an IDOF() ? No need to push parameters + # Just ID substitution is fine + if idfunc: + continue + + + # For GroupItem controls, there are no individual arguments + # The argument is assigned for the group itself + if isinstance(ctrl, Form.GroupItemControl): + # GroupItem controls will have their position dynamically set + ctrl.assign_pos() + else: + # Push argument(s) + # (Some controls need more than one argument) + arg = ctrl.get_arg() + if isinstance(arg, (types.ListType, types.TupleType)): + # Push all args + args.extend(arg) + else: + # Push one arg + args.append(arg) + + ctrlcnt += 1 + + # Patch in the final form string + args[0] = form + + self.title = self._ParseFormTitle(form) + return args + + + def Compile(self): + """ + Compiles a form and returns the form object (self) and the argument list. + The form object will contain object names corresponding to the form elements + + @return: It will raise an exception on failure. Otherwise the return value is ignored + """ + + # Reset controls + self._reset() + + # Insert controls + self.AddControls(self.controls) + + # Compile form and get args + self.__args = self.CompileEx(self.form) + + return (self, self.__args) + + + def Execute(self): + """ + Displays a compiled form. + @return: 1 - ok ; 0 - cancel + """ + if self.__args is None: + raise SyntaxError("Form is not compiled") + + # Call AskUsingForm() + return AskUsingForm(*self.__args) + + + def EnableField(self, ctrl, enable): + """ + Enable or disable an input field + @return: False - no such control + """ + return _idaapi.formchgcbfa_enable_field(self.p_fa, ctrl.id, enable) + + + def ShowField(self, ctrl, show): + """ + Show or hide an input field + @return: False - no such control + """ + return _idaapi.formchgcbfa_show_field(self.p_fa, ctrl.id, show) + + + def MoveField(self, ctrl, x, y, w, h): + """ + Move/resize an input field + + @return: False - no such fiel + """ + return _idaapi.formchgcbfa_move_field(self.p_fa, ctrl.id, x, y, w, h) + + + def GetFocusedField(self): + """ + Get currently focused input field. + @return: None if no field is selected otherwise the control ID + """ + id = _idaapi.formchgcbfa_get_focused_field(self.p_fa) + return self.FindControlById(id) + + + def SetFocusedField(self, ctrl): + """ + Set currently focused input field + @return: False - no such control + """ + return _idaapi.formchgcbfa_set_focused_field(self.p_fa, ctrl.id) + + + def RefreshField(self, ctrl): + """ + Refresh a field + @return: False - no such control + """ + return _idaapi.formchgcbfa_refresh_field(self.p_fa, ctrl.id) + + + def GetControlValue(self, ctrl): + """ + Returns the control's value depending on its type + @param ctrl: Form control instance + @return: + - number: color button, radio controls + - string: file/dir input, string input and string label + - int list: for embedded chooser control (0-based indices of selected items) + - None: on failure + """ + tid, sz = self.ControlToFieldTypeIdAndSize(ctrl) + return _idaapi.formchgcbfa_get_field_value( + self.p_fa, + ctrl.id, + tid, + sz) + + + def SetControlValue(self, ctrl, value): + """ + Set the control's value depending on its type + @param ctrl: Form control instance + @param value: + - embedded chooser: base a 0-base indices list to select embedded chooser items + @return: Boolean true on success + """ + tid, _ = self.ControlToFieldTypeIdAndSize(ctrl) + return _idaapi.formchgcbfa_set_field_value( + self.p_fa, + ctrl.id, + tid, + value) + + + @staticmethod + def ControlToFieldTypeIdAndSize(ctrl): + """ + Converts a control object to a tuple containing the field id + and the associated buffer size + """ + # Input control depend on the associate buffer size (supplied by the user) + + # Make sure you check instances types taking into account inheritance + if isinstance(ctrl, Form.EmbeddedChooserControl): + return (5, 0) + # Group items or controls + elif isinstance(ctrl, (Form.GroupItemControl, Form.GroupControl)): + return (2, 0) + elif isinstance(ctrl, Form.StringLabel): + return (3, min(_idaapi.MAXSTR, ctrl.size)) + elif isinstance(ctrl, Form.ColorInput): + return (4, 0) + elif isinstance(ctrl, Form.InputControl): + return (1, ctrl.size) + else: + raise NotImplementedError, "Not yet implemented" + +# -------------------------------------------------------------------------- +# Instantiate AskUsingForm function pointer +try: + import ctypes + # Setup the numeric argument size + Form.NumericArgument.DefI64 = _idaapi.BADADDR == 0xFFFFFFFFFFFFFFFFL + AskUsingForm__ = ctypes.CFUNCTYPE(ctypes.c_long)(_idaapi.py_get_AskUsingForm()) +except: + def AskUsingForm__(*args): + warning("AskUsingForm() needs ctypes library in order to work") + return 0 + + +def AskUsingForm(*args): + """ + Calls the AskUsingForm() + @param: Compiled Arguments obtain through the Form.Compile() function + @return: 1 = ok, 0 = cancel + """ + old = set_script_timeout(0) + r = AskUsingForm__(*args) + set_script_timeout(old) + return r + + +# + +# class PluginForm(object): """ PluginForm class. @@ -2860,7 +4624,7 @@ class PluginForm(object): def Show(self, caption, options = 0): """ Creates the form if not was not created or brings to front if it was already created - + @param caption: The form caption @param options: One of PluginForm.FORM_ constants """ @@ -2926,7 +4690,80 @@ class PluginForm(object): FORM_DONT_SAVE_SIZE = 0x4 """don't save size of the window""" -# +# + +class Choose: + """ + Choose - class for choose() with callbacks + """ + def __init__(self, list, title, flags=0): + self.list = list + self.title = title + + self.flags = flags + self.x0 = -1 + self.x1 = -1 + self.y0 = -1 + self.y1 = -1 + + self.width = -1 + + # HACK: Add a circular reference for non-modal choosers. This prevents the GC + # from collecting the class object the callbacks need. Unfortunately this means + # that the class will never be collected, unless refhack is set to None explicitly. + if (flags & Choose2.CH_MODAL) == 0: + self.refhack = self + + def sizer(self): + """ + Callback: sizer - returns the length of the list + """ + return len(self.list) + + def getl(self, n): + """ + Callback: getl - get one item from the list + """ + if n == 0: + return self.title + if n <= self.sizer(): + return str(self.list[n-1]) + else: + return "" + + + def ins(self): + pass + + + def update(self, n): + pass + + + def edit(self, n): + pass + + + def enter(self, n): + print "enter(%d) called" % n + + + def destroy(self): + pass + + + def get_icon(self, n): + pass + + + def choose(self): + """ + choose - Display the choose dialogue + """ + old = set_script_timeout(0) + n = _idaapi.choose_choose(self, self.flags, self.x0, self.y0, self.x1, self.y1, self.width) + set_script_timeout(old) + return n %} %pythoncode %{ diff --git a/swig/lines.i b/swig/lines.i index 7828aad..ce901b4 100644 --- a/swig/lines.i +++ b/swig/lines.i @@ -78,7 +78,6 @@ // //------------------------------------------------------------------------ static PyObject *py_get_user_defined_prefix = NULL; -static qstring py_get_user_defined_prefix_err; static void idaapi s_py_get_user_defined_prefix( ea_t ea, int lnnum, @@ -92,12 +91,9 @@ static void idaapi s_py_get_user_defined_prefix( PY_FMT64 "iis" PY_FMT64, ea, lnnum, indent, line, bufsize); - if ( PyW_GetError(&py_get_user_defined_prefix_err) || py_ret == NULL ) - { - msg("py_get_user_defined_prefix() error: %s\n", py_get_user_defined_prefix_err.c_str()); - PyErr_Clear(); - } - else + // Error? Display it + // No error? Copy the buffer + if ( !PyW_ShowCbErr("py_get_user_defined_prefix") ) { Py_ssize_t py_len; char *py_str; @@ -192,6 +188,7 @@ PyObject *py_tag_remove(const char *instr) char *buf = new char[sz + 5]; if ( buf == NULL ) Py_RETURN_NONE; + ssize_t r = tag_remove(instr, buf, sz); PyObject *res; if ( r < 0 ) diff --git a/swig/loader.i b/swig/loader.i index 6971548..d1476f5 100644 --- a/swig/loader.i +++ b/swig/loader.i @@ -12,6 +12,7 @@ // TODO: These could be wrapped if needed %ignore load_info_t; %ignore add_plugin_option; +%ignore get_plugins_path; %ignore build_loaders_list; %ignore free_loaders_list; %ignore get_loader_name_from_dll; @@ -147,6 +148,7 @@ static int py_mem2base(PyObject *py_mem, ea_t ea, long fpos = -1) char *buf; if ( PyString_AsStringAndSize(py_mem, &buf, &len) == -1 ) return 0; + return mem2base((void *)buf, ea, ea+len, fpos); } @@ -168,6 +170,7 @@ static PyObject *py_load_plugin(const char *name) plugin_t *r = load_plugin(name); if ( r == NULL ) Py_RETURN_NONE; + return PyCObject_FromVoidPtr(r, NULL); } @@ -187,6 +190,7 @@ static bool py_run_plugin(PyObject *plg, int arg) { if ( !PyCObject_Check(plg) ) return false; + return run_plugin((plugin_t *)PyCObject_AsVoidPtr(plg), arg); } diff --git a/swig/nalt.i b/swig/nalt.i index c8600b6..bb78ec8 100644 --- a/swig/nalt.i +++ b/swig/nalt.i @@ -25,7 +25,11 @@ static int idaapi py_import_enum_cb( uval_t ord, void *param) { - PyObject *py_ea = Py_BuildValue(PY_FMT64, pyul_t(ea)); + // If no name, try to get the name associated with the 'ea'. It may be coming from IDS + char name_buf[MAXSTR]; + if ( name == NULL ) + name = get_true_name(BADADDR, ea, name_buf, sizeof(name_buf)); + PyObject *py_name; if ( name == NULL ) { @@ -36,13 +40,25 @@ static int idaapi py_import_enum_cb( { py_name = PyString_FromString(name); } + PyObject *py_ord = Py_BuildValue(PY_FMT64, pyul_t(ord)); - PyObject *py_result = PyObject_CallFunctionObjArgs((PyObject *)param, py_ea, py_name, py_ord, NULL); + PyObject *py_ea = Py_BuildValue(PY_FMT64, pyul_t(ea)); + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallFunctionObjArgs( + (PyObject *)param, + py_ea, + py_name, + py_ord, + NULL); + PYW_GIL_RELEASE; + int r = py_result != NULL && PyObject_IsTrue(py_result) ? 1 : 0; + Py_DECREF(py_ea); Py_DECREF(py_name); Py_DECREF(py_ord); Py_XDECREF(py_result); + return r; } @@ -90,6 +106,7 @@ static PyObject *py_get_import_module_name(int mod_index) char buf[MAXSTR]; if ( !get_import_module_name(mod_index, buf, sizeof(buf)) ) Py_RETURN_NONE; + return PyString_FromString(buf); } @@ -234,6 +251,7 @@ static int py_enum_import_names(int mod_index, PyObject *py_cb) { if ( !PyCallable_Check(py_cb) ) return -1; + return enum_import_names(mod_index, py_import_enum_cb, py_cb); } @@ -244,6 +262,7 @@ static PyObject *switch_info_ex_t_create() return PyCObject_FromVoidPtr(inst, NULL); } +//--------------------------------------------------------------------------- static bool switch_info_ex_t_destroy(PyObject *py_obj) { if ( !PyCObject_Check(py_obj) ) diff --git a/swig/name.i b/swig/name.i index c216e98..94c47ed 100644 --- a/swig/name.i +++ b/swig/name.i @@ -45,7 +45,7 @@ PyObject *py_get_debug_names(ea_t ea1, ea_t ea2) for (ea_name_vec_t::iterator it=names.begin();it!=names.end();++it) { PyDict_SetItem(dict, - PyInt_FromSize_t(it->ea), + Py_BuildValue(PY_FMT64, it->ea), PyString_FromString(it->name.c_str())); } return dict; @@ -63,12 +63,14 @@ class NearestName: def __init__(self, ea_names): self.update(ea_names) + def update(self, ea_names): """Updates the ea/names map""" self._names = ea_names self._addrs = ea_names.keys() self._addrs.sort() + def find(self, ea): """ Returns a tupple (ea, name, pos) that is the nearest to the passed ea @@ -85,13 +87,21 @@ class NearestName: return None return self[pos] + + def _get_item(self, index): + ea = self._addrs[index] + return (ea, self._names[ea], index) + + + def __iter__(self): + return (self._get_item(index) for index in xrange(0, len(self._addrs))) + + def __getitem__(self, index): """Returns the tupple (ea, name, index)""" if index > len(self._addrs): raise StopIteration - ea = self._addrs[index] - return (ea, self._names[ea], index) + return self._get_item(index) %} %include "name.hpp" - diff --git a/swig/netnode.i b/swig/netnode.i index 63725ac..884e366 100644 --- a/swig/netnode.i +++ b/swig/netnode.i @@ -88,4 +88,12 @@ // Renaming one version of hashset() otherwise SWIG will not be able to activate the other one %rename (hashset_idx) netnode::hashset(const char *idx, nodeidx_t value, char tag=htag); -%include "netnode.hpp" \ No newline at end of file +%include "netnode.hpp" + +%extend netnode +{ + nodeidx_t index() + { + return self->operator nodeidx_t(); + } +} \ No newline at end of file diff --git a/swig/pro.i b/swig/pro.i index 55a3610..85cb2cf 100644 --- a/swig/pro.i +++ b/swig/pro.i @@ -23,7 +23,14 @@ %ignore qthread_join; %ignore qthread_kill; %ignore qthread_self; +%ignore qthread_same; %ignore qthread_t; +%ignore qhandle_t; +%ignore qpipe_create; +%ignore qpipe_read; +%ignore qpipe_write; +%ignore qpipe_close; +%ignore qwait_for_handles; %ignore qstrlen; %ignore qstrcmp; %ignore qstrstr; @@ -65,8 +72,11 @@ %ignore swap_value; %ignore qalloc_or_throw; %ignore qrealloc_or_throw; -%ignore launch_process_t; -%ignore init_process; +%ignore get_buffer_for_sysdir; +%ignore get_buffer_for_winerr; +%ignore call_atexits; +%ignore launch_process_params_t; +%ignore launch_process; %ignore term_process; %ignore get_process_exit_code; %ignore BELOW_NORMAL_PRIORITY_CLASS; diff --git a/swig/typeconv.i b/swig/typeconv.i index 0c2f1b9..13c9cc5 100644 --- a/swig/typeconv.i +++ b/swig/typeconv.i @@ -135,7 +135,7 @@ else { Py_INCREF(Py_None); - resultobj = Py_None; + resultobj = Py_None; } #ifdef __cplusplus delete [] (char *)$1; @@ -148,8 +148,20 @@ // Check that the argument is a callable Python object %typemap(in) PyObject *pyfunc { if (!PyCallable_Check($input)) { - PyErr_SetString(PyExc_TypeError, "Expecting a callable object"); + PyErr_SetString(PyExc_TypeError, "Expected a callable object"); return NULL; } $1 = $input; } + +// Convert ea_t +%typemap(in) ea_t +{ + uint64 $1_temp; + if ( !PyW_GetNumber($input, &$1_temp) ) + { + PyErr_SetString(PyExc_TypeError, "Expected an ea_t type"); + return NULL; + } + $1 = ea_t($1_temp); +} diff --git a/swig/typeinf.i b/swig/typeinf.i index 027b04c..75e57ce 100644 --- a/swig/typeinf.i +++ b/swig/typeinf.i @@ -62,6 +62,9 @@ %ignore print_type; %ignore show_type; %ignore show_plist; +%ignore skip_function_arg_names; +%ignore perform_funcarg_conversion; +%ignore get_argloc_info; %ignore extract_pstr; %ignore extract_name; @@ -167,6 +170,7 @@ PyObject *idc_parse_decl(til_t *ti, const char *decl, int flags) bool ok = parse_decl(ti, decl, &name, &type, &fields, flags); if ( !ok ) Py_RETURN_NONE; + return Py_BuildValue("(sss)", name.c_str(), (char *)type.c_str(), @@ -256,7 +260,14 @@ PyObject *py_unpack_object_from_idb( type_t *type = (type_t *) PyString_AsString(py_type); p_list *fields = (p_list *) PyString_AsString(py_fields); idc_value_t idc_obj; - error_t err = unpack_object_from_idb(&idc_obj, ti, type, fields, ea, NULL, pio_flags); + error_t err = unpack_object_from_idb( + &idc_obj, + ti, + type, + fields, + ea, + NULL, + pio_flags); // Unpacking failed? if ( err != eOk ) @@ -265,9 +276,11 @@ PyObject *py_unpack_object_from_idb( // Convert PyObject *py_ret(NULL); err = idcvar_to_pyvar(idc_obj, &py_ret); + // Conversion failed? if ( err != CIP_OK ) return Py_BuildValue("(ii)", 0, err); + PyObject *py_result = Py_BuildValue("(iO)", 1, py_ret); Py_DECREF(py_ret); return py_result; @@ -315,7 +328,13 @@ PyObject *py_unpack_object_from_bv( memcpy(bytes.begin(), PyString_AsString(py_bytes), bytes.size()); idc_value_t idc_obj; - error_t err = unpack_object_from_bv(&idc_obj, ti, type, fields, bytes, pio_flags); + error_t err = unpack_object_from_bv( + &idc_obj, + ti, + type, + fields, + bytes, + pio_flags); // Unpacking failed? if ( err != eOk ) @@ -324,9 +343,11 @@ PyObject *py_unpack_object_from_bv( // Convert PyObject *py_ret(NULL); err = idcvar_to_pyvar(idc_obj, &py_ret); + // Conversion failed? if ( err != CIP_OK ) return Py_BuildValue("(ii)", 0, err); + PyObject *py_result = Py_BuildValue("(iO)", 1, py_ret); Py_DECREF(py_ret); return py_result; diff --git a/swig/ua.i b/swig/ua.i index 36e1fe9..bf7b659 100644 --- a/swig/ua.i +++ b/swig/ua.i @@ -64,6 +64,7 @@ PyObject *py_init_output_buffer(size_t size = MAXSTR) PyObject *py_str = PyString_FromStringAndSize(NULL, size); if ( py_str == NULL ) Py_RETURN_NONE; + init_output_buffer(PyString_AsString(py_str), size); return py_str; } @@ -326,6 +327,7 @@ bool py_out_name_expr( off = adiff_t(v); else off = BADADDR; + return op == NULL ? false : out_name_expr(*op, ea, off); } @@ -449,6 +451,7 @@ static void insn_t_set_cs(PyObject *self, PyObject *value) insn_t *link = insn_t_get_clink(self); if ( link == NULL ) return; + uint64 v(0); PyW_GetNumber(value, &v); link->cs = ea_t(v); @@ -886,7 +889,7 @@ class op_t(py_clinked_object_t): def is_reg(self, r): """Checks if the register operand is the given processor register""" - return self.type == idaapi.o_reg and self == r + return self.type == _idaapi.o_reg and self == r def has_reg(self, r): """Checks if the operand accesses the given processor register""" @@ -1000,15 +1003,24 @@ class insn_t(py_clinked_object_t): def _create_clink(self): return _idaapi.insn_t_create() + def _del_clink(self, lnk): return _idaapi.insn_t_destroy(lnk) + + def __iter__(self): + return (self.Operands[idx] for idx in xrange(0, UA_MAXOP)) + + def __getitem__(self, idx): """ Operands can be accessed directly as indexes @return op_t: Returns an operand of type op_t """ - return self.Operands[idx] + if idx >= UA_MAXOP: + raise KeyError + else: + return self.Operands[idx] def is_macro(self): return self.flags & INSN_MACRO != 0