diff --git a/AUTHORS.txt b/AUTHORS.txt index a041c7e..6f8d4ee 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -7,7 +7,7 @@ The IDAPython Team: * Hex-Rays - http://www.hex-rays.com/ - - Hex-Rays joined the project in September 2009 and started contributing. + Hex-Rays joined the IDAPython project in September 2009 and started contributing. It is primarily maintained by Elias Bachaalany. @@ -21,3 +21,4 @@ The IDAPython Team: Igor Skochinsky Sebastian Muniz cbwhiz + Arnaud Diederen diff --git a/BUILDING.txt b/BUILDING.txt index f38c913..f65c4d6 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -1,6 +1,6 @@ ----------------------------------------------------------- -IDAPython - Python plugin for Interactive Disassembler Pro ----------------------------------------------------------- +------------------------------------------------------ +IDAPython - Python plugin for Interactive Disassembler +------------------------------------------------------ Building From Source -------------------- @@ -36,10 +36,10 @@ BUILDING Make sure all the needed tools (compiler, swig) are on the PATH. -1. Unpack the IDAPython source and IDA Pro SDK into the following +1. Unpack the IDAPython source and IDA SDK into the following directory structure: - swigsdk-versions/x.y/ - A supported version of the IDA Pro SDK + swigsdk-versions/x.y/ - A supported version of the IDA SDK idapython/ - IDAPython source code 2. On Mac OS X copy libida.dylib from the IDA install directory to diff --git a/CHANGES.txt b/CHANGES.txt index 17a1a0d..4ccdb92 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,12 +1,32 @@ Please see http://code.google.com/p/idapython/source/list for a detailed list of changes. +Changes from version 1.5.2 to 1.5.3 +------------------------------------ +- added set_idc_func_ex(): it is now possible to add new IDC functions using Python +- added visit_patched_bytes() (see ex_patch.py) +- added support for the multiline text input control in the Form class +- added support for the editable/readonly dropdown list control in the Form class +- added execute_sync() to register a function call into the UI message queue +- added execute_ui_requests() / check ex_uirequests.py +- added add_hotkey() / del_hotkey() to bind Python methods to hotkeys +- added register_timer()/unregister_timer(). Check ex_timer.py +- added the IDC (Arrays) netnode manipulation layer into idc.py +- added idautils.Structs() and StructMembers() generator functions +- removed the "Run Python Statment" menu item. IDA now has a unified dialog. + Use RunPlugin("python", 0) to invoke it manually. +- better error messages for script plugins, loaders and processor modules +- bugfix: Dbg_Hooks.dbg_run_to() was receiving wrong input +- bugfix: A few Enum related functions were not properly working in idc.py +- bugfix: GetIdaDirectory() and GetProcessName() were broken in idc.py +- bugfix: idaapi.get_item_head() / idc.ItemHead() were not working + Changes from version 1.5.1 to 1.5.2 ------------------------------------ - added ui_term/ui_save/ui_saved/ui_get_ea_hint UI notifications - added ph_get_operand_info() to retrieve operand information while debugging - added PteDump.py script -- some code refactoring +- bugfix: read/write_dbg_memory() and dbg_get_thread_sreg_base() were not working with all debugger modules - bugfix: idaapi.netnode.getblob() was limited to MAXSPECSIZE - bugfix: idc.GetString()/idaapi.get_ascii_contents()/idautils.Strings() were limited to MAXSTR string length - bugfix: idaapi.del_menu_item() was failing to delete some menu items diff --git a/README.txt b/README.txt index f90bab5..3e90854 100644 --- a/README.txt +++ b/README.txt @@ -1,8 +1,8 @@ ----------------------------------------------------------- -IDAPython - Python plugin for Interactive Disassembler Pro ----------------------------------------------------------- +------------------------------------------------------ +IDAPython - Python plugin for Interactive Disassembler +------------------------------------------------------ -WHAT IS IDAPTYHON? +What is IDAPython? ------------------ IDAPython is an IDA plugin which makes it possible to write scripts @@ -12,7 +12,7 @@ access to both the IDA API and any installed Python module. Check the scripts in the examples directory to get an quick glimpse. -AVAILABILITY +Availability ------------ Latest stable versions of IDAPython are available from @@ -22,7 +22,7 @@ Development builds are available from http://code.google.com/p/idapython/ -RESOURCES +Resources --------- The full function cross-reference is readable online at @@ -35,22 +35,23 @@ Mailing list for the project is hosted by Google Groups at http://groups.google.com/group/idapython -INSTALLATION FROM BINARIES +Installation from binaries -------------------------- 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\ +2. Copy the whole "python" directory to %IDADIR% +3. Copy the contents of the "plugins" directory to the %IDADIR%\plugins\ 4. Copy "python.cfg" to %IDADIR%\cfg -USAGE +Usage ----- - Run script: File / Script file (Alt-F7) - Execute Python statement(s) (Ctrl-F3) - Run previously executed script again: View / Recent Scripts (Alt+F9) -Batch mode execution: + +* Batch mode execution: Start IDA with the following command line options: @@ -75,7 +76,7 @@ Where N can be: 1: run script when UI is ready 2: run script immediately on plugin load (shortly after IDA starts and before processor modules and loaders) -User init file: +* User init file You can place your custom settings to a file called 'idapythonrc.py' that should be placed to @@ -89,3 +90,25 @@ 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. + +* Invoking Python from IDC + +The IDAPython plugin exposes a new IDC function "RunPythonStatement(string idc_code)" that allows execution +of Python code from IDC + +* Invoking IDC from Python + +It is possible to use the idc.Eval() to evaluate IDC expressions from Python + +* Making Python the default language + +By default, IDA will use IDC to evaluate expressions. It is possible to change the default language to use +Python instead of IDC. + +In order to do that, please use the following IDC code: + +RunPlugin("python", 3) + +To disable Python language and revert back to IDC: +RunPlugin("python", 4) + diff --git a/Scripts/VirusTotal.py b/Scripts/VirusTotal.py index 0b9af4f..2ddc437 100644 --- a/Scripts/VirusTotal.py +++ b/Scripts/VirusTotal.py @@ -20,7 +20,7 @@ PLUGIN_TEST = 0 # ----------------------------------------------------------------------- # Configuration file -VT_CFGFILE = idaapi.get_user_idadir() + os.sep + "virustotal.cfg" +VT_CFGFILE = os.path.join(idaapi.get_user_idadir(), "virustotal.cfg") # ----------------------------------------------------------------------- # VirusTotal Icon in PNG format diff --git a/build.py b/build.py index fd7c305..64aca0f 100644 --- a/build.py +++ b/build.py @@ -1,8 +1,8 @@ #!/usr/bin/env python #--------------------------------------------------------------------- -# IDAPython - Python plugin for Interactive Disassembler Pro +# IDAPython - Python plugin for Interactive Disassembler # -# Copyright (c) 2004-2010 Gergely Erdelyi +# (c) The IDAPython Team # # All rights reserved. # @@ -24,19 +24,19 @@ from distutils import sysconfig VERBOSE = True IDA_MAJOR_VERSION = 6 -IDA_MINOR_VERSION = 1 +IDA_MINOR_VERSION = 2 if 'IDA' in os.environ: IDA_SDK = os.environ['IDA'] else: - IDA_SDK = ".." + os.sep + "swigsdk-versions" + os.sep + "%d.%d" % (IDA_MAJOR_VERSION, IDA_MINOR_VERSION) + IDA_SDK = os.path.join("..", "swigsdk-versions", ("%d.%d" % (IDA_MAJOR_VERSION, IDA_MINOR_VERSION))) # End of user configurable options # IDAPython version VERSION_MAJOR = 1 VERSION_MINOR = 5 -VERSION_PATCH = 2 +VERSION_PATCH = 3 # Determine Python version PYTHON_MAJOR_VERSION = int(platform.python_version()[0]) @@ -72,6 +72,8 @@ BINDIST_MANIFEST = [ "docs/notes.txt", "examples/chooser.py", "examples/colours.py", + "examples/ex_idphook_asm.py", + "examples/ex_uirequests.py", "examples/debughook.py", "examples/ex_cli.py", "examples/ex1.idc", @@ -87,6 +89,10 @@ BINDIST_MANIFEST = [ "examples/ex_choose2.py", "examples/ex_debug_names.py", "examples/ex_graph.py", + "examples/ex_hotkey.py", + "examples/ex_patch.py", + "examples/ex_expr.py", + "examples/ex_timer.py", "examples/ex_dbg.py", "examples/ex_custview.py", "examples/ex_prefix_plugin.py", @@ -94,6 +100,7 @@ BINDIST_MANIFEST = [ "examples/ex_pyqt.py", "examples/ex_askusingform.py", "examples/ex_uihook.py", + "examples/ex_idphook_asm.py", "examples/ex_imports.py" ] @@ -216,7 +223,7 @@ class GCCBuilder(BuilderBase): self.include_delimiter = "-I" self.macro_delimiter = "-D" self.libpath_delimiter = "-L" - self.compiler_parameters = "-fpermissive" + self.compiler_parameters = "-fpermissive -Wno-write-strings" self.linker_parameters = "-shared" self.basemacros = [ ] self.compiler = "g++ -m32" @@ -279,8 +286,8 @@ def build_distribution(manifest, distrootdir, ea64, nukeold): if type(f) == types.TupleType: srcfilepath = f[0] srcfilename = os.path.basename(srcfilepath) - dstdir = distrootdir + os.sep + f[1] - dstfilepath = dstdir + os.sep + srcfilename + dstdir = os.path.join(distrootdir, f[1]) + dstfilepath = os.path.join(dstdir, srcfilename) else: srcfilepath = f srcfilename = os.path.basename(f) @@ -288,12 +295,12 @@ def build_distribution(manifest, distrootdir, ea64, nukeold): if srcdir == "": dstdir = distrootdir else: - dstdir = distrootdir + os.sep + srcdir + dstdir = os.path.join(distrootdir, srcdir) if not os.path.exists(dstdir): os.makedirs(dstdir) - dstfilepath = dstdir + os.sep + srcfilename + dstfilepath = os.path.join(dstdir, srcfilename) shutil.copyfile(srcfilepath, dstfilepath) zip.write(dstfilepath) @@ -304,14 +311,14 @@ def build_plugin(platform, idasdkdir, plugin_name, ea64): global SWIG_OPTIONS """ Build the plugin from the SWIG wrapper and plugin main source """ # Path to the IDA SDK headers - ida_include_directory = idasdkdir + os.sep + "include" + ida_include_directory = os.path.join(idasdkdir, "include") builder = None # Platform-specific settings for the Linux build if platform == "linux": builder = GCCBuilder() platform_macros = [ "__LINUX__" ] - python_libpath = sysconfig.EXEC_PREFIX + os.sep + "lib" + python_libpath = os.path.join(sysconfig.EXEC_PREFIX, "lib") python_library = "-lpython%d.%d" % (PYTHON_MAJOR_VERSION, PYTHON_MINOR_VERSION) ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "x86_linux_gcc_64" or "x86_linux_gcc_32") ida_lib = "" @@ -320,7 +327,7 @@ def build_plugin(platform, idasdkdir, plugin_name, ea64): elif platform == "win32": builder = MSVCBuilder() platform_macros = [ "__NT__" ] - python_libpath = sysconfig.EXEC_PREFIX + os.sep + "libs" + python_libpath = os.path.join(sysconfig.EXEC_PREFIX, "libs") python_library = "python%d%d.lib" % (PYTHON_MAJOR_VERSION, PYTHON_MINOR_VERSION) ida_libpath = os.path.join(idasdkdir, "lib", ea64 and "x86_win_vc_64" or "x86_win_vc_32") ida_lib = "ida.lib" diff --git a/examples/chooser.py b/examples/chooser.py index cacced1..de3a540 100644 --- a/examples/chooser.py +++ b/examples/chooser.py @@ -9,7 +9,7 @@ from idaapi import Choose # # Modal chooser -# +# # Get a modal Choose instance chooser = Choose([], "MyChooser", 1) @@ -21,28 +21,30 @@ chooser.width = 50 ch = chooser.choose() # Print the results if ch > 0: - print "You chose %d which is %s" % (ch, chooser.list[ch-1]) + print "You chose %d which is %s" % (ch, chooser.list[ch-1]) else: - print "Escape from chooser" + print "Escape from chooser" # # Normal chooser # class MyChoose(Choose): - """ - You have to subclass Chooser to override the enter() method - """ - def __init__(self, list=[], name="Choose"): - Choose.__init__(self, list, name) - # Set the width - self.width = 50 + """ + You have to subclass Chooser to override the enter() method + """ + def __init__(self, list=[], name="Choose"): + Choose.__init__(self, list, name) + # Set the width + self.width = 50 + self.deflt = 1 - def enter(self, n): - print "Enter called. Do some stuff here." - print "The chosen item is %d = %s" % (n, self.list[n-1]) - print "Now press ESC to leave." + def enter(self, n): + print "Enter called. Do some stuff here." + print "The chosen item is %d = %s" % (n, self.list[n-1]) + print "Now press ESC to leave." # Get a Choose instance chooser = MyChoose([ "First", "Second", "Third" ], "MyChoose") + # Run the chooser ch = chooser.choose() diff --git a/examples/debughook.py b/examples/debughook.py index 338396b..acc4fea 100644 --- a/examples/debughook.py +++ b/examples/debughook.py @@ -16,16 +16,17 @@ class MyDbgHook(DBG_Hooks): def dbg_process_start(self, pid, tid, ea, name, base, size): print("Process started, pid=%d tid=%d name=%s" % (pid, tid, name)) - return 0 def dbg_process_exit(self, pid, tid, ea, code): print("Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code)) - return 0 def dbg_library_unload(self, pid, tid, ea, info): print("Library unloaded: pid=%d tid=%d ea=0x%x info=%s" % (pid, tid, ea, info)) return 0 + def dbg_process_attach(self, pid, tid, ea, name, base, size): + print("Process attach pid=%d tid=%d ea=0x%x name=%s base=%x size=%x" % (pid, tid, ea, name, base, size)) + def dbg_process_detach(self, pid, tid, ea): print("Process detached, pid=%d tid=%d ea=0x%x" % (pid, tid, ea)) return 0 @@ -57,15 +58,19 @@ class MyDbgHook(DBG_Hooks): def dbg_trace(self, tid, ea): print("Trace tid=%d ea=0x%x" % (tid, ea)) + # return values: + # 1 - do not log this trace event; + # 0 - log it return 0 def dbg_step_into(self): print("Step into") - return self.dbg_step_over() + self.dbg_step_over() + + def dbg_run_to(self, pid, tid=0, ea=0): + print "Runto: tid=%d" % tid + idaapi.continue_process() -# def dbg_run_to(self, tid): -# print "Runto: tid=%d" % tid -# idaapi.continue_process() def dbg_step_over(self): eip = GetRegValue("EIP") @@ -76,7 +81,7 @@ class MyDbgHook(DBG_Hooks): request_exit_process() else: request_step_over() - return 0 + # Remove an existing debug hook try: diff --git a/examples/ex_askusingform.py b/examples/ex_askusingform.py index be61d61..0734b24 100644 --- a/examples/ex_askusingform.py +++ b/examples/ex_askusingform.py @@ -218,6 +218,148 @@ using address %$ if ok == 1: print("You entered: %x" % num.value) +# -------------------------------------------------------------------------- +def test_multilinetext_legacy(): + # Here we text the multi line text control in legacy mode + + # Sample form from kernwin.hpp + s = """Sample dialog box + +This is sample dialog box + +""" + # Use either StringArgument or NumericArgument to pass values to the function + ti = textctrl_info_t("Some initial value") + ok = idaapi.AskUsingForm(s, pointer(c_void_p.from_address(ti.clink_ptr))) + if ok == 1: + print("You entered: %s" % ti.text) + + del ti + +# -------------------------------------------------------------------------- +class MyForm2(Form): + """Simple Form to test multilinetext and combo box controls""" + def __init__(self): + Form.__init__(self, r"""STARTITEM 0 +BUTTON YES* Yeah +BUTTON NO Nope +BUTTON CANCEL NONE +Form Test + +{FormChangeCb} + +""", { + 'txtMultiLineText': Form.MultiLineTextControl(text="Hello"), + 'FormChangeCb': Form.FormChangeCb(self.OnFormChange), + }) + + + def OnFormChange(self, fid): + if fid == self.txtMultiLineText.id: + pass + elif fid == -2: + ti = self.GetControlValue(self.txtMultiLineText) + print "ti.text = %s" % ti.text + else: + print(">>fid:%d" % fid) + return 1 + +# -------------------------------------------------------------------------- +def test_multilinetext(execute=True): + """Test the multilinetext and combobox controls""" + f = MyForm2() + f, args = f.Compile() + if execute: + ok = f.Execute() + else: + print args[0] + print args[1:] + ok = 0 + + if ok == 1: + assert f.txtMultiLineText.text == f.txtMultiLineText.value + print f.txtMultiLineText.text + + f.Free() + +# -------------------------------------------------------------------------- +class MyForm3(Form): + """Simple Form to test multilinetext and combo box controls""" + def __init__(self): + self.__n = 0 + Form.__init__(self, +r"""BUTTON YES* Yeah +BUTTON NO Nope +BUTTON CANCEL NONE +Dropdown list test + +{FormChangeCb} + + +""", { + 'FormChangeCb': Form.FormChangeCb(self.OnFormChange), + 'cbReadonly': Form.DropdownListControl( + items=["red", "green", "blue"], + readonly=True, + selval=1), + 'cbEditable': Form.DropdownListControl( + items=["1MB", "2MB", "3MB", "4MB"], + readonly=False, + selval="4MB"), + 'iButtonAddelement': Form.ButtonInput(self.OnButtonNop), + 'iButtonSetIndex': Form.ButtonInput(self.OnButtonNop), + 'iButtonSetString': Form.ButtonInput(self.OnButtonNop), + }) + + + def OnButtonNop(self, code=0): + """Do nothing, we will handle events in the form callback""" + pass + + def OnFormChange(self, fid): + if fid == self.iButtonSetString.id: + s = idc.AskStr("none", "Enter value") + if s: + self.SetControlValue(self.cbEditable, s) + elif fid == self.iButtonSetIndex.id: + s = idc.AskStr("1", "Enter index value:") + if s: + try: + i = int(s) + except: + i = 0 + self.SetControlValue(self.cbReadonly, i) + elif fid == self.iButtonAddelement.id: + # add a value to the string list + self.__n += 1 + self.cbReadonly.add("some text #%d" % self.__n) + # Refresh the control + self.RefreshField(self.cbReadonly) + elif fid == -2: + s = self.GetControlValue(self.cbEditable) + print "user entered: %s" % s + sel_idx = self.GetControlValue(self.cbReadonly) + + return 1 + +# -------------------------------------------------------------------------- +def test_dropdown(execute=True): + """Test the combobox controls""" + f = MyForm3() + f, args = f.Compile() + if execute: + ok = f.Execute() + else: + print args[0] + print args[1:] + ok = 0 + + if ok == 1: + print "Editable: %s" % f.cbEditable.value + print "Readonly: %s" % f.cbReadonly.value + + f.Free() + # diff --git a/examples/ex_choose2.py b/examples/ex_choose2.py index 5cc2ddc..c4d72f3 100644 --- a/examples/ex_choose2.py +++ b/examples/ex_choose2.py @@ -3,12 +3,13 @@ from idaapi import Choose2 class MyChoose2(Choose2): - def __init__(self, title, nb = 5): + def __init__(self, title, nb = 5, deflt=1): 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.deflt = deflt self.popup_names = ["Inzert", "Del leet", "Ehdeet", "Ree frech"] print("created %s" % str(self)) @@ -79,6 +80,6 @@ class MyChoose2(Choose2): return [0xFF0000, 0] for i in xrange(1, 5+1): - c = MyChoose2("choose2 - sample %d" % i, i*2) + c = MyChoose2("choose2 - sample %d" % i, i*2, deflt=i) r = c.show() print r diff --git a/examples/ex_expr.py b/examples/ex_expr.py new file mode 100644 index 0000000..765d746 --- /dev/null +++ b/examples/ex_expr.py @@ -0,0 +1,16 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to extend IDC from Python +# (c) Hex-Rays +# +from idaapi import set_idc_func_ex + +# +def py_power(n, e): + return n ** e + +ok = set_idc_func_ex("pow", py_power, (idaapi.VT_LONG, idaapi.VT_LONG), 0) +if ok: + print("Now the pow() will be present IDC!") +else: + print("Failed to register pow() IDC function") +# diff --git a/examples/ex_hotkey.py b/examples/ex_hotkey.py new file mode 100644 index 0000000..d689ed6 --- /dev/null +++ b/examples/ex_hotkey.py @@ -0,0 +1,25 @@ +#--------------------------------------------------------------------- +# This script demonstrates the usage of hotkeys. +# +# +# Author: IDAPython team +#--------------------------------------------------------------------- +import idaapi + +def hotkey_pressed(): + print("hotkey pressed!") + +try: + hotkey_ctx + if idaapi.del_hotkey(hotkey_ctx): + print("Hotkey unregistered!") + del hotkey_ctx + else: + print("Failed to delete hotkey!") +except: + hotkey_ctx = idaapi.add_hotkey("Shift-A", hotkey_pressed) + if hotkey_ctx is None: + print("Failed to register hotkey!") + del hotkey_ctx + else: + print("Hotkey registered!") diff --git a/examples/ex_idphook_asm.py b/examples/ex_idphook_asm.py new file mode 100644 index 0000000..b364212 --- /dev/null +++ b/examples/ex_idphook_asm.py @@ -0,0 +1,48 @@ +import idaapi +import idautils + +""" + This is a sample plugin for extending the assemble(). + + We add support for assembling the following pseudo instructions: + - "zero eax" -> xor eax, eax + - "nothing" -> nop + + +(c) Hex-Rays +""" + +#-------------------------------------------------------------------------- +class assemble_idp_hook_t(idaapi.IDP_Hooks): + def __init__(self): + idaapi.IDP_Hooks.__init__(self) + + def assemble(self, ea, cs, ip, use32, line): + line = line.strip() + if line == "xor eax, eax": + return "\x33\xC0" + elif line == "nop": + # Decode current instruction to figure out its size + cmd = idautils.DecodeInstruction(ea) + if cmd: + # NOP all the instruction bytes + return "\x90" * cmd.size + return None + + +#--------------------------------------------------------------------- +# Remove an existing hook on second run +try: + idp_hook_stat = "un" + print("IDP hook: checking for hook...") + idphook + print("IDP hook: unhooking....") + idphook.unhook() + del idphook +except: + print("IDP hook: not installed, installing now....") + idp_hook_stat = "" + idphook = assemble_idp_hook_t() + idphook.hook() + +print("IDP hook %sinstalled. Run the script again to %sinstall" % (idp_hook_stat, idp_hook_stat)) diff --git a/examples/ex_patch.py b/examples/ex_patch.py new file mode 100644 index 0000000..22bf138 --- /dev/null +++ b/examples/ex_patch.py @@ -0,0 +1,36 @@ +# ------------------------------------------------------------------------- +# This is an example illustrating how to visit all patched bytes in Python +# (c) Hex-Rays + +import idaapi + +# ------------------------------------------------------------------------- +class patched_bytes_visitor(object): + def __init__(self): + self.skip = 0 + self.patch = 0 + + def __call__(self, ea, fpos, o, v, cnt=()): + if fpos == -1: + self.skip += 1 + print(" ea: %x o: %x v: %x...skipped" % (ea, fpos, o, v)) + else: + self.patch += 1 + print(" ea: %x fpos: %x o: %x v: %x" % (ea, fpos, o, v)) + return 0 + + +# ------------------------------------------------------------------------- +def main(): + print("Visiting all patched bytes:") + v = patched_bytes_visitor() + r = idaapi.visit_patched_bytes(0, idaapi.BADADDR, v) + if r != 0: + print("visit_patched_bytes() returned %d" % r) + else: + print("Patched: %d Skipped: %d" % (v.patch, v.skip)) + + +# ------------------------------------------------------------------------- +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/examples/ex_timer.py b/examples/ex_timer.py new file mode 100644 index 0000000..d22cd74 --- /dev/null +++ b/examples/ex_timer.py @@ -0,0 +1,38 @@ +# ------------------------------------------------------------------------- +# This is an example illustrating how to use timers +# (c) Hex-Rays + +import idaapi + +# ------------------------------------------------------------------------- +class timercallback_t(object): + def __init__(self): + self.interval = 1000 + self.obj = idaapi.register_timer(self.interval, self) + if self.obj is None: + raise RuntimeError, "Failed to register timer" + self.times = 5 + + def __call__(self): + print("Timer invoked. %d time(s) left" % self.times) + self.times -= 1 + # Unregister the timer when the counter reaches zero + return -1 if self.times == 0 else self.interval + + def __del__(self): + print("Timer object disposed %s" % id(self)) + + +# ------------------------------------------------------------------------- +def main(): + try: + t = timercallback_t() + # No need to unregister the timer. + # It will unregister itself in the callback when it returns -1 + except Exception as e: + print "Error: %s" % e + + +# ------------------------------------------------------------------------- +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/examples/ex_uirequests.py b/examples/ex_uirequests.py new file mode 100644 index 0000000..292c6bb --- /dev/null +++ b/examples/ex_uirequests.py @@ -0,0 +1,75 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to use the execute_ui_requests() +# and the idautils.ProcessUiActions() +# (c) Hex-Rays +# +import idaapi +import idautils +import idc + +# -------------------------------------------------------------------------- +class __process_ui_actions_helper(object): + def __init__(self, actions, flags = 0): + """Expect a list or a string with a list of actions""" + if isinstance(actions, str): + lst = actions.split(";") + elif isinstance(actions, (list, tuple)): + lst = actions + else: + raise ValueError, "Must pass a string, list or a tuple" + + # Remember the action list and the flags + self.__action_list = lst + self.__flags = flags + + # Reset action index + self.__idx = 0 + + def __len__(self): + return len(self.__action_list) + + def __call__(self): + if self.__idx >= len(self.__action_list): + return False + + # Execute one action + idaapi.process_ui_action( + self.__action_list[self.__idx], + self.__flags) + + # Move to next action + self.__idx += 1 + print "index=%d" % self.__idx + + # Reschedule + return True + +# -------------------------------------------------------------------------- +def ProcessUiActions(actions, flags=0): + """ + @param actions: A string containing a list of actions separated by semicolon, a list or a tuple + @param flags: flags to be passed to process_ui_action() + @return: Boolean. Returns False if the action list was empty or execute_ui_requests() failed. + """ + + # Instantiate a helper + helper = __process_ui_actions_helper(actions, flags) + return False if len(helper) < 1 else idaapi.execute_ui_requests((helper,)) + + +# -------------------------------------------------------------------------- +class print_req_t(object): + def __init__(self, s): + self.s = s + def __call__(self): + idaapi.msg("%s" % self.s) + return False # Don't reschedule + + + +if idc.AskYN(1,("HIDECANCEL\nDo you want to run execute_ui_requests() example?\n" + "Press NO to execute ProcessUiActions() example\n")): + idaapi.execute_ui_requests( + (print_req_t("Hello"), print_req_t(" world\n")) ) +else: + ProcessUiActions("JumpQ;JumpName") diff --git a/python.cfg b/python.cfg index e5e4005..8fadcec 100644 --- a/python.cfg +++ b/python.cfg @@ -5,9 +5,6 @@ ALERT_AUTO_SCRIPTS = 1 // Remove current directory from import search path REMOVE_CWD_SYS_PATH = 0 -// Execute statement hotkey -EXEC_STATEMENT_HOTKEY = "Ctrl-F7"; - // Script timeout (in seconds) // (A value of 0 disables the timeout) SCRIPT_TIMEOUT = 3 diff --git a/python.cpp b/python.cpp index 0fbb719..bd3fb38 100644 --- a/python.cpp +++ b/python.cpp @@ -1,5 +1,5 @@ //--------------------------------------------------------------------- -// IDAPython - Python plugin for Interactive Disassembler Pro +// IDAPython - Python plugin for Interactive Disassembler // // Copyright (c) The IDAPython Team // @@ -42,7 +42,7 @@ // Python-style version tuple comes from the makefile // Only the serial and status is set here -#define VER_SERIAL 3 +#define VER_SERIAL 0 #define VER_STATUS "final" #define IDAPYTHON_RUNSTATEMENT 0 #define IDAPYTHON_ENABLE_EXTLANG 3 @@ -75,11 +75,9 @@ enum script_run_when //------------------------------------------------------------------------- // Global variables static bool g_initialized = false; -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 @@ -223,123 +221,23 @@ int set_script_timeout(int timeout) return timeout; } -//------------------------------------------------------------------------- -// Converts IDC arguments to Python argument list or just one tuple -// If 'decref' is NULL then 'pargs' will contain one element which is the tuple -static bool convert_args( - const idc_value_t args[], - int nargs, - ppyobject_vec_t &pargs, - boolvec_t *decref, - char *errbuf, - size_t errbufsize) -{ - bool as_tupple = decref == NULL; - PyObject *py_tuple(NULL); - - pargs.qclear(); - - if ( as_tupple ) - { - py_tuple = PyTuple_New(nargs); - if ( py_tuple == NULL ) - { - qstrncpy(errbuf, "Failed to create a new tuple to store arguments!", errbufsize); - return false; - } - // Add the tuple - pargs.push_back(py_tuple); - } - else - { - decref->qclear(); - } - - for ( int i=0; ipush_back(cvt == CIP_OK); - } - } - return true; -} - -//------------------------------------------------------------------------- -// Frees arguments returned by convert_args() -static void free_args( - ppyobject_vec_t &pargs, - boolvec_t *decref = NULL) -{ - if ( decref == NULL ) - { - if ( !pargs.empty() ) - Py_XDECREF(pargs[0]); - } - else - { - // free argument objects - for ( int i=(int)pargs.size()-1; i>=0; i-- ) - { - if ( decref->at(i) ) - Py_DECREF(pargs[i]); - } - decref->clear(); - } - pargs.clear(); -} - //------------------------------------------------------------------------ // Return a formatted error or just print it to the console -static void handle_python_error(char *errbuf, size_t errbufsize) +static void handle_python_error( + char *errbuf, + size_t errbufsize, + bool clear_error = true) { if ( errbufsize > 0 ) errbuf[0] = '\0'; + // No exception? if ( !PyErr_Occurred() ) return; - PyObject *result; - PyObject *ptype, *pvalue, *ptraceback; - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - result = PyObject_Str(pvalue); - if ( result != NULL ) - { - qsnprintf(errbuf, errbufsize, "ERROR: %s", PyString_AsString(result)); - PyErr_Clear(); - Py_XDECREF(ptype); - Py_XDECREF(pvalue); - Py_XDECREF(ptraceback); - Py_DECREF(result); - } - else - { - PyErr_Print(); - } + qstring s; + if ( PyW_GetError(&s, clear_error) ) + qstrncpy(errbuf, s.c_str(), errbufsize); } //------------------------------------------------------------------------ @@ -391,23 +289,26 @@ static void PythonEvalOrExec( } //------------------------------------------------------------------------ -// Simple Python statement runner function for IDC -static const char idc_runpythonstatement_args[] = { VT_STR2, 0 }; -static error_t idaapi idc_runpythonstatement(idc_value_t *argv, idc_value_t *res) +// Executes a simple string +static bool idaapi IDAPython_extlang_run_statements( + const char *str, + char *errbuf, + size_t errbufsize) { PyObject *globals = GetMainGlobals(); + bool ok; if ( globals == NULL ) { - res->set_string("internal error"); + ok = false; } else { + errbuf[0] = '\0'; PyErr_Clear(); - begin_execution(); PYW_GIL_ENSURE; PyObject *result = PyRun_String( - argv[0].c_str(), + str, Py_file_input, globals, globals); @@ -415,21 +316,31 @@ static error_t idaapi idc_runpythonstatement(idc_value_t *argv, idc_value_t *res Py_XDECREF(result); end_execution(); - if ( result == NULL || PyErr_Occurred() ) - { - char errbuf[MAXSTR]; - handle_python_error(errbuf, sizeof(errbuf)); - if ( errbuf[0] == '\0' ) - res->set_string("internal error"); - else - res->set_string(errbuf); - } - else - { - // success - res->set_long(0); - } + ok = result != NULL && !PyErr_Occurred(); + + if ( !ok ) + handle_python_error(errbuf, errbufsize); } + if ( !ok && errbuf[0] == '\0' ) + qstrncpy(errbuf, "internal error", errbufsize); + return ok; +} + +//------------------------------------------------------------------------ +// Simple Python statement runner function for IDC +static const char idc_runpythonstatement_args[] = { VT_STR2, 0 }; +static error_t idaapi idc_runpythonstatement( + idc_value_t *argv, + idc_value_t *res) +{ + char errbuf[MAXSTR]; + bool ok = IDAPython_extlang_run_statements(argv[0].c_str(), errbuf, sizeof(errbuf)); + + if ( ok ) + res->set_long(0); + else + res->set_string(errbuf); + return eOk; } @@ -441,15 +352,7 @@ const char *idaapi set_python_options( { 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 ( value_type == IDPOPT_NUM ) { if ( qstrcmp(keyword, "SCRIPT_TIMEOUT") == 0 ) { @@ -567,7 +470,7 @@ static bool IDAPython_ExecFile(const char *FileName, char *errbuf, size_t errbuf char script[MAXSTR]; qstrncpy(script, FileName, sizeof(script)); - strrpl(script, '\\', '//'); + strrpl(script, '\\', '/'); PyObject *py_script = PyString_FromString(script); PYW_GIL_ENSURE; @@ -754,7 +657,7 @@ bool idaapi IDAPython_extlang_run( do { // Convert arguments to python - ok = convert_args(args, nargs, pargs, &decref, errbuf, errbufsize); + ok = pyw_convert_idc_args(args, nargs, pargs, &decref, errbuf, errbufsize); if ( !ok ) break; @@ -792,7 +695,7 @@ bool idaapi IDAPython_extlang_run( ok = return_python_result(result, pres, errbuf, errbufsize); } while ( false ); - free_args(pargs, &decref); + pyw_free_idc_args(pargs, &decref); if ( imported_module ) Py_XDECREF(module); @@ -851,7 +754,7 @@ bool idaapi IDAPython_extlang_create_object( } // Error during conversion? - ok = convert_args(args, nargs, pargs, NULL, errbuf, errbufsize); + ok = pyw_convert_idc_args(args, nargs, pargs, NULL, errbuf, errbufsize); if ( !ok ) break; ok = false; @@ -867,7 +770,7 @@ bool idaapi IDAPython_extlang_create_object( Py_XDECREF(py_cls); // Free the arguments tuple - free_args(pargs); + pyw_free_idc_args(pargs); return ok; } @@ -918,9 +821,41 @@ bool idaapi IDAPython_extlang_get_attr( // No object specified: else { - // then work with main module + // ...then work with main module py_obj = py_mod; } + // Special case: if attribute not passed then retrieve the class + // name associated associated with the passed object + if ( attr == NULL || attr[0] == '\0' ) + { + cvt = CIP_FAILED; + // Get the class + PyObject *cls = PyObject_GetAttrString(py_obj, "__class__"); + if ( cls == NULL ) + break; + + // Get its name + PyObject *name = PyObject_GetAttrString(cls, "__name__"); + Py_DECREF(cls); + if ( name == NULL ) + break; + + // Convert name object to string object + PyObject *string = PyObject_Str(name); + Py_DECREF(name); + if ( string == NULL ) + break; + + // Convert name python string to a C string + const char *clsname = PyString_AsString(name); + if ( clsname == NULL ) + break; + + result->set_string(clsname); + cvt = CIP_OK; + break; + } + PyObject *py_attr = PyW_TryGetAttrString(py_obj, attr); // No attribute? if ( py_attr == NULL ) @@ -1089,7 +1024,7 @@ bool idaapi IDAPython_extlang_call_method( } // Convert arguments to python objects - ok = convert_args(args, nargs, pargs, NULL, errbuf, errbufsize); + ok = pyw_convert_idc_args(args, nargs, pargs, NULL, errbuf, errbufsize); if ( !ok ) break; @@ -1100,7 +1035,7 @@ bool idaapi IDAPython_extlang_call_method( } while ( false ); // Free converted args - free_args(pargs); + pyw_free_idc_args(pargs); // Release reference of object if needed if ( obj_cvt != CIP_OK_NODECREF ) @@ -1124,7 +1059,8 @@ extlang_t extlang_python = IDAPython_extlang_create_object, IDAPython_extlang_get_attr, IDAPython_extlang_set_attr, - IDAPython_extlang_call_method + IDAPython_extlang_call_method, + IDAPython_extlang_run_statements, }; //------------------------------------------------------------------------- @@ -1247,26 +1183,6 @@ void py_print_banner() PYW_GIL_RELEASE; } -//------------------------------------------------------------------------- -// Install python menu items -static void install_python_menus() -{ - if ( g_menu_installed ) - return; - - // 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...", - g_runstmt_hotkey, - SETMENU_APP, - IDAPython_Menu_Callback, - (void *)IDAPYTHON_RUNSTATEMENT); - - g_menu_installed = true; -} - //------------------------------------------------------------------------ // Parse plugin options void parse_plugin_options() @@ -1331,7 +1247,6 @@ static int idaapi menu_installer_cb(void *, int code, va_list) case ui_ready_to_run: g_ui_ready = true; py_print_banner(); - install_python_menus(); if ( g_run_when == run_on_ui_ready ) RunScript(g_run_script); @@ -1429,7 +1344,8 @@ bool IDAPython_Init(void) init_idaapi(); // Set IDAPYTHON_VERSION in Python - qsnprintf(tmp, sizeof(tmp), "IDAPYTHON_VERSION=(%d, %d, %d, '%s', %d)\n" + qsnprintf(tmp, sizeof(tmp), + "IDAPYTHON_VERSION=(%d, %d, %d, '%s', %d)\n" "IDAPYTHON_REMOVE_CWD_SYS_PATH = %s\n", VER_MAJOR, VER_MINOR, @@ -1447,8 +1363,17 @@ bool IDAPython_Init(void) qmakepath(tmp, MAXSTR, g_idapython_dir, S_INIT_PY, NULL); if ( !PyRunFile(tmp) ) { - handle_python_error(tmp, sizeof(tmp)); - warning("IDAPython: error executing " S_INIT_PY ":\n%s", tmp); + // Try to fetch a one line error string. We must do it before printing + // the traceback information. Make sure that the exception is not cleared + handle_python_error(tmp, sizeof(tmp), false); + + // Print the exception traceback + PyRun_SimpleString("import traceback;traceback.print_exc();"); + + warning("IDAPython: error executing " S_INIT_PY ":\n" + "%s\n" + "\n" + "Refer to the message window to see the full error log.", tmp); return false; } @@ -1497,7 +1422,6 @@ void IDAPython_Term(void) #endif /* Remove the menu items before termination */ del_menu_item("File/Python command..."); - g_menu_installed = false; // Notify about IDA closing pywraps_nw_notify(NW_TERMIDA_SLOT); diff --git a/python/idautils.py b/python/idautils.py index 6c3d768..8a5f0bd 100644 --- a/python/idautils.py +++ b/python/idautils.py @@ -1,5 +1,5 @@ #--------------------------------------------------------------------- -# IDAPython - Python plugin for Interactive Disassembler Pro +# IDAPython - Python plugin for Interactive Disassembler # # Copyright (c) 2004-2010 Gergely Erdelyi # @@ -310,6 +310,38 @@ def FuncItems(start): ok = fii.next_code() +def Structs(): + """ + Get a list of structures + + @return: List of tuples (idx, sid, name) + """ + idx = idc.GetFirstStrucIdx() + while idx != idaapi.BADADDR: + sid = idc.GetStrucId(idx) + yield (idx, sid, idc.GetStrucName(sid)) + idx = idc.GetNextStrucIdx(idx) + + +def StructMembers(sid): + """ + Get a list of structure members information. + + @param sid: ID of the structure. + + @return: List of tuples (offset, name, size) + + @note: If 'sid' does not refer to a valid structure, + an exception will be raised. + """ + off = idc.GetFirstMember(sid) + if off == idaapi.BADNODE: + raise Exception("No structure with ID: 0x%x" % sid) + members = idc.GetMemberQty(sid) + for idx in range(0, members): + yield (off, idc.GetMemberName(sid, off), idc.GetMemberSize(sid, off)) + off = idc.GetStrucNextOff(sid, off) + def DecodePrecedingInstruction(ea): """ @@ -648,17 +680,69 @@ class _procregs(object): 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 + #print "cpu.get(%s)" % name return idc.GetRegValue(name) def __setattr__(self, name, value): - #print "cpu.set(%s)"%name + #print "cpu.set(%s)" % name return idc.SetRegValue(value, name) + +# -------------------------------------------------------------------------- +class __process_ui_actions_helper(object): + def __init__(self, actions, flags = 0): + """Expect a list or a string with a list of actions""" + if isinstance(actions, str): + lst = actions.split(";") + elif isinstance(actions, (list, tuple)): + lst = actions + else: + raise ValueError, "Must pass a string, list or a tuple" + + # Remember the action list and the flags + self.__action_list = lst + self.__flags = flags + + # Reset action index + self.__idx = 0 + + def __len__(self): + return len(self.__action_list) + + def __call__(self): + if self.__idx >= len(self.__action_list): + return False + + # Execute one action + idaapi.process_ui_action( + self.__action_list[self.__idx], + self.__flags) + + # Move to next action + self.__idx += 1 + + # Reschedule + return True + + +# -------------------------------------------------------------------------- +def ProcessUiActions(actions, flags=0): + """ + @param actions: A string containing a list of actions separated by semicolon, a list or a tuple + @param flags: flags to be passed to process_ui_action() + @return: Boolean. Returns False if the action list was empty or execute_ui_requests() failed. + """ + + # Instantiate a helper + helper = __process_ui_actions_helper(actions, flags) + return False if len(helper) < 1 else idaapi.execute_ui_requests((helper,)) + + # ----------------------------------------------------------------------- class peutils_t(object): """ diff --git a/python/idc.py b/python/idc.py index 09dea09..684663f 100644 --- a/python/idc.py +++ b/python/idc.py @@ -1,6 +1,6 @@ #!/usr/bin/env python #--------------------------------------------------------------------- -# IDAPython - Python plugin for Interactive Disassembler Pro +# IDAPython - Python plugin for Interactive Disassembler # # Original IDC.IDC: # Copyright (c) 1990-2010 Ilfak Guilfanov @@ -39,8 +39,7 @@ import time import types __EA64__ = idaapi.BADADDR == 0xFFFFFFFFFFFFFFFFL -WORDMASK = __EA64__ and 0xFFFFFFFFFFFFFFFF or 0xFFFFFFFF - +WORDMASK = 0xFFFFFFFFFFFFFFFF if __EA64__ else 0xFFFFFFFF class DeprecatedIDCError(Exception): """ Exception for deprecated function calls @@ -78,7 +77,7 @@ def _IDC_SetAttr(obj, attrmap, attroffs, value): BADADDR = idaapi.BADADDR # Not allowed address value BADSEL = idaapi.BADSEL # Not allowed selector value/number MAXADDR = idaapi.MAXADDR & WORDMASK - +SIZE_MAX = idaapi.SIZE_MAX # # Flag bit definitions (for GetFlags()) # @@ -838,6 +837,21 @@ def MakeStructEx(ea, size, strname): return idaapi.doStruct(ea, size, strid) +def MakeCustomDataEx(ea, size, dtid, fid): + """ + Convert the item at address to custom data. + + @param ea: linear address. + @param size: custom data size in bytes. + @param dtid: data type ID. + @param fid: data format ID. + + @return: 1-ok, 0-failure + """ + return idaapi.doCustomData(ea, size, dtid, fid) + + + def MakeAlign(ea, count, align): """ Convert the current item to an alignment directive @@ -2021,9 +2035,7 @@ def ItemHead(ea): @return: the starting address of the item if the current address is unexplored, returns 'ea' """ - if idaapi.isTail(idaapi.get_flags_novalue(ea)): - ea = idaapi.prev_not_tail(ea) - return ea + return idaapi.get_item_head(ea) def ItemEnd(ea): @@ -2328,9 +2340,9 @@ def GetString(ea, length = -1, strtype = ASCSTR_C): @return: string contents or empty string """ if length == -1: - length = idaapi.get_max_ascii_length(ea, strtype) + length = idaapi.get_max_ascii_length(ea, strtype, idaapi.ALOPT_IGNHEADS) - return idaapi.get_ascii_contents(ea, length, strtype, length + 1) + return idaapi.get_ascii_contents2(ea, length, strtype) def GetStringType(ea): @@ -2935,7 +2947,7 @@ def AskLong(defval, prompt): def ProcessUiAction(name, flags=0): """ - Invokes an IDA Pro UI action by name + Invokes an IDA UI action by name @param name: Command name @param flags: Reserved. Must be zero @@ -4771,8 +4783,8 @@ def GetMemberQty(sid): @return: -1 if bad structure type ID is passed otherwise returns number of members. - @note: Union members are, in IDA's internals, located - at subsequent byte offsets: member 0 -> offset 0x0, + @note: Union members are, in IDA's internals, located + at subsequent byte offsets: member 0 -> offset 0x0, member 1 -> offset 0x1, etc... """ s = idaapi.get_struc(sid) @@ -4797,8 +4809,8 @@ def GetStrucPrevOff(sid, offset): It will return size of the structure if input 'offset' is bigger than the structure size. - @note: Union members are, in IDA's internals, located - at subsequent byte offsets: member 0 -> offset 0x0, + @note: Union members are, in IDA's internals, located + at subsequent byte offsets: member 0 -> offset 0x0, member 1 -> offset 0x1, etc... """ s = idaapi.get_struc(sid) @@ -4826,8 +4838,8 @@ def GetStrucNextOff(sid, offset): It will return size of the structure if input 'offset' belongs to the last member of the structure. - @note: Union members are, in IDA's internals, located - at subsequent byte offsets: member 0 -> offset 0x0, + @note: Union members are, in IDA's internals, located + at subsequent byte offsets: member 0 -> offset 0x0, member 1 -> offset 0x1, etc... """ s = idaapi.get_struc(sid) @@ -4848,8 +4860,8 @@ def GetFirstMember(sid): structure. It treats these 'holes' as unnamed arrays of bytes. - @note: Union members are, in IDA's internals, located - at subsequent byte offsets: member 0 -> offset 0x0, + @note: Union members are, in IDA's internals, located + at subsequent byte offsets: member 0 -> offset 0x0, member 1 -> offset 0x1, etc... """ s = idaapi.get_struc(sid) @@ -4873,8 +4885,8 @@ def GetLastMember(sid): structure. It treats these 'holes' as unnamed arrays of bytes. - @note: Union members are, in IDA's internals, located - at subsequent byte offsets: member 0 -> offset 0x0, + @note: Union members are, in IDA's internals, located + at subsequent byte offsets: member 0 -> offset 0x0, member 1 -> offset 0x1, etc... """ s = idaapi.get_struc(sid) @@ -4895,8 +4907,8 @@ def GetMemberOffset(sid, member_name): or no such member in the structure otherwise returns offset of the specified member. - @note: Union members are, in IDA's internals, located - at subsequent byte offsets: member 0 -> offset 0x0, + @note: Union members are, in IDA's internals, located + at subsequent byte offsets: member 0 -> offset 0x0, member 1 -> offset 0x1, etc... """ s = idaapi.get_struc(sid) @@ -5891,6 +5903,8 @@ def AddEnum(idx, name, flag): @return: id of new enum or BADADDR """ + if idx < 0: + idx = idx & SIZE_MAX return idaapi.add_enum(idx, name, flag) @@ -6077,68 +6091,414 @@ def SetConstCmt(const_id, cmt, repeatable): # A R R A Y S I N I D C #---------------------------------------------------------------------------- +_IDC_ARRAY_PREFIX = "$ idc_array " +def __l2m1(v): + """ + Long to minus 1: If the 'v' appears to be the + 'signed long' version of -1, then return -1. + Otherwise, return 'v'. + """ + if v == idaapi.BADNODE: + return -1 + else: + return v + + + +AR_LONG = idaapi.atag +"""Array of longs""" + +AR_STR = idaapi.stag +"""Array of strings""" + + +class __dummy_netnode(object): + """ + Implements, in an "always failing" fashion, the + netnode functions that are necessary for the + array-related functions. + + The sole purpose of this singleton class is to + serve as a placeholder for netnode-manipulating + functions, that don't want to each have to perform + checks on the existence of the netnode. + (..in other words: it avoids a bunch of if/else's). + + See __GetArrayById() for more info. + """ + def rename(self, *args): return 0 + def kill(self, *args): pass + def index(self, *args): return -1 + def altset(self, *args): return 0 + def supset(self, *args): return 0 + def altval(self, *args): return 0 + def supval(self, *args): return 0 + def altdel(self, *args): return 0 + def supdel(self, *args): return 0 + def alt1st(self, *args): return -1 + def sup1st(self, *args): return -1 + def altlast(self, *args): return -1 + def suplast(self, *args): return -1 + def altnxt(self, *args): return -1 + def supnxt(self, *args): return -1 + def altprev(self, *args): return -1 + def supprev(self, *args): return -1 + def hashset(self, *args): return 0 + def hashval(self, *args): return 0 + def hashstr(self, *args): return 0 + def hashstr_buf(self, *args): return 0 + def hashset_idx(self, *args): return 0 + def hashset_buf(self, *args): return 0 + def hashval_long(self, *args): return 0 + def hashdel(self, *args): return 0 + def hash1st(self, *args): return 0 + def hashnxt(self, *args): return 0 + def hashprev(self, *args): return 0 + def hashlast(self, *args): return 0 +__dummy_netnode.instance = __dummy_netnode() + + + +def __GetArrayById(array_id): + """ + Get an array, by its ID. + + This (internal) wrapper around 'idaaip.netnode(array_id)' + will ensure a certain safety around the retrieval of + arrays (by catching quite unexpect[ed|able] exceptions, + and making sure we don't create & use `transient' netnodes). + + @param array_id: A positive, valid array ID. + """ + try: + node = idaapi.netnode(array_id) + nodename = node.name() + if nodename is None or not nodename.startswith(_IDC_ARRAY_PREFIX): + return __dummy_netnode.instance + else: + return node + except NotImplementedError: + return __dummy_netnode.instance + + def CreateArray(name): - raise DeprecatedIDCError, "Use python pickles instead." + """ + Create array. + + @param name: The array name. + + @return: -1 in case of failure, a valid array_id otherwise. + """ + node = idaapi.netnode() + res = node.create(_IDC_ARRAY_PREFIX + name) + if res == False: + return -1 + else: + return node.index() + def GetArrayId(name): - raise DeprecatedIDCError, "Use python pickles instead." + """ + Get array array_id, by name. -def RenameArray(hashid, newname): - raise DeprecatedIDCError, "Use python pickles instead." + @param name: The array name. -def DeleteArray(hashid): - raise DeprecatedIDCError, "Use python pickles instead." + @return: -1 in case of failure (i.e., no array with that + name exists), a valid array_id otherwise. + """ + return __l2m1(idaapi.netnode(_IDC_ARRAY_PREFIX + name, 0, False).index()) -def SetArrayLong(hashid, idx, value): - raise DeprecatedIDCError, "Use python pickles instead." -def SetArrayString(hashid, idx, s): - raise DeprecatedIDCError, "Use python pickles instead." +def RenameArray(array_id, newname): + """ + Rename array, by its ID. -def GetArrayElement(tag, hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." + @param id: The ID of the array to rename. + @param newname: The new name of the array. -def DelArrayElement(tag, hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." + @return: 1 in case of success, 0 otherwise + """ + return __GetArrayById(array_id).rename(_IDC_ARRAY_PREFIX + newname) == 1 -def GetFirstIndex(tag, hashid): - raise DeprecatedIDCError, "Use python pickles instead." -def GetLastIndex(tag, hashid): - raise DeprecatedIDCError, "Use python pickles instead." +def DeleteArray(array_id): + """ + Delete array, by its ID. -def GetNextIndex(tag, hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." + @param array_id: The ID of the array to delete. + """ + __GetArrayById(array_id).kill() -def GetPrevIndex(tag, hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." -def SetHashLong(hashid, idx, value): - raise DeprecatedIDCError, "Use python pickles instead." +def SetArrayLong(array_id, idx, value): + """ + Sets the long value of an array element. -def SetHashString(hashid, idx, value): - raise DeprecatedIDCError, "Use python pickles instead." + @param array_id: The array ID. + @param idx: Index of an element. + @param value: 32bit or 64bit value to store in the array -def GetHashLong(hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." + @return: 1 in case of success, 0 otherwise + """ + return __GetArrayById(array_id).altset(idx, value) -def GetHashString(hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." -def DelHashElement(hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." +def SetArrayString(array_id, idx, value): + """ + Sets the string value of an array element. -def GetFirstHashKey(hashid): - raise DeprecatedIDCError, "Use python pickles instead." + @param array_id: The array ID. + @param idx: Index of an element. + @param value: String value to store in the array -def GetNextHashKey(hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." + @return: 1 in case of success, 0 otherwise + """ + return __GetArrayById(array_id).supset(idx, value) + + +def GetArrayElement(tag, array_id, idx): + """ + Get value of array element. + + @param tag: Tag of array, specifies one of two array types: AR_LONG, AR_STR + @param array_id: The array ID. + @param idx: Index of an element. + + @return: Value of the specified array element. Note that + this function may return char or long result. Unexistent + array elements give zero as a result. + """ + node = __GetArrayById(array_id) + if tag == AR_LONG: + return node.altval(idx, tag) + elif tag == AR_STR: + res = node.supval(idx, tag) + return 0 if res is None else res + else: + return 0 + + +def DelArrayElement(tag, array_id, idx): + """ + Delete an array element. + + @param tag: Tag of array, specifies one of two array types: AR_LONG, AR_STR + @param array_id: The array ID. + @param idx: Index of an element. + + @return: 1 in case of success, 0 otherwise. + """ + node = __GetArrayById(array_id) + if tag == AR_LONG: + return node.altdel(idx, tag) + elif tag == AR_STR: + return node.supdel(idx, tag) + else: + return 0 + + +def GetFirstIndex(tag, array_id): + """ + Get index of the first existing array element. + + @param tag: Tag of array, specifies one of two array types: AR_LONG, AR_STR + @param array_id: The array ID. + + @return: -1 if the array is empty, otherwise index of first array + element of given type. + """ + node = __GetArrayById(array_id) + if tag == AR_LONG: + return __l2m1(node.alt1st(tag)) + elif tag == AR_STR: + return __l2m1(node.sup1st(tag)) + else: + return -1 + + +def GetLastIndex(tag, array_id): + """ + Get index of last existing array element. + + @param tag: Tag of array, specifies one of two array types: AR_LONG, AR_STR + @param array_id: The array ID. + + @return: -1 if the array is empty, otherwise index of first array + element of given type. + """ + node = __GetArrayById(array_id) + if tag == AR_LONG: + return __l2m1(node.altlast(tag)) + elif tag == AR_STR: + return __l2m1(node.suplast(tag)) + else: + return -1 + + +def GetNextIndex(tag, array_id, idx): + """ + Get index of the next existing array element. + + @param tag: Tag of array, specifies one of two array types: AR_LONG, AR_STR + @param array_id: The array ID. + @param idx: Index of the current element. + + @return: -1 if no more elements, otherwise returns index of the + next array element of given type. + """ + node = __GetArrayById(array_id) + try: + if tag == AR_LONG: + return __l2m1(node.altnxt(idx, tag)) + elif tag == AR_STR: + return __l2m1(node.supnxt(idx, tag)) + else: + return -1 + except OverflowError: + # typically: An index of -1 was passed. + return -1 + + +def GetPrevIndex(tag, array_id, idx): + """ + Get index of the previous existing array element. + + @param tag: Tag of array, specifies one of two array types: AR_LONG, AR_STR + @param array_id: The array ID. + @param idx: Index of the current element. + + @return: -1 if no more elements, otherwise returns index of the + previous array element of given type. + """ + node = __GetArrayById(array_id) + try: + if tag == AR_LONG: + return __l2m1(node.altprev(idx, tag)) + elif tag == AR_STR: + return __l2m1(node.supprev(idx, tag)) + else: + return -1 + except OverflowError: + # typically: An index of -1 was passed. + return -1 + + +# -------------------- hashes ----------------------- + +def SetHashLong(hash_id, key, value): + """ + Sets the long value of a hash element. + + @param hash_id: The hash ID. + @param key: Key of an element. + @param value: 32bit or 64bit value to store in the hash + + @return: 1 in case of success, 0 otherwise + """ + return __GetArrayById(hash_id).hashset_idx(key, value) + + +def GetHashLong(hash_id, key): + """ + Gets the long value of a hash element. + + @param hash_id: The hash ID. + @param key: Key of an element. + + @return: the 32bit or 64bit value of the element, or 0 if no such + element. + """ + return __GetArrayById(hash_id).hashval_long(key); + + +def SetHashString(hash_id, key, value): + """ + Sets the string value of a hash element. + + @param hash_id: The hash ID. + @param key: Key of an element. + @param value: string value to store in the hash + + @return: 1 in case of success, 0 otherwise + """ + return __GetArrayById(hash_id).hashset_buf(key, value) + + +def GetHashString(hash_id, key): + """ + Gets the string value of a hash element. + + @param hash_id: The hash ID. + @param key: Key of an element. + + @return: the string value of the element, or None if no such + element. + """ + return __GetArrayById(hash_id).hashstr_buf(key); + + +def DelHashElement(hash_id, key): + """ + Delete a hash element. + + @param hash_id: The hash ID. + @param key: Key of an element + + @return: 1 upon success, 0 otherwise. + """ + return __GetArrayById(hash_id).hashdel(key) + + +def GetFirstHashKey(hash_id): + """ + Get the first key in the hash. + + @param hash_id: The hash ID. + + @return: the key, 0 otherwise. + """ + r = __GetArrayById(hash_id).hash1st() + return 0 if r is None else r + + +def GetLastHashKey(hash_id): + """ + Get the last key in the hash. + + @param hash_id: The hash ID. + + @return: the key, 0 otherwise. + """ + r = __GetArrayById(hash_id).hashlast() + return 0 if r is None else r + + +def GetNextHashKey(hash_id, key): + """ + Get the next key in the hash. + + @param hash_id: The hash ID. + @param key: The current key. + + @return: the next key, 0 otherwise + """ + r = __GetArrayById(hash_id).hashnxt(key) + return 0 if r is None else r + + +def GetPrevHashKey(hash_id, key): + """ + Get the previous key in the hash. + + @param hash_id: The hash ID. + @param key: The current key. + + @return: the previous key, 0 otherwise + """ + r = __GetArrayById(hash_id).hashprev(key) + return 0 if r is None else r -def GetLastHashKey(hashid): - raise DeprecatedIDCError, "Use python pickles instead." -def GetPrevHashKey(hashid, idx): - raise DeprecatedIDCError, "Use python pickles instead." #---------------------------------------------------------------------------- @@ -7053,6 +7413,20 @@ EXCDLG_ALWAYS = 0x00006000 # always display DOPT_LOAD_DINFO = 0x00008000 # automatically load debug files (pdb) +def GetDebuggerEventCondition(): + """ + Return the debugger event condition + """ + return idaapi.get_debugger_event_cond() + + +def SetDebuggerEventCondition(cond): + """ + Set the debugger event condition + """ + return idaapi.set_debugger_event_cond(cond) + + def SetRemoteDebugger(hostname, password, portnum): """ Set remote debugging options @@ -7628,10 +8002,11 @@ 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 MakeStruct(ea, name): return MakeStructEx(ea, -1, name) +def MakeCustomData(ea, size, dtid, fid): return MakeCustomDataEx(ea, size, dtid, fid) +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 905d4e5..dcdba31 100644 --- a/python/init.py +++ b/python/init.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # ----------------------------------------------------------------------- -# IDAPython - Python plugin for Interactive Disassembler Pro +# IDAPython - Python plugin for Interactive Disassembler # # Copyright (c) The IDAPython Team # @@ -94,7 +94,7 @@ from idautils import * import idaapi # Load the users personal init file -userrc = get_user_idadir() + os.sep + "idapythonrc.py" +userrc = os.path.join(get_user_idadir(), "idapythonrc.py") if os.path.exists(userrc): idaapi.IDAPython_ExecScript(userrc, globals()) diff --git a/pywraps.hpp b/pywraps.hpp index 80efdd4..e7f4a60 100644 --- a/pywraps.hpp +++ b/pywraps.hpp @@ -23,11 +23,14 @@ #define PY_SFMT64 "l" #endif +//------------------------------------------------------------------------ #define S_IDAAPI_MODNAME "idaapi" #define S_IDC_MODNAME "idc" #define S_IDAAPI_EXECSCRIPT "IDAPython_ExecScript" #define S_IDAAPI_COMPLETION "IDAPython_Completion" +#define S_IDAAPI_FORMATEXC "IDAPython_FormatExc" +//------------------------------------------------------------------------ // Vector of PyObject* typedef qvector ppyobject_vec_t; @@ -57,6 +60,36 @@ typedef qvector ppyobject_vec_t; #define CIP_OK 1 // Success #define CIP_OK_NODECREF 2 // Success but do not decrement its reference +//--------------------------------------------------------------------------- +// Helper macro to create C counterparts of Python py_clinked_object_t object +#ifdef __PYWRAPS__ +#define DECLARE_PY_CLINKED_OBJECT(type) \ + static PyObject *type##_create() \ + { \ + return PyCObject_FromVoidPtr(new type(), NULL); \ + } \ + static bool type##_destroy(PyObject *py_obj) \ + { \ + if ( !PyCObject_Check(py_obj) ) \ + return false; \ + delete (type *)PyCObject_AsVoidPtr(py_obj); \ + return true; \ + } \ + static type *type##_get_clink(PyObject *self) \ + { \ + return (type *)pyobj_get_clink(self); \ + } \ + static PyObject *type##_get_clink_ptr(PyObject *self) \ + { \ + return PyLong_FromUnsignedLongLong( \ + (unsigned PY_LONG_LONG)pyobj_get_clink(self)); \ + } +#else +// SWIG does not expand macros and thus those definitions won't be wrapped +// Use DECLARE_PY_CLINKED_OBJECT(type) inside the .i file +#define DECLARE_PY_CLINKED_OBJECT(type) +#endif // __PYWRAPS__ + //--------------------------------------------------------------------------- class CGilStateAuto { @@ -82,11 +115,18 @@ public: #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); op_t *op_t_get_clink(PyObject *self); +// Returns a reference to a class +PyObject *get_idaapi_attr(const char *attr); + +// Returns a reference to a class by its ID +PyObject *get_idaapi_attr_by_id(const int class_id); + // Tries to import a module and swallows the exception if it fails and returns NULL PyObject *PyW_TryImportModule(const char *name); @@ -112,7 +152,8 @@ bool PyW_GetNumber(PyObject *py_var, uint64 *num, bool *is_64 = NULL); bool PyW_IsSequenceType(PyObject *obj); // Returns an error string from the last exception (and clears it) -bool PyW_GetError(qstring *out = NULL); +bool PyW_GetError(qstring *out = NULL, bool clear_err = true); +bool PyW_GetError(char *buf, size_t bufsz, bool clear_err = true); // If an error occured (it calls PyGetError) it displays it and return TRUE // This function is used when calling callbacks @@ -124,10 +165,6 @@ PyObject *create_idaapi_class_instance0(const char *clsname); // Utility function to create linked class instances PyObject *create_idaapi_linked_class_instance(const char *clsname, void *lnk); -// [De]Initializes PyWraps -bool init_pywraps(); -void deinit_pywraps(); - // Returns the string representation of a PyObject bool PyW_ObjectToString(PyObject *obj, qstring *out); @@ -135,6 +172,24 @@ bool PyW_ObjectToString(PyObject *obj, qstring *out); // and sets a python exception on failure. bool convert_pyobj_to_idc_exc(PyObject *py_obj, idc_value_t *idc_obj); +// Creates and initializes an IDC exception +error_t PyW_CreateIdcException(idc_value_t *res, const char *msg); + +// +// Conversion functions +// +bool pyw_convert_idc_args( + const idc_value_t args[], + int nargs, + ppyobject_vec_t &pargs, + boolvec_t *decref, + char *errbuf = NULL, + size_t errbufsize = 0); + +void pyw_free_idc_args( + ppyobject_vec_t &pargs, + boolvec_t *decref = NULL); + // Converts Python variable to IDC variable // gvar_sn is used in case the Python object was a created from a call to idcvar_to_pyvar and the IDC object was a VT_REF int pyvar_to_idcvar( @@ -160,17 +215,22 @@ PyObject *PyW_IntVecToPyList(const intvec_t &intvec); // Converts an Python list to an intvec bool PyW_PyListToIntVec(PyObject *py_list, intvec_t &intvec); -// Returns a reference to a class -PyObject *get_idaapi_attr(const char *attr); - -// Returns a reference to a class by its ID -PyObject *get_idaapi_attr_by_id(const int class_id); +// Converts a Python list to a qstrvec +bool PyW_PyListToStrVec(PyObject *py_list, qstrvec_t &strvec); +//--------------------------------------------------------------------------- +// // notify_when() +// bool pywraps_nw_term(); bool pywraps_nw_notify(int slot, ...); bool pywraps_nw_init(); +//--------------------------------------------------------------------------- const char *pywraps_check_autoscripts(); +// [De]Initializes PyWraps +bool init_pywraps(); +void deinit_pywraps(); + #endif \ No newline at end of file diff --git a/swig/bytes.i b/swig/bytes.i index 544d58c..2cbbe1b 100644 --- a/swig/bytes.i +++ b/swig/bytes.i @@ -13,6 +13,7 @@ %ignore adjust_visea; %ignore prev_visea; %ignore next_visea; +%ignore visit_patched_bytes; %ignore is_first_visea; %ignore is_last_visea; %ignore is_visible_finally; @@ -36,7 +37,6 @@ %ignore noExtra; %ignore coagulate; %ignore coagulate_dref; -%ignore get_item_head; %ignore init_hidden_areas; %ignore save_hidden_areas; %ignore term_hidden_areas; @@ -74,6 +74,7 @@ %ignore register_custom_data_type; %ignore get_many_bytes; %ignore get_ascii_contents; +%ignore get_ascii_contents2; // TODO: This could be fixed (if needed) %ignore set_dbgmem_source; @@ -86,6 +87,7 @@ %clear(void *buf, ssize_t size); %clear(opinfo_t *); +%rename (visit_patched_bytes) py_visit_patched_bytes; %rename (nextthat) py_nextthat; %rename (prevthat) py_prevthat; %rename (get_custom_data_type) py_get_custom_data_type; @@ -95,7 +97,7 @@ %rename (unregister_custom_data_type) py_unregister_custom_data_type; %rename (register_custom_data_type) py_register_custom_data_type; %rename (get_many_bytes) py_get_many_bytes; -%rename (get_ascii_contents) py_get_ascii_contents; +%rename (get_ascii_contents2) py_get_ascii_contents2; %{ // //------------------------------------------------------------------------ @@ -121,6 +123,30 @@ static ea_t py_npthat(ea_t ea, ea_t bound, PyObject *py_callable, bool next) return (next ? nextthat : prevthat)(ea, bound, py_testf_cb, py_callable); } +//--------------------------------------------------------------------------- +static int idaapi py_visit_patched_bytes_cb( + ea_t ea, + int32 fpos, + uint32 o, + uint32 v, + void *ud) +{ + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallFunction( + (PyObject *)ud, + PY_FMT64 "iII", + pyul_t(ea), + fpos, + o, + v); + PYW_GIL_RELEASE; + + PyW_ShowCbErr("visit_patched_bytes"); + int ret = (py_result != NULL && PyInt_Check(py_result)) ? PyInt_AsLong(py_result) : 0; + Py_XDECREF(py_result); + return ret; +} + //------------------------------------------------------------------------ @@ -639,11 +665,54 @@ static PyObject *py_data_format_to_py_dict(const data_format_t *df) // //------------------------------------------------------------------------ +/* +# +def visit_patched_bytes(ea1, ea2, callable): + """ + Enumerates patched bytes in the given range and invokes a callable + @param ea1: start address + @param ea2: end address + @param callable: a Python callable with the following prototype: + callable(ea, fpos, org_val, patch_val). + If the callable returns non-zero then that value will be + returned to the caller and the enumeration will be + interrupted. + @return: Zero if the enumeration was successful or the return + value of the callback if enumeration was interrupted. + """ + pass +# +*/ +static int py_visit_patched_bytes(ea_t ea1, ea_t ea2, PyObject *py_callable) +{ + if ( !PyCallable_Check(py_callable) ) + return 0; + else + return visit_patched_bytes(ea1, ea2, py_visit_patched_bytes_cb, py_callable); +} + +//------------------------------------------------------------------------ +/* +# +def nextthat(ea, maxea, callable): + """ + Find next address with a flag satisfying the function 'testf'. + Start searching from address 'ea'+1 and inspect bytes up to 'maxea'. + maxea is not included in the search range. + + @param callable: a Python callable with the following prototype: + callable(flags). Return True to stop enumeration. + @return: the found address or BADADDR. + """ + pass +# +*/ static ea_t py_nextthat(ea_t ea, ea_t maxea, PyObject *callable) { return py_npthat(ea, maxea, callable, true); } +//--------------------------------------------------------------------------- static ea_t py_prevthat(ea_t ea, ea_t minea, PyObject *callable) { return py_npthat(ea, minea, callable, false); @@ -694,37 +763,40 @@ static PyObject *py_get_many_bytes(ea_t ea, unsigned int size) //--------------------------------------------------------------------------- /* # -def get_ascii_contents(ea, len, type, bufsize): +def get_ascii_contents2(ea, len, type, flags = ACFOPT_ASCII): """ Get contents of ascii string This function returns the displayed part of the string It works even if the string has not been created in the database yet. @param ea: linear address of the string - @param len: length of the string in bytes + @param len: length of the string in bytes (including terminating 0) @param type: type of the string - @param bufsize: size of output buffer - @return: 1-ok, 0-ascii string is too long and was truncated + @param flags: combination of ACFOPT_... + @return: string contents (not including terminating 0) or None """ pass # */ -static PyObject *py_get_ascii_contents( +static PyObject *py_get_ascii_contents2( ea_t ea, size_t len, int32 type, - size_t bufsize = MAXSTR) + int flags = ACFOPT_ASCII) { - char *buf = (char *)qalloc(bufsize); + char *buf = (char *)qalloc(len+1); if ( buf == NULL ) return NULL; - if ( !get_ascii_contents(ea, len, type, buf, bufsize) ) + size_t used_size; + if ( !get_ascii_contents2(ea, len, type, buf, len+1, &used_size) ) { qfree(buf); Py_RETURN_NONE; } - PyObject *py_buf = PyString_FromStringAndSize((const char *)buf, bufsize); + if ( type == ASCSTR_C && used_size > 0 && buf[used_size-1] == '\0' ) + used_size--; + PyObject *py_buf = PyString_FromStringAndSize((const char *)buf, used_size); qfree(buf); return py_buf; } diff --git a/swig/dbg.i b/swig/dbg.i index f823c97..b49aa74 100644 --- a/swig/dbg.i +++ b/swig/dbg.i @@ -114,86 +114,81 @@ static PyObject *refresh_debugger_memory() Py_RETURN_NONE; } -// int idaapi DBG_Callback(void *ud, int notification_code, va_list va); class DBG_Hooks { public: - virtual ~DBG_Hooks() {}; + virtual ~DBG_Hooks() {}; - bool hook() { return hook_to_notification_point(HT_DBG, DBG_Callback, this); }; - bool unhook() { return unhook_from_notification_point(HT_DBG, DBG_Callback, this); }; - /* Hook functions to be overridden in Python */ - virtual void dbg_process_start(pid_t pid, - thid_t tid, - ea_t ea, - char *name, - ea_t base, - asize_t size) { }; - virtual void dbg_process_exit(pid_t pid, - thid_t tid, - ea_t ea, - int exit_code) { }; - virtual void dbg_process_attach(pid_t pid, - thid_t tid, - ea_t ea, - char *name, - ea_t base, - asize_t size) { }; - virtual void dbg_process_detach(pid_t pid, - thid_t tid, - ea_t ea) { }; - virtual void dbg_thread_start(pid_t pid, - thid_t tid, - ea_t ea) { }; - virtual void dbg_thread_exit(pid_t pid, - thid_t tid, - ea_t ea, - int exit_code) { }; - virtual void dbg_library_load(pid_t pid, - thid_t tid, - ea_t ea, - char *name, - ea_t base, - asize_t size) { }; - virtual void dbg_library_unload(pid_t pid, - thid_t tid, - ea_t ea, - char *libname) { }; - virtual void dbg_information(pid_t pid, - thid_t tid, - ea_t ea, - char *info) { }; - virtual int dbg_exception(pid_t pid, - thid_t tid, - ea_t ea, - int code, - bool can_cont, - ea_t exc_ea, - char *info) { return 0; }; - virtual void dbg_suspend_process(void) { }; - virtual int dbg_bpt(thid_t tid, ea_t breakpoint_ea) { return 0; }; - virtual int dbg_trace(thid_t tid, ea_t ip) { return 0; }; - virtual void dbg_request_error(int failed_command, - int failed_dbg_notification) { }; - virtual void dbg_step_into(void) { }; - virtual void dbg_step_over(void) { }; - virtual void dbg_run_to(thid_t tid) { }; - virtual void dbg_step_until_ret(void) { }; + bool hook() { return hook_to_notification_point(HT_DBG, DBG_Callback, this); }; + bool unhook() { return unhook_from_notification_point(HT_DBG, DBG_Callback, this); }; + /* Hook functions to be overridden in Python */ + virtual void dbg_process_start(pid_t pid, + thid_t tid, + ea_t ea, + char *name, + ea_t base, + asize_t size) { }; + virtual void dbg_process_exit(pid_t pid, + thid_t tid, + ea_t ea, + int exit_code) { }; + virtual void dbg_process_attach(pid_t pid, + thid_t tid, + ea_t ea, + char *name, + ea_t base, + asize_t size) { }; + virtual void dbg_process_detach(pid_t pid, + thid_t tid, + ea_t ea) { }; + virtual void dbg_thread_start(pid_t pid, + thid_t tid, + ea_t ea) { }; + virtual void dbg_thread_exit(pid_t pid, + thid_t tid, + ea_t ea, + int exit_code) { }; + virtual void dbg_library_load(pid_t pid, + thid_t tid, + ea_t ea, + char *name, + ea_t base, + asize_t size) { }; + virtual void dbg_library_unload(pid_t pid, + thid_t tid, + ea_t ea, + char *libname) { }; + virtual void dbg_information(pid_t pid, + thid_t tid, + ea_t ea, + char *info) { }; + virtual int dbg_exception(pid_t pid, + thid_t tid, + ea_t ea, + int code, + bool can_cont, + ea_t exc_ea, + char *info) { return 0; }; + virtual void dbg_suspend_process(void) { }; + virtual int dbg_bpt(thid_t tid, ea_t breakpoint_ea) { return 0; }; + virtual int dbg_trace(thid_t tid, ea_t ip) { return 0; }; + virtual void dbg_request_error(int failed_command, + int failed_dbg_notification) { }; + virtual void dbg_step_into(void) { }; + virtual void dbg_step_over(void) { }; + virtual void dbg_run_to(pid_t pid, thid_t tid, ea_t ea) { }; + virtual void dbg_step_until_ret(void) { }; }; int idaapi DBG_Callback(void *ud, int notification_code, va_list va) { class DBG_Hooks *proxy = (class DBG_Hooks *)ud; - debug_event_t *event; - thid_t tid; - int *warn; - ea_t ip; - ea_t breakpoint_ea; - - try { + int code = 0; + try + { switch (notification_code) { case dbg_process_start: @@ -204,135 +199,155 @@ int idaapi DBG_Callback(void *ud, int notification_code, va_list va) event->modinfo.name, event->modinfo.base, event->modinfo.size); - return 0; + break; + case dbg_process_exit: event = va_arg(va, debug_event_t *); - proxy->dbg_process_exit(event->pid, + proxy->dbg_process_exit( + event->pid, event->tid, event->ea, event->exit_code); - return 0; + break; case dbg_process_attach: event = va_arg(va, debug_event_t *); - proxy->dbg_process_attach(event->pid, + proxy->dbg_process_attach( + event->pid, event->tid, event->ea, event->modinfo.name, event->modinfo.base, event->modinfo.size); - return 0; + break; case dbg_process_detach: event = va_arg(va, debug_event_t *); - proxy->dbg_process_detach(event->pid, + proxy->dbg_process_detach( + event->pid, event->tid, event->ea); - return 0; + break; case dbg_thread_start: event = va_arg(va, debug_event_t *); - proxy->dbg_thread_start(event->pid, + proxy->dbg_thread_start( + event->pid, event->tid, event->ea); - return 0; + break; case dbg_thread_exit: event = va_arg(va, debug_event_t *); - proxy->dbg_thread_exit(event->pid, + proxy->dbg_thread_exit( + event->pid, event->tid, event->ea, event->exit_code); - return 0; + break; case dbg_library_load: event = va_arg(va, debug_event_t *); - proxy->dbg_library_load(event->pid, + proxy->dbg_library_load( + event->pid, event->tid, event->ea, event->modinfo.name, event->modinfo.base, event->modinfo.size); - return 0; + break; case dbg_library_unload: event = va_arg(va, debug_event_t *); - proxy->dbg_library_unload(event->pid, + proxy->dbg_library_unload( + event->pid, event->tid, event->ea, event->info); - return 0; + break; case dbg_information: event = va_arg(va, debug_event_t *); - proxy->dbg_information(event->pid, + proxy->dbg_information( + event->pid, event->tid, event->ea, event->info); - return 0; + break; case dbg_exception: + { event = va_arg(va, debug_event_t *); - warn = va_arg(va, int *); - *warn = proxy->dbg_exception(event->pid, + int *warn = va_arg(va, int *); + *warn = proxy->dbg_exception( + event->pid, event->tid, event->ea, event->exc.code, event->exc.can_cont, event->exc.ea, event->exc.info); - return 0; + break; + } case dbg_suspend_process: proxy->dbg_suspend_process(); - return 0; + break; case dbg_bpt: - tid = va_arg(va, thid_t); - breakpoint_ea = va_arg(va, ea_t); - warn = va_arg(va, int *); + { + thid_t tid = va_arg(va, thid_t); + ea_t breakpoint_ea = va_arg(va, ea_t); + int *warn = va_arg(va, int *); *warn = proxy->dbg_bpt(tid, breakpoint_ea); - return 0; + break; + } case dbg_trace: - tid = va_arg(va, thid_t); - ip = va_arg(va, ea_t); - return proxy->dbg_trace(tid, ip); + { + thid_t tid = va_arg(va, thid_t); + ea_t ip = va_arg(va, ea_t); + code = proxy->dbg_trace(tid, ip); + break; + } case dbg_request_error: { int failed_command = (int)va_argi(va, ui_notification_t); int failed_dbg_notification = (int)va_argi(va, dbg_notification_t); proxy->dbg_request_error(failed_command, failed_dbg_notification); - return 0; + break; } + case dbg_step_into: proxy->dbg_step_into(); - return 0; + break; case dbg_step_over: proxy->dbg_step_over(); - return 0; + break; case dbg_run_to: - tid = va_arg(va, thid_t); - proxy->dbg_run_to(tid); - return 0; + event = va_arg(va, debug_event_t *); + proxy->dbg_run_to( + event->pid, + event->tid, + event->ea); + break; case dbg_step_until_ret: proxy->dbg_step_until_ret(); - return 0; + break; } } - catch (Swig::DirectorException &) + catch (Swig::DirectorException &e) { - msg("Exception in DBG Hook function:\n"); + msg("Exception in DBG Hook function: %s\n", e.getMessage()); if (PyErr_Occurred()) - { PyErr_Print(); - } } - return 0; + return code; } +// %} diff --git a/swig/diskio.i b/swig/diskio.i index 75c2f2b..8a62dd1 100644 --- a/swig/diskio.i +++ b/swig/diskio.i @@ -12,6 +12,7 @@ %ignore free_ioports; %ignore lread; %ignore qlread; +%ignore efilelength; %ignore qlgets; %ignore qlgetc; %ignore lreadbytes; diff --git a/swig/expr.i b/swig/expr.i index 4f79e3a..1934667 100644 --- a/swig/expr.i +++ b/swig/expr.i @@ -7,7 +7,12 @@ %ignore register_extlang; %ignore IDCFuncs; %ignore set_idc_func; +%ignore set_idc_dtor; +%ignore set_idc_method; +%ignore set_idc_getattr; +%ignore set_idc_setattr; %ignore set_idc_func_ex; +%ignore run_statements_idc; %ignore VarLong; %ignore VarNum; %ignore extlang_get_attr_exists; @@ -46,54 +51,291 @@ %ignore idc_resolve_label; %ignore idc_resolver_ea; %ignore setup_lowcnd_regfuncs; - %cstring_output_maxstr_none(char *errbuf, size_t errbufsize); -/* Compile* functions return false when error so the return */ -/* value must be negated for the error string to be returned */ +%ignore CompileEx; %rename (CompileEx) CompileEx_wrap; -%inline %{ -bool CompileEx_wrap(const char *file, bool del_macros, - char *errbuf, size_t errbufsize) -{ - return !CompileEx(file, del_macros, errbuf, errbufsize); -} -%} - +%ignore Compile; %rename (Compile) Compile_wrap; -%inline %{ -bool Compile_wrap(const char *file, char *errbuf, size_t errbufsize) -{ - return !Compile(file, errbuf, errbufsize); -} -%} - +%ignore calcexpr; %rename (calcexpr) calcexpr_wrap; -%inline %{ -bool calcexpr_wrap(ea_t where,const char *line, idc_value_t *rv, char *errbuf, size_t errbufsize) -{ - return !calcexpr(where, line, rv, errbuf, errbufsize); -} -%} - +%ignore calc_idc_expr; %rename (calc_idc_expr) calc_idc_expr_wrap; -%inline %{ -bool calc_idc_expr_wrap(ea_t where,const char *line, idc_value_t *rv, char *errbuf, size_t errbufsize) -{ - return !calc_idc_expr(where, line, rv, errbuf, errbufsize); -} -%} - %ignore CompileLine(const char *line, char *errbuf, size_t errbufsize, uval_t (idaapi*_getname)(const char *name)=NULL); %ignore CompileLineEx; - +%ignore CompileLine; %rename (CompileLine) CompileLine_wrap; +%{ +// +struct py_idcfunc_ctx_t +{ + PyObject *py_func; + qstring name; + int nargs; + py_idcfunc_ctx_t(PyObject *py_func, const char *name, int nargs): py_func(py_func), name(name), nargs(nargs) + { + Py_INCREF(py_func); + } + ~py_idcfunc_ctx_t() + { + Py_DECREF(py_func); + } +}; + +//--------------------------------------------------------------------------- +static error_t py_call_idc_func( + void *_ctx, + idc_value_t *argv, + idc_value_t *r) +{ + // Convert IDC arguments to Python list + py_idcfunc_ctx_t *ctx = (py_idcfunc_ctx_t *)_ctx; + int cvt; + ppyobject_vec_t pargs; + char errbuf[MAXSTR]; + if ( !pyw_convert_idc_args(argv, ctx->nargs, pargs, NULL, errbuf, sizeof(errbuf)) ) + { + // Error during conversion? Create an IDC exception + return PyW_CreateIdcException(r, errbuf); + } + + // Call the Python function + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallObject( + ctx->py_func, + pargs.empty() ? NULL : pargs[0]); + + error_t err; + if ( PyW_GetError(errbuf, sizeof(errbuf)) ) + { + err = PyW_CreateIdcException(r, errbuf); + Py_XDECREF(py_result); + } + else + { + // Convert the result to IDC + r->clear(); + cvt = pyvar_to_idcvar(py_result, r); + if ( cvt < CIP_OK ) + err = PyW_CreateIdcException(r, "ERROR: bad return value"); + else + err = eOk; + if ( cvt != CIP_OK_NODECREF ) + Py_XDECREF(py_result); + } + PYW_GIL_RELEASE; + + // Free the converted args + pyw_free_idc_args(pargs); + + return err; +} + +// +%} + %inline %{ +// + +//--------------------------------------------------------------------------- +static size_t py_get_call_idc_func() +{ + return (size_t)py_call_idc_func; +} + +//--------------------------------------------------------------------------- +// Internal function: +// - capture the python callable +// - return a C context as a numeric value +static size_t pyw_register_idc_func( + const char *name, + const char *args, + PyObject *py_fp) +{ + return (size_t)new py_idcfunc_ctx_t(py_fp, name, strlen(args)); +} + +//--------------------------------------------------------------------------- +// Internal function: +// - free the C context +static bool pyw_unregister_idc_func(size_t ctxptr) +{ + // Unregister the function + py_idcfunc_ctx_t *ctx = (py_idcfunc_ctx_t *)ctxptr; + bool ok = set_idc_func_ex(ctx->name.c_str(), NULL, NULL, 0); + + // Delete the context + delete ctx; + + return ok; +} + +//--------------------------------------------------------------------------- +static bool py_set_idc_func_ex( + const char *name, + size_t fp_ptr, + const char *args, + int flags) +{ + return set_idc_func_ex(name, (idc_func_t *)fp_ptr, args, flags); +} + +//--------------------------------------------------------------------------- +// Compile* functions return false when error so the return +// value must be negated for the error string to be returned +bool CompileEx_wrap( + const char *file, + bool del_macros, + char *errbuf, size_t errbufsize) +{ + return !CompileEx(file, del_macros, errbuf, errbufsize); +} + +bool Compile_wrap(const char *file, char *errbuf, size_t errbufsize) +{ + return !Compile(file, errbuf, errbufsize); +} + +bool calcexpr_wrap( + ea_t where, + const char *line, + idc_value_t *rv, + char *errbuf, size_t errbufsize) +{ + return !calcexpr(where, line, rv, errbuf, errbufsize); +} + +bool calc_idc_expr_wrap( + ea_t where, + const char *line, + idc_value_t *rv, + char *errbuf, size_t errbufsize) +{ + return !calc_idc_expr(where, line, rv, errbuf, errbufsize); +} + bool CompileLine_wrap(const char *line, char *errbuf, size_t errbufsize) { - return !CompileLineEx(line, errbuf, errbufsize); + return !CompileLineEx(line, errbuf, errbufsize); } + +// %} %include "expr.hpp" +%pythoncode %{ + +# +try: + import types + import ctypes + # Callback for IDC func callback (On Windows, we use stdcall) + # typedef error_t idaapi idc_func_t(idc_value_t *argv,idc_value_t *r); + _IDCFUNC_CB_T = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p) + + # A trampoline function that is called from idcfunc_t that will + # call the Python callback with the argv and r properly serialized to python + call_idc_func__ = ctypes.CFUNCTYPE(ctypes.c_long)(_idaapi.py_get_call_idc_func()) +except: + def call_idc_func__(*args): + warning("IDC extensions need ctypes library in order to work") + return 0 + try: + _IDCFUNC_CB_T = CFUNCTYPE(c_int, c_void_p, c_void_p) + except: + _IDCFUNC_CB_T = None + + +# -------------------------------------------------------------------------- +EXTFUN_BASE = 0x0001 +"""requires open database""" +EXTFUN_NORET = 0x0002 +"""does not return. the interpreter may clean up its state before calling it.""" +EXTFUN_SAFE = 0x0004 +"""thread safe function. may be called""" + +# -------------------------------------------------------------------------- +class _IdcFunction(object): + """ + Internal class that calls pyw_call_idc_func() with a context + """ + def __init__(self, ctxptr): + self.ctxptr = ctxptr + # Take a reference to the ctypes callback + # (note: this will create a circular reference) + self.cb = _IDCFUNC_CB_T(self) + + fp_ptr = property(lambda self: ctypes.cast(self.cb, ctypes.c_void_p).value) + + def __call__(self, args, res): + return call_idc_func__(self.ctxptr, args, res) + + +# -------------------------------------------------------------------------- +# Dictionary to remember IDC function names along with the context pointer +# retrieved by using the internal pyw_register_idc_func() +__IDC_FUNC_CTXS = {} + +# -------------------------------------------------------------------------- +def set_idc_func_ex(name, fp=None, args=(), flags=0): + """ + Extends the IDC language by exposing a new IDC function that is backed up by a Python function + This function also unregisters the IDC function if 'fp' was passed as None + + @param name: IDC function name to expose + @param fp: Python callable that will receive the arguments and return a tuple. + If this argument is None then the IDC function is unregistered + @param args: Arguments. A tuple of idaapi.VT_XXX constants + @param flags: IDC function flags. A combination of EXTFUN_XXX constants + + @return: Boolean. + """ + global __IDC_FUNC_CTXS + + # Get the context + f = __IDC_FUNC_CTXS.get(name, None) + + # Unregistering? + if fp is None: + # Not registered? + if f is None: + return False + + # Break circular reference + del f.cb + + # Delete the name from the dictionary + del __IDC_FUNC_CTXS[name] + + # Delete the context and unregister the function + return _idaapi.pyw_unregister_idc_func(f.ctxptr) + + # Registering a function that is already registered? + if f is not None: + # Unregister it first + set_idc_func_ex(name, None) + + # Convert the tupple argument info to a string + args = "".join([chr(x) for x in args]) + + # Create a context + ctxptr = _idaapi.pyw_register_idc_func(name, args, fp) + if ctxptr == 0: + return False + + # Bind the context with the IdcFunc object + f = _IdcFunction(ctxptr) + + # Remember the Python context + __IDC_FUNC_CTXS[name] = f + + # Register IDC function with a callback + return _idaapi.py_set_idc_func_ex( + name, + f.fp_ptr, + args, + flags) + +# +%} \ No newline at end of file diff --git a/swig/graph.i b/swig/graph.i index cfa8f58..81daf8f 100644 --- a/swig/graph.i +++ b/swig/graph.i @@ -26,7 +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: @@ -62,24 +62,24 @@ private: private: Py_ssize_t uid; public: - + cmdid_map_t() { // We start by one and keep zero for error id - uid = 1; + uid = 1; } - + void add(py_graph_t *pyg) { (*this)[uid] = pyg; ++uid; } - - const Py_ssize_t id() const - { + + const Py_ssize_t id() const + { return uid; } - + void clear(py_graph_t *pyg) { iterator e = end(); @@ -94,7 +94,7 @@ private: ++it; } } - + py_graph_t *get(Py_ssize_t id) { iterator it = find(id); @@ -123,7 +123,7 @@ private: py_graph_t *_this = cmdid_pyg.get(id); if ( _this != NULL ) _this->on_command(id); - + return true; } @@ -208,11 +208,11 @@ private: edge_ids[j] = v; } - + // Incomplete? if ( j != qnumber(edge_ids) ) break; - + // Add the edge g->add_edge(edge_ids[0], edge_ids[1], NULL); } @@ -312,27 +312,27 @@ private: 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 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_viewer_t * /*gv*/, + selection_item_t * /*item1*/, graph_item_t *item2) { // in: graph_viewer_t *gv @@ -349,12 +349,12 @@ private: PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod( - self, - (char *)S_ON_CLICK, - "i", + self, + (char *)S_ON_CLICK, + "i", item2->n); PYW_GIL_RELEASE; - + if ( result == NULL || !PyObject_IsTrue(result) ) { Py_XDECREF(result); @@ -376,15 +376,15 @@ private: //selection_item_t *s = va_arg(va, selection_item_t *); if ( item == NULL || !item->is_node ) return 1; - + PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod( - self, - (char *)S_ON_DBL_CLICK, - "i", + self, + (char *)S_ON_DBL_CLICK, + "i", item->node); PYW_GIL_RELEASE; - + if ( result == NULL || !PyObject_IsTrue(result) ) { Py_XDECREF(result); @@ -399,8 +399,8 @@ private: { PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod( - self, - (char *)S_ON_ACTIVATE, + self, + (char *)S_ON_ACTIVATE, NULL); PYW_GIL_RELEASE; Py_XDECREF(result); @@ -411,8 +411,8 @@ private: { PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod( - self, - (char *)S_ON_DEACTIVATE, + self, + (char *)S_ON_DEACTIVATE, NULL); PYW_GIL_RELEASE; @@ -430,9 +430,9 @@ private: PYW_GIL_ENSURE; PyObject *result = PyObject_CallMethod( - self, - (char *)S_ON_SELECT, - "i", + self, + (char *)S_ON_SELECT, + "i", curnode); PYW_GIL_RELEASE; @@ -473,7 +473,7 @@ private: else { // Ignore the click - ret = 1; + ret = 1; } break; // @@ -491,20 +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; // @@ -664,7 +664,9 @@ private: { // get a unique graph id netnode id; - id.create(); + char grnode[MAXSTR]; + qsnprintf(grnode, sizeof(grnode), "$ pygraph %s", title); + id.create(grnode); gv = create_graph_viewer(form, id, s_callback, this, 0); open_tform(form, FORM_MDI | FORM_TAB | FORM_MENU); if ( gv != NULL ) @@ -724,7 +726,7 @@ private: if ( _this == NULL || _this->form == NULL ) return; - close_tform(_this->form, 0); + close_tform(_this->form, FORM_CLOSE_LATER); } static void Refresh(PyObject *self) diff --git a/swig/idaapi.i b/swig/idaapi.i index 8f1e5ff..110eea3 100644 --- a/swig/idaapi.i +++ b/swig/idaapi.i @@ -1,4 +1,4 @@ -%module(docstring="IDA Pro Plugin SDK API wrapper",directors="1") idaapi +%module(docstring="IDA Plugin SDK API wrapper",directors="1") idaapi // Suppress 'previous definition of XX' warnings #pragma SWIG nowarn=302 // and others... @@ -12,14 +12,41 @@ #pragma SWIG nowarn=451 #pragma SWIG nowarn=454 // Setting a pointer/reference variable may leak memory +%constant size_t SIZE_MAX = size_t(-1); + // Enable automatic docstring generation %feature(autodoc); +%define SWIG_DECLARE_PY_CLINKED_OBJECT(type) +%inline %{ +static PyObject *type##_create() +{ + return PyCObject_FromVoidPtr(new type(), NULL); +} +static bool type##_destroy(PyObject *py_obj) +{ + if ( !PyCObject_Check(py_obj) ) + return false; + delete (type *)PyCObject_AsVoidPtr(py_obj); + return true; +} +static type *type##_get_clink(PyObject *self) +{ + return (type *)pyobj_get_clink(self); +} +static PyObject *type##_get_clink_ptr(PyObject *self) +{ + return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG)pyobj_get_clink(self)); +} +%} +%enddef + // We use those special maps because SWIG wraps passed PyObject* with 'SwigPtr_PyObject' and 'SwigVar_PyObject' // They act like autoptr and decrement the reference of the object when the scope ends // We need to keep a reference outside SWIG and let the caller manage its references %typemap(directorin) PyObject * "/*%din%*/Py_XINCREF($1_name);$input = $1_name;" %typemap(directorout) PyObject * "/*%dout%*/$result = result;Py_XINCREF($result);" + %{ #include @@ -29,7 +56,7 @@ #ifndef NO_OBSOLETE_FUNCS #define NO_OBSOLETE_FUNCS 1 -#endif +#endif #ifdef HAVE_SSIZE_T #define _SSIZE_T_DEFINED 1 @@ -124,6 +151,7 @@ enum scfield_types_t FT_CHRARR_STATIC, }; +//--------------------------------------------------------------------------- struct scfld_t { const char *field_name; @@ -524,6 +552,30 @@ bool PyW_PyListToIntVec(PyObject *py_list, intvec_t &intvec) return pyvar_walk_list(py_list, pylist_to_intvec_cb, &intvec) != CIP_FAILED; } +//--------------------------------------------------------------------------- +static int idaapi pylist_to_strvec_cb( + PyObject *py_item, + Py_ssize_t /*index*/, + void *ud) +{ + qstrvec_t &strvec = *(qstrvec_t *)ud; + const char *s; + if ( !PyString_Check(py_item) ) + s = ""; + else + s = PyString_AsString(py_item); + + strvec.push_back(s); + return CIP_OK; +} + +//--------------------------------------------------------------------------- +bool PyW_PyListToStrVec(PyObject *py_list, qstrvec_t &strvec) +{ + strvec.clear(); + return pyvar_walk_list(py_list, pylist_to_strvec_cb, &strvec) != CIP_FAILED; +} + //------------------------------------------------------------------------- // 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. @@ -874,7 +926,9 @@ int idcvar_to_pyvar( PyObject *py_cls = get_idaapi_attr_by_id(PY_CLSID_CVT_INT64); if ( py_cls == NULL ) return CIP_FAILED; + PYW_GIL_ENSURE; *py_var = PyObject_CallFunctionObjArgs(py_cls, PyLong_FromLongLong(idc_var.i64), NULL); + PYW_GIL_RELEASE; Py_DECREF(py_cls); if ( PyW_GetError() || *py_var == NULL ) return CIP_FAILED; @@ -928,7 +982,9 @@ int idcvar_to_pyvar( return CIP_FAILED; // Create a byref object with None value. We populate it later + PYW_GIL_ENSURE; *py_var = PyObject_CallFunctionObjArgs(py_cls, Py_None, NULL); + PYW_GIL_RELEASE; Py_DECREF(py_cls); if ( PyW_GetError() || *py_var == NULL ) return CIP_FAILED; @@ -993,7 +1049,9 @@ int idcvar_to_pyvar( return CIP_FAILED; // Call constructor + PYW_GIL_ENSURE; obj = PyObject_CallFunctionObjArgs(py_cls, NULL); + PYW_GIL_RELEASE; Py_DECREF(py_cls); if ( PyW_GetError() || obj == NULL ) return CIP_FAILED; @@ -1045,11 +1103,104 @@ int idcvar_to_pyvar( return CIP_OK; } +//------------------------------------------------------------------------- +// Converts IDC arguments to Python argument list or just one tuple +// If 'decref' is NULL then 'pargs' will contain one element which is the tuple +bool pyw_convert_idc_args( + const idc_value_t args[], + int nargs, + ppyobject_vec_t &pargs, + boolvec_t *decref, + char *errbuf, + size_t errbufsize) +{ + bool as_tupple = decref == NULL; + PyObject *py_tuple(NULL); + + pargs.qclear(); + + if ( as_tupple ) + { + py_tuple = PyTuple_New(nargs); + if ( py_tuple == NULL ) + { + if ( errbuf != 0 && errbufsize > 0 ) + qstrncpy(errbuf, "Failed to create a new tuple to store arguments!", errbufsize); + return false; + } + // Add the tuple + pargs.push_back(py_tuple); + } + else + { + decref->qclear(); + } + + for ( int i=0; i 0 ) + qsnprintf(errbuf, errbufsize, "arg#%d has wrong type %d", i, args[i].vtype); + return false; + } + + if ( as_tupple ) + { + // Opaque object? + if ( cvt == CIP_OK_NODECREF ) + { + // Increment reference for opaque objects. + // (A tupple will steal references of its set items, + // and for an opaque object we want it to still exist + // even if the tuple is gone) + Py_INCREF(py_obj); + } + // Save argument + PyTuple_SetItem(py_tuple, i, py_obj); + } + else + { + pargs.push_back(py_obj); + // Do not decrement reference of opaque objects + decref->push_back(cvt == CIP_OK); + } + } + return true; +} + +//------------------------------------------------------------------------- +// Frees arguments returned by pyw_convert_idc_args() +void pyw_free_idc_args( + ppyobject_vec_t &pargs, + boolvec_t *decref) +{ + if ( decref == NULL ) + { + if ( !pargs.empty() ) + Py_XDECREF(pargs[0]); + } + else + { + // free argument objects + for ( int i=(int)pargs.size()-1; i>=0; i-- ) + { + if ( decref->at(i) ) + Py_DECREF(pargs[i]); + } + decref->clear(); + } + pargs.clear(); +} + //------------------------------------------------------------------------ // String constants used +static const char S_PYINVOKE0[] = "_py_invoke0"; 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_PROPS[] = "props"; @@ -1121,6 +1272,22 @@ struct py_add_del_menu_item_ctx PyObject *cb_data; }; +//--------------------------------------------------------------------------- +// Context structure used by add|del_idc_hotkey() +struct py_idchotkey_ctx_t +{ + qstring hotkey; + PyObject *pyfunc; +}; + +//--------------------------------------------------------------------------- +// Context structure used by register/unregister timer +struct py_timer_ctx_t +{ + qtimer_t timer_id; + PyObject *pycallback; +}; + //------------------------------------------------------------------------ const char *pywraps_check_autoscripts() { @@ -1146,6 +1313,43 @@ const char *pywraps_check_autoscripts() return NULL; } +//------------------------------------------------------------------------ +error_t PyW_CreateIdcException(idc_value_t *res, const char *msg) +{ + // Create exception object + VarObject(res, find_idc_class("exception")); + + // Set the message field + idc_value_t v; + v.set_string(msg); + VarSetAttr(res, "message", &v); + + // Throw exception + return set_qerrno(eExecThrow); +} + +//------------------------------------------------------------------------ +// Calls a Python callable encoded in IDC.pvoid member +static const char idc_py_invoke0_args[] = { VT_PVOID, 0 }; +static error_t idaapi idc_py_invoke0( + idc_value_t *argv, + idc_value_t *res) +{ + PyObject *pyfunc = (PyObject *) argv[0].pvoid; + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallFunctionObjArgs(pyfunc, NULL); + PYW_GIL_RELEASE; + + Py_XDECREF(py_result); + + // Report Python error as IDC exception + qstring err; + if ( PyW_GetError(&err) ) + return PyW_CreateIdcException(res, err.c_str()); + + return eOk; +} + //------------------------------------------------------------------------ // This function must be called on initialization bool init_pywraps() @@ -1163,6 +1367,11 @@ bool init_pywraps() return false; } + // Register the IDC PyInvoke0 method (helper function for add_idc_hotkey()) + if ( !set_idc_func_ex(S_PYINVOKE0, idc_py_invoke0, idc_py_invoke0_args, 0) ) + return false; + + // IDC opaque class not registered? if ( get_py_idc_cvt_opaque() == NULL ) { // Add the class @@ -1196,6 +1405,9 @@ void deinit_pywraps() pywraps_initialized = false; Py_XDECREF(py_cvt_helper_module); py_cvt_helper_module = NULL; + + // Unregister the IDC PyInvoke0 method (helper function for add_idc_hotkey()) + set_idc_func_ex(S_PYINVOKE0, NULL, idc_py_invoke0_args, 0); } //------------------------------------------------------------------------ @@ -1250,7 +1462,7 @@ PyObject *create_idaapi_linked_class_instance( // This function takes a reference to the idaapi module and keeps the reference PyObject *get_idaapi_attr_by_id(const int class_id) { - if ( class_id >= PY_CLSID_LAST ) + if ( class_id >= PY_CLSID_LAST || py_cvt_helper_module == NULL ) return NULL; // Some class names. The array is parallel with the PY_CLSID_xxx consts @@ -1267,7 +1479,9 @@ PyObject *get_idaapi_attr_by_id(const int class_id) // Gets a class reference by name PyObject *get_idaapi_attr(const char *attrname) { - return PyW_TryGetAttrString(py_cvt_helper_module, attrname); + return py_cvt_helper_module == NULL + ? NULL + : PyW_TryGetAttrString(py_cvt_helper_module, attrname); } //------------------------------------------------------------------------ @@ -1467,7 +1681,7 @@ bool PyW_ObjectToString(PyObject *obj, qstring *out) //-------------------------------------------------------------------------- // Checks if a Python error occured and fills the out parameter with the // exception string -bool PyW_GetError(qstring *out) +bool PyW_GetError(qstring *out, bool clear_err) { if ( PyErr_Occurred() == NULL ) return false; @@ -1476,14 +1690,77 @@ bool PyW_GetError(qstring *out) if ( out == NULL ) { // Just clear the error + if ( clear_err ) + PyErr_Clear(); + return true; + } + + // Get the exception info + PyObject *err_type, *err_value, *err_traceback, *py_ret(NULL); + PyErr_Fetch(&err_type, &err_value, &err_traceback); + + if ( !clear_err ) + PyErr_Restore(err_type, err_value, err_traceback); + + // Resolve FormatExc() + PyObject *py_fmtexc = get_idaapi_attr(S_IDAAPI_FORMATEXC); + + // Helper there? + if ( py_fmtexc != NULL ) + { + // Call helper + PYW_GIL_ENSURE; + py_ret = PyObject_CallFunctionObjArgs( + py_fmtexc, + err_type, + err_value, + err_traceback, + NULL); + PYW_GIL_RELEASE; + + // Dispose helper reference + Py_DECREF(py_fmtexc); + } + + // Clear the error + if ( clear_err ) PyErr_Clear(); + + // Helper failed?! + if ( py_ret == NULL ) + { + // Just convert the 'value' part of the original error + py_ret = PyObject_Str(err_value); + } + + // No exception text? + if ( py_ret == NULL ) + { + *out = "IDAPython: unknown error!"; } else { - PyObject *err_type, *err_value, *err_traceback; - PyErr_Fetch(&err_type, &err_value, &err_traceback); - PyW_ObjectToString(err_value, out); + *out = PyString_AsString(py_ret); + Py_DECREF(py_ret); } + + if ( clear_err ) + { + Py_XDECREF(err_traceback); + Py_XDECREF(err_value); + Py_XDECREF(err_type); + } + return true; +} + +//------------------------------------------------------------------------- +bool PyW_GetError(char *buf, size_t bufsz, bool clear_err) +{ + qstring s; + if ( !PyW_GetError(&s, clear_err) ) + return false; + + qstrncpy(buf, s.c_str(), bufsz); return true; } @@ -1770,11 +2047,11 @@ 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; @@ -1874,7 +2151,9 @@ class pyidc_opaque_object_t(object): # ----------------------------------------------------------------------- class py_clinked_object_t(pyidc_opaque_object_t): - """This is a utility and base class for C linked objects""" + """ + This is a utility and base class for C linked objects + """ def __init__(self, lnk = None): # static link: if a link was provided self.__static_clink__ = True if lnk else False @@ -1884,22 +2163,13 @@ class py_clinked_object_t(pyidc_opaque_object_t): def __del__(self): """Delete the link upon object destruction (only if not static)""" - if not self.__static_clink__: + self._free() + + def _free(self): + """Explicitly delete the link (only if not static)""" + if not self.__static_clink__ and self.__clink__ is not None: self._del_clink(self.__clink__) - - def _create_clink(self): - """ - Overwrite me. - Creates a new clink - @return: PyCObject representing the C link - """ - pass - - def _del_clink(self, lnk): - """Overwrite me. - This method deletes the link - """ - pass + self.__clink__ = None def copy(self): """Returns a new copy of this class""" @@ -1912,6 +2182,31 @@ class py_clinked_object_t(pyidc_opaque_object_t): return inst + # + # Methods to be overwritten + # + def _create_clink(self): + """ + Overwrite me. + Creates a new clink + @return: PyCObject representing the C link + """ + pass + + def _del_clink(self, lnk): + """ + Overwrite me. + This method deletes the link + """ + pass + + def _get_clink_ptr(self): + """ + Overwrite me. + Returns the C link pointer as a 64bit number + """ + pass + def assign(self, other): """ Overwrite me. @@ -1921,6 +2216,10 @@ class py_clinked_object_t(pyidc_opaque_object_t): pass clink = property(lambda self: self.__clink__) + """Returns the C link as a PyObject""" + + clink_ptr = property(lambda self: self._get_clink_ptr()) + """Returns the C link pointer as a number""" # ----------------------------------------------------------------------- class object_t(object): @@ -1996,6 +2295,75 @@ class PyIdc_cvt_int64__(pyidc_cvt_helper__): def __rmul__(self, other): return self.__op(2, other, True) def __rdiv__(self, other): return self.__op(3, other, True) +# ----------------------------------------------------------------------- +# qstrvec_t clinked object +class qstrvec_t(py_clinked_object_t): + """Class representing an qstrvec_t""" + + def __init__(self, items=None): + py_clinked_object_t.__init__(self) + # Populate the list if needed + if items: + self.from_list(items) + + def _create_clink(self): + return _idaapi.qstrvec_t_create() + + def _del_clink(self, lnk): + return _idaapi.qstrvec_t_destroy(lnk) + + def _get_clink_ptr(self): + return _idaapi.qstrvec_t_get_clink_ptr(self) + + def assign(self, other): + """Copies the contents of 'other' to 'self'""" + return _idaapi.qstrvec_t_assign(self, other) + + def __setitem__(self, idx, s): + """Sets string at the given index""" + return _idaapi.qstrvec_t_set(self, idx, s) + + def __getitem__(self, idx): + """Gets the string at the given index""" + return _idaapi.qstrvec_t_get(self, idx) + + def __get_size(self): + return _idaapi.qstrvec_t_size(self) + + size = property(__get_size) + """Returns the count of elements""" + + def addressof(self, idx): + """Returns the address (as number) of the qstring at the given index""" + return _idaapi.qstrvec_t_addressof(self, idx) + + def add(self, s): + """Add a string to the vector""" + return _idaapi.qstrvec_t_add(self, s) + + + def from_list(self, lst): + """Populates the vector from a Python string list""" + return _idaapi.qstrvec_t_from_list(self, lst) + + + def clear(self, qclear=False): + """ + Clears all strings from the vector. + @param qclear: Just reset the size but do not actually free the memory + """ + return _idaapi.qstrvec_t_clear(self, qclear) + + + def insert(self, idx, s): + """Insert a string into the vector""" + return _idaapi.qstrvec_t_insert(self, idx, s) + + + def remove(self, idx): + """Removes a string from the vector""" + return _idaapi.qstrvec_t_remove(self, idx) + # ----------------------------------------------------------------------- class PyIdc_cvt_refclass__(pyidc_cvt_helper__): """Helper class for representing references to immutable objects""" @@ -2098,8 +2466,19 @@ def IDAPython_ExecSystem(cmd): s = ''.join(f.readlines()) f.close() return s - except Exception, e: - return str(e) + except Exception as e: + return "%s\n%s" % (str(e), traceback.format_exc()) + +# ------------------------------------------------------------ +def IDAPython_FormatExc(etype, value, tb, limit=None): + """ + This function is used to format an exception given the + values returned by a PyErr_Fetch() + """ + try: + return ''.join(traceback.format_exception(etype, value, tb, limit)) + except: + return str(value) # ------------------------------------------------------------ @@ -2125,12 +2504,12 @@ def IDAPython_ExecScript(script, g): old__file__ = g['__file__'] if '__file__' in g else '' g['__file__'] = script - PY_COMPILE_ERR = None try: execfile(script, g) - except Exception, e: - PY_COMPILE_ERR = str(e) + "\n" + traceback.format_exc() - print PY_COMPILE_ERR + PY_COMPILE_ERR = None + except Exception as e: + PY_COMPILE_ERR = "%s\n%s" % (str(e), traceback.format_exc()) + print(PY_COMPILE_ERR) finally: # Restore the globals to the state before the script was run g['__file__'] = old__file__ @@ -2156,7 +2535,9 @@ class __IDAPython_Completion_Util(object): @staticmethod def parse_identifier(line, prefix, prefix_start): - """Parse a line and extracts""" + """ + Parse a line and extracts identifier + """ id_start = prefix_start while id_start > 0: ch = line[id_start] @@ -2181,7 +2562,7 @@ class __IDAPython_Completion_Util(object): for i in xrange(0, c-1): m = getattr(m, parts[i]) - except Exception, e: + except Exception as e: return (None, None) else: # search in the module @@ -2388,6 +2769,112 @@ def RunPythonStatement(stmt): # */ +//--------------------------------------------------------------------------- +// qstrvec_t wrapper +//--------------------------------------------------------------------------- +DECLARE_PY_CLINKED_OBJECT(qstrvec_t); + +static bool qstrvec_t_assign(PyObject *self, PyObject *other) +{ + qstrvec_t *lhs = qstrvec_t_get_clink(self); + qstrvec_t *rhs = qstrvec_t_get_clink(other); + if (lhs == NULL || rhs == NULL) + return false; + *lhs = *rhs; + return true; +} + +static PyObject *qstrvec_t_addressof(PyObject *self, size_t idx) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + if ( sv == NULL || idx >= sv->size() ) + Py_RETURN_NONE; + else + return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG)&sv->at(idx)); +} + + +static bool qstrvec_t_set( + PyObject *self, + size_t idx, + const char *s) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + if ( sv == NULL || idx >= sv->size() ) + return false; + (*sv)[idx] = s; + return true; +} + +static bool qstrvec_t_from_list( + PyObject *self, + PyObject *py_list) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + return sv == NULL ? false : PyW_PyListToStrVec(py_list, *sv); +} + +static size_t qstrvec_t_size(PyObject *self) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + return sv == NULL ? 0 : sv->size(); +} + +static PyObject *qstrvec_t_get(PyObject *self, size_t idx) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + if ( sv == NULL || idx >= sv->size() ) + Py_RETURN_NONE; + return PyString_FromString(sv->at(idx).c_str()); +} + +static bool qstrvec_t_add(PyObject *self, const char *s) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + if ( sv == NULL ) + return false; + sv->push_back(s); + return true; +} + +static bool qstrvec_t_clear(PyObject *self, bool qclear) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + if ( sv == NULL ) + return false; + + if ( qclear ) + sv->qclear(); + else + sv->clear(); + + return true; +} + +static bool qstrvec_t_insert( + PyObject *self, + size_t idx, + const char *s) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + if ( sv == NULL || idx >= sv->size() ) + return false; + sv->insert(sv->begin() + idx, s); + return true; +} + +static bool qstrvec_t_remove(PyObject *self, size_t idx) +{ + qstrvec_t *sv = qstrvec_t_get_clink(self); + if ( sv == NULL || idx >= sv->size() ) + return false; + + sv->erase(sv->begin()+idx); + return true; +} + +//--------------------------------------------------------------------------- + //------------------------------------------------------------------------ diff --git a/swig/idd.i b/swig/idd.i index 36322b5..7db2323 100644 --- a/swig/idd.i +++ b/swig/idd.i @@ -90,7 +90,7 @@ PyObject *py_appcall( if ( !ok ) { PyErr_SetString( - PyExc_ValueError, + PyExc_ValueError, "PyAppCall: Failed to convert Python values to IDC values"); return NULL; } @@ -275,9 +275,9 @@ static PyObject *dbg_get_thread_sreg_base(PyObject *py_tid, PyObject *py_sreg_va ea_t answer; thid_t tid = PyInt_AsLong(py_tid); int sreg_value = PyInt_AsLong(py_sreg_value); - if ( dbg->thread_get_sreg_base(tid, sreg_value, &answer) != 1 ) + if ( internal_get_sreg_base(tid, sreg_value, &answer) != 1 ) Py_RETURN_NONE; - + return Py_BuildValue(PY_FMT64, pyul_t(answer)); } @@ -310,7 +310,7 @@ static PyObject *dbg_read_memory(PyObject *py_ea, PyObject *py_sz) char *buf; PyString_AsStringAndSize(ret, &buf, &len); - if ( (size_t)dbg->read_memory(ea_t(ea), buf, size_t(sz)) != sz ) + if ( (size_t)read_dbg_memory(ea_t(ea), buf, size_t(sz)) != sz ) { // Release the string on failure Py_DECREF(ret); @@ -339,7 +339,7 @@ static PyObject *dbg_write_memory(PyObject *py_ea, PyObject *py_buf) size_t sz = PyString_GET_SIZE(py_buf); void *buf = (void *)PyString_AS_STRING(py_buf); - if ( dbg->write_memory(ea_t(ea), buf, sz) != sz ) + if ( write_dbg_memory(ea_t(ea), buf, sz) != sz ) Py_RETURN_FALSE; Py_RETURN_TRUE; } @@ -386,7 +386,7 @@ static PyObject *dbg_get_memory_info() invalidate_dbgmem_contents(BADADDR, BADADDR); meminfo_vec_t areas; - dbg->get_memory_info(areas); + get_dbg_memory_info(&areas); return meminfo_vec_t_to_py(areas); } @@ -587,7 +587,7 @@ class Appcall_callable__(object): self.type, self.fields, arg_list) - except Exception, e: + except Exception as e: e_obj = e # Restore appcall options diff --git a/swig/idp.i b/swig/idp.i index 5304f18..1534e65 100644 --- a/swig/idp.i +++ b/swig/idp.i @@ -423,7 +423,7 @@ static PyObject *ph_get_operand_info( regvals_t regvalues; regvalues.resize(dbg->registers_size); // Read registers - if ( dbg->read_registers(tid, -1, regvalues.begin()) != 1 ) + if ( get_reg_vals(tid, -1, regvalues.begin()) != 1 ) break; // Call the processor module @@ -749,7 +749,7 @@ public: virtual PyObject *custom_mnem() { - return NULL; + Py_RETURN_NONE; } virtual int is_sane_insn(int no_crefs) @@ -825,7 +825,7 @@ public: bool /*use32*/, const char * /*line*/) { - return NULL; + Py_RETURN_NONE; } }; @@ -865,7 +865,7 @@ public: 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_deleted(struc_t * /*sptr*/, tid_t /*member_id*/, ea_t /*offset*/) { 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; }; @@ -1067,6 +1067,303 @@ int idaapi IDP_Callback(void *ud, int notification_code, va_list va) Py_XDECREF(py_buffer); break; } + // validate_flirt_func, // flirt has recognized a library function + // // this callback can be used by a plugin or proc module + // // to intercept it and validate such a function + // // args: ea_t start_ea + // // const char *funcname + // // returns: -1-do not create a function, + // // 1-function is validated + // // the idp module is allowed to modify 'cmd' + // set_func_start, // Function chunk start address will be changed + // // args: func_t *pfn + // // ea_t new_start + // // Returns: 1-ok,<=0-do not change + // set_func_end, // Function chunk end address will be changed + // // args: func_t *pfn + // // ea_t new_end + // // Returns: 1-ok,<=0-do not change + // outlabel, // The kernel is going to generate an instruction + // // label line or a function header + // // args: + // // ea_t ea - + // // const char *colored_name - + // // If returns value <=0, then the kernel should + // // not generate the label + // may_show_sreg, // The kernel wants to display the segment registers + // // in the messages window. + // // arg - ea_t current_ea + // // if this function returns 0 + // // then the kernel will not show + // // the segment registers. + // // (assuming that the module have done it) + // coagulate, // Try to define some unexplored bytes + // // This notification will be called if the + // // kernel tried all possibilities and could + // // not find anything more useful than to + // // convert to array of bytes. + // // The module can help the kernel and convert + // // the bytes into something more useful. + // // arg: + // // ea_t start_ea + // // returns: number of converted bytes + 1 + // auto_empty, // Info: all analysis queues are empty + // // args: none + // // returns: none + // // This callback is called once when the + // // initial analysis is finished. If the queue is + // // not empty upon the return from this callback, + // // it will be called later again. + // // See also auto_empty_finally. + // auto_queue_empty, // One analysis queue is empty + // // args: atype_t type + // // returns: 1-yes, keep the queue empty + // // <=0-no, the queue is not empty anymore + // // This callback can be called many times, so + // // only the autoMark() functions can be used from it + // // (other functions may work but it is not tested) + // func_bounds, // find_func_bounds() finished its work + // // The module may fine tune the function bounds + // // args: int *possible_return_code + // // func_t *pfn + // // ea_t max_func_end_ea (from the kernel's point of view) + // // returns: none + // is_jump_func, // is the function a trivial "jump" function? + // // args: func_t *pfn + // // ea_t *jump_target + // // ea_t *func_pointer + // // returns: 0-no, 1-don't know, 2-yes, see jump_target + // // and func_pointer + // gen_regvar_def, // generate register variable definition line + // // args: regvar_t *v + // // returns: 0-ok + // setsgr, // The kernel has changed a segment register value + // // args: ea_t startEA + // // ea_t endEA + // // int regnum + // // sel_t value + // // sel_t old_value + // // uchar tag (SR_... values) + // // returns: 1-ok, 0-error + // set_compiler, // The kernel has changed the compiler information + // // (inf.cc structure) + // is_basic_block_end, // Is the current instruction end of a basic block? + // // This function should be defined for processors + // // with delayed jump slots. The current instruction + // // is stored in 'cmd' + // // args: bool call_insn_stops_block + // // returns: 1-unknown, 0-no, 2-yes + // reglink, // IBM PC only, ignore it + // get_vxd_name, // IBM PC only, ignore it + // // Get Vxd function name + // // args: int vxdnum + // // int funcnum + // // char *outbuf + // // returns: nothing + // + // + // moving_segm, // May the kernel move the segment? + // // args: segment_t - segment to move + // // ea_t to - new segment start address + // // returns: 1-yes, <=0-the kernel should stop + // move_segm, // A segment is moved + // // Fix processor dependent address sensitive information + // // args: ea_t from - old segment address + // // segment_t* - moved segment + // // returns: nothing + // + // + // get_stkvar_scale_factor,// Should stack variable references be multiplied by + // // a coefficient before being used in the stack frame? + // // Currently used by TMS320C55 because the references into + // // the stack should be multiplied by 2 + // // Returns: scaling factor + // // Note: PR_SCALE_STKVARS should be set to use this callback + // + // create_flat_group, // Create special segment representing the flat group + // // (to use for PC mainly) + // // args - ea_t image_base, int bitness, sel_t dataseg_sel + // + // kernel_config_loaded, // This callback is called when ida.cfg is parsed + // // args - none, returns - nothing + // + // might_change_sp, // Does the instruction at 'ea' modify the stack pointer? + // // args: ea_t ea + // // returns: 1-yes, 0-false + // // (not used yet) + // + // is_alloca_probe, // Does the function at 'ea' behave as __alloca_probe? + // // args: ea_t ea + // // returns: 2-yes, 1-false + // + // out_3byte, // Generate text representation of 3byte data + // // init_out_buffer() is called before this function + // // and all Out... function can be used. + // // uFlag contains the flags. + // // This callback might be implemented by the processor + // // module to generate custom representation of 3byte data. + // // args: + // // ea_t dataea - address of the data item + // // uint32 value - value to output + // // bool analyze_only - only create xrefs if necessary + // // do not generate text representation + // // returns: 2-yes, 1-false + // + // get_reg_name, // Generate text representation of a register + // // int reg - internal register number as defined in the processor module + // // size_t width - register width in bytes + // // char *buf - output buffer + // // size_t bufsize - size of output buffer + // // int reghi - if not -1 then this function will return the register pair + // // returns: -1 if error, strlen(buf)+2 otherwise + // // Most processor modules do not need to implement this callback + // // It is useful only if ph.regNames[reg] does not provide + // // the correct register names + // // save its local data + // out_src_file_lnnum, // Callback: generate analog of + // // #line "file.c" 123 + // // directive. + // // const char *file - source file (may be NULL) + // // size_t lnnum - line number + // // returns: 2-directive has been generated + // get_autocmt, // Callback: get dynamic auto comment + // // Will be called if the autocomments are enabled + // // and the comment retrieved from ida.int starts with + // // '$!'. 'cmd' is contains valid info. + // // char *buf - output buffer + // // size_t bufsize - output buffer size + // // returns: 2-new comment has been generated + // // 1-callback has not been handled + // // the buffer must not be changed in this case + // is_insn_table_jump, // Callback: determine if instruction is a table jump or call + // // If CF_JUMP bit can not describe all kinds of table + // // jumps, please define this callback. + // // It will be called for insns with CF_JUMP bit set. + // // input: cmd structure contains the current instruction + // // returns: 1-yes, 0-no + // auto_empty_finally, // Info: all analysis queues are empty definitively + // // args: none + // // returns: none + // // This callback is called only once. + // // See also auto_empty. + // loader_finished, // Event: external file loader finished its work + // // linput_t *li + // // uint16 neflags + // // const char *filetypename + // // Use this event to augment the existing loader functionality + // loader_elf_machine, // Event: ELF loader machine type checkpoint + // // linput_t *li + // // int machine_type + // // const char **p_procname + // // proc_def **p_pd (see ldr\elf.h) + // // set_elf_reloc_t *set_reloc + // // A plugin check the machine_type. If it is the desired one, + // // the the plugin fills p_procname with the processor name. + // // p_pd is used to handle relocations, otherwise can be left untouched + // // set_reloc can be later used by the plugin to specify relocations + // // returns: e_machine value (if it is different from the + // // original e_machine value, procname and p_pd will be ignored + // // and the new value will be used) + // // This event occurs for each loaded ELF file + // is_indirect_jump, // Callback: determine if instruction is an indrect jump + // // If CF_JUMP bit can not describe all jump types + // // jumps, please define this callback. + // // input: cmd structure contains the current instruction + // // returns: 1-use CF_JUMP, 2-no, 3-yes + // verify_noreturn, // The kernel wants to set 'noreturn' flags for a function + // // func_t *pfn + // // Returns: 1-ok, any other value-do not set 'noreturn' flag + // verify_sp, // All function instructions have been analyzed + // // Now the processor module can analyze the stack pointer + // // for the whole function + // // input: func_t *pfn + // // Returns: 1-ok, 0-bad stack pointer + // treat_hindering_item, // An item hinders creation of another item + // // args: ea_t hindering_item_ea + // // flags_t new_item_flags (0 for code) + // // ea_t new_item_ea + // // asize_t new_item_length + // // Returns: 1-no reaction, <=0-the kernel may delete the hindering item + // str2reg, // Convert a register name to a register number + // // args: const char *regname + // // Returns: register number + 2 + // // The register number is the register index in the regNames array + // // Most processor modules do not need to implement this callback + // // It is useful only if ph.regNames[reg] does not provide + // // the correct register names + // create_switch_xrefs, // Create xrefs for a custom jump table + // // in: ea_t jumpea; - address of the jump insn + // // switch_info_ex_t *; - switch information + // // returns: must return 2 + // calc_switch_cases, // Calculate case values and targets for a custom jump table + // // in: ea_t insn_ea - address of the 'indirect jump' instruction + // // switch_info_ex_t *si - switch information + // // casevec_t *casevec - vector of case values... + // // evec_t *targets - ...and corresponding target addresses + // // casevec and targets may be NULL + // // returns: 2-ok, 1-failed + // determined_main, // The main() function has been determined + // // in: ea_t main - address of the main() function + // // returns: none + // preprocess_chart, // gui has retrieved a function flow chart + // // in: qflow_chart_t *fc + // // returns: none + // // Plugins may modify the flow chart in this callback + // get_bg_color, // Get item background color + // // in: ea_t ea, bgcolor_t *color + // // Returns: 1-not implemented, 2-color set + // // Plugins can hook this callback to color disassembly lines + // // dynamically + // get_operand_string, // Request text string for operand (cli, java, ...) + // // args: int opnum + // // char *buf + // // size_t buflen + // // (cmd structure must contain info for the desired insn) + // // opnum is the operand number; -1 means any string operand + // // returns: 1 - no string (or empty string) + // // >1 - original string length with terminating zero + // + // // the following 5 events are very low level + // // take care of possible recursion + // add_cref, // a code reference is being created + // // args: ea_t from, ea_t to, cref_t type + // // returns: <0 - cancel cref creation + // add_dref, // a data reference is being created + // // args: ea_t from, ea_t to, dref_t type + // // returns: <0 - cancel dref creation + // del_cref, // a code reference is being deleted + // // args: ea_t from, ea_t to, bool expand + // // returns: <0 - cancel cref deletion + // del_dref, // a data reference is being deleted + // // args: ea_t from, ea_t to + // // returns: <0 - cancel dref deletion + // coagulate_dref, // data reference is being analyzed + // // args: ea_t from, ea_t to, bool may_define, ea_t *code_ea + // // plugin may correct code_ea (e.g. for thumb mode refs, we clear the last bit) + // // returns: <0 - cancel dref analysis + // custom_fixup, // mutipurpose notification for FIXUP_CUSTOM + // // args: cust_fix oper, ea_t ea, const fixup_data_t*, ... (see cust_fix) + // // returns: 1 - no accepted (fixup ignored by ida) + // // >1 - accepted (see cust_fix) + // off_preproc, // called from get_offset_expr, when refinfo_t + // // contain flag REFINFO_PREPROC. Normally this + // // notification used in a combination with custom_fixup + // // args: ea_t ea, int numop, ea_t* opval, const refinfo_t* ri, + // // char* buf, size_t bufsize, ea_t* target, + // // ea_t* fullvalue, ea_t from, int getn_flags + // // returns: 2 - buf filled as simple expression + // // 3 - buf filled as complex expression + // // 4 - apply standard processing (with - possible - changed values) + // // others - can't convert to offset expression + // + // set_proc_options, // called if the user specified an option string in the command line: + // // -p: + // // can be used for e.g. setting a processor subtype + // // also called if option string is passed to set_processor_type() + // // and IDC's SetProcessorType() + // // args: const char * options + // // returns: <0 - bad option string + // } } catch (Swig::DirectorException &e) @@ -1190,7 +1487,8 @@ int idaapi IDB_Callback(void *ud, int notification_code, va_list va) case idb_event::struc_member_deleted: sptr = va_arg(va, struc_t *); member_id = va_arg(va, tid_t); - return proxy->struc_member_deleted(sptr, member_id); + ea = va_arg(va, ea_t); + return proxy->struc_member_deleted(sptr, member_id, ea); case idb_event::struc_member_renamed: sptr = va_arg(va, struc_t *); diff --git a/swig/kernwin.i b/swig/kernwin.i index c965c90..2db1d1e 100644 --- a/swig/kernwin.i +++ b/swig/kernwin.i @@ -3,6 +3,7 @@ %ignore AskUsingForm_c; %ignore close_form; %ignore vaskstr; +%ignore strvec_t; %ignore load_custom_icon; %ignore vasktext; %ignore add_menu_item; @@ -14,6 +15,8 @@ %ignore choose_idasgn; %rename (choose_idasgn) py_choose_idasgn; +%rename (del_hotkey) py_del_hotkey; +%rename (add_hotkey) py_add_hotkey; %ignore msg; %rename (msg) py_msg; @@ -24,6 +27,7 @@ %ignore error; %rename (error) py_error; +%ignore textctrl_info_t; %ignore vinfo; %ignore UI_Callback; %ignore vnomem; @@ -35,6 +39,8 @@ %ignore askyn_v; %ignore add_custom_viewer_popup_item; %ignore create_custom_viewer; +%ignore take_database_snapshot; +%ignore restore_database_snapshot; %ignore destroy_custom_viewer; %ignore destroy_custom_viewerdestroy_custom_viewer; %ignore get_custom_viewer_place; @@ -64,10 +70,21 @@ %rename (asktext) py_asktext; %rename (str2ea) py_str2ea; +%ignore process_ui_action; %rename (process_ui_action) py_process_ui_action; %ignore execute_sync; %ignore exec_request_t; +%rename (execute_sync) py_execute_sync; +%ignore ui_request_t; +%ignore execute_ui_requests; +%rename (execute_ui_requests) py_execute_ui_requests; + +%ignore timer_t; +%ignore register_timer; +%rename (register_timer) py_register_timer; +%ignore unregister_timer; +%rename (unregister_timer) py_unregister_timer; // Make askaddr(), askseg(), and asklong() return a // tuple: (result, value) @@ -107,11 +124,115 @@ void refresh_lists(void) # This is for read_selection() %apply unsigned long *OUTPUT { ea_t *ea1, ea_t *ea2 }; +SWIG_DECLARE_PY_CLINKED_OBJECT(textctrl_info_t) + %inline %{ // //------------------------------------------------------------------------ //------------------------------------------------------------------------ +/* +# +def register_timer(interval, callback): + """ + Register a timer + + @param interval: Interval in milliseconds + @param callback: A Python callable that takes no parameters and returns an integer. + The callback may return: + -1 : to unregister the timer + >= 0 : the new or same timer interval + @return: None or a timer object + """ + pass +# +*/ +static PyObject *py_register_timer(int interval, PyObject *py_callback) +{ + if ( py_callback == NULL || !PyCallable_Check(py_callback) ) + Py_RETURN_NONE; + + // An inner class hosting the callback method + struct tmr_t + { + static int idaapi callback(void *ud) + { + py_timer_ctx_t *ctx = (py_timer_ctx_t *)ud; + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallFunctionObjArgs(ctx->pycallback, NULL); + int ret = py_result == NULL ? -1 : PyLong_AsLong(py_result); + Py_XDECREF(py_result); + PYW_GIL_RELEASE; + + // Timer has been unregistered? + if ( ret == -1 ) + { + // Fee the context + Py_DECREF(ctx->pycallback); + delete ctx; + } + return ret; + }; + }; + + py_timer_ctx_t *ctx = new py_timer_ctx_t(); + ctx->pycallback = py_callback; + Py_INCREF(py_callback); + ctx->timer_id = register_timer( + interval, + tmr_t::callback, + ctx); + + if ( ctx->timer_id == NULL ) + { + Py_DECREF(py_callback); + delete ctx; + Py_RETURN_NONE; + } + return PyCObject_FromVoidPtr(ctx, NULL); +} + +//------------------------------------------------------------------------ +/* +# +def unregister_timer(timer_obj): + """ + Unregister a timer + + @param timer_obj: a timer object previously returned by a register_timer() + @return: Boolean + @note: After the timer has been deleted, the timer_obj will become invalid. + """ + pass +# +*/ +static PyObject *py_unregister_timer(PyObject *py_timerctx) +{ + if ( py_timerctx == NULL || !PyCObject_Check(py_timerctx) ) + Py_RETURN_FALSE; + + py_timer_ctx_t *ctx = (py_timer_ctx_t *) PyCObject_AsVoidPtr(py_timerctx); + if ( !unregister_timer(ctx->timer_id) ) + Py_RETURN_FALSE; + + Py_DECREF(ctx->pycallback); + delete ctx; + + Py_RETURN_TRUE; +} + +//------------------------------------------------------------------------ +/* +# +def choose_idasgn(): + """ + Opens the signature chooser + + @return: None or the selected signature name + """ + pass +# +*/ static PyObject *py_choose_idasgn() { char *name = choose_idasgn(); @@ -203,7 +324,7 @@ PyObject *py_asktext(int max_text, const char *defval, const char *prompt) Py_RETURN_NONE; PyObject *py_ret; - if ( asktext(size_t(max_text), buf, defval, prompt) != NULL ) + if ( asktext(size_t(max_text), buf, defval, "%s", prompt) != NULL ) { py_ret = PyString_FromString(buf); } @@ -240,7 +361,7 @@ ea_t py_str2ea(const char *str, ea_t screenEA = BADADDR) # def process_ui_action(name, flags): """ - Invokes an IDA Pro UI action by name + Invokes an IDA UI action by name @param name: action name @param flags: Reserved. Must be zero @@ -285,6 +406,108 @@ static bool py_del_menu_item(PyObject *py_ctx) return ok; } +//------------------------------------------------------------------------ +/* +# +def del_hotkey(ctx): + """ + Deletes a previously registered function hotkey + + @param ctx: Hotkey context previously returned by add_hotkey() + + @return: Boolean. + """ + pass +# +*/ +bool py_del_hotkey(PyObject *pyctx) +{ + if ( !PyCObject_Check(pyctx) ) + return false; + + py_idchotkey_ctx_t *ctx = (py_idchotkey_ctx_t *) PyCObject_AsVoidPtr(pyctx); + if ( !del_idc_hotkey(ctx->hotkey.c_str()) ) + return false; + + Py_DECREF(ctx->pyfunc); + delete ctx; + return true; +} + +//------------------------------------------------------------------------ +/* +# +def add_hotkey(hotkey, pyfunc): + """ + Associates a function call with a hotkey. + Callable pyfunc will be called each time the hotkey is pressed + + @param hotkey: The hotkey + @param pyfunc: Callable + + @return: Context object on success or None on failure. + """ + pass +# +*/ +PyObject *py_add_hotkey(const char *hotkey, PyObject *pyfunc) +{ + // Make sure a callable was passed + if ( !PyCallable_Check(pyfunc) ) + return NULL; + + // Form the function name + qstring idc_func_name; + idc_func_name.sprnt("py_hotkeycb_%p", pyfunc); + + // Can add the hotkey? + if ( add_idc_hotkey(hotkey, idc_func_name.c_str()) == IDCHK_OK ) do + { + // Generate global variable name + qstring idc_gvarname; + idc_gvarname.sprnt("_g_pyhotkey_ref_%p", pyfunc); + + // Now add the global variable + idc_value_t *gvar = add_idc_gvar(idc_gvarname.c_str()); + if ( gvar == NULL ) + break; + + // The function body will call a registered IDC function that + // will take a global variable that wraps a PyCallable as a pvoid + qstring idc_func; + idc_func.sprnt("static %s() { %s(%s); }", + idc_func_name.c_str(), + S_PYINVOKE0, + idc_gvarname.c_str()); + + // Compile the IDC condition + char errbuf[MAXSTR]; + if ( !CompileLineEx(idc_func.c_str(), errbuf, sizeof(errbuf)) ) + break; + + // Create new context + // Define context + py_idchotkey_ctx_t *ctx = new py_idchotkey_ctx_t(); + + // Remember the hotkey + ctx->hotkey = hotkey; + + // Take reference to the callable + ctx->pyfunc = pyfunc; + Py_INCREF(pyfunc); + + // Bind IDC variable w/ the PyCallable + gvar->set_pvoid(pyfunc); + + // Return the context + return PyCObject_FromVoidPtr(ctx, NULL); + } while (false); + + // Cleanup + del_idc_hotkey(hotkey); + Py_RETURN_NONE; +} + //------------------------------------------------------------------------ /* # @@ -368,6 +591,200 @@ static PyObject *py_add_menu_item( 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""" + +MFF_NOWAIT = 0x0004 +"""Do not wait for the request to be executed. +he caller should ensure that the request is not +destroyed until the execution completes. +if not, the request will be ignored. +the return code of execute_sync() is meaningless +in this case. +This flag can be used to delay the code execution +until the next UI loop run even from the main thread""" + +def execute_sync(callable, reqf) + """ + Executes a function in the context of the main thread. + If the current thread not the main thread, then the call is queued and + executed afterwards. + + @note: The Python version of execute_sync() cannot be called from a different thread + for the time being. + @param callable: A python callable object + @param reqf: one of MFF_ flags + @return: -1 or the return value of the callable + """ + pass +# +*/ +//------------------------------------------------------------------------ +static int py_execute_sync(PyObject *py_callable, int reqf) +{ + // Not callable? + if ( !PyCallable_Check(py_callable) ) + return -1; + + struct py_exec_request_t: exec_request_t + { + PyObject *py_callable; + bool no_wait; + virtual int idaapi execute() + { + PYW_GIL_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); + PYW_GIL_RELEASE; + + // Free this request + if ( no_wait ) + delete this; + + return r; + } + py_exec_request_t(PyObject *pyc, bool no_wait): + py_callable(pyc), no_wait(no_wait) + { + // Take reference to the callable + Py_INCREF(py_callable); + } + virtual ~py_exec_request_t() + { + // Release callable + Py_XDECREF(py_callable); + } + }; + + bool no_wait = (reqf & MFF_NOWAIT) != 0; + + // Allocate a request + py_exec_request_t *req = new py_exec_request_t(py_callable, no_wait); + + // Execute it + int r = execute_sync(*req, reqf); + + // Delete only if NOWAIT was not specified + // (Otherwise the request will delete itself) + if ( !no_wait ) + delete req; + + return r; +} + +//------------------------------------------------------------------------ +/* +# + +def execute_ui_requests(callable_list) + """ + Inserts a list of callables into the UI message processing queue. + When the UI is ready it will call one callable. + A callable can request to be called more than once if it returns True. + + @param callable_list: A list of python callable objects. + @note: A callable should return True if it wants to be called more than once. + @return: Boolean. False if the list contains a non callabale item + """ + pass +# +*/ +static bool py_execute_ui_requests(PyObject *py_list) +{ + struct py_ui_request_t: public ui_request_t + { + private: + ppyobject_vec_t py_callables; + size_t py_callable_idx; + + static int idaapi s_py_list_walk_cb( + PyObject *py_item, + Py_ssize_t index, + void *ud) + { + // Not callable? Terminate iteration + if ( !PyCallable_Check(py_item) ) + return CIP_FAILED; + + // Append this callable and increment its reference + py_ui_request_t *_this = (py_ui_request_t *)ud; + _this->py_callables.push_back(py_item); + Py_INCREF(py_item); + + return CIP_OK; + } + public: + py_ui_request_t(): py_callable_idx(0) + { + } + + virtual bool idaapi run() + { + // Get callable + PyObject *py_callable = py_callables.at(py_callable_idx); + + PYW_GIL_ENSURE; + PyObject *py_result = PyObject_CallFunctionObjArgs(py_callable, NULL); + bool reschedule = py_result != NULL && PyObject_IsTrue(py_result); + Py_XDECREF(py_result); + PYW_GIL_RELEASE; + + // No rescheduling? Then advance to the next callable + if ( !reschedule ) + ++py_callable_idx; + + // Reschedule this C callback only if there are more callables + return py_callable_idx < py_callables.size(); + } + + // Walk the list and extract all callables + bool init(PyObject *py_list) + { + Py_ssize_t count = pyvar_walk_list( + py_list, + s_py_list_walk_cb, + this); + return count > 0; + } + + virtual idaapi ~py_ui_request_t() + { + // Release all callables + for ( ppyobject_vec_t::const_iterator it=py_callables.begin(); + it != py_callables.end(); + ++it ) + { + Py_XDECREF(*it); + } + } + }; + + py_ui_request_t *req = new py_ui_request_t(); + if ( !req->init(py_list) ) + { + delete req; + return false; + } + execute_ui_requests(req, NULL); + return true; +} + //------------------------------------------------------------------------ /* # @@ -457,7 +874,7 @@ class UI_Hooks(object): def get_ea_hint(self, ea): """ The UI wants to display a simple hint for an address in the navigation band - + @param ea: The address @return: String with the hint or None """ @@ -509,6 +926,7 @@ public: virtual void term() { } + virtual PyObject *get_ea_hint(ea_t /*ea*/) { Py_RETURN_NONE; @@ -566,7 +984,9 @@ uint32 choose_choose( int flags, int x0,int y0, int x1,int y1, - int width) + int width, + int deflt, + int icon) { PyObject *pytitle = PyObject_GetAttrString((PyObject *)self, "title"); const char *title = pytitle != NULL ? PyString_AsString(pytitle) : "Choose"; @@ -580,8 +1000,8 @@ uint32 choose_choose( choose_sizer, choose_getl, title, - 1, - 1, + icon, + deflt, NULL, /* del */ NULL, /* inst */ NULL, /* update */ @@ -607,6 +1027,77 @@ PyObject *choose2_get_embedded_selection(PyObject *self); #define DECLARE_FORM_ACTIONS form_actions_t *fa = (form_actions_t *)p_fa; +//--------------------------------------------------------------------------- +DECLARE_PY_CLINKED_OBJECT(textctrl_info_t); + +static bool textctrl_info_t_assign(PyObject *self, PyObject *other) +{ + textctrl_info_t *lhs = textctrl_info_t_get_clink(self); + textctrl_info_t *rhs = textctrl_info_t_get_clink(other); + if (lhs == NULL || rhs == NULL) + return false; + + *lhs = *rhs; + return true; +} + +//------------------------------------------------------------------------- +static bool textctrl_info_t_set_text(PyObject *self, const char *s) +{ + textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self); + if ( ti == NULL ) + return false; + ti->text = s; + return true; +} + +//------------------------------------------------------------------------- +static const char *textctrl_info_t_get_text(PyObject *self) +{ + textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self); + return ti == NULL ? "" : ti->text.c_str(); +} + +//------------------------------------------------------------------------- +static bool textctrl_info_t_set_flags(PyObject *self, unsigned int flags) +{ + textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self); + if ( ti == NULL ) + return false; + ti->flags = flags; + return true; +} + +//------------------------------------------------------------------------- +static unsigned int textctrl_info_t_get_flags( + PyObject *self, + unsigned int flags) +{ + textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self); + return ti == NULL ? 0 : ti->flags; +} + +//------------------------------------------------------------------------- +static bool textctrl_info_t_set_tabsize( + PyObject *self, + unsigned int tabsize) +{ + textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self); + if ( ti == NULL ) + return false; + ti->tabsize = tabsize; + return true; +} + +//------------------------------------------------------------------------- +static unsigned int textctrl_info_t_get_tabsize( + PyObject *self, + unsigned int tabsize) +{ + textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self); + return ti == NULL ? 0 : ti->tabsize; +} + //--------------------------------------------------------------------------- static bool formchgcbfa_enable_field(size_t p_fa, int fid, bool enable) { @@ -623,11 +1114,11 @@ static bool formchgcbfa_show_field(size_t p_fa, int fid, bool show) //--------------------------------------------------------------------------- static bool formchgcbfa_move_field( - size_t p_fa, - int fid, - int x, - int y, - int w, + size_t p_fa, + int fid, + int x, + int y, + int w, int h) { DECLARE_FORM_ACTIONS; @@ -655,16 +1146,49 @@ static void formchgcbfa_refresh_field(size_t p_fa, int fid) return fa->refresh_field(fid); } +//--------------------------------------------------------------------------- +static void formchgcbfa_close(size_t p_fa, int fid, int close_normally) +{ + DECLARE_FORM_ACTIONS; + fa->close(close_normally); +} + //--------------------------------------------------------------------------- static PyObject *formchgcbfa_get_field_value( - size_t p_fa, - int fid, + size_t p_fa, + int fid, int ft, size_t sz) { DECLARE_FORM_ACTIONS; switch ( ft ) { + case 8: + { + // Readonly? Then return the selected index + if ( sz == 1 ) + { + int sel_idx; + if ( fa->get_field_value(fid, &sel_idx) ) + return PyLong_FromLong(sel_idx); + } + // Not readonly? Then return the qstring + else + { + qstring val; + if ( fa->get_field_value(fid, &val) ) + return PyString_FromString(val.c_str()); + } + break; + } + // multilinetext - tuple representing textctrl_info_t + case 7: + { + textctrl_info_t ti; + if ( fa->get_field_value(fid, &ti) ) + return Py_BuildValue("(sII)", ti.text.c_str(), ti.flags, ti.tabsize); + break; + } // button - uint32 case 4: { @@ -765,8 +1289,8 @@ static PyObject *formchgcbfa_get_field_value( //--------------------------------------------------------------------------- static bool formchgcbfa_set_field_value( - size_t p_fa, - int fid, + size_t p_fa, + int fid, int ft, PyObject *py_val) { @@ -774,6 +1298,29 @@ static bool formchgcbfa_set_field_value( switch ( ft ) { + // dropdown list + case 8: + { + // Editable dropdown list + if ( PyString_Check(py_val) ) + { + qstring val(PyString_AsString(py_val)); + return fa->set_field_value(fid, &val); + } + // Readonly dropdown list + else + { + int sel_idx = PyLong_AsLong(py_val); + return fa->set_field_value(fid, &sel_idx); + } + break; + } + // multilinetext - textctrl_info_t + case 7: + { + textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(py_val); + return ti == NULL ? false : fa->set_field_value(fid, ti); + } // button - uint32 case 4: { @@ -797,7 +1344,7 @@ static bool formchgcbfa_set_field_value( // Passed as 0-based if ( !PyW_PyListToIntVec(py_val, intvec) ) break; - + // Make 1-based for ( intvec_t::iterator it=intvec.begin(); it != intvec.end(); ++it) (*it)++; @@ -869,8 +1416,8 @@ int idaapi UI_Callback(void *ud, int notification_code, va_list va) Py_ssize_t _len; PyObject *py_str = proxy->get_ea_hint(ea); - if ( py_str != NULL - && PyString_Check(py_str) + if ( py_str != NULL + && PyString_Check(py_str) && PyString_AsStringAndSize(py_str, &_buf, &_len) != - 1 ) { qstrncpy(buf, _buf, qmin(_len, sz)); @@ -1017,7 +1564,11 @@ private: if ( notification_code != ui_get_chooser_item_attrs ) return 0; - va_arg(va, void *); + // Pass events that belong to our chooser only + void *chooser_obj = va_arg(va, void *); + if ( obj != chooser_obj ) + return 0; + 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); @@ -1606,6 +2157,7 @@ public: 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; + embedded->get_attrs = NULL; // Fill callbacks that are only present in idaq if ( is_idaq() ) { @@ -1798,6 +2350,7 @@ PyObject *choose2_get_embedded_selection(PyObject *self) } //------------------------------------------------------------------------ +// Return the C instances as 64bit numbers PyObject *choose2_get_embedded(PyObject *self) { py_choose2_t *c2 = choose2_find_instance(self); @@ -2450,14 +3003,14 @@ 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, + int vk_key, + int shift, void *ud) { customviewer_t *_this = (customviewer_t *)ud; @@ -2557,6 +3110,7 @@ public: // OnHint virtual bool on_hint(place_t * /*place*/, int * /*important_lines*/, qstring &/*hint*/) { return false; } + // OnPopupMenuClick virtual bool on_popup_menu(size_t menu_id) { return false; } @@ -2582,7 +3136,7 @@ public: void close() { if ( _form != NULL ) - close_tform(_form, FORM_SAVE); + close_tform(_form, FORM_SAVE | FORM_CLOSE_LATER); } //-------------------------------------------------------------------------- @@ -2884,7 +3438,7 @@ private: PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod(py_self, (char *)S_ON_CLOSE, NULL); PYW_GIL_RELEASE; - + PyW_ShowCbErr(S_ON_CLOSE); Py_XDECREF(py_result); @@ -2900,10 +3454,10 @@ private: { PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod( - py_self, - (char *)S_ON_KEYDOWN, - "ii", - vk_key, + py_self, + (char *)S_ON_KEYDOWN, + "ii", + vk_key, shift); PYW_GIL_RELEASE; @@ -2919,11 +3473,11 @@ private: { PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod( - py_self, - (char *)S_ON_POPUP, + 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); @@ -2937,12 +3491,12 @@ private: size_t ln = data.to_lineno(place); PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod( - py_self, - (char *)S_ON_HINT, - PY_FMT64, + 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 ) @@ -2953,7 +3507,7 @@ private: if ( important_lines != NULL ) *important_lines = PyInt_AsLong(py_nlines); - + hint = PyString_AsString(py_hint); } Py_XDECREF(py_result); @@ -2966,9 +3520,9 @@ private: { PYW_GIL_ENSURE; PyObject *py_result = PyObject_CallMethod( - py_self, - (char *)S_ON_POPUP_MENU, - PY_FMT64, + py_self, + (char *)S_ON_POPUP_MENU, + PY_FMT64, pyul_t(menu_id)); PYW_GIL_RELEASE; @@ -3121,7 +3675,7 @@ public: // Return a reference to the C++ instance (only once) if ( py_this == NULL ) py_this = PyCObject_FromVoidPtr(this, NULL); - + return true; } @@ -3394,7 +3948,9 @@ uint32 choose_choose(PyObject *self, int flags, int x0,int y0, int x1,int y1, - int width); + int width, + int deflt, + int icon); @@ -3706,7 +4262,7 @@ class Choose2(object): #ICON WARNING|QUESTION|INFO|NONE #AUTOHIDE NONE|DATABASE|REGISTRY|SESSION #HIDECANCEL -#BUTTON YES|NO|CANCEL "Value" +#BUTTON YES|NO|CANCEL "Value|NONE" #STARTITEM {id:ItemName} #HELP / ENDHELP try: @@ -3717,19 +4273,92 @@ try: # Callback for buttons # typedef void (idaapi *formcb_t)(TView *fields[], int code); - _FORMCB_T = WINFUNCTYPE(c_void_p, c_void_p, c_int) + _FORMCB_T = WINFUNCTYPE(None, 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) + _FORMCB_T = CFUNCTYPE(None, c_void_p, c_int) _FORMCHGCB_T = CFUNCTYPE(c_int, c_int, c_void_p) except: _FORMCHGCB_T = _FORMCB_T = None +# ----------------------------------------------------------------------- +# textctrl_info_t clinked object +class textctrl_info_t(py_clinked_object_t): + """Class representing textctrl_info_t""" + + # Some constants + TXTF_AUTOINDENT = 0x0001 + """Auto-indent on new line""" + TXTF_ACCEPTTABS = 0x0002 + """Tab key inserts 'tabsize' spaces""" + TXTF_READONLY = 0x0004 + """Text cannot be edited (but can be selected and copied)""" + TXTF_SELECTED = 0x0008 + """Shows the field with its text selected""" + TXTF_MODIFIED = 0x0010 + """Gets/sets the modified status""" + TXTF_FIXEDFONT = 0x0020 + """The control uses IDA's fixed font""" + + def __init__(self, text="", flags=0, tabsize=0): + py_clinked_object_t.__init__(self) + if text: + self.text = text + if flags: + self.flags = flags + if tabsize: + self.tabsize = tabsize + + def _create_clink(self): + return _idaapi.textctrl_info_t_create() + + def _del_clink(self, lnk): + return _idaapi.textctrl_info_t_destroy(lnk) + + def _get_clink_ptr(self): + return _idaapi.textctrl_info_t_get_clink_ptr(self) + + def assign(self, other): + """Copies the contents of 'other' to 'self'""" + return _idaapi.textctrl_info_t_assign(self, other) + + def __set_text(self, s): + """Sets the text value""" + return _idaapi.textctrl_info_t_set_text(self, s) + + def __get_text(self): + """Sets the text value""" + return _idaapi.textctrl_info_t_get_text(self) + + def __set_flags__(self, flags): + """Sets the flags value""" + return _idaapi.textctrl_info_t_set_flags(self, flags) + + def __get_flags__(self): + """Returns the flags value""" + return _idaapi.textctrl_info_t_get_flags(self) + + def __set_tabsize__(self, tabsize): + """Sets the tabsize value""" + return _idaapi.textctrl_info_t_set_tabsize(self, tabsize) + + def __get_tabsize__(self): + """Returns the tabsize value""" + return _idaapi.textctrl_info_t_get_tabsize(self) + + value = property(__get_text, __set_text) + """Alias for the text property""" + text = property(__get_text, __set_text) + """Text value""" + flags = property(__get_flags__, __set_flags__) + """Flags value""" + tabsize = property(__get_tabsize__, __set_tabsize__) + # ----------------------------------------------------------------------- class Form(object): @@ -3775,6 +4404,10 @@ class Form(object): """Form change callback - formchgcb_t""" FT_ECHOOSER = 'E' """Embedded chooser - idaapi.Choose2""" + FT_MULTI_LINE_TEXT = 't' + """Multi text control - textctrl_info_t""" + FT_DROPDOWN_LIST = 'b' + """Dropdown list control - Form.DropdownControl""" FT_CHKGRP = 'C' FT_CHKGRP2= 'c' @@ -3856,8 +4489,7 @@ class Form(object): """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() - """ + """Reference to the parent form. It is filled by Form.Add()""" def get_tag(self): @@ -4239,7 +4871,7 @@ class Form(object): self.arg = _FORMCHGCB_T(self.helper_cb) def helper_cb(self, fid, p_fa): - # Remember the pointer to the forms_action + # Remember the pointer to the forms_action in the parent form self.form.p_fa = p_fa # Call user's handler @@ -4335,8 +4967,117 @@ class Form(object): Form.Control.free(self) + class DropdownListControl(InputControl, qstrvec_t): + """ + Dropdown control + This control allows manipulating a dropdown control + """ + def __init__(self, items=[], readonly=True, selval=0, width=50, swidth=50, hlp = None): + """ + @param items: A string list of items used to prepopulate the control + @param readonly: Specifies whether the dropdown list is editable or not + @param selval: The preselected item index (when readonly) or text value (when editable) + @param width: the control width (n/a if the dropdown list is readonly) + @param swidth: string width + """ + + # Ignore the width if readonly was set + if readonly: + width = 0 + + # Init the input control base class + Form.InputControl.__init__( + self, + Form.FT_DROPDOWN_LIST, + width, + swidth, + hlp) + + # Init the associated qstrvec + qstrvec_t.__init__(self, items) + + # Remember if readonly or not + self.readonly = readonly + + if readonly: + # Create a C integer and remember it + self.__selval = c_int(selval) + val_addr = addressof(self.__selval) + else: + # Create an strvec with one qstring + self.__selval = qstrvec_t([selval]) + # Get address of the first element + val_addr = self.__selval.addressof(0) + + # Two arguments: + # - argument #1: a pointer to the qstrvec containing the items + # - argument #2: an integer to hold the selection + # or + # #2: a qstring to hold the dropdown text control value + self.arg = ( + pointer(c_void_p.from_address(self.clink_ptr)), + pointer(c_void_p.from_address(val_addr)) + ) + + + def __set_selval(self, val): + if self.readonly: + self.__selval.value = val + else: + self.__selval[0] = val + + def __get_selval(self): + # Return the selection index + # or the entered text value + return self.__selval.value if self.readonly else self.__selval[0] + + value = property(__get_selval, __set_selval) + selval = property(__get_selval, __set_selval) + """ + Read/write the selection value. + The value is used as an item index in readonly mode or text value in editable mode + This value can be used only after the form has been closed. + """ + + def free(self): + self._free() + + + def set_items(self, items): + """Sets the dropdown list items""" + self.from_list(items) + + + class MultiLineTextControl(InputControl, textctrl_info_t): + """ + Multi line text control. + This class inherits from textctrl_info_t. Thus the attributes are also inherited + This control allows manipulating a multilinetext control + """ + def __init__(self, text="", flags=0, tabsize=0, width=50, swidth=50, hlp = None): + """ + @param text: Initial text value + @param flags: One of textctrl_info_t.TXTF_.... values + @param tabsize: Tab size + @param width: Display width + @param swidth: String width + """ + # Init the input control base class + Form.InputControl.__init__(self, Form.FT_MULTI_LINE_TEXT, width, swidth, hlp) + + # Init the associated textctrl_info base class + textctrl_info_t.__init__(self, text=text, flags=flags, tabsize=tabsize) + + # Get the argument as a pointer from the embedded ti + self.arg = pointer(c_void_p.from_address(self.clink_ptr)) + + + def free(self): + self._free() + + # - # Class methods + # Form class # def __init__(self, form, controls): """ @@ -4364,6 +5105,8 @@ class Form(object): Frees all resources associated with a compiled form. Make sure you call this function when you finish using the form. """ + + # Free all the controls for ctrl in self.__controls.values(): ctrl.free() @@ -4667,22 +5410,40 @@ class Form(object): return _idaapi.formchgcbfa_refresh_field(self.p_fa, ctrl.id) + def Close(self, close_normally): + """ + Close the form + @param close_normally: + 1: form is closed normally as if the user pressed Enter + 0: form is closed abnormally as if the user pressed Esc + @return: None + """ + return _idaapi.formchgcbfa_close(self.p_fa, close_normally) + + 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) + - color button, radio controls: integer + - file/dir input, string input and string label: string + - embedded chooser control (0-based indices of selected items): integer list + - for multilinetext control: textctrl_info_t + - dropdown list controls: string (when editable) or index (when readonly) - None: on failure """ tid, sz = self.ControlToFieldTypeIdAndSize(ctrl) - return _idaapi.formchgcbfa_get_field_value( + r = _idaapi.formchgcbfa_get_field_value( self.p_fa, ctrl.id, tid, sz) + # Multilinetext? Unpack the tuple into a new textctrl_info_t instance + if r is not None and tid == 7: + return textctrl_info_t(text=r[0], flags=r[1], tabsize=r[2]) + else: + return r def SetControlValue(self, ctrl, value): @@ -4690,7 +5451,10 @@ class Form(object): 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 + - embedded chooser: a 0-base indices list to select embedded chooser items + - multilinetext: a textctrl_info_t + - dropdown list: an integer designating the selection index if readonly + a string designating the edit control value if not readonly @return: Boolean true on success """ tid, _ = self.ControlToFieldTypeIdAndSize(ctrl) @@ -4710,7 +5474,11 @@ class Form(object): # Input control depend on the associated buffer size (supplied by the user) # Make sure you check instances types taking into account inheritance - if isinstance(ctrl, Form.EmbeddedChooserControl): + if isinstance(ctrl, Form.DropdownListControl): + return (8, 1 if ctrl.readonly else 0) + elif isinstance(ctrl, Form.MultiLineTextControl): + return (7, 0) + elif isinstance(ctrl, Form.EmbeddedChooserControl): return (5, 0) # Group items or controls elif isinstance(ctrl, (Form.GroupItemControl, Form.GroupControl)): @@ -4846,21 +5614,23 @@ class PluginForm(object): return _idaapi.plgform_close(self.__clink__) FORM_SAVE = 0x1 - """save state in desktop config""" + """Save state in desktop config""" FORM_NO_CONTEXT = 0x2 - """don't change the current context (useful for toolbars)""" + """Don't change the current context (useful for toolbars)""" FORM_DONT_SAVE_SIZE = 0x4 - """don't save size of the window""" + """Don't save size of the window""" + FORM_CLOSE_LATER = 0x8 + """This flag should be used when Close() is called from an event handler""" # class Choose: """ Choose - class for choose() with callbacks """ - def __init__(self, list, title, flags=0): + def __init__(self, list, title, flags=0, deflt=1, icon=37): self.list = list self.title = title @@ -4871,6 +5641,8 @@ class Choose: self.y1 = -1 self.width = -1 + self.deflt = deflt + self.icon = icon # HACK: Add a circular reference for non-modal choosers. This prevents the GC # from collecting the class object the callbacks need. Unfortunately this means @@ -4925,7 +5697,16 @@ class Choose: 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) + n = _idaapi.choose_choose( + self, + self.flags, + self.x0, + self.y0, + self.x1, + self.y1, + self.width, + self.deflt, + self.icon) set_script_timeout(old) return n %} @@ -5272,3 +6053,5 @@ class simplecustviewer_t(object): # # %} + + diff --git a/swig/loader.i b/swig/loader.i index d1476f5..49c2f66 100644 --- a/swig/loader.i +++ b/swig/loader.i @@ -115,6 +115,12 @@ %ignore mem2base; %rename (mem2base) py_mem2base; +%ignore update_snapshot_attributes; +%ignore build_snapshot_tree; +%ignore visit_snapshot_tree; +%ignore save_database_ex; +%ignore snapshot_t; +%ignore snapshots_t; %ignore load_plugin; %rename (load_plugin) py_load_plugin; %ignore run_plugin; @@ -170,8 +176,8 @@ 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); + else + return PyCObject_FromVoidPtr(r, NULL); } //------------------------------------------------------------------------ @@ -190,8 +196,8 @@ static bool py_run_plugin(PyObject *plg, int arg) { if ( !PyCObject_Check(plg) ) return false; - - return run_plugin((plugin_t *)PyCObject_AsVoidPtr(plg), arg); + else + return run_plugin((plugin_t *)PyCObject_AsVoidPtr(plg), arg); } // diff --git a/swig/nalt.i b/swig/nalt.i index bb78ec8..56848f2 100644 --- a/swig/nalt.i +++ b/swig/nalt.i @@ -566,10 +566,10 @@ class switch_info_ex_t(py_clinked_object_t): return (self.flags & SWI_EXTENDED) != 0 and (self.flags2 & SWI2_SUBTRACT) != 0 def get_jtable_size(self): - return self.jcases if self.is_indirect() else ncases + return self.jcases if self.is_indirect() else self.ncases def get_lowcase(self): - return self.ind_lowcase if is_indirect() else self.lowcase + return self.ind_lowcase if self.is_indirect() else self.lowcase def set_expr(self, r, dt): self.regnum = r diff --git a/swig/netnode.i b/swig/netnode.i index d5bb599..7908dac 100644 --- a/swig/netnode.i +++ b/swig/netnode.i @@ -90,7 +90,7 @@ %include "netnode.hpp" -%extend netnode +%extend netnode { nodeidx_t index() { @@ -111,5 +111,26 @@ return py_str; } + + PyObject *hashstr_buf(const char *idx, char tag=htag) + { + char buf[MAXSPECSIZE]; + ssize_t sz = self->hashstr(idx, buf, sizeof(buf), tag); + if ( sz < 0 ) + Py_RETURN_NONE; + else + return PyString_FromStringAndSize(buf, sz); + } + + bool hashset_buf(const char *idx, PyObject *py_str, char tag=htag) + { + char *buf; + Py_ssize_t sz; + + if ( PyString_AsStringAndSize(py_str, &buf, &sz) == -1 ) + return false; + else + return self->hashset(idx, buf, sz, tag); + } } diff --git a/swig/pro.i b/swig/pro.i index ba77de3..feaebc7 100644 --- a/swig/pro.i +++ b/swig/pro.i @@ -6,7 +6,7 @@ %ignore print_all_counters; %ignore incrementer_t; %ignore reloc_info_t; // swig under mac chokes on this - +%ignore qstrvec_t; %ignore qmutex_create; %ignore qiterator; %ignore qrefcnt_t; @@ -86,3 +86,4 @@ %rename (parse_command_line) py_parse_command_line; %include "pro.h" +SWIG_DECLARE_PY_CLINKED_OBJECT(qstrvec_t) diff --git a/swig/ua.i b/swig/ua.i index 63f18d4..76ec6c2 100644 --- a/swig/ua.i +++ b/swig/ua.i @@ -331,6 +331,31 @@ bool py_out_name_expr( return op == NULL ? false : out_name_expr(*op, ea, off); } +//------------------------------------------------------------------------- +static PyObject *insn_t_get_op_link(PyObject *py_insn_lnk, int i) +{ + if ( i < 0 || i >= UA_MAXOP || !PyCObject_Check(py_insn_lnk) ) + Py_RETURN_NONE; + + // Extract C link + insn_t *insn = (insn_t *)PyCObject_AsVoidPtr(py_insn_lnk); + + // Return a link to the operand + return PyCObject_FromVoidPtr(&insn->Operands[i], NULL); +} + +//------------------------------------------------------------------------- +static PyObject *insn_t_create() +{ + return PyCObject_FromVoidPtr(new insn_t(), NULL); +} + +//------------------------------------------------------------------------- +static PyObject *op_t_create() +{ + return PyCObject_FromVoidPtr(new op_t(), NULL); +} + //------------------------------------------------------------------------- static bool op_t_assign(PyObject *self, PyObject *other) { @@ -355,40 +380,13 @@ static bool insn_t_assign(PyObject *self, PyObject *other) return true; } -//------------------------------------------------------------------------- -static PyObject *insn_t_get_op_link(PyObject *py_insn_lnk, int i) -{ - if ( i < 0 || i >= UA_MAXOP || !PyCObject_Check(py_insn_lnk) ) - Py_RETURN_NONE; - - // Extract C link - insn_t *insn = (insn_t *)PyCObject_AsVoidPtr(py_insn_lnk); - - // Return a link to the operand - return PyCObject_FromVoidPtr(&insn->Operands[i], NULL); -} - -//------------------------------------------------------------------------- -static PyObject *insn_t_create() -{ - insn_t *insn = new insn_t(); - return PyCObject_FromVoidPtr(insn, NULL); -} - -//------------------------------------------------------------------------- -static PyObject *op_t_create() -{ - op_t *op = new op_t(); - return PyCObject_FromVoidPtr(op, NULL); -} - //------------------------------------------------------------------------- static bool op_t_destroy(PyObject *py_obj) { if ( !PyCObject_Check(py_obj) ) return false; - op_t *op = (op_t *) PyCObject_AsVoidPtr(py_obj); + op_t *op = (op_t *)PyCObject_AsVoidPtr(py_obj); delete op; return true; @@ -400,9 +398,7 @@ static bool insn_t_destroy(PyObject *py_obj) if ( !PyCObject_Check(py_obj) ) return false; - insn_t *insn = (insn_t *) PyCObject_AsVoidPtr(py_obj); - delete insn; - + delete (insn_t *)PyCObject_AsVoidPtr(py_obj); return true; } @@ -856,12 +852,6 @@ class op_t(py_clinked_object_t): """Copies the contents of 'other' to 'self'""" return _idaapi.op_t_assign(self, other) -# -# def copy(self): -# """Returns a new copy of this class""" -# pass -# - def __eq__(self, other): """Checks if two register operands are equal by checking the register number and its dtype""" return (self.reg == other.reg) and (self.dtyp == other.dtyp)