Skip to content

debugging

Debugging aids for a connected device.

  • get_process_exit_historyadb shell dumpsys activity exit-info <pkg>: ActivityManager's bounded ApplicationExitInfo ring, parsed into typed records (reason, timestamp, pid, importance, pss/rss, trace availability).
  • set_debug_app / clear_debug_appadb shell am set-debug-app [-w] [--persistent] <pkg> / am clear-debug-app. Marks (or clears) ActivityManager's debug app; does not attach a debugger.
  • list_jdwp_processesadb jdwp: PIDs of processes currently exposing a JDWP transport (a snapshot, since adb jdwp streams and never exits).
  • capture_native_backtraceadb shell debuggerd -b <pid>: native thread backtraces for a running process, parsed to a per-thread summary plus the capped full text. Privileged on production builds.
  • capture_native_tombstoneadb shell debuggerd <pid>: a full native tombstone written into ADB_AUTOMATION_LOCAL_ROOT/tombstones/; returns path + parsed header metadata, not contents.

adb_automation_mcp.modules.debugging.tools

Module-level, statically-introspectable tool functions for the debugging module.

Kept as plain top-level functions, never closures, so that documentation tooling and the registry meta-test can both introspect them directly.

capture_native_backtrace(ctx: Context, serial: str, pid: int) -> NativeBacktrace async

Capture native thread backtraces for a running process: adb shell debuggerd -b <pid>.

Dumps every thread's native call stack without stopping the process. Returns the parsed header (process name, ABI), a per-thread summary (name / tid / frame count), and the full backtrace text (capped). This is a privileged operation on production builds — a device that isn't rooted/userdebug rejects it.

Parameters:

Name Type Description Default
serial str

The target device's adb serial (see list_connected_devices).

required
pid int

The process id to backtrace (a positive integer — see processes.get_process_id / list_processes).

required

Returns:

Type Description
NativeBacktrace

The serial and pid; process_name and abi from the dump header; thread_count and threads (name / sys_tid / frame_count per thread); and text (the full dump, truncated if very large).

Error handling

A non-positive pid raises INVALID_ARGUMENT before anything runs. An unknown serial raises DEVICE_NOT_FOUND. A device that requires root for debuggerd, or otherwise refuses it, raises PERMISSION_DENIED. A dead or invalid pid (debuggerd produced no backtrace) raises BACKEND_ERROR.

Example

Called with serial="emulator-5554", pid=1224. A typical (trimmed) response:

{
  "status": "success",
  "message": "capture_native_backtrace completed successfully.",
  "data": {
    "serial": "emulator-5554",
    "pid": 1224,
    "process_name": "com.android.systemui",
    "abi": "x86_64",
    "thread_count": 2,
    "threads": [
      {"name": "ndroid.systemui", "sys_tid": 1224, "frame_count": 17}
    ],
    "text": "----- pid 1224 at ... -----\nCmd line: com.android.systemui\n..."
  },
  "error": null
}
Source code in src/adb_automation_mcp/modules/debugging/tools.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
@category("read")
async def capture_native_backtrace(ctx: Context, serial: str, pid: int) -> NativeBacktrace:
    """Capture native thread backtraces for a running process: `adb shell
    debuggerd -b <pid>`.

    Dumps every thread's native call stack without stopping the process.
    Returns the parsed header (process name, ABI), a per-thread summary
    (name / tid / frame count), and the full backtrace text (capped). This
    is a privileged operation on production builds — a device that isn't
    rooted/userdebug rejects it.

    Args:
        serial: The target device's adb serial (see list_connected_devices).
        pid: The process id to backtrace (a positive integer — see
            processes.get_process_id / list_processes).

    Returns:
        The serial and pid; process_name and abi from the dump header;
        thread_count and threads (name / sys_tid / frame_count per thread);
        and text (the full dump, truncated if very large).

    Error handling:
        A non-positive pid raises INVALID_ARGUMENT before anything runs. An
        unknown serial raises DEVICE_NOT_FOUND. A device that requires root
        for debuggerd, or otherwise refuses it, raises PERMISSION_DENIED. A
        dead or invalid pid (debuggerd produced no backtrace) raises
        BACKEND_ERROR.

    Example:
        Called with serial="emulator-5554", pid=1224. A typical (trimmed)
        response:

        ```json
        {
          "status": "success",
          "message": "capture_native_backtrace completed successfully.",
          "data": {
            "serial": "emulator-5554",
            "pid": 1224,
            "process_name": "com.android.systemui",
            "abi": "x86_64",
            "thread_count": 2,
            "threads": [
              {"name": "ndroid.systemui", "sys_tid": 1224, "frame_count": 17}
            ],
            "text": "----- pid 1224 at ... -----\\nCmd line: com.android.systemui\\n..."
          },
          "error": null
        }
        ```
    """
    services = cast("dict[str, object]", ctx.lifespan_context["services"])
    debugging = cast(DebuggingService, services["debugging"])
    return await debugging.capture_native_backtrace(serial, pid)

