From 7ea37090210453af06417ded0bc19a2f02bbd472 Mon Sep 17 00:00:00 2001 From: "elias.bachaalany" Date: Fri, 2 Oct 2009 17:40:26 +0000 Subject: [PATCH] graph.i: added graph support (works only with the GUI version) --- STATUS.txt | 2 +- build.py | 10 +- examples/ex_graph.py | 38 ++ swig/graph.i | 941 +++++++++++++++++++++++++++++++++++++++++++ swig/idaapi.i | 10 + 5 files changed, 997 insertions(+), 4 deletions(-) create mode 100644 examples/ex_graph.py create mode 100644 swig/graph.i diff --git a/STATUS.txt b/STATUS.txt index 270c46e..dd2d396 100644 --- a/STATUS.txt +++ b/STATUS.txt @@ -30,7 +30,7 @@ fpro.h - EXCLUDED frame.hpp - COMPLETE funcs.hpp - COMPLETE (necessary SWIGs, minor FIXME) gdl.hpp - EXCLUDED -graph.hpp - EXCLUDED +graph.hpp - INCOMPLETE help.h - EXCLUDED ida.hpp - COMPLETE idd.hpp - COMPLETE (necessary SWIGs) diff --git a/build.py b/build.py index c9f31da..2406127 100644 --- a/build.py +++ b/build.py @@ -74,7 +74,8 @@ BINDIST_MANIFEST = [ "examples/ex_strings.py", "examples/ex_func_chooser.py", "examples/ex_choose2.py", - "examples/ex_debug_names.py" + "examples/ex_debug_names.py", + "examples/ex_graph.py" ] # List files for the source distribution (appended to binary list) @@ -120,6 +121,7 @@ SRCDIST_MANIFEST = [ "swig/typeinf.i", "swig/ua.i", "swig/xref.i", + "swig/graph.i", "tools/gendocs.py", ] @@ -277,6 +279,7 @@ def build_distribution(manifest, distrootdir, ea64, nukeold): 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" @@ -292,16 +295,17 @@ def build_plugin(platform, idasdkdir, plugin_name, ea64): ida_lib = "" extra_link_parameters = "" # Platform-specific settings for the Windows build - if platform == "win32": + elif platform == "win32": builder = MSVCBuilder() platform_macros = [ "__NT__" ] python_libpath = sysconfig.EXEC_PREFIX + os.sep + "libs" python_library = "python%d%d.lib" % (PYTHON_MAJOR_VERSION, PYTHON_MINOR_VERSION) ida_libpath = os.path.join(idasdkdir, ea64 and "libvc.w64" or "libvc.w32") ida_lib = "ida.lib" + SWIG_OPTIONS += " -D__NT__ " extra_link_parameters = "" # Platform-specific settings for the Mac OS X build - if platform == "macosx": + elif platform == "macosx": builder = GCCBuilder() builder.linker_parameters = "-dynamiclib" platform_macros = [ "__MAC__" ] diff --git a/examples/ex_graph.py b/examples/ex_graph.py new file mode 100644 index 0000000..7582020 --- /dev/null +++ b/examples/ex_graph.py @@ -0,0 +1,38 @@ +from idaapi import GraphViewer + +class MyGraph(GraphViewer): + def __init__(self, funcname, result): + GraphViewer.__init__(self, "call graph of " + funcname) + self.funcname = funcname + self.result = result + + def OnRefresh(self): + self.Clear() + id = self.AddNode(self.funcname) + for x in self.result.keys(): + callee = self.AddNode(x) + self.AddEdge(id, callee) + + return True + + def OnGetText(self, node_id): + return str(self[node_id]) + +def main(): + f = idaapi.get_func(here()) + if not f: + print "Must be in a function" + return + # Iterate through all function instructions and take only call instructions + result = {} + for x in [x for x in FuncItems(f.startEA) if idaapi.is_call_insn(x)]: + for xref in XrefsFrom(x, idaapi.XREF_FAR): + if not xref.iscode: continue + t = GetFunctionName(xref.to) + if not t: + t = hex(xref.to) + result[t] = True + g = MyGraph(GetFunctionName(f.startEA), result) + g.Show() + +main() \ No newline at end of file diff --git a/swig/graph.i b/swig/graph.i new file mode 100644 index 0000000..128fd50 --- /dev/null +++ b/swig/graph.i @@ -0,0 +1,941 @@ +%{ + +#ifndef __PY_IDA_GRAPH__ +#define __PY_IDA_GRAPH__ + +#define GR_HAVE_USER_HINT 0x00000001 +#define GR_HAVE_CLICKED 0x00000002 +#define GR_HAVE_DBL_CLICKED 0x00000004 +#define GR_HAVE_GOTFOCUS 0x00000008 +#define GR_HAVE_LOSTFOCUS 0x00000010 +#define GR_HAVE_CHANGED_CURRENT 0x00000020 +#define GR_HAVE_CLOSE 0x00000040 +#define GR_HAVE_COMMAND 0x00000080 +#define S_ON_COMMAND "OnCommand" +#define S_ON_REFRESH "OnRefresh" +#define S_ON_HINT "OnHint" +#define S_ON_GETTEXT "OnGetText" +#define S_ON_CLOSE "OnClose" +#define S_ON_CLICK "OnClick" +#define S_ON_DBL_CLICK "OnDblClick" +#define S_ON_ACTIVATE "OnActivate" +#define S_ON_DEACTIVATE "OnDeactivate" +#define S_ON_SELECT "OnSelect" +#define S_M_EDGES "_edges" +#define S_M_NODES "_nodes" +#define S_M_THIS "_this" +#define S_M_TITLE "_title" + +#include + +class py_graph_t +{ +private: + struct nodetext_cache_t + { + qstring text; + bgcolor_t bgcolor; + nodetext_cache_t(const nodetext_cache_t &rhs): text(rhs.text), bgcolor(rhs.bgcolor) { } + 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: + nodetext_cache_t *get(int node_id) + { + iterator it = find(node_id); + if (it == end()) + return NULL; + return &it->second; + } + nodetext_cache_t *add(const int node_id, const char *text, bgcolor_t bgcolor = DEFCOLOR) + { + return &(insert(std::make_pair(node_id, nodetext_cache_t(text, bgcolor))).first->second); + } + }; + + class tform_pygraph_map_t: public std::map + { + public: + py_graph_t *get(TForm *form) + { + iterator it = find(form); + return it == end() ? NULL : it->second; + } + void add(TForm *form, py_graph_t *py) + { + (*this)[form] = py; + } + }; + class cmdid_map_t: public std::map + { + private: + Py_ssize_t uid; + public: + cmdid_map_t() + { + uid = 1; // we start by one and keep zero for error id + } + void add(py_graph_t *pyg) + { + (*this)[uid] = pyg; + ++uid; + } + const Py_ssize_t id() const { return uid; } + void clear(py_graph_t *pyg) + { + iterator e = end(); + for (iterator it=begin();it!=end();) + { + if (it->second == pyg) + { + iterator temp = it++; + erase(temp); + } + else + ++it; + } + } + py_graph_t *get(Py_ssize_t id) + { + iterator it = find(id); + return it == end() ? NULL : it->second; + } + }; + + static tform_pygraph_map_t tform_pyg; + static cmdid_map_t cmdid_pyg; + int cb_flags; + TForm *form; + graph_viewer_t *gv; + bool refresh_needed; + PyObject *self; + nodetext_cache_map_t node_cache; + + // static callback + static int idaapi s_callback(void *obj, int code, va_list va) + { + return ((py_graph_t *)obj)->gr_callback(code, va); + } + + static bool idaapi s_menucb(void *ud) + { + Py_ssize_t id = (Py_ssize_t)ud; + py_graph_t *_this = cmdid_pyg.get(id); + if (_this != NULL) + _this->on_command(id); + return true; + } + + void on_command(Py_ssize_t id) + { + // Check return value to OnRefresh() call + PyObject *ret = PyObject_CallMethod(self, S_ON_COMMAND, "n", id); + Py_XDECREF(ret); + } + + // Refresh user-defined graph node number and edges + // It calls Python method and expects that the user already filled + // the nodes and edges. The nodes and edges are retrieved and passed to IDA + void on_user_refresh(mutable_graph_t *g) + { + if (!refresh_needed) + return; + + // Check return value to OnRefresh() call + PyObject *ret = PyObject_CallMethod(self, S_ON_REFRESH, NULL); + if (ret == NULL || !PyBool_Check(ret) || ret != Py_True) + { + Py_XDECREF(ret); + return; + } + + // Refer to the nodes + PyObject *nodes = PyObject_TryGetAttrString(self, S_M_NODES); + if (ret == NULL || !PyList_Check(nodes)) + { + Py_XDECREF(nodes); + return; + } + + // Refer to the edges + PyObject *edges = PyObject_TryGetAttrString(self, S_M_EDGES); + if (ret == NULL || !PyList_Check(nodes)) + { + Py_DECREF(nodes); + Py_XDECREF(edges); + return; + } + + // Resize the nodes + int max_nodes = abs(int(PyList_Size(nodes))); + g->clear(); + g->resize(max_nodes); + + // Mark that we refreshed already + refresh_needed = false; + + // Clear cached nodes + node_cache.clear(); + + // Get the edges + for (int i=(int)PyList_Size(edges)-1;i>=0;i--) + { + // Each list item is a sequence (id1, id2) + PyObject *item = PyList_GetItem(edges, i); + if (!PySequence_Check(item)) + continue; + + // Get and validate each of the two elements in the sequence + int edge_ids[2]; + int j; + for (j=0;j max_nodes) + break; + edge_ids[j] = v; + } + // Incomplete? + if (j != qnumber(edge_ids)) + break; + // Add the edge + g->add_edge(edge_ids[0], edge_ids[1], NULL); + } + Py_DECREF(nodes); + Py_DECREF(edges); + } + + // Retrieves the text for user-defined graph node + // It expects either a string or a tuple (string, bgcolor) + bool on_user_text(mutable_graph_t * /*g*/, int node, const char **str, bgcolor_t *bg_color) + { + // If already cached then return the value + nodetext_cache_t *c = node_cache.get(node); + if (c != NULL) + { + *str = c->text.c_str(); + if (bg_color != NULL) + *bg_color = c->bgcolor; + return true; + } + + // Not cached, call Python + PyObject *result = PyObject_CallMethod(self, S_ON_GETTEXT, "l", node); + if (result == NULL) + return false; + + bgcolor_t cl = bg_color == NULL ? DEFCOLOR : *bg_color; + const char *s; + + // User returned a string? + if (PyString_Check(result)) + { + s = PyString_AsString(result); + if (s == NULL) + s = ""; + c = node_cache.add(node, s, cl); + } + // User returned a sequence of text and bgcolor + else if (PySequence_Check(result) && PySequence_Size(result) == 2) + { + PyObject *py_str = PySequence_GetItem(result, 0); + PyObject *py_color = PySequence_GetItem(result, 1); + + if (py_str == NULL || !PyString_Check(py_str) || (s = PyString_AsString(py_str)) == NULL) + s = ""; + if (py_color != NULL && !PyInt_Check(py_color)) + cl = bgcolor_t(PyInt_AsLong(py_color)); + + c = node_cache.add(node, s, cl); + + Py_XDECREF(py_str); + Py_XDECREF(py_color); + } + Py_DECREF(result); + + *str = c->text.c_str(); + if (bg_color != NULL) + *bg_color = c->bgcolor; + return true; + } + + // Retrieves the hint for the user-defined graph + // Calls Python and expects a string or None + int on_user_hint(mutable_graph_t *, int mousenode, int /*mouseedge_src*/, int /*mouseedge_dst*/, char **hint) + { + // 'hint' must be allocated by qalloc() or qstrdup() + // out: 0-use default hint, 1-use proposed hint + + // We dispatch hints over nodes only + if (mousenode == -1) + return 0; + + PyObject *result = PyObject_CallMethod(self, S_ON_HINT, "l", mousenode); + bool ok = result != NULL && PyString_Check(result); + if (!ok) + { + Py_XDECREF(result); + return 0; + } + *hint = qstrdup(PyString_AsString(result)); + Py_DECREF(result); + return 1; // use our hint + } + + // graph is being destroyed + void on_destroy(mutable_graph_t * /*g*/ = NULL) + { + if (self != NULL) + { + if (cb_flags & GR_HAVE_CLOSE) + { + PyObject *result = PyObject_CallMethod(self, S_ON_CLOSE, NULL); + 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_item_t *item2) + { + // in: graph_viewer_t *gv + // selection_item_t *current_item1 + // graph_item_t *current_item2 + // out: 0-ok, 1-ignore click + // this callback allows you to ignore some clicks. + // it occurs too early, internal graph variables are not updated yet + // current_item1, current_item2 point to the same thing + // item2 has more information. + // see also: kernwin.hpp, custom_viewer_click_t + if (item2->n == -1) + return 1; + + PyObject *result = PyObject_CallMethod(self, S_ON_CLICK, "l", item2->n); + if (result == NULL || !PyBool_Check(result) || result != Py_True) + { + Py_XDECREF(result); + return 1; + } + + Py_DECREF(result); + + return 0; + } + // a graph node has been double clicked + int on_dblclicked(graph_viewer_t * /*gv*/, selection_item_t *item) + { + // in: graph_viewer_t *gv + // selection_item_t *current_item + // out: 0-ok, 1-ignore click + //graph_viewer_t *v = va_arg(va, graph_viewer_t *); + //selection_item_t *s = va_arg(va, selection_item_t *); + if (item == NULL || !item->is_node) + return 1; + PyObject *result = PyObject_CallMethod(self, S_ON_DBL_CLICK, "l", item->node); + if (result == NULL || !PyBool_Check(result) || result != Py_True) + { + Py_XDECREF(result); + return 1; + } + Py_DECREF(result); + return 0; + } + + // a graph viewer got focus + void on_gotfocus(graph_viewer_t * /*gv*/) + { + PyObject *result = PyObject_CallMethod(self, S_ON_ACTIVATE, NULL); + Py_XDECREF(result); + } + + // a graph viewer lost focus + void on_lostfocus(graph_viewer_t *gv) + { + PyObject *result = PyObject_CallMethod(self, S_ON_DEACTIVATE, NULL); + Py_XDECREF(result); + } + + // a new graph node became the current node + int on_changed_current(graph_viewer_t * /*gv*/, int curnode) + { + // in: graph_viewer_t *gv + // int curnode + // out: 0-ok, 1-forbid to change the current node + //graph_viewer_t *v = va_arg(va, graph_viewer_t *); + //int curnode = va_argi(va, int); + //msg("%x: current node becomes %d\n", v, curnode); + if (curnode < 0) + return 0; + PyObject *result = PyObject_CallMethod(self, S_ON_SELECT, "l", curnode); + bool allow = (result != NULL && PyBool_Check(result) && result == Py_True); + Py_XDECREF(result); + return allow ? 0 : 1; + } + + int gr_callback(int code, va_list va) + { + int ret; + switch (code) + { + // + case grcode_user_text: + { + mutable_graph_t *g = va_arg(va, mutable_graph_t *); + int node = va_arg(va, int); + const char **result = va_arg(va, const char **); + bgcolor_t *bgcolor = va_arg(va, bgcolor_t *); + ret = on_user_text(g, node, result, bgcolor); + break; + } + // + case grcode_destroyed: + on_destroy(va_arg(va, mutable_graph_t *)); + ret = 0; + break; + // + case grcode_clicked: + if (cb_flags & GR_HAVE_CLICKED) + { + graph_viewer_t *gv = va_arg(va, graph_viewer_t *); + selection_item_t *item = va_arg(va, selection_item_t *); + graph_item_t *gitem = va_arg(va, graph_item_t *); + ret = on_clicked(gv, item, gitem); + } + else + ret = 1; // ignore click + break; + // + case grcode_dblclicked: + if (cb_flags & GR_HAVE_DBL_CLICKED) + { + graph_viewer_t *gv = va_arg(va, graph_viewer_t *); + selection_item_t *item = va_arg(va, selection_item_t *); + ret = on_dblclicked(gv, item); + } + else + ret = 1; // ignore + break; + // + 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_GOTFOCUS) + 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; + case grcode_user_hint: + if (cb_flags & GR_HAVE_USER_HINT) + { + mutable_graph_t *g = va_arg(va, mutable_graph_t *); + int mousenode = va_arg(va, int); + int mouseedge_src = va_arg(va, int); + int mouseedge_dest = va_arg(va, int); + char **hint = va_arg(va, char **); + ret = on_user_hint(g, mousenode, mouseedge_src, mouseedge_dest, hint); + } + else + { + ret = 0; + } + break; + case grcode_changed_current: + if (cb_flags & GR_HAVE_CHANGED_CURRENT) + { + graph_viewer_t *gv = va_arg(va, graph_viewer_t *); + int cur_node = va_arg(va, int); + ret = on_changed_current(gv, cur_node); + } + else + ret = 0; // allow selection change + break; + default: + ret = 0; + break; + } + //grcode_changed_graph, // new graph has been set + //grcode_creating_group, // a group is being created + //grcode_deleting_group, // a group is being deleted + //grcode_group_visibility, // a group is being collapsed/uncollapsed + //grcode_user_size, // calculate node size for user-defined graph + //grcode_user_title, // render node title of a user-defined graph + //grcode_user_draw, // render node of a user-defined graph + return ret; + } + static PyObject *PyObject_TryGetAttrString(PyObject *object, const char *attr) + { + if (!PyObject_HasAttrString(object, attr)) + return NULL; + return PyObject_GetAttrString(object, attr); + } + + void unbind() + { + if (self == NULL) + return; + + // Unbind this object from the python object + PyObject_SetAttrString(self, S_M_THIS, PyCObject_FromVoidPtr(NULL, NULL)); + Py_XDECREF(self); + self = NULL; + } + + void show() + { + open_tform(form, FORM_MDI|FORM_TAB|FORM_MENU); + } + + static py_graph_t *extract_this(PyObject *self) + { + // Try to extract "this" from the python object + PyObject *py_this = PyObject_TryGetAttrString(self, S_M_THIS); + if (py_this == NULL || !PyCObject_Check(py_this)) + { + Py_XDECREF(py_this); + return NULL; + } + py_graph_t *ret = (py_graph_t *) PyCObject_AsVoidPtr(py_this); + Py_DECREF(py_this); + return ret; + } + + void jump_to_node(int nid) + { + viewer_center_on(gv, nid); + int x, y; + + // will return a place only when a node was previously selected + place_t *old_pl = get_custom_viewer_place(gv, false, &x, &y); + if ( old_pl != NULL ) + { +#ifdef __BORLANDC__ + user_graph_place_t *new_pl = (user_graph_place_t *) old_pl->clone(); +#else + // although this works, it may not work in the future + user_graph_place_t *new_pl = (user_graph_place_t *) qalloc(sizeof(user_graph_place_t)); + memcpy(new_pl, old_pl, sizeof(user_graph_place_t)); +#endif + new_pl->node = nid; + jumpto(gv, new_pl, x, y); +#ifdef __BORLANDC__ + delete new_pl; +#else + qfree(new_pl); +#endif + } + } + + void refresh() + { + refresh_needed = true; + refresh_viewer(gv); + } + + static bool extract_title(PyObject *self, qstring *title) + { + PyObject *py_title = PyObject_TryGetAttrString(self, S_M_TITLE); + if ( py_title == NULL ) + return false; + *title = PyString_AsString(py_title); + Py_DECREF(py_title); + return true; + } + + int create(PyObject *self, const char *title) + { + // check what callbacks we have + static const struct + { + const char *name; + int have; + } callbacks[] = + { + {S_ON_REFRESH, 0}, // 0 = mandatory callback + {S_ON_GETTEXT, 0}, + {S_M_EDGES, -1}, // -1 = mandatory attributes + {S_M_NODES, -1}, + {S_ON_HINT, GR_HAVE_USER_HINT}, + {S_ON_CLICK, GR_HAVE_CLICKED}, + {S_ON_DBL_CLICK, GR_HAVE_DBL_CLICKED}, + {S_ON_CLOSE, GR_HAVE_CLOSE}, + {S_ON_COMMAND, GR_HAVE_COMMAND}, + {S_ON_SELECT, GR_HAVE_CHANGED_CURRENT}, + {S_ON_ACTIVATE, GR_HAVE_GOTFOCUS}, + {S_ON_DEACTIVATE, GR_HAVE_LOSTFOCUS} + }; + cb_flags = 0; + for (int i=0;i= 0 && PyCallable_Check(attr) == 0)) + { + Py_XDECREF(attr); + return -1; + } + if (have > 0 && attr != NULL) + cb_flags |= have; + Py_XDECREF(attr); + } + + // Bind py_graph_t to python object + this->self = self; + Py_INCREF(self); + PyObject_SetAttrString(self, S_M_THIS, PyCObject_FromVoidPtr(this, NULL)); + + // Create form + HWND hwnd = NULL; + form = create_tform(title, &hwnd); + + // Link "form" and "py_graph" + tform_pyg.add(form, this); + + if (hwnd != NULL) + { + // get a unique graph id + netnode id; + id.create(); + gv = create_graph_viewer(form, id, s_callback, this, 0); + open_tform(form, FORM_MDI|FORM_TAB|FORM_MENU); + if (gv != NULL) + viewer_fit_window(gv); + } + else + { + show(); + } + viewer_fit_window(gv); + return 0; + } + + Py_ssize_t add_command(const char *title, const char *hotkey) + { + if ( (cb_flags & GR_HAVE_COMMAND) == 0 || gv == NULL) + return 0; + Py_ssize_t cmd_id = cmdid_pyg.id(); + bool ok = viewer_add_menu_item(gv, title, s_menucb, (void *)cmd_id, hotkey, 0); + if (!ok) + return 0; + cmdid_pyg.add(this); + return cmd_id; + } + + public: + py_graph_t() + { + form = NULL; + gv = NULL; + refresh_needed = true; + self = NULL; + } + + static void SelectNode(PyObject *self, int nid) + { + py_graph_t *_this = extract_this(self); + if (_this == NULL || _this->form == NULL) + return; + _this->jump_to_node(0); + } + + static Py_ssize_t AddCommand(PyObject *self, const char *title, const char *hotkey) + { + py_graph_t *_this = extract_this(self); + if (_this == NULL || _this->form == NULL) + return 0; + return _this->add_command(title, hotkey); + } + + static void Close(PyObject *self) + { + py_graph_t *_this = extract_this(self); + if (_this == NULL || _this->form == NULL) + return; + close_tform(_this->form, 0); + } + + static void Refresh(PyObject *self) + { + py_graph_t *_this = extract_this(self); + if (_this == NULL) + return; + _this->refresh(); + } + + static py_graph_t *Show(PyObject *self) + { + py_graph_t *ret = extract_this(self); + if (ret == NULL) + { + qstring title; + if (!extract_title(self, &title)) + return NULL; + + // Form already created? try to get associated py_graph instance + // so that we reuse it + ret = tform_pyg.get(find_tform(title.c_str())); + + // Instance not found? create a new one + if (ret == NULL) + ret = new py_graph_t(); + else + { + // unbind so we are rebound + ret->unbind(); + ret->refresh_needed = true; + } + if (ret->create(self, title.c_str()) < 0) + { + delete ret; + ret = NULL; + } + } + else + { + ret->show(); + } + return ret; + } +}; + +py_graph_t::tform_pygraph_map_t py_graph_t::tform_pyg; +py_graph_t::cmdid_map_t py_graph_t::cmdid_pyg; + +bool pyg_show(PyObject *self) +{ + return py_graph_t::Show(self) != NULL; +} + +void pyg_refresh(PyObject *self) +{ + py_graph_t::Refresh(self); +} + +void pyg_close(PyObject *self) +{ + py_graph_t::Close(self); +} + +Py_ssize_t pyg_add_command(PyObject *self, const char *title, const char *hotkey) +{ + return py_graph_t::AddCommand(self, title, hotkey); +} + +void pyg_select_node(PyObject *self, int nid) +{ + py_graph_t::SelectNode(self, nid); +} + +#undef GR_HAVE_USER_HINT +#undef GR_HAVE_CLICKED +#undef GR_HAVE_DBL_CLICKED +#undef GR_HAVE_GOTFOCUS +#undef GR_HAVE_LOSTFOCUS +#undef GR_HAVE_CHANGED_CURRENT +#undef GR_HAVE_CLOSE +#undef GR_HAVE_COMMAND +#undef S_ON_COMMAND +#undef S_ON_REFRESH +#undef S_ON_HINT +#undef S_ON_GETTEXT +#undef S_ON_CLOSE +#undef S_ON_CLICK +#undef S_ON_DBL_CLICK +#undef S_ON_ACTIVATE +#undef S_ON_DEACTIVATE +#undef S_ON_SELECT +#undef S_M_EDGES +#undef S_M_NODES +#undef S_M_THIS +#undef S_M_TITLE + +#endif + +%} + +%pythoncode %{ +class GraphViewer: + """This class wraps the user graphing facility provided by the graph.hpp file""" + def __init__(self, title, close_open = False): + """ + Constructs the GraphView object. + Please do not remove or rename the private fields + + @param title: The title of the graph window + @param close_open: Should it attempt to close an existing graph (with same title) before creating this graph? + """ + self._title = title + self._nodes = [] + self._edges = [] + self._close_open = close_open + + def AddNode(self, obj): + """Creates a node associated with the given object and returns the node id""" + id = len(self._nodes) + self._nodes.append(obj) + return id + + def AddEdge(self, src_node, dest_node): + """Creates an edge between two given node ids""" + self._edges.append( (src_node, dest_node) ) + + def Clear(self): + """Clears all the nodes and edges""" + self._nodes = [] + self._edges = [] + + def __getitem__(self, idx): + """Returns a reference to the object associated with this node id""" + if idx > len(self._nodes): + raise StopIteration + return self._nodes[idx] + + def Count(self): + """Returns the node count""" + return len(self._nodes) + + def Close(self): + """ + Closes the graph. + It is possible to call Show() again (which will recreate the graph) + """ + _idaapi.pyg_close(self) + + def Refresh(self): + """ + Refreshes the graph. This causes the OnRefresh() to be called + """ + _idaapi.pyg_refresh(self) + + def Show(self): + """ + Shows an existing graph or creates a new one + + @return: Boolean + """ + if self._close_open: + frm = _idaapi.find_tform(self._title) + if frm: + _idaapi.close_tform(frm, 0) + return _idaapi.pyg_show(self) + + def Select(self, node_id): + """Selects a node on the graph""" + _idaapi.pyg_select_node(self, node_id) + + def AddCommand(self, title, hotkey): + """ + Adds a menu command to the graph. + Once a command is added, a command id is returned. The commands are handled inside the OnCommand() handler + + @return: 0 or the command id + """ + return _idaapi.pyg_add_command(self, title, hotkey) + + def OnRefresh(self): + """ + Event called when the graph is refreshed or first created. + From this event you are supposed to create nodes and edges. + @note: ***It is important to clear previous nodes before adding nodes.*** + @return: Returning true tells the graph viewer to use the items. Otherwise old items will be used. + """ + self.Clear() + + return True + +# def OnActivate(self): +# """Triggered when the graph window gets the focus""" +# print "Activated...." + +# def OnDeactivate(self): +# """Triggered when the graph window loses the focus""" +# print "Deactivated...." + +# def OnSelect(self, node_id): +# """ +# Triggered when a node is being selected +# @return: Return True to allow the node to be selected or False to disallow node selection change +# """ +# # allow selection change +# return True + +# def OnGetText(self, node_id): +# """ +# Triggered when the graph viewer wants the text and color for a given node. +# This callback is triggered one time for a given node (the value will be cached and used later without calling Python). +# When you call refresh then again this callback will be called for each node. +# +# @return: Return a string to describe the node text or return a tuple (node_text, node_color) to describe both text and color +# """ +# return str(self[node_id]) + +# def OnHint(self, node_id): +# """ +# Triggered when the graph viewer wants to retrieve hint text associated with a given node +# +# @return: None if no hint is avail or a string designating the hint +# """ +# return "hint for " + str(node_id) + +# def OnClose(self): +# """Triggered when the graph viewer window is being closed""" +# print "Closing......." + +# def OnClick(self, node_id): +# """ +# Triggered when a node is clicked +# @return: False to ignore the click and true otherwise +# """ +# print "clicked on", self[node_id] +# return True + +# def OnDblClick(self, node_id): +# """ +# Triggerd when a node is double-clicked. +# @note: check OnClick() event +# """ +# print "dblclicked on", self[node_id] +# return True + +# def OnCommand(self, cmd_id): +# """ +# Triggered when a menu command is selected through the menu or its hotkey +# """ +# print "command:", cmd_id + +%} + +%inline %{ +void pyg_refresh(PyObject *self); +void pyg_close(PyObject *self); +Py_ssize_t pyg_add_command(PyObject *self, const char *title, const char *hotkey); +void pyg_select_node(PyObject *self, int nid); +bool pyg_show(PyObject *self); + +%} diff --git a/swig/idaapi.i b/swig/idaapi.i index c1547e5..d530702 100644 --- a/swig/idaapi.i +++ b/swig/idaapi.i @@ -9,6 +9,10 @@ #ifdef HAVE_SSIZE_T #define _SSIZE_T_DEFINED 1 #endif +#if defined(__NT__) && !defined(_WINDOWS_) + #define _WINDOWS_ // kernwin.hpp needs it to declare create_tform() + typedef void *HWND; // we don't need to include windows.h for just this definition +#endif #include "ida.hpp" #include "idp.hpp" #include "allins.hpp" @@ -41,6 +45,9 @@ #include "typeinf.hpp" #include "ua.hpp" #include "xref.hpp" +#ifdef __NT__ + #include "graph.hpp" +#endif %} // Do not create separate wrappers for default arguments @@ -147,6 +154,9 @@ idainfo *get_inf_structure(void) %include "typeinf.i" %include "ua.i" %include "xref.i" +#ifdef __NT__ + %include "graph.i" +#endif %inline { void set_script_timeout(int timeout);