mirror of
https://github.com/idapython/src
synced 2026-06-08 14:47:00 +00:00
1335 lines
35 KiB
C++
1335 lines
35 KiB
C++
#ifndef __PY_KERNWIN__
|
|
#define __PY_KERNWIN__
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
//<decls(py_kernwin)>
|
|
//------------------------------------------------------------------------
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Context structure used by add|del_idc_hotkey()
|
|
struct py_idchotkey_ctx_t
|
|
{
|
|
qstring hotkey;
|
|
ref_t pyfunc;
|
|
|
|
py_idchotkey_ctx_t(
|
|
const char *_hotkey,
|
|
PyObject *_pyfunc)
|
|
: hotkey(_hotkey),
|
|
pyfunc(borref_t(_pyfunc)) {}
|
|
};
|
|
|
|
static ref_t py_colorizer;
|
|
|
|
//------------------------------------------------------------------------
|
|
//</decls(py_kernwin)>
|
|
//------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------
|
|
//<inline(py_kernwin)>
|
|
//------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
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
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_register_timer(int interval, PyObject *py_callback)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
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)
|
|
{
|
|
PYW_GIL_GET;
|
|
py_timer_ctx_t *ctx = (py_timer_ctx_t *)ud;
|
|
newref_t py_result(PyObject_CallFunctionObjArgs(ctx->pyfunc.o, NULL));
|
|
int ret = -1;
|
|
if ( PyErr_Occurred() )
|
|
{
|
|
msg("Exception in timer callback. This timer will be unregistered.\n");
|
|
PyErr_Print();
|
|
}
|
|
else if ( py_result != NULL )
|
|
{
|
|
ret = PyLong_AsLong(py_result.o);
|
|
}
|
|
|
|
// Timer has been unregistered?
|
|
if ( ret == -1 )
|
|
python_timer_del(ctx);
|
|
return ret;
|
|
};
|
|
};
|
|
|
|
py_timer_ctx_t *ctx = python_timer_new(py_callback);
|
|
ctx->timer_id = register_timer(
|
|
interval,
|
|
tmr_t::callback,
|
|
ctx);
|
|
|
|
if ( ctx->timer_id != NULL )
|
|
{
|
|
return PyCapsule_New(ctx, VALID_CAPSULE_NAME, NULL);
|
|
}
|
|
else
|
|
{
|
|
python_timer_del(ctx);
|
|
Py_RETURN_NONE;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
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
|
|
#</pydoc>
|
|
*/
|
|
static bool py_unregister_timer(PyObject *py_timerctx)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
if ( py_timerctx == NULL || !PyCapsule_IsValid(py_timerctx, VALID_CAPSULE_NAME) )
|
|
return false;
|
|
|
|
py_timer_ctx_t *ctx = (py_timer_ctx_t *) PyCapsule_GetPointer(py_timerctx, VALID_CAPSULE_NAME);
|
|
if ( ctx == NULL || !unregister_timer(ctx->timer_id) )
|
|
return false;
|
|
|
|
python_timer_del(ctx);
|
|
// invalidate capsule; make sure we don't try and delete twice
|
|
PyCapsule_SetName(py_timerctx, INVALID_CAPSULE_NAME);
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def choose_idasgn():
|
|
"""
|
|
Opens the signature chooser
|
|
|
|
@return: None or the selected signature name
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_choose_idasgn()
|
|
{
|
|
char *name = choose_idasgn();
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( name == NULL )
|
|
{
|
|
Py_RETURN_NONE;
|
|
}
|
|
else
|
|
{
|
|
PyObject *py_str = IDAPyStr_FromUTF8(name);
|
|
qfree(name);
|
|
return py_str;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def get_highlight():
|
|
"""
|
|
Returns the currently highlighted identifier and flags
|
|
|
|
@return: a tuple (text, flags), or None if nothing
|
|
is highlighted or in case of error.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_get_highlight(TWidget *v)
|
|
{
|
|
qstring buf;
|
|
uint32 flags;
|
|
bool ok = get_highlight(&buf, v, &flags);
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( !ok )
|
|
Py_RETURN_NONE;
|
|
return Py_BuildValue("(sk)", buf.c_str(), flags);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
static int py_load_custom_icon_fn(const char *filename)
|
|
{
|
|
return load_custom_icon(filename);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
static int py_load_custom_icon_data(PyObject *data, const char *format)
|
|
{
|
|
Py_ssize_t len;
|
|
char *s;
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( IDAPyBytes_AsMemAndSize(data, &s, &len) == -1 )
|
|
return 0;
|
|
else
|
|
return load_custom_icon(s, len, format);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def free_custom_icon(icon_id):
|
|
"""
|
|
Frees an icon loaded with load_custom_icon()
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
//-------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def read_selection(view, p0, p1):
|
|
"""
|
|
Read the user selection, and store its information in p0 (from) and p1 (to).
|
|
|
|
This can be used as follows:
|
|
|
|
|
|
>>> p0 = idaapi.twinpos_t()
|
|
p1 = idaapi.twinpos_t()
|
|
view = idaapi.get_current_viewer()
|
|
idaapi.read_selection(view, p0, p1)
|
|
|
|
|
|
At that point, p0 and p1 hold information for the selection.
|
|
But, the 'at' property of p0 and p1 is not properly typed.
|
|
To specialize it, call #place() on it, passing it the view
|
|
they were retrieved from. Like so:
|
|
|
|
|
|
>>> place0 = p0.place(view)
|
|
place1 = p1.place(view)
|
|
|
|
|
|
This will effectively "cast" the place into a specialized type,
|
|
holding proper information, depending on the view type (e.g.,
|
|
disassembly, structures, enums, ...)
|
|
|
|
@param view: The view to retrieve the selection for.
|
|
@param p0: Storage for the "from" part of the selection.
|
|
@param p1: Storage for the "to" part of the selection.
|
|
@return: a bool value indicating success.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def msg(text):
|
|
"""
|
|
Prints text into IDA's Output window
|
|
|
|
@param text: text to print
|
|
Can be Unicode, or string in local encoding
|
|
@return: number of bytes printed
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_msg(PyObject *o)
|
|
{
|
|
const char *utf8 = NULL;
|
|
ref_t py_utf8;
|
|
if ( PyUnicode_Check(o) )
|
|
{
|
|
py_utf8 = newref_t(PyUnicode_AsUTF8String(o));
|
|
utf8 = PyString_AsString(py_utf8.o);
|
|
}
|
|
else if ( PyString_Check(o) )
|
|
{
|
|
utf8 = PyString_AsString(o);
|
|
}
|
|
else
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "A string expected");
|
|
return NULL;
|
|
}
|
|
int rc;
|
|
Py_BEGIN_ALLOW_THREADS;
|
|
rc = msg("%s", utf8);
|
|
Py_END_ALLOW_THREADS;
|
|
return PyInt_FromLong(rc);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def ask_text(defval, prompt):
|
|
"""
|
|
Asks for a long text
|
|
|
|
@param max_size: Maximum text length, 0 for unlimited
|
|
@param defval: The default value
|
|
@param prompt: The prompt value
|
|
@return: None or the entered string
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
PyObject *py_ask_text(size_t max_size, const char *defval, const char *prompt)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
qstring qbuf;
|
|
|
|
PyObject *py_ret;
|
|
if ( ask_text(&qbuf, max_size, defval, "%s", prompt) )
|
|
{
|
|
py_ret = IDAPyStr_FromUTF8AndSize(qbuf.begin(), qbuf.length());
|
|
}
|
|
else
|
|
{
|
|
py_ret = Py_None;
|
|
Py_INCREF(py_ret);
|
|
}
|
|
return py_ret;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def ask_str(defval, hist, prompt):
|
|
"""
|
|
Asks for a long text
|
|
|
|
@param hist: history id
|
|
@param defval: The default value
|
|
@param prompt: The prompt value
|
|
@return: None or the entered string
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
PyObject *py_ask_str(qstring *defval, int hist, const char *prompt)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
PyObject *py_ret;
|
|
if ( ask_str(defval, hist, "%s", prompt) )
|
|
{
|
|
py_ret = IDAPyStr_FromUTF8AndSize(defval->begin(), defval->length());
|
|
}
|
|
else
|
|
{
|
|
py_ret = Py_None;
|
|
Py_INCREF(py_ret);
|
|
}
|
|
return py_ret;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def str2ea(addr):
|
|
"""
|
|
Converts a string express to EA. The expression evaluator may be called as well.
|
|
|
|
@return: BADADDR or address value
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
ea_t py_str2ea(const char *str, ea_t screenEA = BADADDR)
|
|
{
|
|
ea_t ea;
|
|
bool ok = str2ea(&ea, str, screenEA);
|
|
return ok ? ea : BADADDR;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def process_ui_action(name):
|
|
"""
|
|
Invokes an IDA UI action by name
|
|
|
|
@param name: action name
|
|
@return: Boolean
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static bool py_process_ui_action(const char *name, int flags = 0)
|
|
{
|
|
return process_ui_action(name, flags, NULL);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def del_hotkey(ctx):
|
|
"""
|
|
Deletes a previously registered function hotkey
|
|
|
|
@param ctx: Hotkey context previously returned by add_hotkey()
|
|
|
|
@return: Boolean.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
bool py_del_hotkey(PyObject *pyctx)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( !PyCapsule_IsValid(pyctx, VALID_CAPSULE_NAME) )
|
|
return false;
|
|
|
|
py_idchotkey_ctx_t *ctx = (py_idchotkey_ctx_t *) PyCapsule_GetPointer(pyctx, VALID_CAPSULE_NAME);
|
|
if ( ctx == NULL || !del_idc_hotkey(ctx->hotkey.c_str()) )
|
|
return false;
|
|
|
|
delete ctx;
|
|
|
|
// invalidate capsule; make sure we don't try and delete twice
|
|
PyCapsule_SetName(pyctx, INVALID_CAPSULE_NAME);
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
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
|
|
#</pydoc>
|
|
*/
|
|
PyObject *py_add_hotkey(const char *hotkey, PyObject *pyfunc)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
// 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
|
|
qstring errbuf;
|
|
if ( !compile_idc_text(idc_func.c_str(), &errbuf) )
|
|
break;
|
|
|
|
// Create new context
|
|
py_idchotkey_ctx_t *ctx = new py_idchotkey_ctx_t(hotkey, pyfunc);
|
|
|
|
// Bind IDC variable w/ the PyCallable
|
|
gvar->set_pvoid(pyfunc);
|
|
|
|
// Return the context
|
|
return PyCapsule_New(ctx, VALID_CAPSULE_NAME, NULL);
|
|
} while (false);
|
|
}
|
|
// Cleanup
|
|
del_idc_hotkey(hotkey);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
static PyObject *py_take_database_snapshot(snapshot_t *ss)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
qstring err_msg;
|
|
|
|
bool b = take_database_snapshot(ss, &err_msg);
|
|
|
|
// Return (b, err_msg)
|
|
return Py_BuildValue("(Ns)", PyBool_FromLong(b), err_msg.empty() ? NULL : err_msg.c_str());
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
static void idaapi py_ss_restore_callback(const char *err_msg, void *userdata)
|
|
{
|
|
PYW_GIL_GET;
|
|
|
|
// userdata is a tuple of ( func, args )
|
|
// func and args are borrowed references from userdata
|
|
|
|
PyObject *o = (PyObject *) userdata;
|
|
if ( !PyTuple_Check(o) )
|
|
return;
|
|
|
|
PyObject *func = PyTuple_GetItem(o, 0);
|
|
PyObject *args = PyTuple_GetItem(o, 1);
|
|
|
|
// Create arguments tuple for python function
|
|
PyObject *cb_args = Py_BuildValue("(sO)", err_msg, args);
|
|
|
|
// Call the python function
|
|
newref_t result(PyEval_CallObject(func, cb_args));
|
|
|
|
// Free cb_args and userdata
|
|
Py_DECREF(cb_args);
|
|
Py_DECREF(o);
|
|
|
|
// We cannot raise an exception in the callback, just print it.
|
|
if ( result == NULL )
|
|
PyErr_Print();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static PyObject *py_restore_database_snapshot(
|
|
const snapshot_t *ss,
|
|
PyObject *pyfunc_or_none,
|
|
PyObject *pytuple_or_none)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
// If there is no callback, just call the function directly
|
|
if ( pyfunc_or_none == Py_None )
|
|
return PyBool_FromLong(restore_database_snapshot(ss, NULL, NULL));
|
|
|
|
// Create a new tuple or increase reference to pytuple_or_none
|
|
if ( pytuple_or_none == Py_None )
|
|
{
|
|
pytuple_or_none = PyTuple_New(0);
|
|
if ( pytuple_or_none == NULL )
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
Py_INCREF(pytuple_or_none);
|
|
}
|
|
|
|
// Create callback data tuple (use 'N' for pytuple_or_none, since its
|
|
// reference has already been incremented)
|
|
PyObject *cb_data = Py_BuildValue("(ON)", pyfunc_or_none, pytuple_or_none);
|
|
|
|
bool b = restore_database_snapshot(ss, py_ss_restore_callback, (void *) cb_data);
|
|
|
|
if ( !b )
|
|
Py_DECREF(cb_data);
|
|
|
|
return PyBool_FromLong(b);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
|
|
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.
|
|
|
|
@param callable: A python callable object, must return an integer value
|
|
@param reqf: one of MFF_ flags
|
|
@return: -1 or the return value of the callable
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
//------------------------------------------------------------------------
|
|
static int py_execute_sync(PyObject *py_callable, int reqf)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
int rc = -1;
|
|
// Callable?
|
|
if ( PyCallable_Check(py_callable) )
|
|
{
|
|
struct py_exec_request_t : exec_request_t
|
|
{
|
|
ref_t py_callable;
|
|
virtual int idaapi execute() override
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, NULL));
|
|
int ret = py_result == NULL || !IDAPyInt_Check(py_result.o)
|
|
? -1
|
|
: IDAPyInt_AsLong(py_result.o);
|
|
// if the requesting thread decided not to wait for the request to
|
|
// complete, we have to self-destroy, nobody else will do it
|
|
if ( (code & MFF_NOWAIT) != 0 )
|
|
delete this;
|
|
return ret;
|
|
}
|
|
py_exec_request_t(PyObject *pyc)
|
|
{
|
|
// No need to GIL-ensure here, since this is created
|
|
// within the py_execute_sync() scope.
|
|
py_callable = borref_t(pyc);
|
|
}
|
|
virtual ~py_exec_request_t()
|
|
{
|
|
// Need to GIL-ensure here, since this might be called
|
|
// from the main thread.
|
|
PYW_GIL_GET;
|
|
py_callable = ref_t(); // Release callable
|
|
}
|
|
};
|
|
py_exec_request_t *req = new py_exec_request_t(py_callable);
|
|
|
|
// Release GIL before executing, or if this is running in the
|
|
// non-main thread, this will wait on the req.sem, while the main
|
|
// thread might be waiting for the GIL to be available.
|
|
Py_BEGIN_ALLOW_THREADS;
|
|
rc = execute_sync(*req, reqf);
|
|
Py_END_ALLOW_THREADS;
|
|
// destroy the request once it is finished. exception: NOWAIT requests
|
|
// will be handled in the future, so do not destroy them yet!
|
|
if ( (reqf & MFF_NOWAIT) == 0 )
|
|
delete req;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
|
|
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
|
|
#</pydoc>
|
|
*/
|
|
static bool py_execute_ui_requests(PyObject *py_list)
|
|
{
|
|
struct py_ui_request_t: public ui_request_t
|
|
{
|
|
private:
|
|
ref_vec_t py_callables;
|
|
size_t py_callable_idx;
|
|
|
|
static int idaapi s_py_list_walk_cb(
|
|
const ref_t &py_item,
|
|
Py_ssize_t /*index*/,
|
|
void *ud)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
// Not callable? Terminate iteration
|
|
if ( !PyCallable_Check(py_item.o) )
|
|
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);
|
|
return CIP_OK;
|
|
}
|
|
public:
|
|
py_ui_request_t(): py_callable_idx(0)
|
|
{
|
|
}
|
|
|
|
virtual bool idaapi run() override
|
|
{
|
|
PYW_GIL_GET;
|
|
|
|
// Get callable
|
|
ref_t py_callable = py_callables.at(py_callable_idx);
|
|
bool reschedule;
|
|
newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, NULL));
|
|
reschedule = py_result != NULL && PyObject_IsTrue(py_result.o);
|
|
|
|
// 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()
|
|
{
|
|
PYW_GIL_GET;
|
|
py_callables.clear();
|
|
}
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def set_dock_pos(src, dest, orient, left = 0, top = 0, right = 0, bottom = 0):
|
|
"""
|
|
Sets the dock orientation of a window relatively to another window.
|
|
|
|
@param src: Source docking control
|
|
@param dest: Destination docking control
|
|
@param orient: One of DOR_XXXX constants
|
|
@param left, top, right, bottom: These parameter if DOR_FLOATING is used, or if you want to specify the width of docked windows
|
|
@return: Boolean
|
|
|
|
Example:
|
|
set_dock_pos('Structures', 'Enums', DOR_RIGHT) <- docks the Structures window to the right of Enums window
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def is_idaq():
|
|
"""
|
|
Returns True or False depending if IDAPython is hosted by IDAQ
|
|
"""
|
|
#</pydoc>
|
|
*/
|
|
|
|
|
|
struct jobj_wrapper_t
|
|
{
|
|
private:
|
|
const jobj_t *o;
|
|
|
|
public:
|
|
jobj_wrapper_t(const jobj_t *_o) : o(_o) {}
|
|
|
|
PyObject *get_dict()
|
|
{
|
|
newref_t json_module(PyImport_ImportModule("json"));
|
|
if ( json_module != NULL )
|
|
{
|
|
borref_t json_globals(PyModule_GetDict(json_module.o));
|
|
if ( json_globals != NULL )
|
|
{
|
|
borref_t json_loads(PyDict_GetItemString(json_globals.o, "loads"));
|
|
if ( json_loads != NULL )
|
|
{
|
|
qstring clob;
|
|
if ( serialize_json(&clob, o) )
|
|
{
|
|
newref_t dict(PyObject_CallFunction(json_loads.o, "s", clob.c_str()));
|
|
if ( dict != NULL )
|
|
{
|
|
dict.incref();
|
|
return dict.o;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static bool fill_jobj_from_dict(jobj_t *out, PyObject *dict)
|
|
{
|
|
if ( PyDict_Check(dict) )
|
|
{
|
|
newref_t json_module(PyImport_ImportModule("json"));
|
|
if ( json_module != NULL )
|
|
{
|
|
borref_t json_globals(PyModule_GetDict(json_module.o));
|
|
if ( json_globals != NULL )
|
|
{
|
|
borref_t json_dumps(PyDict_GetItemString(json_globals.o, "dumps"));
|
|
if ( json_dumps != NULL )
|
|
{
|
|
newref_t str(PyObject_CallFunction(json_dumps.o, "O", dict));
|
|
qstring buf;
|
|
if ( IDAPyStr_AsUTF8(&buf, str.o) )
|
|
{
|
|
jvalue_t tmp;
|
|
if ( parse_json_string(&tmp, buf.c_str()) == eOk )
|
|
{
|
|
out->swap(tmp.obj());
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
// UI hooks
|
|
//---------------------------------------------------------------------------
|
|
ssize_t idaapi UI_Callback(void *ud, int notification_code, va_list va);
|
|
struct UI_Hooks : public hooks_base_t
|
|
{
|
|
// hookgenUI:methodsinfo_decl
|
|
|
|
UI_Hooks(uint32 _flags=0)
|
|
: hooks_base_t("ida_kernwin.UI_Hooks", UI_Callback, HT_UI, _flags) {}
|
|
|
|
bool hook() { return hooks_base_t::hook(); }
|
|
bool unhook() { return hooks_base_t::unhook(); }
|
|
#ifdef TESTABLE_BUILD
|
|
qstring dump_state() { return hooks_base_t::dump_state(mappings, mappings_size); }
|
|
#endif
|
|
|
|
// hookgenUI:methods
|
|
|
|
ssize_t dispatch(int code, va_list va)
|
|
{
|
|
ssize_t ret = 0;
|
|
switch ( code )
|
|
{
|
|
// hookgenUI:notifications
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private:
|
|
static ssize_t handle_get_ea_hint_output(PyObject *o, qstring *buf, ea_t)
|
|
{
|
|
ssize_t rc = 0;
|
|
if ( o != NULL && IDAPyStr_Check(o) && IDAPyStr_AsUTF8(buf, o) )
|
|
rc = 1;
|
|
Py_XDECREF(o);
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t handle_hint_output(PyObject *o, qstring *hint, int *important_lines)
|
|
{
|
|
ssize_t rc = 0;
|
|
if ( o != NULL && PyTuple_Check(o) && PyTuple_Size(o) == 2 )
|
|
{
|
|
borref_t el0(PyTuple_GetItem(o, 0));
|
|
if ( el0 != NULL
|
|
&& IDAPyStr_Check(el0.o)
|
|
&& IDAPyStr_AsUTF8(hint, el0.o)
|
|
&& !hint->empty() )
|
|
{
|
|
borref_t el1(PyTuple_GetItem(o, 1));
|
|
if ( el1 != NULL && IDAPyInt_Check(el1.o) )
|
|
{
|
|
long lns = IDAPyInt_AsLong(el1.o);
|
|
if ( lns > 0 )
|
|
{
|
|
*important_lines = lns;
|
|
rc = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t handle_hint_output(PyObject *o, qstring *hint, ea_t, int, int *important_lines)
|
|
{
|
|
return handle_hint_output(o, hint, important_lines);
|
|
}
|
|
|
|
static ssize_t handle_hint_output(PyObject *o, qstring *hint, TWidget *, place_t *, int *important_lines)
|
|
{
|
|
return handle_hint_output(o, hint, important_lines);
|
|
}
|
|
|
|
static jobj_wrapper_t wrap_widget_cfg(const jobj_t *jobj)
|
|
{
|
|
return jobj_wrapper_t(jobj);
|
|
}
|
|
|
|
static ssize_t handle_create_desktop_widget_output(PyObject *o)
|
|
{
|
|
if ( o == Py_None )
|
|
return 0;
|
|
TWidget *widget = NULL;
|
|
int cvt = SWIG_ConvertPtr(o, (void **) &widget, SWIGTYPE_p_TWidget, 0);
|
|
if ( !SWIG_IsOK(cvt) || widget == NULL )
|
|
return 0;
|
|
return ssize_t(widget);
|
|
}
|
|
|
|
static ssize_t handle_widget_cfg_output(PyObject *o, const TWidget *, jobj_t *cfg)
|
|
{
|
|
return jobj_wrapper_t::fill_jobj_from_dict(cfg, o);
|
|
}
|
|
};
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool py_register_action(action_desc_t *desc)
|
|
{
|
|
desc->flags |= ADF_OWN_HANDLER;
|
|
bool ok = register_action(*desc);
|
|
if ( ok )
|
|
{
|
|
// Let's set this to NULL, so when the wrapping Python action_desc_t
|
|
// instance is deleted, it doesn't try to delete the handler (See
|
|
// kernwin.i's action_desc_t::~action_desc_t()).
|
|
desc->handler = NULL;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
PyObject *py_get_registered_actions()
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
qstrvec_t actions;
|
|
get_registered_actions(&actions);
|
|
return qstrvec2pylist(actions);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def py_attach_dynamic_action_to_popup(
|
|
widget,
|
|
popup_handle,
|
|
desc,
|
|
popuppath = None,
|
|
flags = 0)
|
|
"""
|
|
Create & insert an action into the widget's popup menu
|
|
(::ui_attach_dynamic_action_to_popup).
|
|
Note: The action description in the 'desc' parameter is modified by
|
|
this call so you should prepare a new description for each call.
|
|
For example:
|
|
desc = idaapi.action_desc_t(None, 'Dynamic popup action', Handler())
|
|
idaapi.attach_dynamic_action_to_popup(form, popup, desc)
|
|
|
|
@param widget: target widget
|
|
@param popup_handle: target popup
|
|
@param desc: action description of type action_desc_t
|
|
@param popuppath: can be None
|
|
@param flags: a combination of SETMENU_ constants
|
|
@return: success
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
bool py_attach_dynamic_action_to_popup(
|
|
TWidget *widget,
|
|
TPopupMenu *popup_handle,
|
|
action_desc_t *desc,
|
|
const char *popuppath = NULL,
|
|
int flags = 0)
|
|
{
|
|
bool ok = attach_dynamic_action_to_popup(
|
|
widget, popup_handle, *desc, popuppath, flags);
|
|
// If attaching
|
|
// * succeeded: the action (and its handler) will be deleted after
|
|
// the popup is dismissed,
|
|
// * fails: the action (and its handler) will be deleted right away
|
|
// Therefore, we must always drop ownership.
|
|
desc->handler = NULL;
|
|
return ok;
|
|
}
|
|
|
|
// This is similar to a twinline_t, with improved memory management:
|
|
// twinline_t has a dummy destructor, that performs no cleanup.
|
|
struct disasm_line_t
|
|
{
|
|
disasm_line_t() : at(NULL) {}
|
|
~disasm_line_t() { qfree(at); }
|
|
disasm_line_t(const disasm_line_t &other) { *this = other; }
|
|
disasm_line_t &operator=(const disasm_line_t &other)
|
|
{
|
|
qfree(at);
|
|
at = other.at == NULL ? NULL : other.at->clone();
|
|
return *this;
|
|
}
|
|
place_t *at;
|
|
qstring line;
|
|
color_t prefix_color;
|
|
bgcolor_t bg_color;
|
|
bool is_default;
|
|
};
|
|
DECLARE_TYPE_AS_MOVABLE(disasm_line_t);
|
|
typedef qvector<disasm_line_t> disasm_text_t;
|
|
|
|
//-------------------------------------------------------------------------
|
|
void py_gen_disasm_text(disasm_text_t &text, ea_t ea1, ea_t ea2, bool truncate_lines)
|
|
{
|
|
text_t _text;
|
|
gen_disasm_text(_text, ea1, ea2, truncate_lines);
|
|
for ( size_t i = 0, n = _text.size(); i < n; ++i )
|
|
{
|
|
twinline_t &tl = _text[i];
|
|
disasm_line_t &dl = text.push_back();
|
|
dl.at = tl.at; // Transfer ownership
|
|
dl.line.swap(tl.line); // Transfer ownership
|
|
}
|
|
}
|
|
|
|
/*
|
|
#<pydoc>
|
|
def set_nav_colorizer(callback):
|
|
"""
|
|
Set a new colorizer for the navigation band.
|
|
|
|
The 'callback' is a function of 2 arguments:
|
|
- ea (the EA to colorize for)
|
|
- nbytes (the number of bytes at that EA)
|
|
and must return a 'long' value.
|
|
|
|
The previous colorizer is returned, allowing
|
|
the new 'callback' to use 'call_nav_colorizer'
|
|
with it.
|
|
|
|
Note that the previous colorizer is returned
|
|
only the first time set_nav_colorizer() is called:
|
|
due to the way the colorizers API is defined in C,
|
|
it is impossible to chain more than 2 colorizers
|
|
in IDAPython: the original, IDA-provided colorizer,
|
|
and a user-provided one.
|
|
|
|
Example: colorizer inverting the color provided by the IDA colorizer:
|
|
def my_colorizer(ea, nbytes):
|
|
global ida_colorizer
|
|
orig = idaapi.call_nav_colorizer(ida_colorizer, ea, nbytes)
|
|
return long(~orig)
|
|
|
|
ida_colorizer = idaapi.set_nav_colorizer(my_colorizer)
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
PyObject *py_set_nav_colorizer(PyObject *new_py_colorizer)
|
|
{
|
|
struct ida_local lambda_t
|
|
{
|
|
static uint32 idaapi call_py_colorizer(ea_t ea, asize_t nbytes, void *)
|
|
{
|
|
PYW_GIL_GET;
|
|
|
|
if ( py_colorizer == NULL ) // Shouldn't happen.
|
|
return 0;
|
|
newref_t pyres = PyObject_CallFunction(
|
|
py_colorizer.o, "KK",
|
|
(unsigned long long) ea,
|
|
(unsigned long long) nbytes);
|
|
PyW_ShowCbErr("nav_colorizer");
|
|
uint32 rc = 0;
|
|
bool ok = pyres.o != NULL && PyLong_Check(pyres.o);
|
|
if ( ok )
|
|
{
|
|
int overflow = 0;
|
|
const long l = PyLong_AsLongAndOverflow(pyres.o, &overflow);
|
|
ok = !PyErr_Occurred();
|
|
if ( ok )
|
|
{
|
|
if ( l == -1 && overflow != 0 )
|
|
ok = false;
|
|
else
|
|
rc = uint32(l);
|
|
}
|
|
else
|
|
{
|
|
PyErr_Print();
|
|
}
|
|
}
|
|
|
|
if ( !ok )
|
|
{
|
|
static bool warned = false;
|
|
if ( !warned )
|
|
{
|
|
msg("WARNING: set_nav_colorizer() callback must return an "
|
|
"unsigned 'long', that can be converted into a 32-bit "
|
|
"unsigned integer.\n");
|
|
warned = true;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
};
|
|
|
|
// Always perform the call to set_nav_colorizer(): that has side-effects
|
|
// (e.g., updating the legend.)
|
|
bool first_install = py_colorizer == NULL;
|
|
py_colorizer = borref_t(new_py_colorizer);
|
|
nav_colorizer_t *was_fun = NULL;
|
|
void *was_ud = NULL;
|
|
set_nav_colorizer(&was_fun, &was_ud, lambda_t::call_py_colorizer, NULL);
|
|
if ( !first_install )
|
|
Py_RETURN_NONE;
|
|
PyObject *was_fun_ptr = PyCapsule_New((void *) was_fun, VALID_CAPSULE_NAME, NULL);
|
|
PyObject *was_ud_ptr = PyCapsule_New(was_ud, VALID_CAPSULE_NAME, NULL);
|
|
PyObject *dict = PyDict_New();
|
|
PyDict_SetItemString(dict, "fun", was_fun_ptr);
|
|
PyDict_SetItemString(dict, "ud", was_ud_ptr);
|
|
return dict;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def call_nav_colorizer(colorizer, ea, nbytes):
|
|
"""
|
|
To be used with the IDA-provided colorizer, that is
|
|
returned as result of the first call to set_nav_colorizer().
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
uint32 py_call_nav_colorizer(
|
|
PyObject *dict,
|
|
ea_t ea,
|
|
asize_t nbytes)
|
|
{
|
|
if ( !PyDict_Check(dict) )
|
|
return 0;
|
|
borref_t py_fun(PyDict_GetItemString(dict, "fun"));
|
|
borref_t py_ud(PyDict_GetItemString(dict, "ud"));
|
|
if ( py_fun == NULL
|
|
|| !PyCapsule_IsValid(py_fun.o, VALID_CAPSULE_NAME)
|
|
|| !PyCapsule_IsValid(py_ud.o, VALID_CAPSULE_NAME) )
|
|
{
|
|
return 0;
|
|
}
|
|
nav_colorizer_t *fun = (nav_colorizer_t *) PyCapsule_GetPointer(py_fun.o, VALID_CAPSULE_NAME);
|
|
void *ud = PyCapsule_GetPointer(py_ud.o, VALID_CAPSULE_NAME);
|
|
if ( fun == NULL )
|
|
return 0;
|
|
return fun(ea, nbytes, ud);
|
|
}
|
|
|
|
PyObject *py_msg_get_lines(int count=-1)
|
|
{
|
|
qstrvec_t lines;
|
|
msg_get_lines(&lines, count);
|
|
return qstrvec2pylist(lines);
|
|
}
|
|
|
|
/*
|
|
#<pydoc>
|
|
def msg(message):
|
|
"""
|
|
Display an UTF-8 string in the message window
|
|
|
|
The result of the stringification of the arguments
|
|
will be treated as an UTF-8 string.
|
|
|
|
@param message: message to print (formatting is done in Python)
|
|
|
|
This function can be used to debug IDAPython scripts
|
|
"""
|
|
pass
|
|
|
|
def warning(message):
|
|
"""
|
|
Display a message in a message box
|
|
|
|
@param message: message to print (formatting is done in Python)
|
|
|
|
This function can be used to debug IDAPython scripts
|
|
The user will be able to hide messages if they appear twice in a row on
|
|
the screen
|
|
"""
|
|
pass
|
|
|
|
def error(format):
|
|
"""
|
|
Display a fatal message in a message box and quit IDA
|
|
|
|
@param format: message to print
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
static TWidget *TWidget__from_ptrval__(size_t ptrval)
|
|
{
|
|
return (TWidget *) ptrval;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static PyObject *py_add_spaces(const char *s, size_t len)
|
|
{
|
|
qstring qbuf(s);
|
|
const size_t slen = tag_strlen(qbuf.c_str());
|
|
const size_t delta = qbuf.length() - slen;
|
|
if ( len > slen )
|
|
qbuf.resize(len + delta);
|
|
// we use the actual 'size' because we know that
|
|
// 'add_spaces()' will add a terminating zero anyway
|
|
add_spaces(qbuf.begin(), qbuf.size(), len);
|
|
return IDAPyStr_FromUTF8(qbuf.c_str());
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
THREAD_SAFE void py_show_wait_box(const char *message)
|
|
{
|
|
idapython_show_wait_box(/*internal=*/ false, message);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
void py_hide_wait_box()
|
|
{
|
|
idapython_hide_wait_box();
|
|
}
|
|
|
|
//</inline(py_kernwin)>
|
|
|
|
//---------------------------------------------------------------------------
|
|
//<code(py_kernwin)>
|
|
|
|
// hookgenUI:methodsinfo_def
|
|
|
|
//---------------------------------------------------------------------------
|
|
ssize_t idaapi UI_Callback(void *ud, int code, va_list va)
|
|
{
|
|
// hookgenUI:safecall=UI_Hooks
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool idaapi py_menu_item_callback(void *userdata)
|
|
{
|
|
PYW_GIL_GET;
|
|
|
|
// userdata is a tuple of ( func, args )
|
|
// func and args are borrowed references from userdata
|
|
|
|
PyObject *o = (PyObject *) userdata;
|
|
if ( !PyTuple_Check(o) )
|
|
return false;
|
|
|
|
PyObject *func = PyTuple_GetItem(o, 0);
|
|
PyObject *args = PyTuple_GetItem(o, 1);
|
|
|
|
// Call the python function
|
|
newref_t result(PyEval_CallObject(func, args));
|
|
|
|
// We cannot raise an exception in the callback, just print it.
|
|
if ( result == NULL )
|
|
{
|
|
PyErr_Print();
|
|
return false;
|
|
}
|
|
|
|
return PyObject_IsTrue(result.o) != 0;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static void ida_kernwin_init(void) {}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static void ida_kernwin_term(void)
|
|
{
|
|
py_colorizer = ref_t();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static void ida_kernwin_closebase(void) {}
|
|
|
|
/*
|
|
#<pydoc>
|
|
def get_navband_pixel(ea):
|
|
"""
|
|
Maps an address, onto a pixel coordinate within the navband
|
|
|
|
@param ea: The address to map
|
|
@return: a list [pixel, is_vertical]
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
//</code(py_kernwin)>
|
|
|
|
#endif
|