capture_native_tombstone(ctx: Context, serial: str, pid: int, local_path: str) -> NativeTombstone async

Request a full native tombstone for a process and save it to the host: adb shell debuggerd <pid>.

Triggers a full crash-style dump (registers, memory map, all thread backtraces) for a running process and writes the human-readable tombstone text into <ADB_AUTOMATION_LOCAL_ROOT>/tombstones/. Categorized write because it's a high-impact diagnostic that briefly pauses the target's threads, and is privileged on production builds. The tombstone bytes are not embedded in the response — only the saved path and parsed metadata.

Parameters:

Name Type Description Default
serial str

The target device's adb serial (see list_connected_devices).

required
pid int

The process id to dump (a positive integer).

required
local_path str

Destination path relative to the server's local_root tombstones/ directory, e.g. "sysui.txt" or "run1/sysui.txt". Must resolve inside local_root.

required

Returns:

Type Description
NativeTombstone

The serial and pid; process_name / abi / signal from the header; frame_count (the "N total frames" value); device_tombstone_ref (the tombstone_NN.pb filename the dump names on the device, if any); local_path (the absolute host path written); and size_bytes.

Error handling

A non-positive pid raises INVALID_ARGUMENT. No configured local_root, or a local_path escaping it, raises POLICY_DENIED. An unknown serial raises DEVICE_NOT_FOUND. A device that requires root for debuggerd, or refuses it, raises PERMISSION_DENIED. A dead or invalid pid raises BACKEND_ERROR.

Example

Called with serial="emulator-5554", pid=1224, local_path="sysui.txt". A typical response:

