diff --git a/UserComment.py b/UserComment.py index 5418a78..104ffcf 100644 --- a/UserComment.py +++ b/UserComment.py @@ -8,6 +8,8 @@ import ida_hexrays import ida_nalt import pickle +import ida_segment +import ida_funcs title = "Comments" @@ -17,29 +19,111 @@ class UserAddedComments(): def __init__(self): self.netnode = ida_netnode.netnode() - self.netnode.create("$ UserAddedComments") + node_name = "$ user_comments" + # 尝试获取现有的 netnode + self.netnode = ida_netnode.netnode(node_name) + if not self.netnode: + # 如果不存在则创建新的 + self.netnode = ida_netnode.netnode() + self.netnode.create(node_name) self.imagebase = ida_nalt.get_imagebase() + if self.imagebase == ida_idaapi.BADADDR: + self.imagebase = 0 + self.comments = {} self.load_comments() def save_comments(self): - blob = pickle.dumps(self.comments) - self.netnode.setblob(blob, 0, 'C') + try: + # 清理无效地址的注释 + valid_comments = {} + for (offset, cmt_type, line_num), comment in self.comments.items(): + addr = offset + self.imagebase + if addr != ida_idaapi.BADADDR and addr < 0xFFFFFFFFFFFFFFFF: + valid_comments[(offset, cmt_type, line_num)] = comment + + # 更新注释字典 + self.comments = valid_comments + + # 保存到数据库 + blob = pickle.dumps(self.comments) + self.netnode.setblob(blob, 0, 'C') + print(f"Saved {len(self.comments)} comments") + except Exception as e: + print(f"Save comments error: {e}") def load_comments(self): - blob = self.netnode.getblob(0, 'C') - if blob is not None: - self.comments = pickle.loads(blob) - else: + try: + blob = self.netnode.getblob(0, 'C') + if blob is not None: + self.comments = pickle.loads(blob) + print("加载的注释详情:") + for (offset, cmt_type, line_num), comment in self.comments.items(): + print(f"地址: {hex(offset + self.imagebase)}, 类型: {cmt_type}, 行号: {line_num}, 内容: {comment}") + else: + self.comments = {} + print(f"总共加载了 {len(self.comments)} 条注释") + except Exception as e: + print(f"加载注释时出错: {e}") self.comments = {} def add_comment(self, ea, cmt_type, comment, line_num=None): - offset = ea - self.imagebase - key = (offset, cmt_type, line_num) - if not comment: - self.comments.pop(key, 0) - else: - self.comments[key] = comment - self.save_comments() + try: + # 确保地址有效 + if ea == ida_idaapi.BADADDR: + print(f"Invalid address: {hex(ea)}") + return + + offset = ea - self.imagebase + key = (offset, cmt_type, line_num) + + # 确保注释是字符串类型 + if comment is not None: + comment = str(comment) + + print(f"Adding comment at address {hex(ea)} (offset {hex(offset)})") + print(f"Comment type: {cmt_type}, line: {line_num}, content: {comment}") + + if not comment: + self.comments.pop(key, 0) + else: + self.comments[key] = comment + self.save_comments() + except Exception as e: + print(f"Error in add_comment: {e}") + + def load_all_pseudocode_comments(self): + try: + # 遍历所有函数 + for func in ida_funcs.get_func_ranges(): + # 尝试反编译每个函数 + cfunc = ida_hexrays.decompile(func.start_ea) + if cfunc: + # 获取该函数的所有用户注释 + user_cmts = ida_hexrays.restore_user_cmts(cfunc.entry_ea) + if user_cmts: + for tl, cmt in user_cmts.items(): + loc = ida_hexrays.treeloc_t() + loc.ea = tl.ea + loc.itp = tl.itp + # 保存注释 + self.add_comment(loc.ea, 'pseudocode', cmt) + except Exception as e: + print(f"Error loading all pseudocode comments: {e}") + + def clear_invalid_comments(self): + """清理所有无效地址的注释""" + try: + valid_comments = {} + for (offset, cmt_type, line_num), comment in self.comments.items(): + addr = offset + self.imagebase + if addr != ida_idaapi.BADADDR and addr < 0xFFFFFFFFFFFFFFFF: + valid_comments[(offset, cmt_type, line_num)] = comment + + self.comments = valid_comments + self.save_comments() + print(f"Cleaned up comments, {len(self.comments)} valid comments remaining") + except Exception as e: + print(f"Error clearing invalid comments: {e}") class UIHooks(ida_kernwin.UI_Hooks): @@ -57,9 +141,28 @@ def __init__(self, usr_cmt): ida_hexrays.Hexrays_Hooks.__init__(self) self.usr_cmt = usr_cmt + def func_printed(self, cfunc): + try: + # 当伪代码生成时加载注释 + user_cmts = ida_hexrays.restore_user_cmts(cfunc.entry_ea) + if user_cmts: + for tl, cmt in user_cmts.items(): + loc = ida_hexrays.treeloc_t() + loc.ea = tl.ea + loc.itp = tl.itp + self.usr_cmt.add_comment(loc.ea, 'pseudocode', cmt) + return 0 + except Exception as e: + print(f"Error in func_printed: {e}") + return 0 + def cmt_changed(self, cfunc, loc, cmt): - self.usr_cmt.add_comment(loc.ea, 'pseudocode', cmt) - return 0 + try: + self.usr_cmt.add_comment(loc.ea, 'pseudocode', cmt) + return 0 + except Exception as e: + print(f"Error in cmt_changed: {e}") + return 0 class DisasmHooks(ida_idp.IDB_Hooks): @@ -68,42 +171,37 @@ def __init__(self, usr_cmt): self.usr_cmt = usr_cmt self.rebased = False - # hook common and repeatable cmts + # 常规注释和可重复注释 def changing_cmt(self, ea, is_repeatable, new_comment): - cur_ea = idc.here() - if cur_ea == ea: - # solve start_ea problems - cur = ida_kernwin.get_cursor() - if (cur != (True, 0, 0)): - # Fix rebasing bug: Rebase pragram will trigger 'changing_cmt', causing to capture auto cmts at ea. - if self.rebased: - self.rebased = False - return 0 - if is_repeatable: - self.usr_cmt.add_comment(ea, 'repeatable', new_comment) - else: - self.usr_cmt.add_comment(ea, 'common', new_comment) - return 0 + try: + cmt_type = 'repeatable' if is_repeatable else 'common' + self.usr_cmt.add_comment(ea, cmt_type, new_comment) + return 0 + except Exception as e: + print(f"Error in changing_cmt: {e}") + return 0 - # hook anterior and posterior cmts + # 前置注释和后置注释 def extra_cmt_changed(self, ea, line_idx, cmt): - cur_ea = idc.here() - if cur_ea == ea: - cur = ida_kernwin.get_cursor() - if (cur != (True, 0, 0)): - if line_idx // 1000 == 1: # line_idx = 1xxx - self.usr_cmt.add_comment(ea, 'anterior', cmt, line_num=line_idx % 1000) - if line_idx // 1000 == 2: # line_idx = 2xxx - self.usr_cmt.add_comment(ea, 'posterior', cmt, line_num=line_idx % 1000) - return 0 + try: + if line_idx // 1000 == 1: # 前置注释 (line_idx = 1xxx) + self.usr_cmt.add_comment(ea, 'anterior', cmt, line_num=line_idx % 1000) + elif line_idx // 1000 == 2: # 后置注释 (line_idx = 2xxx) + self.usr_cmt.add_comment(ea, 'posterior', cmt, line_num=line_idx % 1000) + return 0 + except Exception as e: + print(f"Error in extra_cmt_changed: {e}") + return 0 - # hook Function cmts and repeatable Function cmts - def changing_range_cmt(self, kind, a, cmt, is_repeatable): - if is_repeatable: - self.usr_cmt.add_comment(a.start_ea, 'func_repeatable', cmt) - else: - self.usr_cmt.add_comment(a.start_ea, 'func_common', cmt) - return 0 + # 函数注释和可重复函数注释 + def changing_range_cmt(self, kind, a, cmt, repeatable): + try: + cmt_type = 'func_repeatable' if repeatable else 'func_common' + self.usr_cmt.add_comment(a.start_ea, cmt_type, cmt) + return 0 + except Exception as e: + print(f"Error in changing_range_cmt: {e}") + return 0 # program image rebased def allsegs_moved(self, info): @@ -119,14 +217,34 @@ def __init__(self, usr_cmt): [ ["Address", 10 | ida_kernwin.Choose.CHCOL_HEX], ["Type", 20 | ida_kernwin.Choose.CHCOL_PLAIN], ["Comments", 30 | ida_kernwin.Choose.CHCOL_PLAIN]], - flags = ida_kernwin.Choose.CH_CAN_REFRESH) + flags = ida_kernwin.Choose.CH_CAN_REFRESH | ida_kernwin.Choose.CH_CAN_DEL) self.usr_cmt = usr_cmt self.items = [] + self.OnInit() def OnInit(self): - self.usr_cmt.load_comments() # load comments again - self.items = [ [hex(k[0] + self.usr_cmt.imagebase), k[1], v] for k, v in self.usr_cmt.comments.items() ] - return True + try: + self.items = [] + # 强制重新加载注释 + self.usr_cmt.load_comments() + + print(f"Comments count: {len(self.usr_cmt.comments)}") # 调试信息 + + for (offset, cmt_type, line_num), comment in self.usr_cmt.comments.items(): + if comment: # 只添加非空注释 + addr = offset + self.usr_cmt.imagebase + type_str = cmt_type + if line_num is not None: + type_str = f"{cmt_type}:{line_num}" + self.items.append([hex(addr), type_str, str(comment)]) + + # 按地址排序 + self.items.sort(key=lambda x: int(x[0], 16)) + print(f"Loaded items: {len(self.items)}") # 调试信息 + return True + except Exception as e: + print(f"OnInit error: {e}") + return False def OnGetSize(self): return len(self.items) @@ -145,6 +263,28 @@ def OnSelectLine(self, n): addr = int(selected_item[0], 16) ida_kernwin.jumpto(addr) + def OnDeleteLine(self, n): + try: + selected_item = self.items[n] + addr = int(selected_item[0], 16) + type_str = selected_item[1] + + # 解析注释类型和行号 + cmt_type = type_str + line_num = None + if ':' in type_str: + cmt_type, line_num = type_str.split(':') + line_num = int(line_num) + + # 调用 add_comment 传入空注释来删除 + self.usr_cmt.add_comment(addr, cmt_type, None, line_num) + + print(f"Deleted comment at {hex(addr)}, type: {cmt_type}") + return [ida_kernwin.Choose.ALL_CHANGED] + except Exception as e: + print(f"Error deleting comment: {e}") + return [ida_kernwin.Choose.NOTHING_CHANGED] + def register_open_action(cmt_view): """ @@ -180,30 +320,55 @@ class my_plugin_t(ida_idaapi.plugin_t): help = "" def init(self): - self.usr_cmt = UserAddedComments() # Create Comments instance here - - self.idb_hook = DisasmHooks(self.usr_cmt) # Hook disassembly comments(common, repeatable, anterior, posterior cmts) - self.idb_hook.hook() - - self.ray_hook = PseudoHooks(self.usr_cmt) # Hook pseudo-code comments(cmts in "F5" pseudo-code view) - self.ray_hook.hook() - - self.cmt_view = CommentViewer(self.usr_cmt) # Create comment viewer instance - - register_open_action(self.cmt_view) # Register to desktop widget and bind shortcut - - self.ui_hook = UIHooks(self.cmt_view) # Refresh commnets viewer in real time - self.ui_hook.hook() - return ida_idaapi.PLUGIN_KEEP # Keep us in the memory + try: + self.usr_cmt = UserAddedComments() + # 清理无效注释 + self.usr_cmt.clear_invalid_comments() + + # 获取当前段的基址作为测试地址 + first_seg = ida_segment.get_first_seg() + if first_seg: + test_ea = first_seg.start_ea + print(f"Testing with address: {hex(test_ea)}") + #self.usr_cmt.add_comment(test_ea, 'common', "Test Comment 1") + #self.usr_cmt.add_comment(test_ea + 4, 'repeatable', "Test Comment 2") + + # 确保钩子正确安装 + self.idb_hooks = DisasmHooks(self.usr_cmt) + if not self.idb_hooks.hook(): + print("Failed to install IDB hooks") + + self.ray_hooks = PseudoHooks(self.usr_cmt) + if not self.ray_hooks.hook(): + print("Failed to install Hexrays hooks") + + self.cmt_view = CommentViewer(self.usr_cmt) + register_open_action(self.cmt_view) + + self.ui_hooks = UIHooks(self.cmt_view) + if not self.ui_hooks.hook(): + print("Failed to install UI hooks") + + print("Plugin initialized successfully") + return ida_idaapi.PLUGIN_KEEP + except Exception as e: + print(f"Plugin initialization error: {e}") + return ida_idaapi.PLUGIN_SKIP def run(self, arg): #self.cmt_view.Show() pass def term(self): - self.ui_hook.unhook() - self.ray_hook.unhook() - self.idb_hook.unhook() + try: + if hasattr(self, 'ui_hooks'): + self.ui_hooks.unhook() + if hasattr(self, 'ray_hooks'): + self.ray_hooks.unhook() + if hasattr(self, 'idb_hooks'): + self.idb_hooks.unhook() + except Exception as e: + print(f"Plugin termination error: {e}") return