TL;DR
If you were using import
to import your own “currently-in-development” modules from your IDAPython scripts, you may want to use idaapi.require()
, starting with IDA 6.5.
Rationale
When using IDAPython scripts, users were sometimes facing the following issue
Specifically:
- User loads script
- Script
import
s user’s modulemymodule
- Script ends
- User modifies code of
mymodule
(Note: the module is modified, not the script) - User reloads script
- Modifications to
mymodule
aren’t taken into consideration.
While that’s perfectly understandable (the python runtime doesn’t have to reload mymodule
if it has been compiled & loaded already), this is somewhat of an annoyance for users that were importing modules that were often modified.
IDA <= 6.4: Ensuring a user-specified module gets reloaded, by destroying it.
Up until IDA 6.4, the IDAPython plugin would do some magic after you have run your user script.
(click “expand all” to reveal the diff)
The sequence becomes:
- User loads script
- Script
import
s user’s modulemymodule
- Script ends
- [module
mymodule
is deleted] - User modifies code of
mymodule
- User reloads script
- Modifications to
mymodule
are taken into consideration, since module was deleted.
Unfortunately we have to stop doing this because:
- That prevents us from using python-based hooks to be used after the script is finished (see below).
- That goes against the rest of the python philosophy (i.e., modifications to objects are not reverted), and is therefore unexpected.
Issues with hooks.
Imagine you have the following script, dbghooks.py
:
from idaapi import * import mydbghelpers class MyHooks(DBG_Hooks): def __init__(self): ... def dbg_bpt(self, tid, ea): mydbghelpers.do_something() return 0 def dbg_step_into(self): ... hooks = MyHooks() hooks.hook()
- User loads script
- Scripts
import
smydbghelpers
- Script creates instance of
MyHooks
, and hooks it into IDA’s debugger APIs - Script ends
- [module
mydbghelpers
is deleted] - User runs debugger, and a breakpoint is hit. Two things can happen:
- The hook fails executing
- IDA crashes (that can happen if the form
from mydbghelpers import *
was used)
IDA > 6.4: Introducing idaapi.require()
Everywhere else in python, when you modify a runtime object, those changes will remain visible.
We decided it would be better to not go against that standard behaviour anymore, and provide a helper to achieve the same results as what was achieved before with the deletion of user modules.
You can now import & re-import of a module with: idaapi.require(name)
Here is its definition:
def require(modulename): if modulename in sys.modules.keys(): reload(sys.modules[modulename]) else: import importlib import inspect m = importlib.import_module(modulename) frame_obj, filename, line_number, function_name, lines, index = inspect.stack()[1] importer_module = inspect.getmodule(frame_obj) if importer_module is None: # No importer module; called from command line importer_module = sys.modules['__main__'] setattr(importer_module, modulename, m) sys.modules[modulename] = m
EDIT (September 16th, 2016): After one of our users has reported an issue with this, we have updated the definition to:
def require(modulename, package=None): import inspect frame_obj, filename, line_number, function_name, lines, index = inspect.stack()[1] importer_module = inspect.getmodule(frame_obj) if importer_module is None: # No importer module; called from command line importer_module = sys.modules['__main__'] if modulename in sys.modules.keys(): reload(sys.modules[modulename]) m = sys.modules[modulename] else: import importlib m = importlib.import_module(modulename, package) sys.modules[modulename] = m setattr(importer_module, modulename, m)
The problem with the previous definition, is that if module A
loads module C
, and then module A
loads module B
which, itself, also loads module C
, then module A
will have the module C
properly bound to its globals()
, but module B
won’t (since the module was already present in sys.modules
, we were only reloading it, not setting the attribute in module B
)
Example
The example debugger hooks script above becomes:
from idaapi import * idaapi.require("mydbghelpers") class MyHooks(DBG_Hooks): def __init__(self): ... def dbg_bpt(self, tid, ea): mydbghelpers.do_something() return 0 def dbg_step_into(self): ... hooks = MyHooks() hooks.hook()
I.e., only the second line changes.