{
  "status": "success",
  "message": "Saved native tombstone for pid 1224 on emulator-5554 to /data/out/tombstones/sysui.txt.",
  "data": {
    "serial": "emulator-5554",
    "pid": 1224,
    "process_name": "com.android.systemui",
    "abi": "x86_64",
    "signal": "35 (<debuggerd signal>), code -1 (SI_QUEUE from pid 7338, uid 0), fault addr --------",
    "frame_count": 24,
    "device_tombstone_ref": "tombstone_27.pb",
    "local_path": "/data/out/tombstones/sysui.txt",
    "size_bytes": 18244,
    "success": true
  },
  "error": null
}
Source code in src/adb_automation_mcp/modules/debugging/tools.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
@category("write")
async def capture_native_tombstone(
    ctx: Context, serial: str, pid: int, local_path: str
) -> NativeTombstone:
    """Request a full native tombstone for a process and save it to the
    host: `adb shell debuggerd <pid>`.

    Triggers a full crash-style dump (registers, memory map, all thread
    backtraces) for a running process and writes the human-readable
    tombstone text into `<ADB_AUTOMATION_LOCAL_ROOT>/tombstones/`.
    Categorized `write` because it's a high-impact diagnostic that briefly
    pauses the target's threads, and is privileged on production builds. The
    tombstone bytes are not embedded in the response — only the saved path
    and parsed metadata.

    Args:
        serial: The target device's adb serial (see list_connected_devices).
        pid: The process id to dump (a positive integer).
        local_path: Destination path relative to the server's local_root
            `tombstones/` directory, e.g. "sysui.txt" or "run1/sysui.txt".
            Must resolve inside local_root.

    Returns:
        The serial and pid; process_name / abi / signal from the header;
        frame_count (the "N total frames" value); device_tombstone_ref (the
        `tombstone_NN.pb` filename the dump names on the device, if any);
        local_path (the absolute host path written); and size_bytes.

    Error handling:
        A non-positive pid raises INVALID_ARGUMENT. No configured
        local_root, or a local_path escaping it, raises POLICY_DENIED. An
        unknown serial raises DEVICE_NOT_FOUND. A device that requires root
        for debuggerd, or refuses it, raises PERMISSION_DENIED. A dead or
        invalid pid raises BACKEND_ERROR.

    Example:
        Called with serial="emulator-5554", pid=1224,
        local_path="sysui.txt". A typical response:

        ```json
        {
          "status": "success",
          "message": "Saved native tombstone for pid 1224 on emulator-5554 to /data/out/tombstones/sysui.txt.",
          "data": {
            "serial": "emulator-5554",
            "pid": 1224,
            "process_name": "com.android.systemui",
            "abi": "x86_64",
            "signal": "35 (<debuggerd signal>), code -1 (SI_QUEUE from pid 7338, uid 0), fault addr --------",
            "frame_count": 24,
            "device_tombstone_ref": "tombstone_27.pb",
            "local_path": "/data/out/tombstones/sysui.txt",
            "size_bytes": 18244,
            "success": true
          },
          "error": null
        }
        ```
    """
    services = cast("dict[str, object]", ctx.lifespan_context["services"])
    debugging = cast(DebuggingService, services["debugging"])
    return await debugging.capture_native_tombstone(serial, pid, local_path)

clear_debug_app(ctx: Context, serial: str) -> ClearDebugAppResult async

Clear ActivityManager's configured debug app: adb shell am clear-debug-app.

Undoes a previous set_debug_app. Idempotent — calling it when no debug app is set is a successful no-op, and cleared being true does not imply one had been configured.

Parameters:

Name Type Description Default
serial str

The target device's adb serial (see list_connected_devices).

required

Returns:

Type Description
ClearDebugAppResult

The serial and cleared (always true on success).

Error handling

An unknown serial or unresponsive adb binary raises DEVICE_NOT_FOUND/ADB_UNAVAILABLE. A permission rejection raises PERMISSION_DENIED; any other non-zero exit raises BACKEND_ERROR.

Example

Called with serial="emulator-5554". A typical response:

{
  "status": "success",
  "message": "Cleared the debug app on emulator-5554.",
  "data": {"serial": "emulator-5554", "cleared": true},
  "error": null
}
Source code in src/adb_automation_mcp/modules/debugging/tools.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
@category("write")
async def clear_debug_app(ctx: Context, serial: str) -> ClearDebugAppResult:
    """Clear ActivityManager's configured debug app: `adb shell am
    clear-debug-app`.

    Undoes a previous set_debug_app. Idempotent — calling it when no debug
    app is set is a successful no-op, and `cleared` being true does not
    imply one had been configured.

    Args:
        serial: The target device's adb serial (see list_connected_devices).

    Returns:
        The serial and cleared (always true on success).

    Error handling:
        An unknown serial or unresponsive adb binary raises
        DEVICE_NOT_FOUND/ADB_UNAVAILABLE. A permission rejection raises
        PERMISSION_DENIED; any other non-zero exit raises BACKEND_ERROR.

    Example:
        Called with serial="emulator-5554". A typical response:

        ```json
        {
          "status": "success",
          "message": "Cleared the debug app on emulator-5554.",
          "data": {"serial": "emulator-5554", "cleared": true},
          "error": null
        }
        ```
    """
    services = cast("dict[str, object]", ctx.lifespan_context["services"])
    debugging = cast(DebuggingService, services["debugging"])
    return await debugging.clear_debug_app(serial)

