diff options
| author | blasty <blasty@local> | 2026-08-06 15:11:54 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-08-06 15:11:54 +0200 |
| commit | 8d18e16ffbd88579bd35a16b6477cc66771026fb (patch) | |
| tree | 376dbe66d0b4540c3b6e079973d2ce483dccfbd3 /server | |
| parent | graph: a layered CFG layout engine (diff) | |
| download | ida-tui-8d18e16ffbd88579bd35a16b6477cc66771026fb.tar.gz ida-tui-8d18e16ffbd88579bd35a16b6477cc66771026fb.tar.xz ida-tui-8d18e16ffbd88579bd35a16b6477cc66771026fb.zip | |
server: a flowchart tool
Blocks and typed edges for the function at an address -- not their text.
The block body is an address range the client already knows how to render
with heads, and keeping text out is what lets the graph reuse the listing
rows instead of growing a second disassembly renderer.
Diffstat (limited to 'server')
| -rw-r--r-- | server/patch_server.py | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/server/patch_server.py b/server/patch_server.py index c9d2705..73147d0 100644 --- a/server/patch_server.py +++ b/server/patch_server.py @@ -1967,6 +1967,68 @@ def pc_num_format( return out +@tool +@idasync +def flowchart( + addr: Annotated[str, "Address or name inside the function to chart"], +) -> dict: + """Basic-block control-flow graph of the function containing ``addr``. + + Returns the blocks and the edges between them -- NOT their text: the block + body is just an address range, which the client already knows how to render + with ``heads``. Keeping text out means the graph view reuses the exact same + listing rows (colours, operand marks and all) instead of growing a second + disassembly renderer. + + Edge ``kind`` is what the graph view colours by: + * ``fall`` -- control falls through to the next address (IDA draws red) + * ``jump`` -- a taken conditional branch (green) + * ``uncond`` -- the block's only successor (blue) + * ``switch`` -- one of an n-way dispatch + """ + import ida_funcs + import ida_gdl + + try: + ea = parse_address(addr) + except Exception as e: + return {"addr": str(addr), "error": str(e), "blocks": []} + fn = ida_funcs.get_func(ea) + if fn is None: + return {"addr": str(addr), "error": "no function at that address", + "blocks": []} + + fc = ida_gdl.FlowChart(fn, flags=ida_gdl.FC_PREDS) + index = {} + order = [] + for bb in fc: + index[bb.start_ea] = len(order) + order.append(bb) + blocks = [] + for bb in order: + sl = [s for s in bb.succs() if s.start_ea in index] + succs = [] + for s in sl: + if len(sl) > 2: + kind = "switch" + elif s.start_ea == bb.end_ea: + kind = "fall" + else: + kind = "jump" + succs.append([index[s.start_ea], kind]) + blocks.append({ + "id": index[bb.start_ea], + "start": hex(bb.start_ea), + "end": hex(bb.end_ea), + "succs": succs, + }) + return { + "addr": hex(ea), + "func": {"addr": hex(fn.start_ea), "end": hex(fn.end_ea), + "name": ida_funcs.get_func_name(fn.start_ea)}, + "entry": index.get(fn.start_ea, 0), + "blocks": blocks, + } ''' SNIPPET = f"{BEGIN}\n{BODY.strip()}\n{END}\n" |
