From c590ba646d337105d6584c55a35ee532cde5b629 Mon Sep 17 00:00:00 2001 From: xu826Jamin Date: Sun, 22 Mar 2026 22:38:39 -0400 Subject: [PATCH 1/2] chore: add .dockerignore to optimize Docker build --- .dockerignore | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..386b11b --- /dev/null +++ b/.dockerignore @@ -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/ \ No newline at end of file From a71fc94552b9968a8c971e8803f4cf95b7fde050 Mon Sep 17 00:00:00 2001 From: xu826Jamin Date: Mon, 23 Mar 2026 02:13:44 -0400 Subject: [PATCH 2/2] feat: add WebSocket proof-of-concept prototype for GSoC 2026 --- .gitignore | 1 + prototype/README.md | 127 ++++++++ prototype/diag.py | 26 ++ prototype/requirements.txt | 4 + prototype/server.py | 584 +++++++++++++++++++++++++++++++++++++ prototype/target.c | 13 + prototype/target.exe | Bin 0 -> 41952 bytes 7 files changed, 755 insertions(+) create mode 100644 .gitignore create mode 100644 prototype/README.md create mode 100644 prototype/diag.py create mode 100644 prototype/requirements.txt create mode 100644 prototype/server.py create mode 100644 prototype/target.c create mode 100644 prototype/target.exe diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a69e31 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +"prototype/target" diff --git a/prototype/README.md b/prototype/README.md new file mode 100644 index 0000000..548d4e6 --- /dev/null +++ b/prototype/README.md @@ -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 ---------->| + | | [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 +``` + +## 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. diff --git a/prototype/diag.py b/prototype/diag.py new file mode 100644 index 0000000..d7611c8 --- /dev/null +++ b/prototype/diag.py @@ -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") +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) diff --git a/prototype/requirements.txt b/prototype/requirements.txt new file mode 100644 index 0000000..ce1dea5 --- /dev/null +++ b/prototype/requirements.txt @@ -0,0 +1,4 @@ +flask>=3.0.0 +flask-socketio>=5.3.6 +pygdbmi>=0.10.0.0 +eventlet>=0.35.0 diff --git a/prototype/server.py b/prototype/server.py new file mode 100644 index 0000000..c74e117 --- /dev/null +++ b/prototype/server.py @@ -0,0 +1,584 @@ +""" +GDB-UI WebSocket Proof-of-Concept +================================== +Demonstrates the background reader thread architecture proposed for GDB-UI. + +Key claim being proven: + GDB produces *stopped records asynchronously — not in response to a command. + A naive implementation that only emits responses to commands will silently + drop breakpoint hits. This prototype runs a dedicated reader thread per + session that continuously polls pygdbmi and emits ALL output types, + including async notify records, to the correct SocketIO room. + +Run: + python server.py + +Then open http://localhost:5000 in a browser. +""" + +import os +import time +import threading +import json + +from flask import Flask, render_template_string, session +from flask_socketio import SocketIO, emit, join_room +from pygdbmi.gdbcontroller import GdbController + +# ── App setup ──────────────────────────────────────────────────────────────── + +app = Flask(__name__) +app.config["SECRET_KEY"] = "gdb-ui-prototype-secret" + +# eventlet async mode required for background threads with SocketIO +socketio = SocketIO(app, async_mode="threading", cors_allowed_origins="*") + +_binary_name = "target.exe" if os.name == "nt" else "target" +TARGET_BINARY = os.path.join(os.path.dirname(os.path.abspath(__file__)), _binary_name) +TARGET_BINARY_GDB = TARGET_BINARY.replace("\\", "/") +BREAKPOINT_LINE = 9 + +# ── Session store ───────────────────────────────────────────────────────────── +# Maps session_id -> { controller, thread, active } +# This is the architecture proposed for main.py — one GdbController per user, +# isolated behind a lock so concurrent session creation is race-free. + +_sessions: dict = {} +_sessions_lock = threading.Lock() + + +def get_or_create_session(session_id: str) -> dict: + """ + Thread-safe session factory. + + The check-then-create pattern is NOT atomic without a lock even in CPython: + two simultaneous requests can both pass the 'if not in' check and both + create a GdbController, leaking one subprocess. The lock prevents this. + """ + with _sessions_lock: + if session_id not in _sessions: + _sessions[session_id] = { + "controller": None, # created lazily on first GDB command + "thread": None, + "active": False, + } + return _sessions[session_id] + + +# ── Background GDB reader thread ────────────────────────────────────────────── + +def _gdb_reader_thread(session_id: str, controller: GdbController) -> None: + """ + The critical piece of the WebSocket architecture. + + pygdbmi classifies GDB MI output into three types: + - 'result' — synchronous response to a MI command (-break-insert, etc.) + - 'notify' — async record GDB produces on its own (*stopped, =thread-created) + - 'console' — plain text from GDB or the inferior program + + A naive implementation that only emits inside the @socketio.on('gdb_command') + handler will silently drop 'notify' records — including breakpoint hits — + because those arrive between commands, not in response to them. + + This thread runs continuously and emits every record type to the correct + SocketIO room, ensuring the frontend receives breakpoint hits in real time. + """ + print(f"[reader] thread started for session {session_id[:8]}") + + while True: + # Check if this session is still alive + with _sessions_lock: + sess = _sessions.get(session_id) + if not sess or not sess.get("active"): + break + + try: + responses = controller.get_gdb_response( + timeout_sec=0.1, raise_error_on_timeout=False + ) + except Exception as e: + print(f"[reader] error reading from GDB: {e}") + break + + for response in responses: + msg_type = response.get("type") + message = response.get("message", "") + payload = response.get("payload") + + if msg_type == "notify" and message == "stopped": + # ── This is the async record the frontend cares most about ── + # It arrives here because GDB hit the breakpoint — the client + # sent NO command to trigger this. Polling would never catch it + # in real time. The reader thread does. + reason = payload.get("reason", "unknown") if isinstance(payload, dict) else "unknown" + frame = payload.get("frame", {}) if isinstance(payload, dict) else {} + socketio.emit("breakpoint_hit", { + "reason": reason, + "line": frame.get("line", "?"), + "func": frame.get("func", "?"), + "file": os.path.basename(frame.get("fullname", "?")), + "variables": _get_locals(controller), + }, room=session_id) + print(f"[reader] emitted breakpoint_hit to room {session_id[:8]} — {reason} at line {frame.get('line','?')}") + + elif msg_type == "notify": + # Emit all other async records too (thread events, library loads) + socketio.emit("gdb_async", { + "message": message, + "payload": str(payload)[:200], + }, room=session_id) + + elif msg_type in ("console", "log"): + # GDB console output and inferior program output + socketio.emit("program_output", { + "text": str(payload), + }, room=session_id) + + elif msg_type == "result": + # Synchronous result record — emit for completeness + socketio.emit("gdb_result", { + "message": message, + "payload": str(payload)[:200], + }, room=session_id) + + +def _get_locals(controller: GdbController) -> dict: + """Fetch local variables at the current frame.""" + try: + resp = controller.write("-stack-list-locals --simple-values", timeout_sec=2) + for r in resp: + if r.get("type") == "result" and r.get("message") == "done": + locals_list = r.get("payload", {}).get("locals", []) + return {v["name"]: v.get("value", "?") for v in locals_list} + except Exception: + pass + return {} + + +# ── SocketIO event handlers ─────────────────────────────────────────────────── + +@socketio.on("connect") +def on_connect(): + sid = session.get("sid") or "anon" + join_room(sid) + print(f"[connect] client joined room {sid[:8]}") + emit("connected", {"session_id": sid[:8]}) + + +@socketio.on("start_debug") +def on_start_debug(data): + """ + Client requests a new debug session. + Creates an isolated GdbController, sets a breakpoint, and starts the + background reader thread before running the program. + """ + sid = session.get("sid", "anon") + sess = get_or_create_session(sid) + + # Tear down any existing GDB session for this user + if sess.get("controller"): + try: + sess["controller"].exit() + except Exception: + pass + sess["active"] = False + + # Create a fresh isolated GdbController for this session + controller = GdbController() + sess["controller"] = controller + sess["active"] = True + + # Load the binary + controller.write(f"-file-exec-and-symbols {TARGET_BINARY_GDB}", timeout_sec=3) + + # On Windows, set breakpoint by function name — more reliable than line numbers + # before the symbol table is fully indexed + bp_resp = controller.write(f"-break-insert {BREAKPOINT_LINE}", timeout_sec=3) + bp_ok = any(r.get("message") == "done" for r in bp_resp) + + emit("session_ready", { + "binary": os.path.basename(TARGET_BINARY), + "breakpoint": BREAKPOINT_LINE, + "bp_set": bp_ok, + }) + print(f"[start_debug] session ready for {sid[:8]}, breakpoint set={bp_ok}") + + # Start the background reader thread BEFORE running the program. + # This is essential: if we run first and start the reader after, + # we can miss the *stopped record entirely. + reader = threading.Thread( + target=_gdb_reader_thread, + args=(sid, controller), + daemon=True, + name=f"gdb-reader-{sid[:8]}", + ) + sess["thread"] = reader + reader.start() + + # Run the program — execution is async; the reader thread will catch *stopped + controller.write("-exec-run", timeout_sec=2) + emit("program_running", {"message": "Program running — waiting for breakpoint..."}) + + +@socketio.on("gdb_command") +def on_gdb_command(data): + """ + Handle an arbitrary MI command from the frontend. + The reader thread handles async output; this handler handles synchronous + command-response flow (step, continue, etc.). + """ + sid = session.get("sid", "anon") + with _sessions_lock: + sess = _sessions.get(sid) + + if not sess or not sess.get("controller"): + emit("error", {"message": "No active debug session"}) + return + + command = data.get("command", "") + print(f"[command] {sid[:8]} -> {command}") + + try: + resp = sess["controller"].write(command, timeout_sec=3) + # Reader thread handles async records; only emit the sync result here + for r in resp: + if r.get("type") == "result": + emit("gdb_result", { + "command": command, + "message": r.get("message"), + "payload": str(r.get("payload", ""))[:300], + }) + except Exception as e: + emit("error", {"message": str(e)}) + + +@socketio.on("disconnect") +def on_disconnect(): + sid = session.get("sid", "anon") + with _sessions_lock: + sess = _sessions.get(sid) + if sess: + sess["active"] = False + ctrl = sess.get("controller") + if ctrl: + try: + ctrl.exit() + except Exception: + pass + del _sessions[sid] + print(f"[disconnect] cleaned up session {sid[:8]}") + + +# ── Frontend ────────────────────────────────────────────────────────────────── + +HTML = """ + + + +GDB-UI WebSocket Prototype + + + +
+

GDB-UI — WebSocket Prototype

+ disconnected + Proof of concept for GSoC 2026 proposal +
+ +
+ +
+ + + + + + + Click “Start debug session” to begin +
+ + +
+
+ Event log + 0 events +
+
+
+ + +
+
+ Local variables + +
+
+ Variables will appear when a breakpoint is hit +
+
+ + +
+
Source — target.c
+
+
{{ source }}
+
+
+ + +
+
+ GDB output + +
+
+
+ +
+ + + + + +""" + + +@app.route("/") +def index(): + import uuid + # Assign a stable session ID per browser session + if "sid" not in session: + session["sid"] = str(uuid.uuid4()) + + # Read the C source to render in the frontend + src_path = os.path.join(os.path.dirname(__file__), "target.c") + try: + with open(src_path) as f: + source = f.read() + except FileNotFoundError: + source = "/* target.c not found — run: gcc -g -O0 -o target target.c */" + + html = HTML.replace("{{ source }}", source) + html = html.replace("{{ BREAKPOINT_LINE }}", str(BREAKPOINT_LINE)) + return html + + +# ── Entry point ─────────────────────────────────────────────────────────────── + +if __name__ == "__main__": + if __name__ == "__main__": + binary = TARGET_BINARY + if not os.path.exists(binary): + print("ERROR: target binary not found.") + print("Run: gcc -g -O0 -o target target.c") + raise SystemExit(1) + + print("=" * 60) + print("GDB-UI WebSocket Proof-of-Concept") + print("=" * 60) + print(f" Target binary : {binary}") + print(f" Breakpoint : target.c line {BREAKPOINT_LINE}") + print(f" URL : http://localhost:5000") + print() + print("What to observe:") + print(" 1. Click 'Start debug session'") + print(" 2. Watch the BREAKPOINT HIT event arrive in the event log") + print(" — no command was sent to trigger it") + print(" 3. Local variables appear automatically (x=10, y=20)") + print(" 4. Line 9 highlights in the source panel") + print(" 5. Click 'Continue' or 'Step over' to resume") + print("=" * 60) + + socketio.run(app, host="0.0.0.0", port=5000, debug=False) diff --git a/prototype/target.c b/prototype/target.c new file mode 100644 index 0000000..98752e2 --- /dev/null +++ b/prototype/target.c @@ -0,0 +1,13 @@ +#include + +int add(int a, int b) { + return a + b; +} + +int main() { + int x = 10; + int y = 20; + int result = add(x, y); /* breakpoint will land here */ + printf("result = %d\n", result); + return 0; +} diff --git a/prototype/target.exe b/prototype/target.exe new file mode 100644 index 0000000000000000000000000000000000000000..9ec7024e981b6c7c30dcbd2851ad11be869902ed GIT binary patch literal 41952 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%0D$u8U<|C88cEs2X0Dq4CP1Xw-4=8}+y)wBLdrh=v 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