GDB-UI is a user-friendly interface built for the GNU Debugger (GDB), providing a modern web-based UI for debugging your applications. It allows developers to monitor program execution, inspect variables, set breakpoints, and more, all through an intuitive web application.
GitHub Repository: c2siorg/GDB-UI
GDB-UI simplifies the debugging process by integrating the powerful features of GDB with a sleek and easy-to-use web interface. This project is particularly useful for developers working with languages like C, C++, and Ada. The interface offers a more accessible and visual approach to debugging, making it easier to identify and fix issues in your code.
The quickest way to get started with GDB-UI is by using Docker. A docker-compose.yml file is provided to handle the entire setup.
-
Ensure Docker and Docker Compose are installed on your machine.
-
Run the following command in your terminal:
docker-compose up
This command will build and start both the frontend and backend services, making the application available at http://localhost:5173 (or your specified port). backend API runs on (http://localhost:10000)
If you prefer a manual setup or are unable to use Docker, follow these steps:
- Node.js: Version 18
- Python: Version 3.10
-
Navigate to the
webappdirectory:cd webapp -
Install the necessary dependencies:
npm install
-
Start the development server:
npm run dev
-
Navigate to the
gdbui_serverdirectory:cd gdbui_server -
Install the required Python packages:
pip install -r requirements.txt
-
Run the backend server:
python main.py
The backend listens on http://127.0.0.1:10000 by default.
Existing unversioned routes keep the legacy response shape so current clients can continue reading top-level fields such as result, output, message, and file_path. Internal code fields are no longer returned.
Example success response from POST /gdb_command:
{
"success": true,
"result": "..."
}Example error response from POST /gdb_command:
{
"success": false,
"error": "GDB command failed.",
"trace_id": "..."
}Every backend route is also available under /v2 with the standardized response envelope:
{
"success": true,
"data": {
"result": "..."
}
}V2 errors return stable client-safe messages and codes. Exception details are logged server-side and correlated with trace_id.
{
"success": false,
"error": {
"code": "GDB_COMMAND_FAILED",
"message": "GDB command failed.",
"trace_id": "..."
}
}V2 JSON endpoints validate required fields before running compiler or GDB actions. Missing or blank required fields return 400 Bad Request with error.code set to INVALID_REQUEST.
All responses include an X-Correlation-ID header. For V2 errors, the header value matches error.trace_id.
Available V2 endpoints:
POST /v2/compilePOST /v2/upload_filePOST /v2/gdb_commandPOST /v2/set_breakpointPOST /v2/info_breakpointsPOST /v2/stack_tracePOST /v2/threadsPOST /v2/get_registersPOST /v2/get_localsPOST /v2/runPOST /v2/memory_mapPOST /v2/continuePOST /v2/step_overPOST /v2/step_intoPOST /v2/step_outPOST /v2/add_watchpointPOST /v2/delete_breakpoint
To run the frontend tests, follow these steps:
-
Navigate to the
webappdirectory:cd webapp -
Run the tests using Vite:
npm run test
To run the backend tests, use the following procedure:
-
Ensure your Python environment is set up as described in the manual setup.
-
From the repository root, run the tests using the
unittestmodule:python3 -m unittest discover -s gdbui_server -p "flask_test.py"
We welcome contributions from the community! To get started:
-
Fork the repository at c2siorg/GDB-UI.
-
Clone your fork:
git clone https://github.com/your-username/GDB-UI.git
-
Create a new branch for your feature or bugfix:
git checkout -b feature-name
-
Make your changes and commit them:
git commit -m "Description of your changes" -
Push your branch to your fork:
git push origin feature-name
-
Open a pull request on the main repository.
Please ensure your code adheres to our coding standards and is thoroughly tested before submitting your pull request.
User A ──► POST /gdb_command
│
User B ──► POST /gdb_command
│
▼
┌────────────────┐
│ global state │ (shared — one GDB for everyone)
│ gdb_ctrl │
│ program_name │
└───────┬────────┘
│
▼
┌────────────────┐
│ one GDB proc │ ← User B's request kills User A's session
└────────────────┘
Problem: Requests from multiple users overwrite the shared controller.
The second user's start_gdb_session() silently kills the first user's GDB process.
User A ──► POST /create_session ──► abc-123
User B ──► POST /create_session ──► def-456
┌────────────────────────────────────────────────────┐
│ SessionManager (in-memory dict) │
│ │
│ sessions["abc-123"] sessions["def-456"] │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ session_lock │ │ session_lock │ │
│ │ (threading.RLock)│ │ (threading.RLock)│ │
│ │ controller: GDB │ │ controller: GDB │ │
│ │ program: "progA" │ │ program: "progB" │ │
│ │ output/abc-123/ │ │ output/def-456/ │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ Global lock (dict mutations only — microseconds) │
│ Per-session lock (GDB I/O — milliseconds/seconds) │
│ controller.write() NEVER called under global lock │
└────────────────────────────────────────────────────┘
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ GDB proc A │ │ GDB proc B │
│ (isolated) │ │ (isolated) │
└────────────────┘ └────────────────┘
Request A (thread 1) Request B (thread 2)
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ global lock │ │ global lock │
│ (acquire, micros) │ │ (acquire, micros) │
│ sessions[sid] lookup│ │ sessions[sid] lookup │
│ (release) │ │ (release) │
│ ✓ dict safe │ │ ✓ dict safe │
└──────────────────────┘ └──────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ session_lock A │ │ session_lock B │
│ (acquire, RLock) │ │ (acquire, RLock) │
│ controller.write() │ │ controller.write() │
│ (release) │ │ (release) │
│ ✓ GDB I/O serialized│ │ ✓ GDB I/O serialized│
└──────────────────────┘ └──────────────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ GDB A │ │ GDB B │
└──────────┘ └──────────┘
Two sessions proceed in parallel because global lock is only held for dict lookups (microseconds). GDB I/O under per-session locks never collides.
output/
├── abc-123/ ← User A
│ ├── progA (binary)
│ ├── progA.cpp (source)
│ └── progA.exe (Windows binary)
│
└── def-456/ ← User B
├── progB
├── progB.cpp
└── progB.exe
Each session's output directory is created on first use. TTL cleanup or
manual end_session removes the entire directory. No cross-session
filesystem access is possible.
For development and production, GDB-UI's session manager requires concurrent/threaded execution. Single-threaded configurations will serialize all requests and defeat the per-session GDB isolation mechanism.
The Flask development server runs with threaded=True by default since Flask 1.0+. Do not disable threading.
python main.pyFor production deployments, Gunicorn must be configured with exactly 1 worker process and multiple threads. Since GDB session metadata and locks are stored in-memory, running with multiple worker processes will cause requests to be routed to different processes where their sessions are not recognized, resulting in 404 Not Found errors.
gunicorn -w 1 --threads 4 main:appRequirements:
- At least 1 worker with multiple threads (e.g.,
--threads 4). - Do NOT use:
gunicorn -w 1 --threads 1(disables concurrency). - Do NOT use:
gunicorn -w N(N > 1), as session states are stored in-memory per process.
GDB-UI implements robust multi-user isolation to support multiple concurrent users debugging different programs independently on a single backend instance.
- Creation: On page mount, the React client requests a new session via
/create_session. The server generates a unique UUID (e.g.,session_id), creates isolated output directoryoutput/{session_id}/, and registers a reentrant lockthreading.RLockto serialize debugging operations for that session. - Execution: Every API request (such as compiling code, setting breakpoints, executing commands) must include the
session_id. The server routes the command to the session's isolatedpygdbmi.GdbControllerinstance. - Automatic Cleanup (TTL): A background cleanup thread runs every 60 seconds on the server. If a session is inactive for more than the TTL (default: 3600 seconds/1 hour), GDB is closed, the session is removed, and its isolated directory
output/{session_id}/is deleted. - Manual Termination: When the webapp unmounts or a user resets their session, the client issues a POST
/end_sessionrequest to trigger immediate cleanup.
- Isolation Boundary: Each session runs its own OS-level GDB process. Output files, compilation units, and debugged binaries are isolated under
output/{session_id}/to prevent cross-session access. - Path Traversal Blocking: Program names are validated using strict sanitization rules (
sanitize_program_name), preventing path traversal attacks (e.g., passing../../to access files outside the session directory). - Two-Tier Locking: A global lock protects the SessionManager's session dictionary structure from concurrent mutations, while per-session
RLockinstances serialize all GDB I/O operations and file compilation/upload phases for each session. - Compiling Safely: When compilation (
/compile) or file upload (/upload_file) is requested, the server locks the session, checks if a debug session is active, and if so, blocks compilation and returns409 Conflict. Otherwise, it writes the file and runs the compiler cleanly, updating the program state. - Error Resilience: Malformed GDB Machine Interface (MI) tokens are caught by the
_parse_responsewrapper insideSessionManager, returning structured error payloads to the client without terminating the debug session.
- BLOCKED_COMMANDS is not sufficient against expression injection: The
BLOCKED_COMMANDSset blocks shell-level commands (shell,python,!, etc.) but does NOT prevent malicious expressions from executing through GDB's expression evaluator. For example,call system("rm -rf /")is a valid GDB MI expression that bypasses the command-level blocklist. Full sandboxing requires Phase 3 Docker container isolation. - Session ID as sole auth token: The
session_idUUID is the only authorization mechanism. This is acceptable for local or trusted-network deployments. Public internet exposure would require additional authentication (signed tokens, user accounts, or HTTPS + HttpOnly cookies).