get_process_exit_history(ctx: Context, serial: str, package: str) -> ProcessExitHistory async

Get a package's recent process-exit records: adb shell dumpsys activity exit-info <package>.

ActivityManager keeps a bounded ApplicationExitInfo ring per package. This parses it into typed records — reason (crash / ANR / low-memory kill / self-exit / signalled), timestamp, pid, importance, pss/rss, and whether a trace was captured — so an agent can explain why an app died. Reading only. A package with no retained history returns an empty list, not an error.

Parameters:

Name Type Description Default
serial str

The target device's adb serial (see list_connected_devices).

required
package str

The package to look up, e.g. "com.example.app".

required

Returns:

Type Description
ProcessExitHistory

The serial / package / count, and records (newest first). Each record has timestamp, pid, real_uid, user, process, reason_code + reason label, subreason_code + subreason label, status, importance, pss and rss (the raw strings the dump reports), state, description, trace_available (bool), and has_anr_info (bool). Fields the record didn't carry are null.

Error handling

A blank package raises INVALID_ARGUMENT before anything runs. An unknown serial or unresponsive adb binary raises DEVICE_NOT_FOUND/ADB_UNAVAILABLE. A permission rejection raises PERMISSION_DENIED; any other non-zero exit raises BACKEND_ERROR. An unknown package or one with no history is an empty result, not an error.

Example

Called with serial="emulator-5554", package="com.example.app". A typical response:

{
  "status": "success",
  "message": "2 exit records for com.example.app on emulator-5554 (newest: APP CRASH(EXCEPTION) at 2026-09-06 22:39:39.266).",
  "data": {
    "serial": "emulator-5554",
    "package": "com.example.app",
    "count": 2,
    "records": [
      {
        "timestamp": "2026-09-06 22:39:39.266",
        "pid": 5486,
        "real_uid": 10234,
        "user": 0,
        "process": "com.example.app",
        "reason_code": 4,
        "reason": "APP CRASH(EXCEPTION)",
        "subreason_code": 0,
        "subreason": "UNKNOWN",
        "status": 0,
        "importance": 400,
        "pss": "0.00",
        "rss": "167MB",
        "state": "empty",
        "description": "crash",
        "trace_available": false,
        "has_anr_info": false
      }
    ]
  },
  "error": null
}
Source code in src/adb_automation_mcp/modules/debugging/tools.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
@category("read")
async def get_process_exit_history(
    ctx: Context, serial: str, package: str
) -> ProcessExitHistory:
    """Get a package's recent process-exit records: `adb shell dumpsys
    activity exit-info <package>`.

    ActivityManager keeps a bounded `ApplicationExitInfo` ring per package.
    This parses it into typed records — reason (crash / ANR / low-memory
    kill / self-exit / signalled), timestamp, pid, importance, pss/rss, and
    whether a trace was captured — so an agent can explain why an app died.
    Reading only. A package with no retained history returns an empty list,
    not an error.

    Args:
        serial: The target device's adb serial (see list_connected_devices).
        package: The package to look up, e.g. "com.example.app".

    Returns:
        The serial / package / count, and records (newest first). Each
        record has timestamp, pid, real_uid, user, process, reason_code +
        reason label, subreason_code + subreason label, status, importance,
        pss and rss (the raw strings the dump reports), state, description,
        trace_available (bool), and has_anr_info (bool). Fields the record
        didn't carry are null.

    Error handling:
        A blank package raises INVALID_ARGUMENT before anything runs. An
        unknown serial or unresponsive adb binary raises
        DEVICE_NOT_FOUND/ADB_UNAVAILABLE. A permission rejection raises
        PERMISSION_DENIED; any other non-zero exit raises BACKEND_ERROR. An
        unknown package or one with no history is an empty result, not an
        error.

    Example:
        Called with serial="emulator-5554", package="com.example.app". A
        typical response:

        ```json
        {
          "status": "success",
          "message": "2 exit records for com.example.app on emulator-5554 (newest: APP CRASH(EXCEPTION) at 2026-09-06 22:39:39.266).",
          "data": {
            "serial": "emulator-5554",
            "package": "com.example.app",
            "count": 2,
            "records": [
              {
                "timestamp": "2026-09-06 22:39:39.266",
                "pid": 5486,
                "real_uid": 10234,
                "user": 0,
                "process": "com.example.app",
                "reason_code": 4,
                "reason": "APP CRASH(EXCEPTION)",
                "subreason_code": 0,
                "subreason": "UNKNOWN",
                "status": 0,
                "importance": 400,
                "pss": "0.00",
                "rss": "167MB",
                "state": "empty",
                "description": "crash",
                "trace_available": false,
                "has_anr_info": false
              }
            ]
          },
          "error": null
        }
        ```
    """
    services = cast("dict[str, object]", ctx.lifespan_context["services"])
    debugging = cast(DebuggingService, services["debugging"])
    return await debugging.get_process_exit_history(serial, package)

