Loading...
Loading...
Develop plugins for IDA Pro in Python, using idiomatic patterns, lessons, and tricks, including the Python Domain API (ida-domain). Use when creating both GUI (Qt) and background plugins for inspecting and rendering things program structure, functions, disassembly, cross-references, and strings.
npx skill4agent add hexrayssa/ida-claude-code-plugins ida-plugin-developmenthttps://ida-domain.docs.hex-rays.com/ref/functions/index.mdhttps://ida-domain.docs.hex-rays.com/ref/xrefs/index.mdhttps://ida-domain.docs.hex-rays.com/ref/strings/index.mdbytescommentsdatabaseentriesflowchartfunctionsheadshooksinstructionsnamesoperandssegmentssignature_filesstringstypesxrefswith Database.open(path, ida_options) as db:
db.minimum_ea # Start address
db.maximum_ea # End address
db.metadata # Database metadata
db.architecture # Target architecture
db.functions # All functions (iterable)
db.strings # All strings (iterable)
db.segments # Memory segments
db.names # Symbols and labels
db.entries # Entry points
db.types # Type definitions
db.comments # All comments
db.xrefs # Cross-reference utilities
db.bytes # Byte manipulation
db.instructions # Instruction accessfunc: func_t
for func in db.functions:
name = db.functions.get_name(func)
print(f"{hex(func.start_ea)}: {name} ({func.size} bytes)")func_tclass func_t:
name: str
flags: int
start_ea: int
end_ea: int
size: int
does_return: bool
referers: list[int] # function start addresses
addresses: list[int]
frame_object: tinfo_t
prototype: tinfo_tfor xref in db.xrefs.to_ea(target_addr):
print(f"Referenced from {hex(xref.from_ea)} (type: {xref.type.name})")
for xref in db.xrefs.from_ea(source_addr):
print(f"References {hex(xref.to_ea)}")
for xref in db.xrefs.calls_to_ea(func_addr):
print(f"Called from {hex(xref.from_ea)}")XrefInfoXrefInfo(
from_ea: int,
to_ea: int,
is_code: bool,
type: XrefType,
user: bool,
)db.bytes.get_byte_at(addr)
db.bytes.get_bytes_at(addr)
db.bytes.get_cstring_at(addr)
db.bytes.get_word_at(addr)
db.bytes.get_dword_at(addr)
db.bytes.get_qword_at(addr)
db.bytes.get_disassembly_at(addr)
db.bytes.get_flags_at(addr)ida-plugin.jsonida-plugin.json{
"IDAMetadataDescriptorVersion": 1,
"plugin": {
"name": "ida-terminal-plugin",
"entryPoint": "index.py",
"version": "1.0.0",
"idaVersions": ">=9.2",
"platforms": [
"windows-x86_64",
"linux-x86_64",
"macos-x86_64",
"macos-aarch64",
],
"description": "A lightweight terminal integration for IDA Pro that lets you open a fully functional terminal within the IDA GUI.\nQuickly access shell commands, scripts, or tooling without leaving your reversing environment.",
"license": "MIT",
"logoPath": "ida-plugin.png",
"categories": [
"ui-ux-and-visualization"
],
"keywords": [
"terminal",
"shell",
"cli",
],
"pythonDependencies": [
"pydantic>=2.12"
],
"urls": {
"repository": "https://github.com/williballenthin/idawilli"
},
"authors": [{
"name": "Willi Ballenthin",
"email": "wballenthin@hex-rays.com"
}],
"settings": [
{
"key": "theme",
"type": "string",
"required": true,
"default": "darcula",
"name": "color theme",
"documentation": "the color theme name, picked from https://windowsterminalthemes.dev/",
}
]
}
}./scripts/hcli-package.pyida-config.jsonimport ida_settings
api_key = ida_settings.get_current_plugin_setting("openai_key")plugin_tplugmod_tclass Hooks(idaapi.IDP_Hooks):
def __init__(self, settings):
super().__init__()
self.settings = settings
def ev_get_bg_color(self, color, ea):
mnem = ida_ua.print_insn_mnem(ea)
if mnem == "call" or mnem == "CALL":
bgcolor = ctypes.cast(int(color), ctypes.POINTER(ctypes.c_int))
bgcolor[0] = int(settings.get_setting("bg_color"))
return 1
else:
return 0
class FooPluginMod(ida_idaapi.plugmod_t):
def run(self, arg):
settings = ida_settings.get_current_plugin_settings()
self.hooks = Hooks(settings)
self.hooks.hook()
del(_current)_plugin_settingget(_current)_plugin_settinghas(_current)_plugin_settingset(_current)_plugin_settinglist(_current)_plugin_settingsprintlogging.*foo_entry.pyfoo.pyfoo_entry.pyimport logging
import os
import ida_kernwin
logger = logging.getLogger(__name__)
def should_load():
"""Returns True if IDA 9.2+ is running interactively."""
if not ida_kernwin.is_idaq():
# https://community.hex-rays.com/t/how-to-check-if-idapythonrc-py-is-running-in-ida-pro-or-idalib/297/4
return False
if os.environ.get("IDA_IS_INTERACTIVE") != "1":
# https://community.hex-rays.com/t/how-to-check-if-idapythonrc-py-is-running-in-ida-pro-or-idalib/297/2
return False
kernel_version: tuple[int, ...] = tuple(
int(part) for part in ida_kernwin.get_kernel_version().split(".") if part.isdigit()
) or (0,)
if kernel_version < (9, 2): # type: ignore
logger.warning("IDA too old (must be 9.2+): %s", ida_kernwin.get_kernel_version())
return False
return True
if should_load():
# only attempt to import the plugin once we know the required dependencies are present.
# otherwise we'll hit ImportError and other problems
from foo import foo_plugin_t
def PLUGIN_ENTRY():
return foo_plugin_t()
else:
try:
import ida_idaapi
except ImportError:
import idaapi as ida_idaapi
class foo_nop_plugin_t(ida_idaapi.plugin_t):
flags = ida_idaapi.PLUGIN_HIDE | ida_idaapi.PLUGIN_UNL
wanted_name = "foo disabled"
comment = "foo is disabled for this IDA version"
help = ""
wanted_hotkey = ""
def init(self):
return ida_idaapi.PLUGIN_SKIP
# we have to define this symbol, or IDA logs a message
def PLUGIN_ENTRY():
# we have to return something here, or IDA logs a message
return foo_nop_plugin_t()foo.pyclass foo_plugmod_t(ida_idaapi.plugmod_t):
def __init__(self):
# IDA doesn't invoke this for plugmod_t, only plugin_t
self.init()
def init(self):
# do things here that will always run,
# and don't require the menu entry (edit > plugins > ...) being selected.
#
# note: IDA doesn't call init, we do in __init__
if not ida_auto.auto_is_ok():
# don't capture events before auto-analysis is done, or we get all the system events.
#
# note:
# - when we first load a program, this plugin will be run before auto-analysis is complete
# (actually, before auto-analysis even starts).
# so auto_is_ok() returns False
# - when we load an existing IDB, auto_is_ok() return True.
# so we can safely use this to wait until auto-analysis is complete for the first time.
logger.debug("waiting for auto-analysis to complete before subscribing to events")
ida_auto.auto_wait()
logger.debug("auto-analysis complete, now subscribing to events")
...
def run(self, arg):
# do things here that users invoke via the menu entry (edit > plugins > ...)
...
def term(self):
# cleanup resources, unhook handlers, etc.
...
class foo_plugin_t(ida_idaapi.plugin_t):
flags = ida_idaapi.PLUGIN_MULTI
help = "Do some foo"
comment = ""
wanted_name = "Foo"
wanted_hotkey = ""
def init(self):
return foo_plugmod_t()inittermclass oplog_plugmod_t(ida_idaapi.plugmod_t):
def __init__(self):
self.idb_hooks: IDBChangedHook | None = None
self.location_hooks: UILocationHook | None = None
...
def register_idb_hooks(self):
assert self.events is not None
self.idb_hooks = IDBChangedHook(self.events)
self.idb_hooks.hook()
def unregister_idb_hooks(self):
if self.idb_hooks:
self.idb_hooks.unhook()
def register_location_hooks(self):
assert self.events is not None
self.location_hooks = UILocationHook(self.events)
self.location_hooks.hook()
def unregister_location_hooks(self):
if self.location_hooks:
self.location_hooks.unhook()
def init(self):
...
self.register_idb_hooks()
self.register_location_hooks()
def run(self, arg):
...
def term(self):
# cleanup in reverse order
self.unregister_location_hooks()
self.unregister_idb_hooks()
...ida_expr.add_idc_funcidc.eval_idcterm()import ida_expr
class foo_plugmod_t(ida_idaapi.plugmod_t):
def __init__(self):
self.data: list[str] = []
self.init()
def register_idc_func(self):
data = self.data
def foo_get_data(index: int) -> str:
if 0 <= index < len(data):
return data[index]
return ""
def foo_add_data(value: str) -> int:
data.append(value)
return len(data)
if ida_expr.add_idc_func("foo_get_data", foo_get_data, (ida_expr.VT_LONG,)):
logger.debug("registered foo_get_data IDC function")
else:
logger.warning("failed to register foo_get_data IDC function")
if ida_expr.add_idc_func("foo_add_data", foo_add_data, (ida_expr.VT_STR,)):
logger.debug("registered foo_add_data IDC function")
else:
logger.warning("failed to register foo_add_data IDC function")
def unregister_idc_func(self):
ida_expr.del_idc_func("foo_get_data")
ida_expr.del_idc_func("foo_add_data")
def init(self):
self.register_idc_func()
def term(self):
self.unregister_idc_func()idc.eval_idcimport idc
idc.eval_idc('foo_add_data("hello")')
result = idc.eval_idc('foo_get_data(0)')add_idc_funcida_expr.VT_STRida_expr.VT_LONGida_expr.VT_FLOATimport pydantic
OUR_NETNODE = "$ com.williballenthin.idawilli.foo"
class State(pydantic.BaseModel):
...
def to_json(self):
return self.model_dump_json()
@classmethod
def from_json(cls, json_str: str):
return cls(State.model_validate_json(json_str))
def save_state(state: State):
buf = zlib.compress(state.to_json().encode("utf-8"))
node = ida_netnode.netnode(OUR_NETNODE)
node.setblob(buf, 0, "I")
logger.info("saved state")
def load_state() -> State:
node = ida_netnode.netnode(OUR_NETNODE)
if not node:
logger.info("no existing state")
return State()
buf = node.getblob(0, "I")
if not buf:
logger.info("no existing state (no data)")
return State()
state = State.from_json(zlib.decompress(buf).decode("utf-8"))
logger.info("loaded state")
return state
class UI_Closing_Hooks(ida_kernwin.UI_Hooks):
"""Respond to UI events and save the events into the database."""
# we could also use IDB_Hooks, but I found it less reliable:
# - closebase: "the database will be closed now", however, I couldn't figure out when its actually triggered.
# - savebase: notified during File -> Save, but not File -> Close.
# easier to keep all the hooks in one place.
def __init__(self, events: Events, *args, **kwargs):
super().__init__(*args, **kwargs)
self.events = events
def preprocess_action(self, action: str):
if action == "CloseBase":
# File -> Close
save_events(self.events)
return 0
elif action == "QuitIDA":
# File -> Quit
save_events(self.events)
return 0
elif action == "SaveBase":
# File -> Save
save_events(self.events)
return 0
else:
return 0class UILocationHook(ida_kernwin.UI_Hooks):
def handle_current_address_change(self, ea: int):
...
def handle_current_selection_change(self, start: int, end: int):
...
def screen_ea_changed(self, ea: ida_idaapi.ea_t, prev_ea: ida_idaapi.ea_t) -> None:
if ea == prev_ea:
return
v = ida_kernwin.get_current_viewer()
if ida_kernwin.get_widget_type(v) not in (
ida_kernwin.BWN_HEXVIEW,
ida_kernwin.BWN_DISASM,
# BWN_PSEUDOCODE
# BWN_CUSTVIEW
# BWN_OUTPUT the text area, in the output window
# BWN_CLI the command-line, in the output window
# BWN_STRINGS
# ...
):
return
if ida_kernwin.get_viewer_place_type(v) != ida_kernwin.TCCPT_IDAPLACE:
# other viewers might have other place types, when not address-oriented
return
has_range, start, end = ida_kernwin.read_range_selection(v)
if not has_range:
return self.handle_current_address_change(ea)
if ida_idaapi.BADADDR in (start, end):
return
return self.handle_current_selection_change(start, end)def list_widgets(prefix: str) -> list[str]:
"""Probe A-Z for existing widgets, return found captions.
Args:
prefix: Caption prefix to search for
Returns: List of found widget captions (e.g., ["Foo-A", "Foo-C"])
"""
if not prefix.endswith("-"):
raise ValueError("prefix must end with dash")
found = []
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
caption = f"{prefix}{letter}"
if ida_kernwin.find_widget(caption) is not None:
found.append(caption)
return found
def find_next_available_caption(prefix: str) -> str:
"""Find first gap or next letter for widget caption.
Args:
prefix: Caption prefix to use
Returns: First available caption (e.g., "Foo-B")
Raises:
RuntimeError: If all 26 instances are in use
"""
if not prefix.endswith("-"):
raise ValueError("prefix must end with dash")
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
caption = f"{prefix}{letter}"
if ida_kernwin.find_widget(caption) is None:
return caption
raise RuntimeError("All 26 instances in use")ida_kernwin.PluginFormclass FooForm(ida_kernwin.PluginForm):
def __init__(
self,
caption: str = "Foo-A",
form_registry: dict[str, "FooForm"] | None = None,
) -> None:
super().__init__()
self.TITLE = caption
self.form_registry = form_registry
def OnCreate(self, form):
self.parent = self.FormToPyQtWidget(form)
self.w = FooWidget(parent=self.parent, show_ida_buttons=True)
... # other Qt stuff here
if self.form_registry is not None:
self.form_registry[self.TITLE] = self
def OnClose(self, form):
if self.form_registry is not None:
self.form_registry.pop(self.TITLE, None)
class create_foo_widget_action_handler_t(ida_kernwin.action_handler_t):
def __init__(self, plugmod: "foo_plugmod_t", *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.plugmod = plugmod
def activate(self, ctx):
self.plugmod.create_viewer()
def update(self, ctx):
return ida_kernwin.AST_ENABLE_ALWAYS
class send_to_foo_action_handler_t(ida_kernwin.action_handler_t):
"""Action handler for 'Send to Foo' context menu item."""
def __init__(self, plugmod: "foo_plugmod_t", *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.plugmod = plugmod
def activate(self, ctx):
"""Handle 'Send to Foo' action - always creates new instance."""
v = ida_kernwin.get_current_viewer()
if ida_kernwin.get_widget_type(v) not in (
ida_kernwin.BWN_HEXVIEW,
ida_kernwin.BWN_DISASM,
):
# for example: only allow sending from hexview or disassembly view
return 0
form = self.plugmod.create_viewer()
if form and form.w:
... # do initialization
return 1
def update(self, ctx):
"""Enable action when there's a valid selection."""
v = ida_kernwin.get_current_viewer()
if ida_kernwin.get_widget_type(v) not in (
ida_kernwin.BWN_HEXVIEW,
ida_kernwin.BWN_DISASM,
):
# for example: only allow sending from hexview or disassembly view
return ida_kernwin.AST_DISABLE
return ida_kernwin.AST_ENABLE
class send_to_specific_widget_action_handler_t(ida_kernwin.action_handler_t):
"""Action handler for sending to a specific Foo instance."""
def __init__(
self,
form_registry: dict[str, FooForm],
caption: str,
*args,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
self.form_registry = form_registry
self.caption = caption
def activate(self, ctx):
"""Send selection to specific Foo instance."""
v = ida_kernwin.get_current_viewer()
widget = ida_kernwin.find_widget(self.caption)
if widget is None:
logger.warning(f"Widget {self.caption} not found")
return 0
ida_kernwin.activate_widget(widget, True)
form = self.form_registry.get(self.caption)
if form and hasattr(form, "w"):
# access some specific model methods on the form
...
else:
logger.warning(f"Cannot populate {self.caption} - unable to access form")
return 1
def update(self, ctx):
"""Enable action when there's a valid selection."""
v = ida_kernwin.get_current_viewer()
if ida_kernwin.get_widget_type(v) not in (
ida_kernwin.BWN_HEXVIEW,
ida_kernwin.BWN_DISASM,
):
# for example: only allow sending from hexview or disassembly view
return ida_kernwin.AST_DISABLE
return ida_kernwin.AST_ENABLE
class foo_plugmod_t(ida_idaapi.plugmod_t):
ACTION_NAME = "foo:create"
SEND_ACTION_NAME = "foo:send_selection"
MENU_PATH = "View/Open subviews/Foo"
def __init__(self):
super().__init__()
self.form_registry: dict[str, FooForm] = {}
...
def register_instance_actions(self):
"""Register actions for all existing widget instances."""
existing = list_widgets("Foo-")
for caption in existing:
action_name = f"foo:send_to_{caption.replace('-', '_').lower()}"
if ida_kernwin.unregister_action(action_name):
pass
ida_kernwin.register_action(
ida_kernwin.action_desc_t(
action_name,
f"Send to {caption}",
send_to_specific_widget_action_handler_t(
self.form_registry, caption
),
None,
f"Send selected bytes to {caption}",
-1,
)
)
def create_viewer(self, caption: str | None = None) -> FooForm:
if caption is None:
caption = find_next_available_caption()
form = FooForm(caption, self.form_registry)
form.Show(form.TITLE)
return form
def register_open_action(self):
ida_kernwin.register_action(
ida_kernwin.action_desc_t(
self.ACTION_NAME,
"Foo",
create_foo_widget_action_handler_t(self),
)
)
# TODO: add icon
ida_kernwin.attach_action_to_menu(
self.MENU_PATH, self.ACTION_NAME, ida_kernwin.SETMENU_APP
)
def unregister_open_action(self):
ida_kernwin.unregister_action(self.ACTION_NAME)
ida_kernwin.detach_action_from_menu(self.MENU_PATH, self.ACTION_NAME)
def init(self):
self.register_open_action()
...
def run(self, arg):
self.create_viewer()
def term(self):
...
self.unregister_open_action()def refresh_disassembly():
ida_kernwin.request_refresh(ida_kernwin.IWID_DISASM)
class FooPrefix(ida_lines.user_defined_prefix_t):
ICON = " β "
def __init__(self, marks: set[int]):
super().__init__(len(self.ICON))
self.marks = marks
def get_user_defined_prefix(self, ea, insn, lnnum, indent, line):
if ea in self.marks:
# wrap the icon in color tags so its easy to identify.
# otherwise, the icon may merge with other spans, which
# makes checking for equality more difficult.
return ida_lines.COLSTR(self.ICON, ida_lines.SCOLOR_SYMBOL)
return " " * len(self.ICON)
class FooPrefixPluginMod(ida_idaapi.plugmod_t):
def __init__(self):
self.marks: set[int] = {1, 2, 3}
self.prefixer: FooPrefix | None = None
def run(self, arg):
# self.prefixer is installed simply by constructing it
self.prefixer = FooPrefix(self.marks)
# since we're updating the disassembly listing by adding the line prefix,
# we need to re-render all the lines.
refresh_disassembly()
def term(self):
# gc will clean up prefixer and uninstall it (during plugin termination)
self.prefixer = None
# refresh and remove the prefix entries
refresh_disassembly()class FooHints(ida_kernwin.UI_Hooks):
def __init__(self, notes: dict[int, str], *args, **kwargs):
super().__init__(*args, **kwargs)
self.notes = notes
def get_custom_viewer_hint(self, viewer, place):
if not place:
return
ea = place.toea()
if not ea:
return
if ea not in self.notes:
return
curline = ida_kernwin.get_custom_viewer_curline(viewer, True)
curline = ida_lines.tag_remove(curline)
_, x, _ = ida_kernwin.get_custom_viewer_place(viewer, True)
# example: show on first column
# more advanced: inspect the symbol, and if it matches a query, then show some data
if x == 1:
note = self.notes.get(ea)
if not note:
return
return (f"note: {note}", 1)
class FooHintsPluginMod(ida_idaapi.plugmod_t):
def __init__(self):
self.notes: dict[int, str] = {}
self.hinter: FooHints | None = None
def run(self, arg):
self.hinter = FooHints(self.notes)
self.hinter.hook()
def term(self):
if self.hinter is not None:
self.hinter.unhook()
self.hinter = Noneclass ColorHooks(idaapi.IDP_Hooks):
def ev_get_bg_color(self, color, ea):
"""
Get item background color.
Plugins can hook this callback to color disassembly lines dynamically
// background color in RGB
typedef uint32 bgcolor_t;
ref: https://hex-rays.com/products/ida/support/sdkdoc/pro_8h.html#a3df5040891132e50157aee66affdf1de
args:
color: (bgcolor_t *), out
ea: (::ea_t)
returns:
retval 0: not implemented
retval 1: color set
"""
mnem = ida_ua.print_insn_mnem(ea)
if mnem == "call" or mnem == "CALL":
bgcolor = ctypes.cast(int(color), ctypes.POINTER(ctypes.c_int))
bgcolor[0] = 0xDDDDDD
return 1
else:
return 0
def ev_out_mnem(self, ctx) -> int:
"""
Generate instruction mnemonics.
This callback should append the colored mnemonics to ctx.outbuf
Optional notification, if absent, out_mnem will be called.
args:
ctx: (outctx_t *)
returns:
retval 1: if appended the mnemonics
retval 0: not implemented
"""
mnem = ctx.insn.get_canon_mnem()
if mnem == "call":
# you can manipulate this, but note that it affects `ida_ua.print_insn_mnem` which is inconvenient for formatting.
# also, you only have access to theme colors, like COLOR_PREFIX, not arbitrary control.
ctx.out_custom_mnem("CALL")
return 1
else:
return 0
class ColoringPluginMod(ida_idaapi.plugmod_t):
def __init__(self):
self.hooks: ColorHooks | None = None
def run(self, arg):
self.hooks = ColorHooks()
self.hooks.hook()
def term(self):
if self.hooks is not None:
self.hooks.unhook()
self.hooks = Nonedef addr_from_tag(raw: bytes) -> int:
assert raw[0] == 0x01 # ida_lines.COLOR_ON
assert raw[1] == ida_lines.COLOR_ADDR
addr_hex = raw[2 : 2 + ida_lines.COLOR_ADDR_SIZE].decode("ascii")
try:
# Parse as hex address (IDA uses qsscanf with "%a" format)
return int(addr_hex, 16)
except ValueError:
raise
def get_tagged_line_section_byte_offsets(section: ida_kernwin.tagged_line_section_t) -> tuple[int, int]:
# tagged_line_section_t.byte_offsets is not exposed by swig
# so we parse directly from the string representation (puke)
s = str(section)
text_start_index = s.index("text_start=")
text_end_index = s.index("text_end=")
text_start_s = s[text_start_index + len("text_start=") :].partition(",")[0]
text_end_s = s[text_end_index + len("text_end=") :].partition("}")[0]
return int(text_start_s), int(text_end_s)
@dataclass
class TaggedLineSection:
tag: int
string: str
# valid when the found tag section starts with an embedded address
address: int | None
def get_current_tag(line: str, x: int) -> TaggedLineSection:
ret = TaggedLineSection(ida_lines.COLOR_DEFAULT, line, None)
tls = ida_kernwin.tagged_line_sections_t()
if not ida_kernwin.parse_tagged_line_sections(tls, line):
return ret
# find any section at the X coordinate
current_section = tls.nearest_at(x, 0) # 0 = any tag
if not current_section:
# TODO: we only want the section that isn't tagged
# while there might be a section totally before or totally after x.
return ret
ret.tag = current_section.tag
boring_line = ida_lines.tag_remove(line)
ret.string = boring_line[current_section.start : current_section.start + current_section.length]
# try to find an embedded address at the start of the current segment
current_section_start, _ = get_tagged_line_section_byte_offsets(current_section)
addr_section = tls.nearest_before(current_section, x, ida_lines.COLOR_ADDR)
if addr_section:
addr_section_start, _ = get_tagged_line_section_byte_offsets(addr_section)
# addr_section_start initially points just after the address data (ON ADDR 001122...FF)
# so rewind to the start of the tag (16 bytes of hex integer, 2 bytes of tags "ON ADDR")
addr_tag_start = addr_section_start - (ida_lines.COLOR_ADDR_SIZE + 2)
assert addr_tag_start >= 0
# and this should match current_section_start, since that points just after the tag "ON SYMBOL"
# if it doesn't, we're dealing with an edge case we didn't prepare for
# maybe like multiple ADDR tags or something.
# skip those and stick to things we know.
if current_section_start == addr_tag_start:
raw = line.encode("utf-8")
addr = addr_from_tag(raw[addr_tag_start : addr_tag_start + ida_lines.COLOR_ADDR_SIZE + 2])
ret.address = addr
return ret
class foo_viewer_t(ida_kernwin.simplecustviewer_t):
TITLE = "foo"
def __init__(self):
super().__init__()
self.timer: QtCore.QTimer = QtCore.QTimer()
self.timer.timeout.connect(self.on_timer_timeout)
def Create(self):
if not super().Create(self.TITLE):
return False
self.render()
return True
def Show(self, *args):
if not super().Show(*args):
return False
ida_kernwin.attach_action_to_popup(self.GetWidget(), None, some_action_handler_t.ACTION_NAME)
return True
def on_timer_timeout(self):
self.render()
def OnClose(self):
self.timer.stop()
def render(self):
self.ClearLines()
self.AddLine(datetime.datetime.now.isoformat())
self.AddLine(ida_lines.COLSTR(ida_lines.tag_addr(0x401000) + "sub_401000", ida_lines.SCOLOR_CNAME))
def OnDblClick(self, shift):
line = self.GetCurrentLine()
if not line:
return False
_linen, x, _y = self.GetPos()
section = get_current_tag(line, x)
if section.address is not None:
ida_kernwin.jumpto(section.address)
item_address = ida_name.get_name_ea(0, section.string)
if item_address != ida_idaapi.BADADDR:
logger.debug(f"found address for '{section.string}': {item_address:x}")
ida_kernwin.jumpto(item_address)
return True # handled