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.
When using IDAPython scripts, users were sometimes facing the following issue
Specifically:
import
s user’s module mymodule
mymodule
(Note: the module is modified, not the script)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.
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:
import
s user’s module mymodule
mymodule
is deleted]mymodule
mymodule
are taken into consideration, since module was deleted.Unfortunately we have to stop doing this because:
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()
import
s mydbghelpers
MyHooks
, and hooks it into IDA’s debugger APIsmydbghelpers
is deleted]from mydbghelpers import *
was used)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
)
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.