list_jdwp_processes(ctx: Context, serial: str) -> JdwpProcessList async

List processes exposing a JDWP transport: adb jdwp.

Returns the PIDs of the device's debuggable processes that currently have a Java Debug Wire Protocol endpoint open — the candidates you can attach a debugger or jdb to. adb jdwp streams and never exits on its own, so this returns a snapshot taken after a short settle window.

Parameters:

Name Type Description Default
serial str

The target device's adb serial (see list_connected_devices).

required

Returns:

Type Description
JdwpProcessList

The serial, count, and pids (a list of integers, in first-seen order, deduplicated). An empty list is a normal result.

Error handling

An unknown/offline serial raises DEVICE_NOT_FOUND; the adb binary being unreachable raises ADB_UNAVAILABLE. Any other non-zero exit raises BACKEND_ERROR. Non-numeric lines in the output are ignored.

Example

Called with serial="emulator-5554". A typical response:

{
  "status": "success",
  "message": "3 JDWP-debuggable process(es) on emulator-5554: [1224, 1568, 2411].",
  "data": {"serial": "emulator-5554", "count": 3, "pids": [1224, 1568, 2411]},
  "error": null
}
Source code in src/adb_automation_mcp/modules/debugging/tools.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
@category("read")
async def list_jdwp_processes(ctx: Context, serial: str) -> JdwpProcessList:
    """List processes exposing a JDWP transport: `adb jdwp`.

    Returns the PIDs of the device's debuggable processes that currently
    have a Java Debug Wire Protocol endpoint open — the candidates you can
    attach a debugger or `jdb` to. `adb jdwp` streams and never exits on its
    own, so this returns a snapshot taken after a short settle window.

    Args:
        serial: The target device's adb serial (see list_connected_devices).

    Returns:
        The serial, count, and pids (a list of integers, in first-seen
        order, deduplicated). An empty list is a normal result.

    Error handling:
        An unknown/offline serial raises DEVICE_NOT_FOUND; the adb binary
        being unreachable raises ADB_UNAVAILABLE. Any other non-zero exit
        raises BACKEND_ERROR. Non-numeric lines in the output are ignored.

    Example:
        Called with serial="emulator-5554". A typical response:

        ```json
        {
          "status": "success",
          "message": "3 JDWP-debuggable process(es) on emulator-5554: [1224, 1568, 2411].",
          "data": {"serial": "emulator-5554", "count": 3, "pids": [1224, 1568, 2411]},
          "error": null
        }
        ```
    """
    services = cast("dict[str, object]", ctx.lifespan_context["services"])
    debugging = cast(DebuggingService, services["debugging"])
    return await debugging.list_jdwp_processes(serial)

