1use serde_json::{json, Value as J};
22use std::cell::RefCell;
23use std::collections::HashSet;
24use std::io::{Read, Write};
25use std::os::unix::io::{FromRawFd, RawFd};
26
27use fusevm::{Op, VM};
28
29#[derive(Clone, Copy, PartialEq)]
31enum Mode {
32 Continue,
33 StepIn,
34 StepOver(usize),
35 StepOut(usize),
36}
37
38struct DebugState {
39 breakpoints: HashSet<u32>,
40 verified: HashSet<u32>,
42 function_breakpoints: HashSet<String>,
44 last_depth: usize,
47 mode: Mode,
48 proto_fd: RawFd,
51 pipe_r: RawFd,
54 program: String,
56 seq: i64,
57 active: bool,
59}
60
61thread_local! {
62 static DBG: RefCell<DebugState> = RefCell::new(DebugState {
63 breakpoints: HashSet::new(),
64 verified: HashSet::new(),
65 function_breakpoints: HashSet::new(),
66 last_depth: 0,
67 mode: Mode::Continue,
68 proto_fd: 1,
69 pipe_r: -1,
70 program: String::new(),
71 seq: 1,
72 active: false,
73 });
74}
75
76pub fn run() -> Result<(), String> {
78 let proto = unsafe { libc::dup(1) };
81 DBG.with(|d| d.borrow_mut().proto_fd = proto);
82
83 let mut input = std::io::stdin();
84 while let Some(msg) = read_message(&mut input)? {
85 let command = msg.get("command").and_then(|c| c.as_str()).unwrap_or("");
86 let req_seq = msg.get("seq").and_then(|s| s.as_i64()).unwrap_or(0);
87 match command {
88 "initialize" => {
89 respond(
90 req_seq,
91 command,
92 json!({
93 "supportsConfigurationDoneRequest": true,
94 "supportsEvaluateForHovers": true,
95 "supportsFunctionBreakpoints": true,
96 "supportsTerminateRequest": true,
97 }),
98 );
99 event("initialized", json!({}));
100 }
101 "setBreakpoints" => set_breakpoints(&msg, req_seq),
102 "setFunctionBreakpoints" => set_function_breakpoints(&msg, req_seq),
103 "setExceptionBreakpoints" => {
104 respond(req_seq, command, json!({ "breakpoints": [] }));
108 }
109 "evaluate" => {
110 respond(
113 req_seq,
114 command,
115 json!({ "result": "", "variablesReference": 0 }),
116 );
117 }
118 "pause" => respond(req_seq, command, json!({})),
119 "configurationDone" => respond(req_seq, command, json!({})),
120 "threads" => respond(
121 req_seq,
122 command,
123 json!({ "threads": [{ "id": 1, "name": "main" }] }),
124 ),
125 "launch" => {
126 let program = msg
127 .get("arguments")
128 .and_then(|a| a.get("program"))
129 .and_then(|p| p.as_str())
130 .unwrap_or("")
131 .to_string();
132 respond(req_seq, command, json!({}));
133 launch(&program);
134 }
135 "disconnect" | "terminate" => {
136 respond(req_seq, command, json!({}));
137 break;
138 }
139 _ => respond(req_seq, command, json!({})),
140 }
141 }
142 unsafe {
143 libc::close(proto);
144 }
145 Ok(())
146}
147
148fn set_breakpoints(msg: &J, req_seq: i64) {
153 let path = msg
154 .get("arguments")
155 .and_then(|a| a.get("source"))
156 .and_then(|s| s.get("path"))
157 .and_then(|p| p.as_str())
158 .unwrap_or("")
159 .to_string();
160 let lines: Vec<u32> = msg
161 .get("arguments")
162 .and_then(|a| a.get("breakpoints"))
163 .and_then(|b| b.as_array())
164 .map(|bps| {
165 bps.iter()
166 .filter_map(|b| b.get("line").and_then(|l| l.as_u64()).map(|l| l as u32))
167 .collect()
168 })
169 .unwrap_or_default();
170
171 let markers = marker_lines(&path);
172 DBG.with(|d| {
173 let mut s = d.borrow_mut();
174 if !path.is_empty() {
175 s.program = path;
176 }
177 s.breakpoints = lines.iter().copied().collect();
178 s.verified = markers;
179 });
180 let bps: Vec<J> = DBG.with(|d| {
181 let s = d.borrow();
182 lines
183 .iter()
184 .map(|l| json!({ "verified": s.verified.contains(l), "line": l }))
185 .collect()
186 });
187 respond(req_seq, "setBreakpoints", json!({ "breakpoints": bps }));
188}
189
190fn set_function_breakpoints(msg: &J, req_seq: i64) {
194 let names: Vec<String> = msg
195 .get("arguments")
196 .and_then(|a| a.get("breakpoints"))
197 .and_then(|b| b.as_array())
198 .map(|arr| {
199 arr.iter()
200 .filter_map(|b| b.get("name").and_then(|n| n.as_str()).map(String::from))
201 .collect()
202 })
203 .unwrap_or_default();
204 DBG.with(|d| d.borrow_mut().function_breakpoints = names.iter().cloned().collect());
205 let bps: Vec<J> = names.iter().map(|_| json!({ "verified": true })).collect();
206 respond(
207 req_seq,
208 "setFunctionBreakpoints",
209 json!({ "breakpoints": bps }),
210 );
211}
212
213fn evaluate_expression(expr: &str) -> String {
217 if expr.is_empty() {
218 return String::new();
219 }
220 for (name, repr) in crate::host::with_host(|h| h.dbg_locals()) {
221 if name == expr {
222 return repr;
223 }
224 }
225 format!("<cannot evaluate `{expr}`>")
226}
227
228fn marker_lines(path: &str) -> HashSet<u32> {
232 let mut set = HashSet::new();
233 let Ok(src) = std::fs::read_to_string(path) else {
234 return set;
235 };
236 let Ok(prog) = crate::compile_debug(&src) else {
237 return set;
238 };
239 let mut scan = |chunk: &fusevm::Chunk| {
240 for (i, op) in chunk.ops.iter().enumerate() {
241 if let Op::CallBuiltin(id, _) = op {
242 if *id == crate::host::ops::DBG_LINE {
243 if let Some(l) = chunk.lines.get(i) {
244 set.insert(*l);
245 }
246 }
247 }
248 }
249 };
250 scan(&prog.main);
251 for (_, f) in &prog.functions {
252 scan(&f.chunk);
253 }
254 for t in &prog.tries {
255 scan(&t.block);
256 if let Some((_name, handler)) = &t.handler {
257 scan(handler);
258 }
259 if let Some(finalizer) = &t.finalizer {
260 scan(finalizer);
261 }
262 }
263 set
264}
265
266fn launch(program: &str) {
270 if program.is_empty() {
271 return;
272 }
273 DBG.with(|d| {
274 let mut s = d.borrow_mut();
275 if s.program.is_empty() {
276 s.program = program.to_string();
277 }
278 });
279 let pipe_r = unsafe {
282 let mut fds = [0i32; 2];
283 if libc::pipe(fds.as_mut_ptr()) != 0 {
284 -1
285 } else {
286 libc::dup2(fds[1], 1);
287 libc::close(fds[1]);
288 let flags = libc::fcntl(fds[0], libc::F_GETFL);
289 libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK);
290 fds[0]
291 }
292 };
293 DBG.with(|d| {
294 let mut s = d.borrow_mut();
295 s.pipe_r = pipe_r;
296 s.mode = Mode::Continue;
297 s.active = true;
298 });
299
300 if let Err(e) = crate::eval_file_debug(program) {
301 eprintln!("node: {e}");
302 }
303
304 let _ = std::io::stdout().flush();
306 DBG.with(|d| d.borrow_mut().active = false);
307 drain_output();
308 let saved = DBG.with(|d| d.borrow().proto_fd);
309 unsafe {
310 if saved >= 0 {
311 libc::dup2(saved, 1);
312 }
313 if pipe_r >= 0 {
314 libc::close(pipe_r);
315 }
316 }
317 DBG.with(|d| d.borrow_mut().pipe_r = -1);
318 event("terminated", json!({}));
319}
320
321pub fn on_ext(vm: &mut VM, id: u16) {
328 if id == crate::host::ops::DBG_LINE {
329 let line = *vm.chunk.lines.get(vm.ip.saturating_sub(1)).unwrap_or(&0);
330 on_debug_line(line);
331 }
332}
333
334pub fn on_debug_line(line: u32) {
338 if line == 0 {
339 return;
340 }
341 let (depth, fname) = crate::host::with_host(|h| {
342 h.set_cur_line(line);
343 (
344 h.frame_depth(),
345 h.dbg_stack()
346 .first()
347 .map(|(n, _)| n.clone())
348 .unwrap_or_default(),
349 )
350 });
351 let (stop, reason) = DBG.with(|d| {
352 let mut s = d.borrow_mut();
353 if !s.active {
354 s.last_depth = depth;
355 return (false, "");
356 }
357 let bp = s.breakpoints.contains(&line) && s.verified.contains(&line);
358 let fbp = depth > s.last_depth && s.function_breakpoints.contains(&fname);
361 let step = match s.mode {
362 Mode::Continue => false,
363 Mode::StepIn => true,
364 Mode::StepOver(d0) => depth <= d0,
365 Mode::StepOut(d0) => depth < d0,
366 };
367 s.last_depth = depth;
368 let reason = if bp {
369 "breakpoint"
370 } else if fbp {
371 "function breakpoint"
372 } else {
373 "step"
374 };
375 (bp || fbp || step, reason)
376 });
377 if !stop {
378 return;
379 }
380 drain_output();
381 event(
382 "stopped",
383 json!({
384 "reason": reason,
385 "threadId": 1,
386 "allThreadsStopped": true,
387 }),
388 );
389
390 let mut stdin = std::io::stdin();
392 loop {
393 match read_message(&mut stdin) {
394 Ok(Some(msg)) => {
395 if handle_stopped(&msg, depth) {
396 break;
397 }
398 }
399 _ => {
400 DBG.with(|d| d.borrow_mut().mode = Mode::Continue);
402 break;
403 }
404 }
405 }
406}
407
408fn handle_stopped(msg: &J, depth: usize) -> bool {
411 let command = msg.get("command").and_then(|c| c.as_str()).unwrap_or("");
412 let req_seq = msg.get("seq").and_then(|s| s.as_i64()).unwrap_or(0);
413 match command {
414 "threads" => {
415 respond(
416 req_seq,
417 command,
418 json!({ "threads": [{ "id": 1, "name": "main" }] }),
419 );
420 false
421 }
422 "stackTrace" => {
423 let program = DBG.with(|d| d.borrow().program.clone());
424 let frames: Vec<J> = crate::host::with_host(|h| h.dbg_stack())
425 .into_iter()
426 .enumerate()
427 .map(|(i, (name, line))| {
428 json!({
429 "id": i,
430 "name": name,
431 "line": line,
432 "column": 1,
433 "source": { "path": program },
434 })
435 })
436 .collect();
437 respond(
438 req_seq,
439 command,
440 json!({ "stackFrames": frames, "totalFrames": frames.len() }),
441 );
442 false
443 }
444 "scopes" => {
445 respond(
446 req_seq,
447 command,
448 json!({ "scopes": [{ "name": "Locals", "variablesReference": 1, "expensive": false }] }),
449 );
450 false
451 }
452 "variables" => {
453 let vars: Vec<J> = crate::host::with_host(|h| h.dbg_locals())
454 .into_iter()
455 .map(|(n, v)| json!({ "name": n, "value": v, "variablesReference": 0 }))
456 .collect();
457 respond(req_seq, command, json!({ "variables": vars }));
458 false
459 }
460 "setBreakpoints" => {
461 set_breakpoints(msg, req_seq);
462 false
463 }
464 "setFunctionBreakpoints" => {
465 set_function_breakpoints(msg, req_seq);
466 false
467 }
468 "setExceptionBreakpoints" => {
469 respond(req_seq, command, json!({ "breakpoints": [] }));
470 false
471 }
472 "evaluate" => {
473 let expr = msg
474 .get("arguments")
475 .and_then(|a| a.get("expression"))
476 .and_then(|e| e.as_str())
477 .unwrap_or("")
478 .trim()
479 .to_string();
480 let result = evaluate_expression(&expr);
481 respond(
482 req_seq,
483 command,
484 json!({ "result": result, "variablesReference": 0 }),
485 );
486 false
487 }
488 "pause" => {
489 respond(req_seq, command, json!({}));
492 false
493 }
494 "continue" => {
495 DBG.with(|d| d.borrow_mut().mode = Mode::Continue);
496 respond(req_seq, command, json!({ "allThreadsContinued": true }));
497 true
498 }
499 "next" => {
500 DBG.with(|d| d.borrow_mut().mode = Mode::StepOver(depth));
501 respond(req_seq, command, json!({}));
502 true
503 }
504 "stepIn" => {
505 DBG.with(|d| d.borrow_mut().mode = Mode::StepIn);
506 respond(req_seq, command, json!({}));
507 true
508 }
509 "stepOut" => {
510 DBG.with(|d| d.borrow_mut().mode = Mode::StepOut(depth));
511 respond(req_seq, command, json!({}));
512 true
513 }
514 "disconnect" | "terminate" => {
515 DBG.with(|d| d.borrow_mut().mode = Mode::Continue);
516 respond(req_seq, command, json!({}));
517 true
518 }
519 _ => {
520 respond(req_seq, command, json!({}));
521 false
522 }
523 }
524}
525
526fn drain_output() {
529 let fd = DBG.with(|d| d.borrow().pipe_r);
530 if fd < 0 {
531 return;
532 }
533 let mut out = Vec::new();
534 let mut buf = [0u8; 4096];
535 loop {
536 let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
537 if n > 0 {
538 out.extend_from_slice(&buf[..n as usize]);
539 } else {
540 break;
541 }
542 }
543 if !out.is_empty() {
544 let text = String::from_utf8_lossy(&out).to_string();
545 event("output", json!({ "category": "stdout", "output": text }));
546 }
547}
548
549fn read_message(input: &mut std::io::Stdin) -> Result<Option<J>, String> {
553 let mut header = Vec::new();
554 let mut byte = [0u8; 1];
555 loop {
556 match input.read(&mut byte) {
557 Ok(0) => return Ok(None),
558 Ok(_) => {
559 header.push(byte[0]);
560 if header.ends_with(b"\r\n\r\n") {
561 break;
562 }
563 }
564 Err(e) => return Err(format!("dap read: {e}")),
565 }
566 }
567 let header = String::from_utf8_lossy(&header);
568 let len: usize = header
569 .lines()
570 .find_map(|l| l.strip_prefix("Content-Length:"))
571 .and_then(|v| v.trim().parse().ok())
572 .ok_or("dap: missing Content-Length")?;
573 let mut body = vec![0u8; len];
574 input
575 .read_exact(&mut body)
576 .map_err(|e| format!("dap body: {e}"))?;
577 serde_json::from_slice(&body)
578 .map(Some)
579 .map_err(|e| format!("dap json: {e}"))
580}
581
582fn send(msg: &J) {
585 let body = msg.to_string();
586 let frame = format!("Content-Length: {}\r\n\r\n{}", body.len(), body);
587 let fd = DBG.with(|d| d.borrow().proto_fd);
588 unsafe {
591 let mut f = std::mem::ManuallyDrop::new(std::fs::File::from_raw_fd(fd));
592 let _ = f.write_all(frame.as_bytes());
593 let _ = f.flush();
594 }
595}
596
597fn next_seq() -> i64 {
598 DBG.with(|d| {
599 let mut s = d.borrow_mut();
600 let n = s.seq;
601 s.seq += 1;
602 n
603 })
604}
605
606fn respond(req_seq: i64, command: &str, body: J) {
607 send(&json!({
608 "seq": next_seq(),
609 "type": "response",
610 "request_seq": req_seq,
611 "success": true,
612 "command": command,
613 "body": body,
614 }));
615}
616
617fn event(ev: &str, body: J) {
618 send(&json!({ "seq": next_seq(), "type": "event", "event": ev, "body": body }));
619}