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.
- ida_pro_mcp/__init__.py +0 -0
- ida_pro_mcp/__main__.py +6 -0
- ida_pro_mcp/ida_mcp/__init__.py +68 -0
- ida_pro_mcp/ida_mcp/api_analysis.py +1296 -0
- ida_pro_mcp/ida_mcp/api_core.py +337 -0
- ida_pro_mcp/ida_mcp/api_debug.py +617 -0
- ida_pro_mcp/ida_mcp/api_memory.py +304 -0
- ida_pro_mcp/ida_mcp/api_modify.py +406 -0
- ida_pro_mcp/ida_mcp/api_python.py +179 -0
- ida_pro_mcp/ida_mcp/api_resources.py +295 -0
- ida_pro_mcp/ida_mcp/api_stack.py +167 -0
- ida_pro_mcp/ida_mcp/api_types.py +480 -0
- ida_pro_mcp/ida_mcp/auth.py +166 -0
- ida_pro_mcp/ida_mcp/cache.py +232 -0
- ida_pro_mcp/ida_mcp/config.py +228 -0
- ida_pro_mcp/ida_mcp/framework.py +547 -0
- ida_pro_mcp/ida_mcp/http.py +859 -0
- ida_pro_mcp/ida_mcp/port_utils.py +104 -0
- ida_pro_mcp/ida_mcp/rpc.py +187 -0
- ida_pro_mcp/ida_mcp/server_manager.py +339 -0
- ida_pro_mcp/ida_mcp/sync.py +233 -0
- ida_pro_mcp/ida_mcp/tests/__init__.py +14 -0
- ida_pro_mcp/ida_mcp/tests/test_api_analysis.py +336 -0
- ida_pro_mcp/ida_mcp/tests/test_api_core.py +237 -0
- ida_pro_mcp/ida_mcp/tests/test_api_memory.py +207 -0
- ida_pro_mcp/ida_mcp/tests/test_api_modify.py +123 -0
- ida_pro_mcp/ida_mcp/tests/test_api_resources.py +199 -0
- ida_pro_mcp/ida_mcp/tests/test_api_stack.py +77 -0
- ida_pro_mcp/ida_mcp/tests/test_api_types.py +249 -0
- ida_pro_mcp/ida_mcp/ui.py +357 -0
- ida_pro_mcp/ida_mcp/utils.py +1186 -0
- ida_pro_mcp/ida_mcp/zeromcp/__init__.py +5 -0
- ida_pro_mcp/ida_mcp/zeromcp/jsonrpc.py +384 -0
- ida_pro_mcp/ida_mcp/zeromcp/mcp.py +883 -0
- ida_pro_mcp/ida_mcp.py +186 -0
- ida_pro_mcp/idalib_server.py +354 -0
- ida_pro_mcp/idalib_session_manager.py +259 -0
- ida_pro_mcp/server.py +1060 -0
- ida_pro_mcp/test.py +170 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/METADATA +405 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/RECORD +45 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/WHEEL +5 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/entry_points.txt +4 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/licenses/LICENSE +21 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""Debugger operations for IDA Pro MCP.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive debugging functionality including:
|
|
4
|
+
- Debugger control (start, exit, continue, step, run_to)
|
|
5
|
+
- Breakpoint management (add, delete, enable/disable, list)
|
|
6
|
+
- Register inspection (all registers, GP registers, specific registers)
|
|
7
|
+
- Memory operations (read/write debugger memory)
|
|
8
|
+
- Call stack inspection
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
import ida_dbg
|
|
15
|
+
import ida_entry
|
|
16
|
+
import ida_idd
|
|
17
|
+
import ida_idaapi
|
|
18
|
+
import ida_name
|
|
19
|
+
import idaapi
|
|
20
|
+
|
|
21
|
+
from .rpc import tool, unsafe, ext
|
|
22
|
+
from .sync import idasync, IDAError
|
|
23
|
+
from .utils import (
|
|
24
|
+
RegisterValue,
|
|
25
|
+
ThreadRegisters,
|
|
26
|
+
Breakpoint,
|
|
27
|
+
BreakpointOp,
|
|
28
|
+
MemoryRead,
|
|
29
|
+
MemoryPatch,
|
|
30
|
+
normalize_list_input,
|
|
31
|
+
normalize_dict_list,
|
|
32
|
+
parse_address,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ============================================================================
|
|
37
|
+
# Constants and Helper Functions
|
|
38
|
+
# ============================================================================
|
|
39
|
+
|
|
40
|
+
GENERAL_PURPOSE_REGISTERS = {
|
|
41
|
+
"EAX",
|
|
42
|
+
"EBX",
|
|
43
|
+
"ECX",
|
|
44
|
+
"EDX",
|
|
45
|
+
"ESI",
|
|
46
|
+
"EDI",
|
|
47
|
+
"EBP",
|
|
48
|
+
"ESP",
|
|
49
|
+
"EIP",
|
|
50
|
+
"RAX",
|
|
51
|
+
"RBX",
|
|
52
|
+
"RCX",
|
|
53
|
+
"RDX",
|
|
54
|
+
"RSI",
|
|
55
|
+
"RDI",
|
|
56
|
+
"RBP",
|
|
57
|
+
"RSP",
|
|
58
|
+
"RIP",
|
|
59
|
+
"R8",
|
|
60
|
+
"R9",
|
|
61
|
+
"R10",
|
|
62
|
+
"R11",
|
|
63
|
+
"R12",
|
|
64
|
+
"R13",
|
|
65
|
+
"R14",
|
|
66
|
+
"R15",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def dbg_ensure_running() -> "ida_idd.debugger_t":
|
|
71
|
+
dbg = ida_idd.get_dbg()
|
|
72
|
+
if not dbg:
|
|
73
|
+
raise IDAError("Debugger not running")
|
|
74
|
+
if ida_dbg.get_ip_val() is None:
|
|
75
|
+
raise IDAError("Debugger not running")
|
|
76
|
+
return dbg
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_registers_for_thread(dbg: "ida_idd.debugger_t", tid: int) -> ThreadRegisters:
|
|
80
|
+
"""Helper to get registers for a specific thread."""
|
|
81
|
+
regs = []
|
|
82
|
+
regvals: ida_idd.regvals_t = ida_dbg.get_reg_vals(tid)
|
|
83
|
+
for reg_index, rv in enumerate(regvals):
|
|
84
|
+
rv: ida_idd.regval_t
|
|
85
|
+
reg_info = dbg.regs(reg_index)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
reg_value = rv.pyval(reg_info.dtype)
|
|
89
|
+
except ValueError:
|
|
90
|
+
reg_value = ida_idaapi.BADADDR
|
|
91
|
+
|
|
92
|
+
if isinstance(reg_value, int):
|
|
93
|
+
reg_value = hex(reg_value)
|
|
94
|
+
if isinstance(reg_value, bytes):
|
|
95
|
+
reg_value = reg_value.hex(" ")
|
|
96
|
+
else:
|
|
97
|
+
reg_value = str(reg_value)
|
|
98
|
+
regs.append(
|
|
99
|
+
RegisterValue(
|
|
100
|
+
name=reg_info.name,
|
|
101
|
+
value=reg_value,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
return ThreadRegisters(
|
|
105
|
+
thread_id=tid,
|
|
106
|
+
registers=regs,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _get_registers_general_for_thread(
|
|
111
|
+
dbg: "ida_idd.debugger_t", tid: int
|
|
112
|
+
) -> ThreadRegisters:
|
|
113
|
+
"""Helper to get general-purpose registers for a specific thread."""
|
|
114
|
+
all_registers = _get_registers_for_thread(dbg, tid)
|
|
115
|
+
general_registers = [
|
|
116
|
+
reg
|
|
117
|
+
for reg in all_registers["registers"]
|
|
118
|
+
if reg["name"] in GENERAL_PURPOSE_REGISTERS
|
|
119
|
+
]
|
|
120
|
+
return ThreadRegisters(
|
|
121
|
+
thread_id=tid,
|
|
122
|
+
registers=general_registers,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _get_registers_specific_for_thread(
|
|
127
|
+
dbg: "ida_idd.debugger_t", tid: int, register_names: list[str]
|
|
128
|
+
) -> ThreadRegisters:
|
|
129
|
+
"""Helper to get specific registers for a given thread."""
|
|
130
|
+
all_registers = _get_registers_for_thread(dbg, tid)
|
|
131
|
+
specific_registers = [
|
|
132
|
+
reg for reg in all_registers["registers"] if reg["name"] in register_names
|
|
133
|
+
]
|
|
134
|
+
return ThreadRegisters(
|
|
135
|
+
thread_id=tid,
|
|
136
|
+
registers=specific_registers,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def list_breakpoints():
|
|
141
|
+
breakpoints: list[Breakpoint] = []
|
|
142
|
+
for i in range(ida_dbg.get_bpt_qty()):
|
|
143
|
+
bpt = ida_dbg.bpt_t()
|
|
144
|
+
if ida_dbg.getn_bpt(i, bpt):
|
|
145
|
+
breakpoints.append(
|
|
146
|
+
Breakpoint(
|
|
147
|
+
addr=hex(bpt.ea),
|
|
148
|
+
enabled=bpt.flags & ida_dbg.BPT_ENABLED,
|
|
149
|
+
condition=str(bpt.condition) if bpt.condition else None,
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
return breakpoints
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ============================================================================
|
|
156
|
+
# Debugger Control Operations
|
|
157
|
+
# ============================================================================
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@ext("dbg")
|
|
161
|
+
@unsafe
|
|
162
|
+
@tool
|
|
163
|
+
@idasync
|
|
164
|
+
def dbg_start():
|
|
165
|
+
"""Start debugger"""
|
|
166
|
+
if len(list_breakpoints()) == 0:
|
|
167
|
+
for i in range(ida_entry.get_entry_qty()):
|
|
168
|
+
ordinal = ida_entry.get_entry_ordinal(i)
|
|
169
|
+
addr = ida_entry.get_entry(ordinal)
|
|
170
|
+
if addr != ida_idaapi.BADADDR:
|
|
171
|
+
ida_dbg.add_bpt(addr, 0, idaapi.BPT_SOFT)
|
|
172
|
+
|
|
173
|
+
if idaapi.start_process("", "", "") == 1:
|
|
174
|
+
ip = ida_dbg.get_ip_val()
|
|
175
|
+
if ip is not None:
|
|
176
|
+
return hex(ip)
|
|
177
|
+
raise IDAError("Failed to start debugger")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@ext("dbg")
|
|
181
|
+
@unsafe
|
|
182
|
+
@tool
|
|
183
|
+
@idasync
|
|
184
|
+
def dbg_exit():
|
|
185
|
+
"""Exit debugger"""
|
|
186
|
+
dbg_ensure_running()
|
|
187
|
+
if idaapi.exit_process():
|
|
188
|
+
return
|
|
189
|
+
raise IDAError("Failed to exit debugger")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@ext("dbg")
|
|
193
|
+
@unsafe
|
|
194
|
+
@tool
|
|
195
|
+
@idasync
|
|
196
|
+
def dbg_continue() -> str:
|
|
197
|
+
"""Continue debugger"""
|
|
198
|
+
dbg_ensure_running()
|
|
199
|
+
if idaapi.continue_process():
|
|
200
|
+
ip = ida_dbg.get_ip_val()
|
|
201
|
+
if ip is not None:
|
|
202
|
+
return hex(ip)
|
|
203
|
+
raise IDAError("Failed to continue debugger")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@ext("dbg")
|
|
207
|
+
@unsafe
|
|
208
|
+
@tool
|
|
209
|
+
@idasync
|
|
210
|
+
def dbg_run_to(
|
|
211
|
+
addr: Annotated[str, "Address"],
|
|
212
|
+
):
|
|
213
|
+
"""Run to address"""
|
|
214
|
+
dbg_ensure_running()
|
|
215
|
+
ea = parse_address(addr)
|
|
216
|
+
if idaapi.run_to(ea):
|
|
217
|
+
ip = ida_dbg.get_ip_val()
|
|
218
|
+
if ip is not None:
|
|
219
|
+
return hex(ip)
|
|
220
|
+
raise IDAError(f"Failed to run to address {hex(ea)}")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@ext("dbg")
|
|
224
|
+
@unsafe
|
|
225
|
+
@tool
|
|
226
|
+
@idasync
|
|
227
|
+
def dbg_step_into():
|
|
228
|
+
"""Step into"""
|
|
229
|
+
dbg_ensure_running()
|
|
230
|
+
if idaapi.step_into():
|
|
231
|
+
ip = ida_dbg.get_ip_val()
|
|
232
|
+
if ip is not None:
|
|
233
|
+
return hex(ip)
|
|
234
|
+
raise IDAError("Failed to step into")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@ext("dbg")
|
|
238
|
+
@unsafe
|
|
239
|
+
@tool
|
|
240
|
+
@idasync
|
|
241
|
+
def dbg_step_over():
|
|
242
|
+
"""Step over"""
|
|
243
|
+
dbg_ensure_running()
|
|
244
|
+
if idaapi.step_over():
|
|
245
|
+
ip = ida_dbg.get_ip_val()
|
|
246
|
+
if ip is not None:
|
|
247
|
+
return hex(ip)
|
|
248
|
+
raise IDAError("Failed to step over")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ============================================================================
|
|
252
|
+
# Breakpoint Operations
|
|
253
|
+
# ============================================================================
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@ext("dbg")
|
|
257
|
+
@unsafe
|
|
258
|
+
@tool
|
|
259
|
+
@idasync
|
|
260
|
+
def dbg_bps():
|
|
261
|
+
"""List breakpoints"""
|
|
262
|
+
return list_breakpoints()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@ext("dbg")
|
|
266
|
+
@unsafe
|
|
267
|
+
@tool
|
|
268
|
+
@idasync
|
|
269
|
+
def dbg_add_bp(
|
|
270
|
+
addrs: Annotated[list[str] | str, "Address(es) to add breakpoints at"],
|
|
271
|
+
) -> list[dict]:
|
|
272
|
+
"""Add breakpoints"""
|
|
273
|
+
addrs = normalize_list_input(addrs)
|
|
274
|
+
results = []
|
|
275
|
+
|
|
276
|
+
for addr in addrs:
|
|
277
|
+
try:
|
|
278
|
+
ea = parse_address(addr)
|
|
279
|
+
if idaapi.add_bpt(ea, 0, idaapi.BPT_SOFT):
|
|
280
|
+
results.append({"addr": addr, "ok": True})
|
|
281
|
+
else:
|
|
282
|
+
breakpoints = list_breakpoints()
|
|
283
|
+
for bpt in breakpoints:
|
|
284
|
+
if bpt["addr"] == hex(ea):
|
|
285
|
+
results.append({"addr": addr, "ok": True})
|
|
286
|
+
break
|
|
287
|
+
else:
|
|
288
|
+
results.append({"addr": addr, "error": "Failed to set breakpoint"})
|
|
289
|
+
except Exception as e:
|
|
290
|
+
results.append({"addr": addr, "error": str(e)})
|
|
291
|
+
|
|
292
|
+
return results
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@ext("dbg")
|
|
296
|
+
@unsafe
|
|
297
|
+
@tool
|
|
298
|
+
@idasync
|
|
299
|
+
def dbg_delete_bp(
|
|
300
|
+
addrs: Annotated[list[str] | str, "Address(es) to delete breakpoints from"],
|
|
301
|
+
) -> list[dict]:
|
|
302
|
+
"""Delete breakpoints"""
|
|
303
|
+
addrs = normalize_list_input(addrs)
|
|
304
|
+
results = []
|
|
305
|
+
|
|
306
|
+
for addr in addrs:
|
|
307
|
+
try:
|
|
308
|
+
ea = parse_address(addr)
|
|
309
|
+
if idaapi.del_bpt(ea):
|
|
310
|
+
results.append({"addr": addr, "ok": True})
|
|
311
|
+
else:
|
|
312
|
+
results.append({"addr": addr, "error": "Failed to delete breakpoint"})
|
|
313
|
+
except Exception as e:
|
|
314
|
+
results.append({"addr": addr, "error": str(e)})
|
|
315
|
+
|
|
316
|
+
return results
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@ext("dbg")
|
|
320
|
+
@unsafe
|
|
321
|
+
@tool
|
|
322
|
+
@idasync
|
|
323
|
+
def dbg_toggle_bp(items: list[BreakpointOp] | BreakpointOp) -> list[dict]:
|
|
324
|
+
"""Enable/disable breakpoints"""
|
|
325
|
+
|
|
326
|
+
items = normalize_dict_list(items)
|
|
327
|
+
|
|
328
|
+
results = []
|
|
329
|
+
for item in items:
|
|
330
|
+
addr = item.get("addr", "")
|
|
331
|
+
enable = item.get("enabled", True)
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
ea = parse_address(addr)
|
|
335
|
+
if idaapi.enable_bpt(ea, enable):
|
|
336
|
+
results.append({"addr": addr, "ok": True})
|
|
337
|
+
else:
|
|
338
|
+
results.append(
|
|
339
|
+
{
|
|
340
|
+
"addr": addr,
|
|
341
|
+
"error": f"Failed to {'enable' if enable else 'disable'} breakpoint",
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
results.append({"addr": addr, "error": str(e)})
|
|
346
|
+
|
|
347
|
+
return results
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ============================================================================
|
|
351
|
+
# Register Operations
|
|
352
|
+
# ============================================================================
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@ext("dbg")
|
|
356
|
+
@unsafe
|
|
357
|
+
@tool
|
|
358
|
+
@idasync
|
|
359
|
+
def dbg_regs_all() -> list[ThreadRegisters]:
|
|
360
|
+
"""Get all registers"""
|
|
361
|
+
result: list[ThreadRegisters] = []
|
|
362
|
+
dbg = dbg_ensure_running()
|
|
363
|
+
for thread_index in range(ida_dbg.get_thread_qty()):
|
|
364
|
+
tid = ida_dbg.getn_thread(thread_index)
|
|
365
|
+
result.append(_get_registers_for_thread(dbg, tid))
|
|
366
|
+
return result
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@ext("dbg")
|
|
370
|
+
@unsafe
|
|
371
|
+
@tool
|
|
372
|
+
@idasync
|
|
373
|
+
def dbg_regs_remote(
|
|
374
|
+
tids: Annotated[list[int] | int, "Thread ID(s) to get registers for"],
|
|
375
|
+
) -> list[dict]:
|
|
376
|
+
"""Get thread registers"""
|
|
377
|
+
if isinstance(tids, int):
|
|
378
|
+
tids = [tids]
|
|
379
|
+
|
|
380
|
+
dbg = dbg_ensure_running()
|
|
381
|
+
available_tids = [ida_dbg.getn_thread(i) for i in range(ida_dbg.get_thread_qty())]
|
|
382
|
+
results = []
|
|
383
|
+
|
|
384
|
+
for tid in tids:
|
|
385
|
+
try:
|
|
386
|
+
if tid not in available_tids:
|
|
387
|
+
results.append(
|
|
388
|
+
{"tid": tid, "regs": None, "error": f"Thread {tid} not found"}
|
|
389
|
+
)
|
|
390
|
+
continue
|
|
391
|
+
regs = _get_registers_for_thread(dbg, tid)
|
|
392
|
+
results.append({"tid": tid, "regs": regs})
|
|
393
|
+
except Exception as e:
|
|
394
|
+
results.append({"tid": tid, "regs": None, "error": str(e)})
|
|
395
|
+
|
|
396
|
+
return results
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
@ext("dbg")
|
|
400
|
+
@unsafe
|
|
401
|
+
@tool
|
|
402
|
+
@idasync
|
|
403
|
+
def dbg_regs() -> ThreadRegisters:
|
|
404
|
+
"""Get current thread registers"""
|
|
405
|
+
dbg = dbg_ensure_running()
|
|
406
|
+
tid = ida_dbg.get_current_thread()
|
|
407
|
+
return _get_registers_for_thread(dbg, tid)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@ext("dbg")
|
|
411
|
+
@unsafe
|
|
412
|
+
@tool
|
|
413
|
+
@idasync
|
|
414
|
+
def dbg_gpregs_remote(
|
|
415
|
+
tids: Annotated[list[int] | int, "Thread ID(s) to get GP registers for"],
|
|
416
|
+
) -> list[dict]:
|
|
417
|
+
"""Get GP registers for threads"""
|
|
418
|
+
if isinstance(tids, int):
|
|
419
|
+
tids = [tids]
|
|
420
|
+
|
|
421
|
+
dbg = dbg_ensure_running()
|
|
422
|
+
available_tids = [ida_dbg.getn_thread(i) for i in range(ida_dbg.get_thread_qty())]
|
|
423
|
+
results = []
|
|
424
|
+
|
|
425
|
+
for tid in tids:
|
|
426
|
+
try:
|
|
427
|
+
if tid not in available_tids:
|
|
428
|
+
results.append(
|
|
429
|
+
{"tid": tid, "regs": None, "error": f"Thread {tid} not found"}
|
|
430
|
+
)
|
|
431
|
+
continue
|
|
432
|
+
regs = _get_registers_general_for_thread(dbg, tid)
|
|
433
|
+
results.append({"tid": tid, "regs": regs})
|
|
434
|
+
except Exception as e:
|
|
435
|
+
results.append({"tid": tid, "regs": None, "error": str(e)})
|
|
436
|
+
|
|
437
|
+
return results
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@ext("dbg")
|
|
441
|
+
@unsafe
|
|
442
|
+
@tool
|
|
443
|
+
@idasync
|
|
444
|
+
def dbg_gpregs() -> ThreadRegisters:
|
|
445
|
+
"""Get current thread GP registers"""
|
|
446
|
+
dbg = dbg_ensure_running()
|
|
447
|
+
tid = ida_dbg.get_current_thread()
|
|
448
|
+
return _get_registers_general_for_thread(dbg, tid)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@ext("dbg")
|
|
452
|
+
@unsafe
|
|
453
|
+
@tool
|
|
454
|
+
@idasync
|
|
455
|
+
def dbg_regs_named_remote(
|
|
456
|
+
thread_id: Annotated[int, "Thread ID"],
|
|
457
|
+
register_names: Annotated[
|
|
458
|
+
str, "Comma-separated register names (e.g., 'RAX, RBX, RCX')"
|
|
459
|
+
],
|
|
460
|
+
) -> ThreadRegisters:
|
|
461
|
+
"""Get specific thread registers"""
|
|
462
|
+
dbg = dbg_ensure_running()
|
|
463
|
+
if thread_id not in [
|
|
464
|
+
ida_dbg.getn_thread(i) for i in range(ida_dbg.get_thread_qty())
|
|
465
|
+
]:
|
|
466
|
+
raise IDAError(f"Thread with ID {thread_id} not found")
|
|
467
|
+
names = [name.strip() for name in register_names.split(",")]
|
|
468
|
+
return _get_registers_specific_for_thread(dbg, thread_id, names)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@ext("dbg")
|
|
472
|
+
@unsafe
|
|
473
|
+
@tool
|
|
474
|
+
@idasync
|
|
475
|
+
def dbg_regs_named(
|
|
476
|
+
register_names: Annotated[
|
|
477
|
+
str, "Comma-separated register names (e.g., 'RAX, RBX, RCX')"
|
|
478
|
+
],
|
|
479
|
+
) -> ThreadRegisters:
|
|
480
|
+
"""Get specific current thread registers"""
|
|
481
|
+
dbg = dbg_ensure_running()
|
|
482
|
+
tid = ida_dbg.get_current_thread()
|
|
483
|
+
names = [name.strip() for name in register_names.split(",")]
|
|
484
|
+
return _get_registers_specific_for_thread(dbg, tid, names)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# ============================================================================
|
|
488
|
+
# Call Stack Operations
|
|
489
|
+
# ============================================================================
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@ext("dbg")
|
|
493
|
+
@unsafe
|
|
494
|
+
@tool
|
|
495
|
+
@idasync
|
|
496
|
+
def dbg_stacktrace() -> list[dict[str, str]]:
|
|
497
|
+
"""Get call stack"""
|
|
498
|
+
callstack = []
|
|
499
|
+
try:
|
|
500
|
+
tid = ida_dbg.get_current_thread()
|
|
501
|
+
trace = ida_idd.call_stack_t()
|
|
502
|
+
|
|
503
|
+
if not ida_dbg.collect_stack_trace(tid, trace):
|
|
504
|
+
return []
|
|
505
|
+
for frame in trace:
|
|
506
|
+
frame_info = {
|
|
507
|
+
"addr": hex(frame.callea),
|
|
508
|
+
}
|
|
509
|
+
try:
|
|
510
|
+
module_info = ida_idd.modinfo_t()
|
|
511
|
+
if ida_dbg.get_module_info(frame.callea, module_info):
|
|
512
|
+
frame_info["module"] = os.path.basename(module_info.name)
|
|
513
|
+
else:
|
|
514
|
+
frame_info["module"] = "<unknown>"
|
|
515
|
+
|
|
516
|
+
name = (
|
|
517
|
+
ida_name.get_nice_colored_name(
|
|
518
|
+
frame.callea,
|
|
519
|
+
ida_name.GNCN_NOCOLOR
|
|
520
|
+
| ida_name.GNCN_NOLABEL
|
|
521
|
+
| ida_name.GNCN_NOSEG
|
|
522
|
+
| ida_name.GNCN_PREFDBG,
|
|
523
|
+
)
|
|
524
|
+
or "<unnamed>"
|
|
525
|
+
)
|
|
526
|
+
frame_info["symbol"] = name
|
|
527
|
+
|
|
528
|
+
except Exception as e:
|
|
529
|
+
frame_info["module"] = "<error>"
|
|
530
|
+
frame_info["symbol"] = str(e)
|
|
531
|
+
|
|
532
|
+
callstack.append(frame_info)
|
|
533
|
+
|
|
534
|
+
except Exception:
|
|
535
|
+
pass
|
|
536
|
+
return callstack
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# ============================================================================
|
|
540
|
+
# Debugger Memory Operations
|
|
541
|
+
# ============================================================================
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@ext("dbg")
|
|
545
|
+
@unsafe
|
|
546
|
+
@tool
|
|
547
|
+
@idasync
|
|
548
|
+
def dbg_read(regions: list[MemoryRead] | MemoryRead) -> list[dict]:
|
|
549
|
+
"""Read debug memory"""
|
|
550
|
+
|
|
551
|
+
regions = normalize_dict_list(regions)
|
|
552
|
+
dbg_ensure_running()
|
|
553
|
+
results = []
|
|
554
|
+
|
|
555
|
+
for region in regions:
|
|
556
|
+
try:
|
|
557
|
+
addr = parse_address(region["addr"])
|
|
558
|
+
size = region["size"]
|
|
559
|
+
|
|
560
|
+
data = idaapi.dbg_read_memory(addr, size)
|
|
561
|
+
if data:
|
|
562
|
+
results.append(
|
|
563
|
+
{
|
|
564
|
+
"addr": region["addr"],
|
|
565
|
+
"size": len(data),
|
|
566
|
+
"data": data.hex(),
|
|
567
|
+
"error": None,
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
else:
|
|
571
|
+
results.append(
|
|
572
|
+
{
|
|
573
|
+
"addr": region["addr"],
|
|
574
|
+
"size": 0,
|
|
575
|
+
"data": None,
|
|
576
|
+
"error": "Failed to read memory",
|
|
577
|
+
}
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
except Exception as e:
|
|
581
|
+
results.append(
|
|
582
|
+
{"addr": region.get("addr"), "size": 0, "data": None, "error": str(e)}
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
return results
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@ext("dbg")
|
|
589
|
+
@unsafe
|
|
590
|
+
@tool
|
|
591
|
+
@idasync
|
|
592
|
+
def dbg_write(regions: list[MemoryPatch] | MemoryPatch) -> list[dict]:
|
|
593
|
+
"""Write debug memory"""
|
|
594
|
+
|
|
595
|
+
regions = normalize_dict_list(regions)
|
|
596
|
+
dbg_ensure_running()
|
|
597
|
+
results = []
|
|
598
|
+
|
|
599
|
+
for region in regions:
|
|
600
|
+
try:
|
|
601
|
+
addr = parse_address(region["addr"])
|
|
602
|
+
data = bytes.fromhex(region["data"])
|
|
603
|
+
|
|
604
|
+
success = idaapi.dbg_write_memory(addr, data)
|
|
605
|
+
results.append(
|
|
606
|
+
{
|
|
607
|
+
"addr": region["addr"],
|
|
608
|
+
"size": len(data) if success else 0,
|
|
609
|
+
"ok": success,
|
|
610
|
+
"error": None if success else "Write failed",
|
|
611
|
+
}
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
except Exception as e:
|
|
615
|
+
results.append({"addr": region.get("addr"), "size": 0, "error": str(e)})
|
|
616
|
+
|
|
617
|
+
return results
|