Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Git
.git
.gitignore

# Python
__pycache__
*.pyc
*.pyo
*.pyd
venv/
*.egg-info/

# Node
node_modules/
npm-debug.log

# Build output
build/
dist/

# Environment files
.env
.env.local

# OS files
.DS_Store
Thumbs.db

# IDE files
.vscode/
.idea/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"prototype/target"
127 changes: 127 additions & 0 deletions prototype/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# GDB-UI WebSocket Proof-of-Concept

Prototype for the GSoC 2026 proposal: **GDB-UI — Web-Based GNU Debugger Interface** (C2SI).

## What this proves

The proposal's most technically ambitious claim is that replacing GDB-UI's polling
architecture with WebSockets requires a **background reader thread** — not just wiring
up Flask-SocketIO. Here is why:

GDB MI output falls into three types:

| Type | Example | When it arrives |
|------|---------|----------------|
| `result` | response to `-break-insert` | synchronously, after your command |
| `notify` | `*stopped` breakpoint hit | **asynchronously — no command triggers it** |
| `console` | program stdout | any time the inferior writes |

A naive WebSocket implementation that only emits inside the `@socketio.on('gdb_command')`
handler will **silently drop breakpoint hits**, because those arrive between commands.

This prototype runs a dedicated reader thread per session (`_gdb_reader_thread`) that
continuously polls `pygdbmi` and emits all record types to the correct SocketIO room.
The browser receives the breakpoint hit in real time — without having sent any command.

## Architecture

```
Browser server.py GDB process
| | |
|-- start_debug -----------> | |
| |-- GdbController() ---------->|
| |-- -file-exec-and-symbols --->|
| |-- -break-insert 9 ---------->|
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The architecture diagram hard-codes -break-insert 9, but target.c’s annotated breakpoint location is on line 10 (and server.py defines BREAKPOINT_LINE). Update this line number (or reference the constant) so the README matches the actual demo behavior.

Suggested change
| |-- -break-insert 9 ---------->|
| |-- -break-insert 10 --------->|

Copilot uses AI. Check for mistakes.
| | [start reader thread] |
| |-- -exec-run ---------------->|
| | |
| | reader thread polls: |
| |<-- *stopped (notify) --------|
|<-- breakpoint_hit ---------| |
| (no command sent!) | |
| | |
|-- gdb_command: -exec-next->| |
| |-- -exec-next --------------->|
```

## Files

```
gdb-websocket-prototype/
├── server.py # Flask-SocketIO backend with background reader thread
├── target.c # C program with a deliberate breakpoint location
├── target # compiled binary (build with command below)
└── README.md
```
Comment on lines +49 to +55
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README “Files” section suggests a compiled binary is part of the prototype directory, but the setup section instructs building it locally. To keep the repo clean (and avoid platform-specific binaries), prefer not committing compiled outputs; document the build step and ignore the generated target/target.exe files.

Copilot uses AI. Check for mistakes.

## Setup

```bash
# 1. Install dependencies
pip install flask flask-socketio pygdbmi eventlet

# 2. Compile the C target (requires gcc and gdb)
gcc -g -O0 -o target target.c

# 3. Run the server
python server.py

# 4. Open http://localhost:5000
```

## What to observe in the browser

1. Click **Start debug session**
2. Watch the **BREAKPOINT HIT** event arrive in the event log — highlighted in red
3. The label says *"This record arrived unprompted — no command was sent"*
4. Local variables (`x = 10`, `y = 20`) appear automatically in the Variables panel
5. Line 9 highlights in the source panel
6. Use **Continue**, **Step over**, or **Step into** to resume execution

## Key code: the reader thread

```python
def _gdb_reader_thread(session_id, controller, socketio):
while True:
responses = controller.get_gdb_response(timeout_sec=0.1)
for response in responses:
msg_type = response.get('type')
if msg_type == 'notify' and response.get('message') == 'stopped':
# Breakpoint hit — arrived with NO client command
socketio.emit('breakpoint_hit', {...}, room=session_id)
elif msg_type == 'notify':
socketio.emit('gdb_async', {...}, room=session_id)
elif msg_type in ('console', 'log'):
socketio.emit('program_output', {...}, room=session_id)
```

## Thread-safe session management

Each browser session gets an isolated `GdbController` instance, created behind a lock
to prevent the check-then-create race condition:

```python
_sessions_lock = threading.Lock()

def get_or_create_session(session_id):
with _sessions_lock: # atomic check-and-create
if session_id not in _sessions:
_sessions[session_id] = {
'controller': GdbController(),
'active': True,
}
return _sessions[session_id]
```

Without the lock, two simultaneous requests can both pass the `if not in` check and
both create a `GdbController`, leaking one GDB subprocess.

## Relation to main.py in GDB-UI

The current `main.py` uses:
```python
gdb_controller = GdbController() # global — shared across all requests
```

This prototype replaces that with per-session isolation and demonstrates the
WebSocket architecture that Objective 4 of the proposal implements.
26 changes: 26 additions & 0 deletions prototype/diag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from pygdbmi.gdbcontroller import GdbController
import os, time

TARGET = os.path.join(os.path.dirname(os.path.abspath(__file__)), "target.exe")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diag.py hard-codes target.exe, so it won’t work on non-Windows platforms (and server.py already handles target vs target.exe). Consider using the same OS-dependent binary name logic here to keep the prototype runnable cross-platform.

Suggested change
TARGET = os.path.join(os.path.dirname(os.path.abspath(__file__)), "target.exe")
_target_name = "target.exe" if os.name == "nt" else "target"
TARGET = os.path.join(os.path.dirname(os.path.abspath(__file__)), _target_name)

Copilot uses AI. Check for mistakes.
print("Target:", TARGET)
print("Exists:", os.path.exists(TARGET))

try:
ctrl = GdbController()
print("GDB started OK")

resp = ctrl.write(f"-file-exec-and-symbols {TARGET}", timeout_sec=5)
print("file-exec responses:")
for r in resp:
print(" ", r["type"], r.get("message",""), str(r.get("payload",""))[:80])

resp2 = ctrl.write("-break-insert main", timeout_sec=5)
print("break-insert responses:")
for r in resp2:
print(" ", r["type"], r.get("message",""), str(r.get("payload",""))[:80])

ctrl.exit()
print("Done")

except Exception as e:
print("ERROR:", type(e).__name__, e)
4 changes: 4 additions & 0 deletions prototype/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flask>=3.0.0
flask-socketio>=5.3.6
pygdbmi>=0.10.0.0
eventlet>=0.35.0
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requirements.txt uses loose >= pins, which makes the prototype non-reproducible and can break unexpectedly with new dependency releases. The main server’s requirements file in this repo is pinned to exact versions; consider pinning here too (and ensure the selected versions match the chosen SocketIO async mode).

Suggested change
eventlet>=0.35.0
eventlet==0.35.0

Copilot uses AI. Check for mistakes.
Loading
Loading