ida-pro-mcp-xjoker 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. ida_pro_mcp/__init__.py +0 -0
  2. ida_pro_mcp/__main__.py +6 -0
  3. ida_pro_mcp/ida_mcp/__init__.py +68 -0
  4. ida_pro_mcp/ida_mcp/api_analysis.py +1296 -0
  5. ida_pro_mcp/ida_mcp/api_core.py +337 -0
  6. ida_pro_mcp/ida_mcp/api_debug.py +617 -0
  7. ida_pro_mcp/ida_mcp/api_memory.py +304 -0
  8. ida_pro_mcp/ida_mcp/api_modify.py +406 -0
  9. ida_pro_mcp/ida_mcp/api_python.py +179 -0
  10. ida_pro_mcp/ida_mcp/api_resources.py +295 -0
  11. ida_pro_mcp/ida_mcp/api_stack.py +167 -0
  12. ida_pro_mcp/ida_mcp/api_types.py +480 -0
  13. ida_pro_mcp/ida_mcp/auth.py +166 -0
  14. ida_pro_mcp/ida_mcp/cache.py +232 -0
  15. ida_pro_mcp/ida_mcp/config.py +228 -0
  16. ida_pro_mcp/ida_mcp/framework.py +547 -0
  17. ida_pro_mcp/ida_mcp/http.py +859 -0
  18. ida_pro_mcp/ida_mcp/port_utils.py +104 -0
  19. ida_pro_mcp/ida_mcp/rpc.py +187 -0
  20. ida_pro_mcp/ida_mcp/server_manager.py +339 -0
  21. ida_pro_mcp/ida_mcp/sync.py +233 -0
  22. ida_pro_mcp/ida_mcp/tests/__init__.py +14 -0
  23. ida_pro_mcp/ida_mcp/tests/test_api_analysis.py +336 -0
  24. ida_pro_mcp/ida_mcp/tests/test_api_core.py +237 -0
  25. ida_pro_mcp/ida_mcp/tests/test_api_memory.py +207 -0
  26. ida_pro_mcp/ida_mcp/tests/test_api_modify.py +123 -0
  27. ida_pro_mcp/ida_mcp/tests/test_api_resources.py +199 -0
  28. ida_pro_mcp/ida_mcp/tests/test_api_stack.py +77 -0
  29. ida_pro_mcp/ida_mcp/tests/test_api_types.py +249 -0
  30. ida_pro_mcp/ida_mcp/ui.py +357 -0
  31. ida_pro_mcp/ida_mcp/utils.py +1186 -0
  32. ida_pro_mcp/ida_mcp/zeromcp/__init__.py +5 -0
  33. ida_pro_mcp/ida_mcp/zeromcp/jsonrpc.py +384 -0
  34. ida_pro_mcp/ida_mcp/zeromcp/mcp.py +883 -0
  35. ida_pro_mcp/ida_mcp.py +186 -0
  36. ida_pro_mcp/idalib_server.py +354 -0
  37. ida_pro_mcp/idalib_session_manager.py +259 -0
  38. ida_pro_mcp/server.py +1060 -0
  39. ida_pro_mcp/test.py +170 -0
  40. ida_pro_mcp_xjoker-1.0.1.dist-info/METADATA +405 -0
  41. ida_pro_mcp_xjoker-1.0.1.dist-info/RECORD +45 -0
  42. ida_pro_mcp_xjoker-1.0.1.dist-info/WHEEL +5 -0
  43. ida_pro_mcp_xjoker-1.0.1.dist-info/entry_points.txt +4 -0
  44. ida_pro_mcp_xjoker-1.0.1.dist-info/licenses/LICENSE +21 -0
  45. ida_pro_mcp_xjoker-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,179 @@
