如何用GDB调试asyncio协程?解决aiohttp应用栈追踪缺失问题
Hey there, I've run into this exact issue before—when debugging asyncio apps with GDB, the default py-bt only shows the event loop's main stack because asyncio coroutines are managed as Task objects, each with their own separate call stacks that aren't automatically traversed by the basic command. Let's break down how to fix this and take control of your coroutines:
1. List & Inspect Active Asyncio Tasks
Most modern GDB Python extensions (built for Python asyncio debugging) include commands to list all running/suspended tasks. Try these first:
Step 1: List all tasks
Run this in GDB to get an overview of active coroutines:
(gdb) py-list-tasks
You'll get output like this:
Found 2 active tasks:
Task 0x7f8a12345670: <handle_request() running at ./my-server/main.py:65>
Task 0x7f8a12345ab0: <fetch_external_data() suspended at ./my-server/utils.py:32>
Step 2: View a specific coroutine's stack
Once you have the task address, pass it to py-bt to see its actual call stack:
(gdb) py-bt 0x7f8a12345670
This will show you the full traceback for that coroutine, including where it's stuck or suspended:
Traceback (most recent call first):
File "./my-server/utils.py", line 35, in fetch_external_data
await asyncio.sleep(5)
File "./my-server/main.py", line 68, in handle_request
data = await fetch_external_data()
File "/usr/local/lib/python3.7/dist-packages/aiohttp/web_protocol.py", line 422, in _handle_request
resp = await self._request_handler(request)
2. Manual Task Inspection (If py-list-tasks Isn't Available)
If your GDB extension doesn't have the task-listing command, you can manually dig into the asyncio event loop to find tasks:
- First, get the current running event loop:
(gdb) py import asyncio (gdb) py loop = asyncio.get_running_loop()
- Check ready (about to run) and scheduled (waiting) tasks:
(gdb) py print("Ready tasks:", [t for t in loop._ready]) (gdb) py print("Scheduled tasks:", [t for t in loop._scheduled])
- For a specific task, print its stack directly:
(gdb) py task = loop._ready[0] # Pick the first ready task (gdb) py print("Coroutine stack:") (gdb) py for frame in task.get_stack(): > print(f"{frame.f_code.co_filename}:{frame.f_lineno} - {frame.f_code.co_name}")
3. Interrupting & Resuming Coroutines
Setting Breakpoints in Coroutines
Just like regular Python code, you can set breakpoints directly in coroutine code to pause execution when it hits that line:
(gdb) b ./my-server/main.py:68 (gdb) c
When the coroutine reaches line 68, GDB will pause, and you can use py-bt (with or without the task address) to inspect the stack.
Canceling/Suspending a Coroutine
If you need to force a coroutine to stop, you can cancel it directly from GDB:
(gdb) py task.cancel()
This will raise a asyncio.CancelledError in the coroutine—make sure your app handles this properly, or it might crash.
Resuming Execution
To let the event loop continue scheduling all coroutines, just use the standard GDB continue command:
(gdb) c
If you want to run a specific coroutine to completion (outside the main event loop), you can do this (note: use carefully, as it might interfere with the running app):
(gdb) py asyncio.run_until_complete(task)
Quick Tips
- Avoid running your Python app with the
-O(optimize) flag—it strips out stack frame metadata that GDB needs to show coroutine stacks. - Make sure you're using a GDB version compatible with your Python 3.7 installation (matching Python debug symbols helps too).
- If you're using a custom GDB Python extension, double-check that it explicitly supports asyncio task traversal (many newer extensions do out of the box).
内容的提问来源于stack exchange,提问作者AivanF.

