38volatile sig_atomic_t g_shutdown_requested = 0;
40void signalHandler(
int sig) {
42 g_shutdown_requested = 1;
48void printUsage(
const char *program) {
49 fprintf(stderr,
"Usage: %s [options]\n\n", program);
50 fprintf(stderr,
"Options:\n");
51 fprintf(stderr,
" --attach <pid> Attach to process by PID\n");
52 fprintf(stderr,
" --attach-name <name> Attach to process by name\n");
53 fprintf(stderr,
" --wait Wait for process to start (with --attach-name)\n");
54 fprintf(stderr,
" --port <port> HTTP server port (default: 9999)\n");
55 fprintf(stderr,
" --help Show this help\n");
56 fprintf(stderr,
"\nExamples:\n");
57 fprintf(stderr,
" %s --attach 12345 --port 9999\n", program);
58 fprintf(stderr,
" %s --attach-name ascii-chat --wait\n", program);
59 fprintf(stderr,
"\nQuery endpoints:\n");
60 fprintf(stderr,
" GET / Status page\n");
61 fprintf(stderr,
" GET /process Process information\n");
62 fprintf(stderr,
" GET /threads Thread list\n");
63 fprintf(stderr,
" GET /frames Stack frames (when stopped)\n");
64 fprintf(stderr,
" GET /query Query a variable\n");
65 fprintf(stderr,
" POST /continue Resume execution\n");
66 fprintf(stderr,
" POST /step Single step\n");
67 fprintf(stderr,
" POST /detach Detach from process\n");
92 obj.
set(
"id", thread.
id);
93 obj.
set(
"index",
static_cast<int64_t
>(thread.
index));
98 obj.
set(
"line",
static_cast<int64_t
>(thread.
line));
106 obj.
set(
"index",
static_cast<int64_t
>(frame.
index));
109 obj.
set(
"line",
static_cast<int64_t
>(frame.
line));
110 obj.
set(
"pc", frame.
pc);
111 obj.
set(
"fp", frame.
fp);
125 obj.
set(
"size",
static_cast<int64_t
>(var.
size));
130 if (!var.
children.empty() && max_depth > 0) {
132 for (
const auto &child : var.children) {
133 children.
add(variableToJson(child, max_depth - 1));
135 obj.
set(
"children", children);
144 obj.
set(
"id",
static_cast<int64_t
>(bp.
id));
146 obj.
set(
"line",
static_cast<int64_t
>(bp.
line));
148 obj.
set(
"hit_count",
static_cast<int64_t
>(bp.
hit_count));
162 std::string state_str = processStateToString(state);
164 std::string text =
"ascii-query-server\n";
165 text +=
"==================\n\n";
166 text +=
"Target PID: " + std::to_string(controller.
targetPid()) +
"\n";
167 text +=
"Target Name: " + controller.
targetName() +
"\n";
168 text +=
"State: " + state_str +
"\n";
170 text +=
"Endpoints:\n";
171 text +=
" GET / Status page (this)\n";
172 text +=
" GET /process Process information (JSON)\n";
173 text +=
" GET /threads Thread list (JSON)\n";
174 text +=
" GET /frames Stack frames when stopped (JSON)\n";
175 text +=
" GET /query?name=VAR Query a variable (JSON)\n";
176 text +=
" GET /breakpoints List breakpoints (JSON)\n";
177 text +=
" POST /continue Resume execution\n";
178 text +=
" POST /stop Stop execution\n";
179 text +=
" POST /step Single step (add ?over or ?out)\n";
180 text +=
" POST /detach Detach from process\n";
182 text +=
"Examples:\n";
183 text +=
" curl localhost:9999/process\n";
184 text +=
" curl localhost:9999/query?name=frame.width\n";
185 text +=
" curl 'localhost:9999/query?file=server.c&line=100&name=client_count&break'\n";
186 text +=
" curl -X POST localhost:9999/continue\n";
188 return HttpResponse::text(text);
194 obj.
set(
"pid",
static_cast<int64_t
>(controller.
targetPid()));
196 obj.
set(
"state", processStateToString(controller.
state()));
201 return HttpResponse::json(obj.
toString());
208 for (
const auto &thread : threads) {
209 arr.
add(threadToJson(thread));
212 obj.
set(
"count",
static_cast<int64_t
>(threads.size()));
213 obj.
set(
"threads", arr);
214 return HttpResponse::json(obj.
toString());
219 int max_frames = req.
paramInt(
"max", 50);
222 for (
const auto &frame : frames) {
223 arr.
add(frameToJson(frame));
226 obj.
set(
"count",
static_cast<int64_t
>(frames.size()));
227 obj.
set(
"frames", arr);
228 return HttpResponse::json(obj.
toString());
233 std::string file = req.
param(
"file");
235 std::string name = req.
param(
"name");
236 int frame_index = req.
paramInt(
"frame", 0);
237 int expand_depth = req.
paramInt(
"depth", 0);
238 bool should_break = req.
hasParam(
"break");
239 int timeout_ms = req.
paramInt(
"timeout", 5000);
242 return HttpResponse::badRequest(
"Missing 'name' parameter");
246 bool is_stopped = (state == ProcessState::Stopped);
249 if (!file.empty() && line > 0 && should_break && !is_stopped) {
253 obj.
set(
"status",
"error");
254 obj.
set(
"error",
"breakpoint_failed");
255 obj.
set(
"message",
"Failed to set breakpoint at " + file +
":" + std::to_string(line));
256 return HttpResponse::json(obj.
toString());
263 obj.
set(
"status",
"error");
264 obj.
set(
"error",
"timeout");
265 obj.
set(
"message",
"Breakpoint not hit within " + std::to_string(timeout_ms) +
"ms");
266 return HttpResponse::json(obj.
toString());
272 int actual_expand = expand_depth > 0 ? expand_depth : (req.
hasParam(
"expand") ? 3 : 0);
274 static_cast<uint32_t>(actual_expand));
278 obj.
set(
"status",
"error");
279 obj.
set(
"error",
"not_found");
280 obj.
set(
"message",
"Variable '" + name +
"' not found");
281 obj.
set(
"stopped", is_stopped);
282 return HttpResponse::json(obj.
toString());
286 obj.
set(
"status",
"ok");
287 obj.
set(
"stopped", is_stopped);
288 obj.
set(
"result", variableToJson(*var, actual_expand > 0 ? actual_expand : 3));
289 return HttpResponse::json(obj.
toString());
296 for (
const auto &bp : breakpoints) {
297 arr.
add(breakpointToJson(bp));
300 obj.
set(
"count",
static_cast<int64_t
>(breakpoints.size()));
301 obj.
set(
"breakpoints", arr);
302 return HttpResponse::json(obj.
toString());
307 std::string file = req.
param(
"file");
309 std::string condition = req.
param(
"condition");
311 if (file.empty() || line <= 0) {
312 return HttpResponse::badRequest(
"Missing 'file' and 'line' parameters");
318 obj.
set(
"status",
"error");
320 return HttpResponse::json(obj.
toString());
325 obj.
set(
"status",
"ok");
327 obj.
set(
"breakpoint", breakpointToJson(*bp));
329 return HttpResponse::json(obj.
toString());
336 return HttpResponse::badRequest(
"Missing 'id' parameter");
340 obj.
set(
"status", removed ?
"ok" :
"error");
342 obj.
set(
"message",
"Breakpoint not found");
344 return HttpResponse::json(obj.
toString());
349 bool resumed = controller.
resume();
351 obj.
set(
"status", resumed ?
"running" :
"error");
355 return HttpResponse::json(obj.
toString());
360 bool stopped = controller.
stop();
362 obj.
set(
"status", stopped ?
"stopped" :
"error");
366 return HttpResponse::json(obj.
toString());
375 obj.
set(
"status", success ?
"ok" :
"error");
379 return HttpResponse::json(obj.
toString());
386 obj.
set(
"status",
"detached");
387 return HttpResponse::json(obj.
toString());
393int main(
int argc,
char *argv[]) {
395 pid_t attach_pid = 0;
396 std::string attach_name;
397 bool wait_for =
false;
400 for (
int i = 1; i < argc; i++) {
401 if (strcmp(argv[i],
"--help") == 0 || strcmp(argv[i],
"-h") == 0) {
404 }
else if (strcmp(argv[i],
"--attach") == 0 && i + 1 < argc) {
406 unsigned long pid_val = 0;
408 fprintf(stderr,
"Error: Invalid PID value\n");
411 attach_pid =
static_cast<pid_t
>(pid_val);
412 }
else if (strcmp(argv[i],
"--attach-name") == 0 && i + 1 < argc) {
413 attach_name = argv[++i];
414 }
else if (strcmp(argv[i],
"--wait") == 0) {
416 }
else if (strcmp(argv[i],
"--port") == 0 && i + 1 < argc) {
419 fprintf(stderr,
"Error: Invalid port number (must be 1-65535)\n");
423 fprintf(stderr,
"Unknown option: %s\n", argv[i]);
430 if (attach_pid == 0 && attach_name.empty()) {
431 fprintf(stderr,
"Error: Must specify --attach <pid> or --attach-name <name>\n\n");
437 signal(SIGINT, signalHandler);
438 signal(SIGTERM, signalHandler);
440 signal(SIGPIPE, SIG_IGN);
445 g_controller = &controller;
448 fprintf(stderr,
"Error: Failed to initialize LLDB: %s\n", controller.
lastError().c_str());
453 fprintf(stderr,
"[ascii-query-server] Attaching to ");
454 if (attach_pid > 0) {
455 fprintf(stderr,
"PID %llu...\n",
static_cast<unsigned long long>(attach_pid));
456 if (!controller.
attach(attach_pid)) {
457 fprintf(stderr,
"Error: Failed to attach: %s\n", controller.
lastError().c_str());
461 fprintf(stderr,
"process '%s'%s...\n", attach_name.c_str(), wait_for ?
" (waiting)" :
"");
463 fprintf(stderr,
"Error: Failed to attach: %s\n", controller.
lastError().c_str());
468 fprintf(stderr,
"[ascii-query-server] Attached to %s (PID %llu)\n", controller.
targetName().c_str(),
469 static_cast<unsigned long long>(controller.
targetPid()));
473 fprintf(stderr,
"[ascii-query-server] Resuming target...\n");
481 setupRoutes(server, controller);
484 if (!server.
start(port)) {
485 fprintf(stderr,
"Error: Failed to start HTTP server: %s\n", server.
lastError().c_str());
490 fprintf(stderr,
"[ascii-query-server] HTTP server listening on http://localhost:%d\n", port);
491 fprintf(stderr,
"[ascii-query-server] Press Ctrl+C to stop\n");
494 while (!g_shutdown_requested && controller.
isAttached()) {
502 fprintf(stderr,
"[ascii-query-server] Target %s, shutting down\n",
509 fprintf(stderr,
"[ascii-query-server] Shutting down...\n");
514 fprintf(stderr,
"[ascii-query-server] Done\n");
Simple single-threaded HTTP server.
void addRoute(const std::string &method, const std::string &path, RouteHandler handler)
Add a route handler.
bool start(uint16_t port)
Start the server.
const std::string & lastError() const
Get the last error message.
void stop()
Stop the server.
int32_t setBreakpoint(const std::string &file, uint32_t line, const std::string &condition="")
Set a breakpoint at file:line.
bool attachByName(const std::string &process_name, bool wait_for=false)
Attach to a process by name.
bool removeBreakpoint(int32_t breakpoint_id)
Remove a breakpoint.
bool stepInto()
Single step the current thread (step into)
pid_t targetPid() const
Get the PID of the attached process.
std::string targetName() const
Get the name of the attached process.
std::optional< VariableInfo > readVariable(const std::string &name, uint32_t frame_index=0, uint32_t expand_depth=0) const
Read a variable from the current frame.
const std::string & lastError() const
Get the last error message.
bool initialize()
Initialize LLDB. Must be called before any other methods.
void shutdown()
Shutdown LLDB and release resources.
bool stop()
Stop the target process.
bool isAttached() const
Check if attached to a process.
std::vector< ThreadInfo > getThreads() const
Get list of all threads.
std::vector< BreakpointInfo > getBreakpoints() const
Get all breakpoints.
bool resume()
Resume the target process.
std::vector< FrameInfo > getFrames(uint32_t max_frames=0) const
Get stack frames for the selected thread.
bool stepOver()
Step over the current line.
std::optional< BreakpointInfo > getBreakpoint(int32_t breakpoint_id) const
Get information about a specific breakpoint.
bool stepOut()
Step out of the current function.
void detach()
Detach from the current process.
bool attach(pid_t pid)
Attach to a process by PID.
ProcessState state() const
Get the current process state.
bool waitForBreakpoint(uint32_t timeout_ms=0)
Wait for any breakpoint to be hit.
JsonArray & add(const JsonValue &value)
std::string toString() const
JsonObject & set(const std::string &key, const JsonValue &value)
Minimal single-threaded HTTP/1.1 server.
Minimal JSON serialization helpers (header-only)
LLDB process attachment and control wrapper.
int main(int argc, char *argv[])
ProcessState
Process state enumeration.
@ Exited
Process has exited.
@ Crashed
Process crashed.
@ Detached
Detached from process.
@ Invalid
No valid process attached.
@ Running
Process is running normally.
@ Stopped
Process is stopped (breakpoint, signal, etc.)
asciichat_error_t parse_ulong(const char *str, unsigned long *out_value, unsigned long min_value, unsigned long max_value)
Parse unsigned long integer with range validation.
asciichat_error_t parse_port(const char *str, uint16_t *out_port)
Parse port number (1-65535) from string.
int32_t id
LLDB breakpoint ID.
std::string condition
Condition expression (may be empty)
bool enabled
Is breakpoint enabled?
uint32_t line
Line number.
bool resolved
Is breakpoint resolved (has valid location)?
uint32_t hit_count
Number of times hit.
std::string file
Source file.
uint32_t line
Line number.
std::string function
Function name.
uint64_t pc
Program counter.
uint64_t fp
Frame pointer.
uint32_t index
Frame index (0 = innermost)
std::string file
Source file.
int paramInt(const std::string &name, int default_value=0) const
Get a query parameter as integer.
std::string param(const std::string &name, const std::string &default_value="") const
Get a query parameter value.
bool hasParam(const std::string &name) const
Check if a query parameter exists (flag-style, like &break)
std::string name
Thread name (may be empty)
uint32_t line
Current line number.
bool is_selected
Is this the selected thread?
std::string stop_reason
Why thread is stopped (if stopped)
std::string function
Current function name.
uint32_t index
Index in thread list.
std::string file
Current source file.
Variable information (from LLDB SBValue)
std::vector< VariableInfo > children
Struct members / array elements.
std::string value
Value as string.
bool is_aggregate
Whether this is struct/class/array.
size_t size
Size in bytes.
std::string type
Type name.
std::string name
Variable name.
bool is_pointer
Whether this is a pointer type.
std::string summary
LLDB summary (for complex types)
bool is_valid
Whether value could be read.
uint64_t address
Memory address.