1
+ from typing import Annotated
2
+ import ast
3
+ import io
4
+ import sys
5
+ import idaapi
6
+ import idc
7
+ import ida_bytes
8
+ import ida_dbg
9
+ import ida_entry
10
+ import ida_frame
11
+ import ida_funcs
12
+ import ida_hexrays
13
+ import ida_ida
14
+ import ida_kernwin
15
+ import ida_lines
16
+ import ida_nalt
17
+ import ida_name
18
+ import ida_segment
19
+ import ida_typeinf
20
+ import ida_xref
21
+
22
+ from .rpc import tool, unsafe
23
+ from .sync import idasync
24
+ from .utils import parse_address, get_function
25
+
26
+ # ============================================================================
27
+ # Python Evaluation
28
+ # ============================================================================
29
+
30
+
31
+ @tool
32
+ @idasync
33
+ @unsafe
34
+ def py_eval(
35
+ code: Annotated[str, "Python code"],
36
+ ) -> dict:
37
+ """Execute Python code in IDA context.
38
+ Returns dict with result/stdout/stderr.
39
+ Has access to all IDA API modules.
40
+ Supports Jupyter-style evaluation."""
41
+ # Capture stdout/stderr
42
+ stdout_capture = io.StringIO()
43
+ stderr_capture = io.StringIO()
44
+ old_stdout = sys.stdout
45
+ old_stderr = sys.stderr
46
+
47
+ try:
48
+ sys.stdout = stdout_capture
49
+ sys.stderr = stderr_capture
50
+
51
+ # Create execution context with IDA modules (lazy import to avoid errors)
52
+ def lazy_import(module_name):
53
+ try:
54
+ return __import__(module_name)
55
+ except Exception:
56
+ return None
57
+
58
+ exec_globals = {
59
+ "__builtins__": __builtins__,
60
+ "idaapi": idaapi,
61
+ "idc": idc,
62
+ "idautils": lazy_import("idautils"),
63
+ "ida_allins": lazy_import("ida_allins"),
64
+ "ida_auto": lazy_import("ida_auto"),
65
+ "ida_bitrange": lazy_import("ida_bitrange"),
66
+ "ida_bytes": ida_bytes,
67
+ "ida_dbg": ida_dbg,
68
+ "ida_dirtree": lazy_import("ida_dirtree"),
69
+ "ida_diskio": lazy_import("ida_diskio"),
70
+ "ida_entry": ida_entry,
71
+ "ida_expr": lazy_import("ida_expr"),
72
+ "ida_fixup": lazy_import("ida_fixup"),
73
+ "ida_fpro": lazy_import("ida_fpro"),
74
+ "ida_frame": ida_frame,
75
+ "ida_funcs": ida_funcs,
76
+ "ida_gdl": lazy_import("ida_gdl"),
77
+ "ida_graph": lazy_import("ida_graph"),
78
+ "ida_hexrays": ida_hexrays,
79
+ "ida_ida": ida_ida,
80
+ "ida_idd": lazy_import("ida_idd"),
81
+ "ida_idp": lazy_import("ida_idp"),
82
+ "ida_ieee": lazy_import("ida_ieee"),
83
+ "ida_kernwin": ida_kernwin,
84
+ "ida_libfuncs": lazy_import("ida_libfuncs"),
85
+ "ida_lines": ida_lines,
86
+ "ida_loader": lazy_import("ida_loader"),
87
+ "ida_merge": lazy_import("ida_merge"),
88
+ "ida_mergemod": lazy_import("ida_mergemod"),
89
+ "ida_moves": lazy_import("ida_moves"),
90
+ "ida_nalt": ida_nalt,
91
+ "ida_name": ida_name,
92
+ "ida_netnode": lazy_import("ida_netnode"),
93
+ "ida_offset": lazy_import("ida_offset"),
94
+ "ida_pro": lazy_import("ida_pro"),
95
+ "ida_problems": lazy_import("ida_problems"),
96
+ "ida_range": lazy_import("ida_range"),
97
+ "ida_regfinder": lazy_import("ida_regfinder"),
98
+ "ida_registry": lazy_import("ida_registry"),
99
+ "ida_search": lazy_import("ida_search"),
100
+ "ida_segment": ida_segment,
101
+ "ida_segregs": lazy_import("ida_segregs"),
102
+ "ida_srclang": lazy_import("ida_srclang"),
103
+ "ida_strlist": lazy_import("ida_strlist"),
104
+ "ida_struct": lazy_import("ida_struct"),
105
+ "ida_tryblks": lazy_import("ida_tryblks"),
106
+ "ida_typeinf": ida_typeinf,
107
+ "ida_ua": lazy_import("ida_ua"),
108
+ "ida_undo": lazy_import("ida_undo"),
109
+ "ida_xref": ida_xref,
110
+ "ida_enum": lazy_import("ida_enum"),
111
+ "parse_address": parse_address,
112
+ "get_function": get_function,
113
+ }
114
+
115
+ result_value = None
116
+ exec_locals = {}
117
+
118
+ # Parse code with AST to properly handle execution
119
+ try:
120
+ tree = ast.parse(code)
121
+ except SyntaxError:
122
+ # If parsing fails, fall back to direct exec
123
+ exec(code, exec_globals, exec_locals)
124
+ exec_globals.update(exec_locals)
125
+ if "result" in exec_locals:
126
+ result_value = str(exec_locals["result"])
127
+ elif exec_locals:
128
+ last_key = list(exec_locals.keys())[-1]
129
+ result_value = str(exec_locals[last_key])
130
+ else:
131
+ if not tree.body:
132
+ # Empty code
133
+ pass
134
+ elif len(tree.body) == 1 and isinstance(tree.body[0], ast.Expr):
135
+ # Single expression - use eval
136
+ result_value = str(eval(code, exec_globals))
137
+ elif isinstance(tree.body[-1], ast.Expr):
138
+ # Multiple statements, last one is an expression (Jupyter-style)
139
+ # Execute all statements except the last
140
+ if len(tree.body) > 1:
141
+ exec_tree = ast.Module(body=tree.body[:-1], type_ignores=[])
142
+ exec(compile(exec_tree, "<string>", "exec"), exec_globals, exec_locals)
143
+ exec_globals.update(exec_locals)
144
+ # Eval only the last expression
145
+ eval_tree = ast.Expression(body=tree.body[-1].value)
146
+ result_value = str(eval(compile(eval_tree, "<string>", "eval"), exec_globals))
147
+ else:
148
+ # All statements (no trailing expression)
149
+ exec(code, exec_globals, exec_locals)
150
+ exec_globals.update(exec_locals)
151
+ # Return 'result' variable if explicitly set
152
+ if "result" in exec_locals:
153
+ result_value = str(exec_locals["result"])
154
+ # Return last assigned variable
155
+ elif exec_locals:
156
+ last_key = list(exec_locals.keys())[-1]
157
+ result_value = str(exec_locals[last_key])
158
+
159
+ # Collect output
160
+ stdout_text = stdout_capture.getvalue()
161
+ stderr_text = stderr_capture.getvalue()
162
+
163
+ return {
164
+ "result": result_value or "",
165
+ "stdout": stdout_text,
166
+ "stderr": stderr_text,
167
+ }
168
+
169
+ except Exception:
170
+ import traceback
171
+
172
+ return {
173
+ "result": "",
174
+ "stdout": "",
175
+ "stderr": traceback.format_exc(),
176
+ }
177
+ finally:
178
+ sys.stdout = old_stdout
179
+ sys.stderr = old_stderr
@@ -0,0 +1,295 @@
1
+ """MCP Resources - browsable IDB state
2
+
3
+ Resources represent browsable state (read-only data) following MCP's philosophy.
4
+ Use tools for actions that modify state or perform expensive computations.
5
+ """
6
+
7
+ from typing import Annotated
8
+
9
+ import ida_funcs
10
+ import ida_nalt
11
+ import ida_segment
12
+ import ida_typeinf
13
+ import idaapi
14
+ import idautils
15
+ import idc
16
+
17
+ from .rpc import resource
18
+ from .sync import idasync
19
+ from .utils import (
20
+ Metadata,
21
+ Segment,
22
+ StructureDefinition,
23
+ StructureMember,
24
+ get_image_size,
25
+ parse_address,
26
+ )
27
+
28
+
29
+ # ============================================================================
30
+ # Core IDB State
31
+ # ============================================================================
32
+
33
+
34
+ @resource("ida://idb/metadata")
35
+ @idasync
36
+ def idb_metadata_resource() -> Metadata:
37
+ """Get IDB file metadata (path, arch, base address, size, hashes)"""
38
+ import hashlib
39
+
40
+ path = idc.get_idb_path()
41
+ module = ida_nalt.get_root_filename()
42
+ base = hex(idaapi.get_imagebase())
43
+ size = hex(get_image_size())
44
+
45
+ input_path = ida_nalt.get_input_file_path()
46
+ try:
47
+ with open(input_path, "rb") as f:
48
+ data = f.read()
49
+ md5 = hashlib.md5(data).hexdigest()
50
+ sha256 = hashlib.sha256(data).hexdigest()
51
+ import zlib
52
+
53
+ crc32 = hex(zlib.crc32(data) & 0xFFFFFFFF)
54
+ filesize = hex(len(data))
55
+ except Exception:
56
+ md5 = sha256 = crc32 = filesize = "unavailable"
57
+
58
+ return Metadata(
59
+ path=path,
60
+ module=module,
61
+ base=base,
62
+ size=size,
63
+ md5=md5,
64
+ sha256=sha256,
65
+ crc32=crc32,
66
+ filesize=filesize,
67
+ )
68
+
69
+
70
+ @resource("ida://idb/segments")
71
+ @idasync
72
+ def idb_segments_resource() -> list[Segment]:
73
+ """Get all memory segments with permissions"""
74
+ segments = []
75
+ for seg_ea in idautils.Segments():
76
+ seg = idaapi.getseg(seg_ea)
77
+ if seg:
78
+ perms = []
79
+ if seg.perm & idaapi.SEGPERM_READ:
80
+ perms.append("r")
81
+ if seg.perm & idaapi.SEGPERM_WRITE:
82
+ perms.append("w")
83
+ if seg.perm & idaapi.SEGPERM_EXEC:
84
+ perms.append("x")
85
+
86
+ segments.append(
87
+ Segment(
88
+ name=ida_segment.get_segm_name(seg),
89
+ start=hex(seg.start_ea),
90
+ end=hex(seg.end_ea),
91
+ size=hex(seg.size()),
92
+ permissions="".join(perms) if perms else "---",
93
+ )
94
+ )
95
+ return segments
96
+
97
+
98
+ @resource("ida://idb/entrypoints")
99
+ @idasync
100
+ def idb_entrypoints_resource() -> list[dict]:
101
+ """Get entry points (main, TLS callbacks, etc.)"""
102
+ entrypoints = []
103
+ entry_count = ida_nalt.get_entry_qty()
104
+ for i in range(entry_count):
105
+ ordinal = ida_nalt.get_entry_ordinal(i)
106
+ ea = ida_nalt.get_entry(ordinal)
107
+ name = ida_nalt.get_entry_name(ordinal)
108
+ entrypoints.append({"addr": hex(ea), "name": name, "ordinal": ordinal})
109
+ return entrypoints
110
+
111
+
112
+ # ============================================================================
113
+ # UI State
114
+ # ============================================================================
115
+
116
+
117
+ @resource("ida://cursor")
118
+ @idasync
119
+ def cursor_resource() -> dict:
120
+ """Get current cursor position and function"""
121
+ import ida_kernwin
122
+
123
+ ea = ida_kernwin.get_screen_ea()
124
+ func = idaapi.get_func(ea)
125
+
126
+ result = {"addr": hex(ea)}
127
+ if func:
128
+ try:
129
+ func_name = func.get_name()
130
+ except AttributeError:
131
+ func_name = ida_funcs.get_func_name(func.start_ea)
132
+
133
+ result["function"] = {
134
+ "addr": hex(func.start_ea),
135
+ "name": func_name,
136
+ }
137
+
138
+ return result
139
+
140
+
141
+ @resource("ida://selection")
142
+ @idasync
143
+ def selection_resource() -> dict:
144
+ """Get current selection range (if any)"""
145
+ import ida_kernwin
146
+
147
+ start = ida_kernwin.read_range_selection(None)
148
+ if start:
149
+ return {"start": hex(start[0]), "end": hex(start[1]) if start[1] else None}
150
+ return {"selection": None}
151
+
152
+
153
+ # ============================================================================
154
+ # Type Information
155
+ # ============================================================================
156
+
157
+
158
+ @resource("ida://types")
159
+ @idasync
160
+ def types_resource() -> list[dict]:
161
+ """Get all local types"""
162
+ types = []
163
+ for ordinal in range(1, ida_typeinf.get_ordinal_qty(None)):
164
+ tif = ida_typeinf.tinfo_t()
165
+ if tif.get_numbered_type(None, ordinal):
166
+ name = tif.get_type_name()
167
+ types.append({"ordinal": ordinal, "name": name, "type": str(tif)})
168
+ return types
169
+
170
+
171
+ @resource("ida://structs")
172
+ @idasync
173
+ def structs_resource() -> list[dict]:
174
+ """Get all structures/unions"""
175
+ structs = []
176
+ limit = ida_typeinf.get_ordinal_limit()
177
+ for ordinal in range(1, limit):
178
+ tif = ida_typeinf.tinfo_t()
179
+ if tif.get_numbered_type(None, ordinal) and tif.is_udt():
180
+ udt_data = ida_typeinf.udt_type_data_t()
181
+ is_union = False
182
+ if tif.get_udt_details(udt_data):
183
+ is_union = udt_data.is_union
184
+ structs.append(
185
+ {
186
+ "name": tif.get_type_name(),
187
+ "size": hex(tif.get_size()),
188
+ "is_union": is_union,
189
+ }
190
+ )
191
+ return structs
192
+
193
+
194
+ @resource("ida://struct/{name}")
195
+ @idasync
196
+ def struct_name_resource(name: Annotated[str, "Structure name"]) -> dict:
197
+ """Get structure definition with fields"""
198
+ tif = ida_typeinf.tinfo_t()
199
+ if not tif.get_named_type(None, name):
200
+ return {"error": f"Structure not found: {name}"}
201
+
202
+ if not tif.is_udt():
203
+ return {"error": f"'{name}' is not a structure/union"}
204
+
205
+ udt_data = ida_typeinf.udt_type_data_t()
206
+ if not tif.get_udt_details(udt_data):
207
+ return {"error": f"Failed to get struct details for '{name}'"}
208
+
209
+ members = []
210
+ for member in udt_data:
211
+ members.append(
212
+ StructureMember(
213
+ name=member.name,
214
+ offset=hex(member.offset // 8),
215
+ size=hex(member.size // 8),
216
+ type=str(member.type),
217
+ )
218
+ )
219
+
220
+ return StructureDefinition(name=name, size=hex(tif.get_size()), members=members)
221
+
222
+
223
+ # ============================================================================
224
+ # Import/Export Lookup by Name
225
+ # ============================================================================
226
+
227
+
228
+ @resource("ida://import/{name}")
229
+ @idasync
230
+ def import_name_resource(name: Annotated[str, "Import name"]) -> dict:
231
+ """Get specific import details by name"""
232
+ nimps = ida_nalt.get_import_module_qty()
233
+ for i in range(nimps):
234
+ module = ida_nalt.get_import_module_name(i)
235
+ result = {}
236
+
237
+ def callback(ea, imp_name, ordinal):
238
+ if imp_name == name or f"ord_{ordinal}" == name:
239
+ result.update(
240
+ {
241
+ "addr": hex(ea),
242
+ "name": imp_name or f"ord_{ordinal}",
243
+ "module": module,
244
+ "ordinal": ordinal,
245
+ }
246
+ )
247
+ return False # Stop enumeration
248
+ return True
249
+
250
+ ida_nalt.enum_import_names(i, callback)
251
+ if result:
252
+ return result
253
+
254
+ return {"error": f"Import not found: {name}"}
255
+
256
+
257
+ @resource("ida://export/{name}")
258
+ @idasync
259
+ def export_name_resource(name: Annotated[str, "Export name"]) -> dict:
260
+ """Get specific export details by name"""
261
+ entry_count = ida_nalt.get_entry_qty()
262
+ for i in range(entry_count):
263
+ ordinal = ida_nalt.get_entry_ordinal(i)
264
+ ea = ida_nalt.get_entry(ordinal)
265
+ entry_name = ida_nalt.get_entry_name(ordinal)
266
+
267
+ if entry_name == name:
268
+ return {
269
+ "addr": hex(ea),
270
+ "name": entry_name,
271
+ "ordinal": ordinal,
272
+ }
273
+
274
+ return {"error": f"Export not found: {name}"}
275
+
276
+
277
+ # ============================================================================
278
+ # Cross-references
279
+ # ============================================================================
280
+
281
+
282
+ @resource("ida://xrefs/from/{addr}")
283
+ @idasync
284
+ def xrefs_from_resource(addr: Annotated[str, "Source address"]) -> list[dict]:
285
+ """Get cross-references from address"""
286
+ ea = parse_address(addr)
287
+ xrefs = []
288
+ for xref in idautils.XrefsFrom(ea, 0):
289
+ xrefs.append(
290
+ {
291
+ "addr": hex(xref.to),
292
+ "type": "code" if xref.iscode else "data",
293
+ }
294
+ )
295
+ return xrefs
@@ -0,0 +1,167 @@
1
+ """Stack frame operations for IDA Pro MCP.
2
+
3
+ This module provides batch operations for managing stack frame variables,
4
+ including reading, creating, and deleting stack variables in functions.
5
+ """
6
+
7
+ from typing import Annotated
8
+ import ida_typeinf
9
+ import ida_frame
10
+ import idaapi
11
+
12
+ from .rpc import tool
13
+ from .sync import idasync
14
+ from .utils import (
15
+ normalize_list_input,
16
+ normalize_dict_list,
17
+ parse_address,
18
+ get_type_by_name,
19
+ StackVarDecl,
20
+ StackVarDelete,
21
+ get_stack_frame_variables_internal,
22
+ )
23
+
24
+
25
+ # ============================================================================
26
+ # Stack Frame Operations
27
+ # ============================================================================
28
+
29
+
30
+ @tool
31
+ @idasync
32
+ def stack_frame(addrs: Annotated[list[str] | str, "Address(es)"]) -> list[dict]:
33
+ """Get stack vars"""
34
+ addrs = normalize_list_input(addrs)
35
+ results = []
36
+
37
+ for addr in addrs:
38
+ try:
39
+ ea = parse_address(addr)
40
+ vars = get_stack_frame_variables_internal(ea, True)
41
+ results.append({"addr": addr, "vars": vars})
42
+ except Exception as e:
43
+ results.append({"addr": addr, "vars": None, "error": str(e)})
44
+
45
+ return results
46
+
47
+
48
+ @tool
49
+ @idasync
50
+ def declare_stack(
51
+ items: list[StackVarDecl] | StackVarDecl,
52
+ ):
53
+ """Create stack vars"""
54
+ items = normalize_dict_list(items)
55
+ results = []
56
+ for item in items:
57
+ fn_addr = item.get("addr", "")
58
+ offset = item.get("offset", "")
59
+ var_name = item.get("name", "")
60
+ type_name = item.get("ty", "")
61
+
62
+ try:
63
+ func = idaapi.get_func(parse_address(fn_addr))
64
+ if not func:
65
+ results.append(
66
+ {"addr": fn_addr, "name": var_name, "error": "No function found"}
67
+ )
68
+ continue
69
+
70
+ ea = parse_address(offset)
71
+
72
+ frame_tif = ida_typeinf.tinfo_t()
73
+ if not ida_frame.get_func_frame(frame_tif, func):
74
+ results.append(
75
+ {"addr": fn_addr, "name": var_name, "error": "No frame returned"}
76
+ )
77
+ continue
78
+
79
+ tif = get_type_by_name(type_name)
80
+ if not ida_frame.define_stkvar(func, var_name, ea, tif):
81
+ results.append(
82
+ {"addr": fn_addr, "name": var_name, "error": "Failed to define"}
83
+ )
84
+ continue
85
+
86
+ results.append({"addr": fn_addr, "name": var_name, "ok": True})
87
+ except Exception as e:
88
+ results.append({"addr": fn_addr, "name": var_name, "error": str(e)})
89
+
90
+ return results
91
+
92
+
93
+ @tool
94
+ @idasync
95
+ def delete_stack(
96
+ items: list[StackVarDelete] | StackVarDelete,
97
+ ):
98
+ """Delete stack vars"""
99
+
100
+ items = normalize_dict_list(items)
101
+ results = []
102
+ for item in items:
103
+ fn_addr = item.get("addr", "")
104
+ var_name = item.get("name", "")
105
+
106
+ try:
107
+ func = idaapi.get_func(parse_address(fn_addr))
108
+ if not func:
109
+ results.append(
110
+ {"addr": fn_addr, "name": var_name, "error": "No function found"}
111
+ )
112
+ continue
113
+
114
+ frame_tif = ida_typeinf.tinfo_t()
115
+ if not ida_frame.get_func_frame(frame_tif, func):
116
+ results.append(
117
+ {"addr": fn_addr, "name": var_name, "error": "No frame returned"}
118
+ )
119
+ continue
120
+
121
+ idx, udm = frame_tif.get_udm(var_name)
122
+ if not udm:
123
+ results.append(
124
+ {
125
+ "addr": fn_addr,
126
+ "name": var_name,
127
+ "error": f"{var_name} not found",
128
+ }
129
+ )
130
+ continue
131
+
132
+ tid = frame_tif.get_udm_tid(idx)
133
+ if ida_frame.is_special_frame_member(tid):
134
+ results.append(
135
+ {
136
+ "addr": fn_addr,
137
+ "name": var_name,
138
+ "error": f"{var_name} is special frame member",
139
+ }
140
+ )
141
+ continue
142
+
143
+ udm = ida_typeinf.udm_t()
144
+ frame_tif.get_udm_by_tid(udm, tid)
145
+ offset = udm.offset // 8
146
+ size = udm.size // 8
147
+ if ida_frame.is_funcarg_off(func, offset):
148
+ results.append(
149
+ {
150
+ "addr": fn_addr,
151
+ "name": var_name,
152
+ "error": f"{var_name} is argument member",
153
+ }
154
+ )
155
+ continue
156
+
157
+ if not ida_frame.delete_frame_members(func, offset, offset + size):
158
+ results.append(
159
+ {"addr": fn_addr, "name": var_name, "error": "Failed to delete"}
160
+ )
161
+ continue
162
+
163
+ results.append({"addr": fn_addr, "name": var_name, "ok": True})
164
+ except Exception as e:
165
+ results.append({"addr": fn_addr, "name": var_name, "error": str(e)})
166
+
167
+ return results