set_debug_app(ctx: Context, serial: str, package: str, wait_for_debugger: bool = False, persistent: bool = False) -> SetDebugAppResult async

Mark an app as ActivityManager's debug app: adb shell am set-debug-app.

Records package as the debug app so its next launch is debugger-friendly. This does NOT attach a debugger — it only sets the marker. With wait_for_debugger the next launch of the app blocks until a debugger connects (-w); with persistent the setting survives reboot (--persistent). Pair with clear_debug_app when done.

Parameters:

Name Type Description Default
serial str

The target device's adb serial (see list_connected_devices).

required
package str

The package to mark, e.g. "com.example.app".

required
wait_for_debugger bool

Block the app's next launch until a debugger attaches (-w).

False
persistent bool

Keep the setting across reboots (--persistent).

False

Returns:

Type Description
SetDebugAppResult

The serial, package, and the wait_for_debugger / persistent flags that were applied.

Error handling

A blank package raises INVALID_ARGUMENT before anything runs. An unknown serial or unresponsive adb binary raises DEVICE_NOT_FOUND/ADB_UNAVAILABLE. A permission rejection raises PERMISSION_DENIED; any other non-zero exit raises BACKEND_ERROR. An unknown package is NOT an error — am doesn't validate it.

Example

Called with serial="emulator-5554", package="com.example.app", wait_for_debugger=true. A typical response:

{
  "status": "success",
  "message": "Set com.example.app as the debug app on emulator-5554 (waits for debugger on next launch).",
  "data": {
    "serial": "emulator-5554",
    "package": "com.example.app",
    "wait_for_debugger": true,
    "persistent": false
  },
  "error": null
}
Source code in src/adb_automation_mcp/modules/debugging/tools.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@category("write")
async def set_debug_app(
    ctx: Context,
    serial: str,
    package: str,
    wait_for_debugger: bool = False,
    persistent: bool = False,
) -> SetDebugAppResult:
    """Mark an app as ActivityManager's debug app: `adb shell am set-debug-app`.

    Records package as the debug app so its next launch is
    debugger-friendly. This does NOT attach a debugger — it only sets the
    marker. With wait_for_debugger the next launch of the app blocks until a
    debugger connects (`-w`); with persistent the setting survives reboot
    (`--persistent`). Pair with clear_debug_app when done.

    Args:
        serial: The target device's adb serial (see list_connected_devices).
        package: The package to mark, e.g. "com.example.app".
        wait_for_debugger: Block the app's next launch until a debugger
            attaches (`-w`).
        persistent: Keep the setting across reboots (`--persistent`).

    Returns:
        The serial, package, and the wait_for_debugger / persistent flags
        that were applied.

    Error handling:
        A blank package raises INVALID_ARGUMENT before anything runs. An
        unknown serial or unresponsive adb binary raises
        DEVICE_NOT_FOUND/ADB_UNAVAILABLE. A permission rejection raises
        PERMISSION_DENIED; any other non-zero exit raises BACKEND_ERROR. An
        unknown package is NOT an error — `am` doesn't validate it.

    Example:
        Called with serial="emulator-5554", package="com.example.app",
        wait_for_debugger=true. A typical response:

        ```json
        {
          "status": "success",
          "message": "Set com.example.app as the debug app on emulator-5554 (waits for debugger on next launch).",
          "data": {
            "serial": "emulator-5554",
            "package": "com.example.app",
            "wait_for_debugger": true,
            "persistent": false
          },
          "error": null
        }
        ```
    """
    services = cast("dict[str, object]", ctx.lifespan_context["services"])
    debugging = cast(DebuggingService, services["debugging"])
    return await debugging.set_debug_app(
        serial, package, wait_for_debugger=wait_for_debugger, persistent=persistent
    )