From 2737e5d352007a746e5d8018ac465002404e834f Mon Sep 17 00:00:00 2001 From: William Wallace Date: Sat, 28 Nov 2020 01:34:52 +0000 Subject: [PATCH] Add Auxtools debugging support (#230) This adds support for a debug server written in auxtools (currently located at https://github.com/willox/auxtools). The dependency is similar to extools, where SpacemanDMM's `auxtools_types.rs` has to be up-to-date with the `server_types.rs` file located in whichever version of debug server is used. In the future this could change to be some shared dependency, or maybe the debug server could just be moved into SpacemanDMM. There's a bunch of repeated code in `mod.rs` where there's match statements where one branch is for the extools client and one branch is for auxtools client. It's a bit iffy, but they all have minor differences and wouldn't be super easy to merge. I accidentally ran a `cargo fmt` on the files I was working with at some point, so there's a few formatting changes about. I don't think it's too much to read over. I haven't edited any documentation yet, so here's how it works: In your DM project: ```dm // Currently needed for auxtools' error reporting. TG code already has this defined. /proc/stack_trace(msg) CRASH(msg) /proc/enable_debugging(mode, port) CRASH("auxtools not loaded") /world/New() var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") if (debug_server) call(debug_server, "auxtools_init")() enable_debugging() . = ..() /world/Del() var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") if (debug_server) call(debug_server, "auxtools_shutdown")() . = ..() ``` In your project's SpacemanDMM.toml ```toml [debugger] engine = "auxtools" ``` The extension doesn't have a way to override the DLL being used (and I don't think it should), so if you're testing stuff I suggest you set the env vars to something like below and use the attach mode: ``` AUXTOOLS_DEBUG_DLL=path_to_your_build AUXTOOLS_DEBUG_MODE=BLOCK ``` --- Cargo.lock | 11 + src/dreammaker/config.rs | 22 + src/langserver/Cargo.toml | 1 + src/langserver/build.rs | 6 + src/langserver/debugger/auxtools.rs | 453 +++++++ src/langserver/debugger/auxtools_bundle.rs | 24 + src/langserver/debugger/auxtools_types.rs | 145 +++ src/langserver/debugger/evaluate.rs | 59 +- src/langserver/debugger/launched.rs | 56 +- src/langserver/debugger/mod.rs | 1362 +++++++++++++------- src/langserver/main.rs | 2 +- 11 files changed, 1669 insertions(+), 472 deletions(-) create mode 100644 src/langserver/debugger/auxtools.rs create mode 100644 src/langserver/debugger/auxtools_bundle.rs create mode 100644 src/langserver/debugger/auxtools_types.rs diff --git a/Cargo.lock b/Cargo.lock index f8154196..b3493287 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "bincode" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" +dependencies = [ + "byteorder", + "serde", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -406,6 +416,7 @@ dependencies = [ name = "dm-langserver" version = "1.4.0" dependencies = [ + "bincode", "chrono", "dreamchecker", "dreammaker", diff --git a/src/dreammaker/config.rs b/src/dreammaker/config.rs index bb8785c8..3b6ff81a 100644 --- a/src/dreammaker/config.rs +++ b/src/dreammaker/config.rs @@ -24,6 +24,7 @@ pub struct Config { // tool-specific configuration pub langserver: Langserver, pub dmdoc: DMDoc, + pub debugger: Debugger, } /// General error display options @@ -53,6 +54,13 @@ pub struct DMDoc { pub use_typepath_names: bool, } +// Debugger config options +#[derive(Deserialize, Default, Debug, Clone)] +pub struct Debugger { + #[serde(default)] + pub engine: DebugEngine, +} + /// Severity overrides from configuration #[derive(Debug, Deserialize, Clone, Copy, PartialEq)] #[serde(rename_all(deserialize = "lowercase"))] @@ -70,6 +78,14 @@ pub enum WarningLevel { Unset = 6, } +#[derive(Debug, Deserialize, Clone, Copy, PartialEq)] +pub enum DebugEngine { + #[serde(alias = "extools")] + Extools, + #[serde(alias = "auxtools")] + Auxtools, +} + impl Config { /// Read a config TOML and generate a [`Config`] struct /// @@ -152,6 +168,12 @@ impl PartialEq for WarningLevel { } } +impl Default for DebugEngine { + fn default() -> Self { + Self::Extools + } +} + /// Config parse error #[derive(Debug)] pub enum Error { diff --git a/src/langserver/Cargo.toml b/src/langserver/Cargo.toml index a69d0ff0..3b8e0d07 100644 --- a/src/langserver/Cargo.toml +++ b/src/langserver/Cargo.toml @@ -13,6 +13,7 @@ url = "2.1.0" serde = "1.0.27" serde_json = "1.0.10" serde_derive = "1.0.27" +bincode = "1.3.1" jsonrpc-core = "14.0.3" lsp-types = "0.80.0" dreammaker = { path = "../dreammaker" } diff --git a/src/langserver/build.rs b/src/langserver/build.rs index 2e673d14..fbc67b76 100644 --- a/src/langserver/build.rs +++ b/src/langserver/build.rs @@ -21,6 +21,12 @@ fn main() { if env::var_os("EXTOOLS_BUNDLE_DLL").is_some() { println!("cargo:rustc-cfg=extools_bundle"); } + + // auxtools bundling + println!("cargo:rerun-if-env-changed=AUXTOOLS_BUNDLE_DLL"); + if env::var_os("AUXTOOLS_BUNDLE_DLL").is_some() { + println!("cargo:rustc-cfg=auxtools_bundle"); + } } fn read_commit() -> Result { diff --git a/src/langserver/debugger/auxtools.rs b/src/langserver/debugger/auxtools.rs new file mode 100644 index 00000000..a3fad8ba --- /dev/null +++ b/src/langserver/debugger/auxtools.rs @@ -0,0 +1,453 @@ +use super::auxtools_types::*; +use std::{net::TcpListener, sync::mpsc}; +use std::thread; +use std::{ + io::{Read, Write}, + net::Ipv4Addr, + net::SocketAddr, + net::TcpStream, + sync::{Arc, RwLock}, + thread::JoinHandle, +}; + +use super::dap_types; +use super::SequenceNumber; + +enum StreamState { + // The client is waiting for a Stream to be sent from the thread + Waiting(mpsc::Receiver), + + Connected(TcpStream), + + // The server has finished being used + Disconnected, +} + +pub struct Auxtools { + seq: Arc, + responses: mpsc::Receiver, + _thread: JoinHandle<()>, + stream: StreamState, + last_error: Arc>, +} + +pub struct AuxtoolsThread { + seq: Arc, + responses: mpsc::Sender, + last_error: Arc>, +} + +impl Auxtools { + pub fn connect(seq: Arc, port: Option) -> std::io::Result { + let addr: SocketAddr = (Ipv4Addr::LOCALHOST, port.unwrap_or(DEFAULT_PORT)).into(); + let (responses_sender, responses_receiver) = mpsc::channel(); + let last_error = Arc::new(RwLock::new("".to_owned())); + let stream = TcpStream::connect_timeout(&addr, std::time::Duration::from_secs(5))?; + + let thread = { + let seq = seq.clone(); + let last_error = last_error.clone(); + let stream = stream.try_clone().unwrap(); + thread::spawn(move || { + AuxtoolsThread { + seq, + responses: responses_sender, + last_error: last_error, + }.run(stream); + }) + }; + + Ok(Auxtools { + seq, + responses: responses_receiver, + _thread: thread, + stream: StreamState::Connected(stream), + last_error, + }) + } + + pub fn listen(seq: Arc) -> std::io::Result<(u16, Self)> { + let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0))?; + let port = listener.local_addr()?.port(); + + let (connection_sender, connection_receiver) = mpsc::channel(); + let (responses_sender, responses_receiver) = mpsc::channel(); + let last_error = Arc::new(RwLock::new("".to_owned())); + + let thread = { + let seq = seq.clone(); + let last_error = last_error.clone(); + AuxtoolsThread { + seq, + responses: responses_sender, + last_error: last_error, + }.spawn_listener(listener, connection_sender) + }; + + Ok((port, Auxtools { + seq, + responses: responses_receiver, + _thread: thread, + stream: StreamState::Waiting(connection_receiver), + last_error, + })) + } + + fn read_response_or_disconnect(&mut self) -> Result> { + match self.responses.recv_timeout(std::time::Duration::from_secs(5)) { + Ok(response) => Ok(response), + Err(_) => { + self.disconnect(); + Err(Box::new(super::GenericError("timed out waiting for response"))) + } + } + } + + fn send(&mut self, request: Request) -> Result<(), Box> { + if let StreamState::Waiting(recv) = &self.stream { + if let Ok(stream) = recv.try_recv() { + self.stream = StreamState::Connected(stream); + } + } + + match &mut self.stream { + StreamState::Connected(stream) => { + let data = bincode::serialize(&request)?; + stream.write_all(&(data.len() as u32).to_le_bytes())?; + stream.write_all(&data[..])?; + stream.flush()?; + Ok(()) + } + + _ => { + // Success if not connected (kinda dumb) + Ok(()) + } + } + } + + fn send_or_disconnect(&mut self, request: Request) -> Result<(), Box> { + if let Err(e) = self.send(request) { + self.disconnect(); + return Err(e); + } + + Ok(()) + } + + pub fn disconnect(&mut self) { + debug_output!(in self.seq, "[auxtools] disconnecting"); + let _ = self.send(Request::Disconnect); + + if let StreamState::Connected(stream) = &self.stream { + let _ = stream.shutdown(std::net::Shutdown::Both); + } + + self.stream = StreamState::Disconnected; + } + + pub fn get_line_number(&mut self, path: &str, override_id: u32, offset: u32) -> Result, Box> { + self.send_or_disconnect(Request::LineNumber { + proc: ProcRef { + path: path.to_owned(), + override_id, + }, + offset, + })?; + + match self.read_response_or_disconnect()? { + Response::LineNumber { line } => Ok(line), + response => Err(Box::new(UnexpectedResponse::new("LineNumber", response))), + } + } + + pub fn get_offset(&mut self, path: &str, override_id: u32, line: u32) -> Result, Box> { + self.send_or_disconnect(Request::Offset { + proc: ProcRef { + path: path.to_owned(), + override_id, + }, + line, + })?; + + match self.read_response_or_disconnect()? { + Response::Offset { offset } => Ok(offset), + response => Err(Box::new(UnexpectedResponse::new("Offset", response))), + } + } + + pub fn set_breakpoint(&mut self, instruction: &InstructionRef) -> Result> { + self.send_or_disconnect(Request::BreakpointSet { + instruction: instruction.clone(), + })?; + + match self.read_response_or_disconnect()? { + Response::BreakpointSet { result } => Ok(result), + response => Err(Box::new(UnexpectedResponse::new("BreakpointSet", response))), + } + } + + pub fn unset_breakpoint(&mut self, instruction: &InstructionRef) -> Result<(), Box> { + self.send_or_disconnect(Request::BreakpointUnset { + instruction: instruction.clone(), + })?; + + match self.read_response_or_disconnect()? { + Response::BreakpointUnset { .. } => Ok(()), + response => Err(Box::new(UnexpectedResponse::new("BreakpointUnset", response))), + } + } + + pub fn continue_execution(&mut self) -> Result<(), Box> { + self.send_or_disconnect(Request::Continue { + kind: ContinueKind::Continue, + })?; + + match self.read_response_or_disconnect()? { + Response::Ack { .. } => Ok(()), + response => Err(Box::new(UnexpectedResponse::new("Ack", response))), + } + } + + pub fn next(&mut self, stack_id: u32) -> Result<(), Box> { + self.send_or_disconnect(Request::Continue { + kind: ContinueKind::StepOver { stack_id }, + })?; + + match self.read_response_or_disconnect()? { + Response::Ack { .. } => Ok(()), + response => Err(Box::new(UnexpectedResponse::new("Ack", response))), + } + } + + pub fn step_into(&mut self, stack_id: u32) -> Result<(), Box> { + self.send_or_disconnect(Request::Continue { + kind: ContinueKind::StepInto { stack_id }, + })?; + + match self.read_response_or_disconnect()? { + Response::Ack { .. } => Ok(()), + response => Err(Box::new(UnexpectedResponse::new("Ack", response))), + } + } + + pub fn step_out(&mut self, stack_id: u32) -> Result<(), Box> { + self.send_or_disconnect(Request::Continue { + kind: ContinueKind::StepOut { stack_id }, + })?; + + match self.read_response_or_disconnect()? { + Response::Ack { .. } => Ok(()), + response => Err(Box::new(UnexpectedResponse::new("Ack", response))), + } + } + + pub fn pause(&mut self) -> Result<(), Box> { + self.send_or_disconnect(Request::Pause)?; + + match self.read_response_or_disconnect()? { + Response::Ack { .. } => Ok(()), + response => Err(Box::new(UnexpectedResponse::new("Ack", response))), + } + } + + pub fn get_stacks(&mut self) -> Result, Box> { + self.send_or_disconnect(Request::Stacks)?; + + match self.read_response_or_disconnect()? { + Response::Stacks { stacks } => Ok(stacks), + response => Err(Box::new(UnexpectedResponse::new("Stacks", response))), + } + } + + pub fn get_stack_frames( + &mut self, + stack_id: u32, + start_frame: Option, + count: Option, + ) -> Result<(Vec, u32), Box> { + self.send_or_disconnect(Request::StackFrames { + stack_id, + start_frame, + count, + })?; + + match self.read_response_or_disconnect()? { + Response::StackFrames { + frames, + total_count, + } => Ok((frames, total_count)), + response => Err(Box::new(UnexpectedResponse::new("StackFrames", response))), + } + } + + // TODO: return all the scopes + pub fn get_scopes(&mut self, frame_id: u32) -> Result<(Option, Option, Option), Box> { + self.send_or_disconnect(Request::Scopes { + frame_id + })?; + + match self.read_response_or_disconnect()? { + Response::Scopes { + arguments, + locals, + globals, + } => Ok((arguments, locals, globals)), + response => Err(Box::new(UnexpectedResponse::new("Scopes", response))), + } + } + + pub fn get_variables(&mut self, vars: VariablesRef) -> Result, Box> { + self.send_or_disconnect(Request::Variables { + vars + })?; + + match self.read_response_or_disconnect()? { + Response::Variables { vars } => Ok(vars), + response => Err(Box::new(UnexpectedResponse::new("Variables", response))), + } + } + + pub fn get_last_error_message(&self) -> String { + self.last_error.read().unwrap().clone() + } + + pub fn set_catch_runtimes(&mut self, should_catch: bool) -> Result<(), Box> { + self.send_or_disconnect(Request::CatchRuntimes { should_catch }) + } +} + +impl AuxtoolsThread { + fn spawn_listener( + self, + listener: TcpListener, + connection_sender: mpsc::Sender, + ) -> JoinHandle<()> { + thread::spawn(move || match listener.accept() { + Ok((stream, _)) => { + match connection_sender.send(stream.try_clone().unwrap()) { + Ok(_) => {} + Err(e) => { + eprintln!("Debug client thread failed to pass cloned TcpStream: {}", e); + return; + } + } + + self.run(stream); + } + + Err(e) => { + eprintln!("Debug client failed to accept connection: {}", e); + } + }) + } + + // returns true if we should disconnect + fn handle_response(&mut self, data: &[u8]) -> Result> { + let response = bincode::deserialize::(data)?; + + match response { + Response::Disconnect => return Ok(true), + + Response::Notification { message } => { + debug_output!(in self.seq, "[auxtools] {}", message); + } + + Response::BreakpointHit { reason } => { + let mut description = None; + + let reason = match reason { + BreakpointReason::Step => dap_types::StoppedEvent::REASON_STEP, + BreakpointReason::Pause => dap_types::StoppedEvent::REASON_PAUSE, + BreakpointReason::Breakpoint => dap_types::StoppedEvent::REASON_BREAKPOINT, + BreakpointReason::Runtime(error) => { + *(self.last_error.write().unwrap()) = error.clone(); + description = Some(error); + dap_types::StoppedEvent::REASON_EXCEPTION + } + }; + + self.seq.issue_event(dap_types::StoppedEvent { + threadId: Some(0), + reason: reason.to_owned(), + description, + allThreadsStopped: Some(true), + ..Default::default() + }); + } + + x => { + self.responses.send(x)?; + } + } + + Ok(false) + } + + pub fn run(mut self, mut stream: TcpStream) { + let mut buf = vec![]; + + // Look at this little trouble-maker right here + self.seq.issue_event(dap_types::InitializedEvent); + + // The incoming stream is a u32 followed by a bincode-encoded Request. + loop { + let mut len_bytes = [0u8; 4]; + let len = match stream.read_exact(&mut len_bytes) { + Ok(_) => u32::from_le_bytes(len_bytes), + + Err(e) => { + eprintln!("Debug server thread read error: {}", e); + break; + } + }; + + buf.resize(len as usize, 0); + match stream.read_exact(&mut buf) { + Ok(_) => (), + + Err(e) => { + eprintln!("Debug server thread read error: {}", e); + break; + } + }; + + match self.handle_response(&buf[..]) { + Ok(requested_disconnect) => { + if requested_disconnect { + eprintln!("Debug server disconnected"); + break; + } + } + + Err(e) => { + eprintln!("Debug server thread failed to handle request: {}", e); + break; + } + } + } + + self.seq.issue_event(dap_types::TerminatedEvent::default()); + } +} + +#[derive(Debug)] +pub struct UnexpectedResponse(String); + +impl UnexpectedResponse { + fn new(expected: &'static str, received: Response) -> Self { + Self(format!("received unexpected response: expected {}, got {:?}", expected, received)) + } +} + +impl std::error::Error for UnexpectedResponse { + fn description(&self) -> &str { + &self.0 + } +} + +impl std::fmt::Display for UnexpectedResponse { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.write_str(&self.0) + } +} diff --git a/src/langserver/debugger/auxtools_bundle.rs b/src/langserver/debugger/auxtools_bundle.rs new file mode 100644 index 00000000..240ba15d --- /dev/null +++ b/src/langserver/debugger/auxtools_bundle.rs @@ -0,0 +1,24 @@ +#![cfg(auxtools_bundle)] +use std::fs::File; +use std::io::{Result, Write}; +use std::path::{Path, PathBuf}; + +const BYTES: &[u8] = include_bytes!(env!("AUXTOOLS_BUNDLE_DLL")); + +fn write(path: &Path) -> Result<()> { + File::create(path)?.write_all(BYTES) +} + +pub fn extract() -> Result { + let exe = std::env::current_exe()?; + let directory = exe.parent().unwrap(); + for i in 0..9 { + let dll = directory.join(format!("auxtools_debug_server{}.dll", i)); + if let Ok(()) = write(&dll) { + return Ok(dll); + } + } + let dll = directory.join("auxtools_debug_server9.dll"); + write(&dll)?; + Ok(dll) +} diff --git a/src/langserver/debugger/auxtools_types.rs b/src/langserver/debugger/auxtools_types.rs new file mode 100644 index 00000000..f01ed3ad --- /dev/null +++ b/src/langserver/debugger/auxtools_types.rs @@ -0,0 +1,145 @@ +// TODO: These will be shared properly + +use serde::{Deserialize, Serialize}; + +#[allow(dead_code)] +pub const DEFAULT_PORT: u16 = 2448; + +// Message from client -> server +#[derive(Serialize, Deserialize, Debug)] +pub enum Request { + Disconnect, + BreakpointSet { + instruction: InstructionRef, + }, + BreakpointUnset { + instruction: InstructionRef, + }, + CatchRuntimes { + should_catch: bool, + }, + LineNumber { + proc: ProcRef, + offset: u32, + }, + Offset { + proc: ProcRef, + line: u32, + }, + Stacks, + StackFrames { + stack_id: u32, + start_frame: Option, + count: Option, + }, + Scopes { + frame_id: u32, + }, + Variables { + vars: VariablesRef, + }, + Continue { + kind: ContinueKind, + }, + Pause, +} + +// Message from server -> client +#[derive(Serialize, Deserialize, Debug)] +pub enum Response { + Ack, + BreakpointSet { + result: BreakpointSetResult, + }, + BreakpointUnset { + success: bool, + }, + LineNumber { + line: Option, + }, + Offset { + offset: Option, + }, + Stacks { + stacks: Vec, + }, + StackFrames { + frames: Vec, + total_count: u32, + }, + Scopes { + arguments: Option, + locals: Option, + globals: Option, + }, + Variables { + vars: Vec, + }, + + // These responses can occur at any moment, even between a request and its response + // I guess they aren't really responses... + Disconnect, + Notification { + message: String, + }, + BreakpointHit { + reason: BreakpointReason, + }, +} + +#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, Clone)] +pub struct ProcRef { + pub path: String, + pub override_id: u32, +} + +#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, Clone)] +pub struct InstructionRef { + pub proc: ProcRef, + pub offset: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum BreakpointReason { + Breakpoint, + Step, + Pause, + Runtime(String), +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum ContinueKind { + Continue, + StepOver { stack_id: u32 }, + StepInto { stack_id: u32 }, + StepOut { stack_id: u32 }, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Stack { + pub id: u32, + pub name: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct StackFrame { + pub id: u32, + pub instruction: InstructionRef, + pub line: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum BreakpointSetResult { + Success { line: Option }, + Failed, +} + +#[derive(Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Debug)] +pub struct VariablesRef(pub i32); + +#[derive(Serialize, Deserialize, Debug)] +pub struct Variable { + pub name: String, + pub value: String, + pub variables: Option, +} diff --git a/src/langserver/debugger/evaluate.rs b/src/langserver/debugger/evaluate.rs index f57f352b..e813f1ee 100644 --- a/src/langserver/debugger/evaluate.rs +++ b/src/langserver/debugger/evaluate.rs @@ -1,45 +1,68 @@ -use super::*; use super::dap_types::*; +use super::*; const EVALUATE_HELP: &str = " #dis, #disassemble: show disassembly for current stack frame"; impl Debugger { - pub fn evaluate(&mut self, params: EvaluateArguments) -> Result> { + pub fn evaluate( + &mut self, + params: EvaluateArguments, + ) -> Result> { let input = params.expression.trim_start(); if input.starts_with("#help") { return Ok(EvaluateResponse::from(EVALUATE_HELP.trim())); } - let extools = self.extools.get()?; + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; - guard!(let Some(frame_id) = params.frameId else { - return Err(Box::new(GenericError("Must select a stack frame to evaluate in"))); - }); - - let (thread, frame_no) = extools.get_thread_by_frame_id(frame_id)?; - - if input.starts_with('#') { - if input == "#dis" || input == "#disassemble" { - guard!(let Some(frame) = thread.call_stack.get(frame_no) else { - return Err(Box::new(GenericError("Stack frame out of range"))); + guard!(let Some(frame_id) = params.frameId else { + return Err(Box::new(GenericError("Must select a stack frame to evaluate in"))); }); - let bytecode = extools.bytecode(&frame.proc, frame.override_id); - return Ok(EvaluateResponse::from(Self::format_disassembly(bytecode))); + let (thread, frame_no) = extools.get_thread_by_frame_id(frame_id)?; + + if input.starts_with('#') { + if input == "#dis" || input == "#disassemble" { + guard!(let Some(frame) = thread.call_stack.get(frame_no) else { + return Err(Box::new(GenericError("Stack frame out of range"))); + }); + + let bytecode = extools.bytecode(&frame.proc, frame.override_id); + return Ok(EvaluateResponse::from(Self::format_disassembly(bytecode))); + } + } } + + DebugClient::Auxtools(_) => {} } Err(Box::new(GenericError("Not yet implemented"))) } - pub fn format_disassembly(bytecode: &[super::extools_types::DisassembledInstruction]) -> String { + pub fn format_disassembly( + bytecode: &[super::extools_types::DisassembledInstruction], + ) -> String { let mut buf = String::new(); - let bytes_max_len = bytecode.iter().map(|elem| elem.bytes.len()).max().unwrap_or(0); + let bytes_max_len = bytecode + .iter() + .map(|elem| elem.bytes.len()) + .max() + .unwrap_or(0); for instr in bytecode { use std::fmt::Write; - let _ = writeln!(buf, "{:#8X} {:width$} {} {}", instr.offset, instr.bytes, instr.mnemonic, instr.comment, width = bytes_max_len); + let _ = writeln!( + buf, + "{:#8X} {:width$} {} {}", + instr.offset, + instr.bytes, + instr.mnemonic, + instr.comment, + width = bytes_max_len + ); } buf diff --git a/src/langserver/debugger/launched.rs b/src/langserver/debugger/launched.rs index d3dc9d70..baf72b87 100644 --- a/src/langserver/debugger/launched.rs +++ b/src/langserver/debugger/launched.rs @@ -1,9 +1,9 @@ //! Child process lifecycle management. #![allow(unsafe_code)] -use std::sync::{Arc, Mutex}; -use std::process::{Command, Stdio}; use super::{dap_types, SequenceNumber}; +use std::process::{Command, Stdio}; +use std::sync::{Arc, Mutex}; // active --kill--> killed: emit Terminated, send SIGKILL // active --detach--> detached: emit Terminated @@ -27,8 +27,24 @@ pub struct Launched { mutex: Arc>, } +pub enum EngineParams { + Extools { + port: u16, + dll: Option + }, + Auxtools { + port: u16, + dll: Option + } +} + impl Launched { - pub fn new(seq: Arc, dreamseeker_exe: &str, dmb: &str, port: Option, extools_dll: Option<&std::path::Path>) -> std::io::Result { + pub fn new( + seq: Arc, + dreamseeker_exe: &str, + dmb: &str, + params: Option, + ) -> std::io::Result { let mut command = Command::new(dreamseeker_exe); command .arg(dmb) @@ -36,15 +52,27 @@ impl Launched { .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()); - if let Some(extools_dll) = extools_dll { - command.env("EXTOOLS_DLL", extools_dll); - } - if let Some(port) = port { - command.env("EXTOOLS_MODE", "LAUNCHED"); - command.env("EXTOOLS_PORT", port.to_string()); - } else { - command.env("EXTOOLS_MODE", "NONE"); + + match params { + Some(EngineParams::Extools { port, dll }) => { + command.env("EXTOOLS_MODE", "LAUNCHED"); + command.env("EXTOOLS_PORT", port.to_string()); + if let Some(dll) = dll { + command.env("EXTOOLS_DLL", dll); + } + } + + Some(EngineParams::Auxtools { port, dll }) => { + command.env("AUXTOOLS_DEBUG_MODE", "LAUNCHED"); + command.env("AUXTOOLS_DEBUG_PORT", port.to_string()); + if let Some(dll) = dll { + command.env("AUXTOOLS_DEBUG_DLL", dll); + } + } + + None => (), } + let mut child = command.spawn()?; output!(in seq, "[launched] Started: {:?}", command); let mutex = Arc::new(Mutex::new(State::Active)); @@ -83,11 +111,7 @@ impl Launched { }); })?; - Ok(Launched { - handle, - seq, - mutex, - }) + Ok(Launched { handle, seq, mutex }) } pub fn kill(self) -> std::io::Result<()> { diff --git a/src/langserver/debugger/mod.rs b/src/langserver/debugger/mod.rs index 78e739d3..9b40b30b 100644 --- a/src/langserver/debugger/mod.rs +++ b/src/langserver/debugger/mod.rs @@ -24,29 +24,39 @@ macro_rules! debug_output { #[cfg(not(debug_assertions))] macro_rules! debug_output { - ($($rest:tt)*) => { {} } + ($($rest:tt)*) => {{}}; } +mod auxtools; +mod auxtools_bundle; +mod auxtools_types; mod dap_types; -mod launched; -mod extools_types; +mod evaluate; mod extools; mod extools_bundle; -mod evaluate; +mod extools_types; +mod launched; +use std::collections::{HashMap, HashSet}; use std::error::Error; use std::sync::{atomic, Arc, Mutex}; -use std::collections::{HashMap, HashSet}; -use dm::FileId; use dm::objtree::ObjectTree; +use dm::FileId; +use dreammaker::config::DebugEngine; + +use auxtools::Auxtools; -use crate::jrpc_io; use self::dap_types::*; -use self::launched::Launched; use self::extools::ExtoolsHolder; +use self::launched::{Launched, EngineParams}; +use crate::jrpc_io; -pub fn start_server(dreamseeker_exe: String, db: DebugDatabaseBuilder) -> std::io::Result<(u16, std::thread::JoinHandle<()>)> { +pub fn start_server( + engine: DebugEngine, + dreamseeker_exe: String, + db: DebugDatabaseBuilder, +) -> std::io::Result<(u16, std::thread::JoinHandle<()>)> { use std::net::*; let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0))?; @@ -57,37 +67,44 @@ pub fn start_server(dreamseeker_exe: String, db: DebugDatabaseBuilder) -> std::i .spawn(move || { let (stream, _) = listener.accept().unwrap(); drop(listener); - let mut input = std::io::BufReader::new(stream.try_clone().unwrap()); - let mut debugger = Debugger::new(dreamseeker_exe, db, Box::new(stream)); + let mut debugger = Debugger::new(engine, dreamseeker_exe, db, Box::new(stream)); jrpc_io::run_with_read(&mut input, |message| debugger.handle_input(message)); })?; Ok((port, handle)) } -pub fn debugger_main>(mut args: I) { +pub fn debugger_main>(mut args: I) { eprintln!("acting as debug adapter"); let mut dreamseeker_exe = None; while let Some(arg) = args.next() { if arg == "--dreamseeker-exe" { - dreamseeker_exe = Some(args.next().expect("must specify a value for --dreamseeker-exe")); + dreamseeker_exe = Some( + args.next() + .expect("must specify a value for --dreamseeker-exe"), + ); } else { panic!("unknown argument {:?}", arg); } } - let dreamseeker_exe = dreamseeker_exe.expect("must provide argument `--dreamseeker-exe path/to/dreamseeker.exe`"); + let dreamseeker_exe = + dreamseeker_exe.expect("must provide argument `--dreamseeker-exe path/to/dreamseeker.exe`"); eprintln!("dreamseeker: {}", dreamseeker_exe); // This isn't the preferred way to run the DAP server so it's okay for it // to be kind of sloppy. - let environment = dm::detect_environment_default().expect("detect .dme error").expect("did not detect a .dme"); + let environment = dm::detect_environment_default() + .expect("detect .dme error") + .expect("did not detect a .dme"); let ctx = dm::Context::default(); + ctx.autodetect_config(&environment); let mut pp = dm::preprocessor::Preprocessor::new(&ctx, environment).unwrap(); let objtree = { - let mut parser = dm::parser::Parser::new(&ctx, dm::indents::IndentProcessor::new(&ctx, &mut pp)); + let mut parser = + dm::parser::Parser::new(&ctx, dm::indents::IndentProcessor::new(&ctx, &mut pp)); parser.enable_procs(); Arc::new(parser.parse_object_tree()) }; @@ -98,7 +115,7 @@ pub fn debugger_main>(mut args: I) { objtree, extools_dll: None, }; - let mut debugger = Debugger::new(dreamseeker_exe, db, Box::new(std::io::stdout())); + let mut debugger = Debugger::new(ctx.config().debugger.engine, dreamseeker_exe, db, Box::new(std::io::stdout())); jrpc_io::run_until_stdin_eof(|message| debugger.handle_input(message)); } @@ -111,23 +128,32 @@ pub struct DebugDatabaseBuilder { impl DebugDatabaseBuilder { fn build(self) -> DebugDatabase { - let DebugDatabaseBuilder { root_dir, files, objtree, extools_dll: _ } = self; - let mut line_numbers: HashMap> = HashMap::new(); + let DebugDatabaseBuilder { + root_dir, + files, + objtree, + extools_dll: _, + } = self; + let mut line_numbers: HashMap> = + HashMap::new(); objtree.root().recurse(&mut |ty| { for (name, proc) in ty.procs.iter() { - for (override_id, pv) in proc.value.iter() - .skip_while(|pv| pv.location.is_builtins() && !STDDEF_PROCS.contains(&format!("{}/{}", ty.path, name).as_str())) + for (override_id, pv) in proc + .value + .iter() + .skip_while(|pv| { + pv.location.is_builtins() + && !STDDEF_PROCS.contains(&format!("{}/{}", ty.path, name).as_str()) + }) .enumerate() { - line_numbers.entry(pv.location.file) - .or_default() - .push(( - pv.location.line.into(), - ty.path.to_owned(), - name.to_owned(), - override_id, - )); + line_numbers.entry(pv.location.file).or_default().push(( + pv.location.line.into(), + ty.path.to_owned(), + name.to_owned(), + override_id, + )); } } }); @@ -152,11 +178,17 @@ pub struct DebugDatabase { line_numbers: HashMap>, } -fn get_proc<'o>(objtree: &'o ObjectTree, proc_ref: &str, override_id: usize) -> Option<&'o dm::objtree::ProcValue> { +fn get_proc<'o>( + objtree: &'o ObjectTree, + proc_ref: &str, + override_id: usize, +) -> Option<&'o dm::objtree::ProcValue> { let mut bits: Vec<&str> = proc_ref.split('/').collect(); let procname = bits.pop().unwrap(); match bits.last() { - Some(&"proc") | Some(&"verb") => { bits.pop(); } + Some(&"proc") | Some(&"verb") => { + bits.pop(); + } _ => {} } let typename = bits.join("/"); @@ -164,7 +196,9 @@ fn get_proc<'o>(objtree: &'o ObjectTree, proc_ref: &str, override_id: usize) -> if let Some(ty) = objtree.find(&typename) { if let Some(ty_proc) = ty.get().procs.get(procname) { // Don't consider (most) builtins against the override_id count. - return ty_proc.value.iter() + return ty_proc + .value + .iter() .skip_while(|pv| pv.location.is_builtins() && !STDDEF_PROCS.contains(&proc_ref)) .nth(override_id); } @@ -179,7 +213,8 @@ impl DebugDatabase { fn file_id(&self, file_path: &str) -> Option { let path = std::path::Path::new(file_path); - self.files.get_id(path.strip_prefix(&self.root_dir).unwrap_or(path)) + self.files + .get_id(path.strip_prefix(&self.root_dir).unwrap_or(path)) } fn location_to_proc_ref(&self, file_id: FileId, line: i64) -> Option<(&str, &str, usize)> { @@ -194,12 +229,18 @@ impl DebugDatabase { } } +enum DebugClient { + Extools(ExtoolsHolder), + Auxtools(Auxtools), +} + struct Debugger { + engine: DebugEngine, dreamseeker_exe: String, extools_dll: Option, db: DebugDatabase, launched: Option, - extools: ExtoolsHolder, + client: DebugClient, seq: Arc, client_caps: ClientCaps, @@ -209,13 +250,14 @@ struct Debugger { } impl Debugger { - fn new(dreamseeker_exe: String, mut db: DebugDatabaseBuilder, stream: OutStream) -> Self { + fn new(engine: DebugEngine, dreamseeker_exe: String, mut db: DebugDatabaseBuilder, stream: OutStream) -> Self { Debugger { + engine, dreamseeker_exe, extools_dll: db.extools_dll.take(), db: db.build(), launched: None, - extools: ExtoolsHolder::default(), + client: DebugClient::Extools(ExtoolsHolder::default()), seq: Arc::new(SequenceNumber::new(stream)), client_caps: Default::default(), @@ -227,7 +269,8 @@ impl Debugger { fn handle_input(&mut self, message: &str) { // TODO: error handling - self.handle_input_inner(message).expect("error in handle_input"); + self.handle_input_inner(message) + .expect("error in handle_input"); } fn handle_input_inner(&mut self, message: &str) -> Result<(), Box> { @@ -239,7 +282,9 @@ impl Debugger { let command = request.command.clone(); let handled = match Self::handle_request_table(&request.command) { - Some(handler) => handler(self, request.arguments.unwrap_or(serde_json::Value::Null)), + Some(handler) => { + handler(self, request.arguments.unwrap_or(serde_json::Value::Null)) + } None => Err(format!("Request NYI: {}", request.command).into()), }; @@ -259,13 +304,14 @@ impl Debugger { } debug_output!(in self.seq, " - {}", message); None - }, + } }, command, }; - self.seq.send_raw(&serde_json::to_string(&response).expect("response encode error")) + self.seq + .send_raw(&serde_json::to_string(&response).expect("response encode error")) } - other => return Err(format!("unknown `type` field {:?}", other).into()) + other => return Err(format!("unknown `type` field {:?}", other).into()), } Ok(()) } @@ -292,16 +338,34 @@ impl Debugger { // An alternative would be to send these in real-time when sleeping // threads enter or exit existence. + match &mut self.client { + DebugClient::Extools(extools) => { + let keys: Vec<_> = { + guard!(let Ok(extools) = extools.get() else { return }); + extools + .get_all_threads() + .keys() + .cloned() + .filter(|&k| k != 0) + .collect() + }; + for k in keys { + self.issue_event(dap_types::ThreadEvent { + reason: dap_types::ThreadEvent::REASON_EXITED.to_owned(), + threadId: k, + }); + } + } - let keys: Vec<_> = { - guard!(let Ok(extools) = self.extools.get() else { return }); - extools.get_all_threads().keys().cloned().filter(|&k| k != 0).collect() - }; - for k in keys { - self.issue_event(dap_types::ThreadEvent { - reason: dap_types::ThreadEvent::REASON_EXITED.to_owned(), - threadId: k, - }); + DebugClient::Auxtools(auxtools) => for stack in auxtools.get_stacks().unwrap_or(vec![]) { + if stack.id == 0 { + continue; + } + self.issue_event(dap_types::ThreadEvent { + reason: dap_types::ThreadEvent::REASON_EXITED.to_owned(), + threadId: stack.id as i64, + }); + }, } } } @@ -346,43 +410,79 @@ handle_request! { on LaunchVsc(&mut self, params) { // Determine port number to pass if debugging is enabled. let debug = !params.base.noDebug.unwrap_or(false); - let port = if debug { - let (port, extools) = ExtoolsHolder::listen(self.seq.clone())?; - self.extools = extools; - Some(port) + + let engine_params = if debug { + Some(match self.engine { + DebugEngine::Extools => { + let (port, extools) = ExtoolsHolder::listen(self.seq.clone())?; + self.client = DebugClient::Extools(extools); + + // Set EXTOOLS_DLL based on configuration or on bundle if available. + #[allow(unused_mut)] + let mut extools_dll = self.extools_dll.as_ref().map(|x| std::path::Path::new(x).to_path_buf()); + + debug_output!(in self.seq, "[main] configured override: {:?}", extools_dll); + + #[cfg(extools_bundle)] { + if extools_dll.is_none() { + extools_dll = Some(self::extools_bundle::extract()?); + } + } + + EngineParams::Extools { + port, + dll: extools_dll, + } + } + + DebugEngine::Auxtools => { + let (port, auxtools) = Auxtools::listen(self.seq.clone())?; + self.client = DebugClient::Auxtools(auxtools); + let auxtools_dll = None; + + #[cfg(extools_bundle)] { + auxtools_dll = Some(self::auxtools_bundle::extract()?); + } + + EngineParams::Auxtools { + port, + dll: auxtools_dll, + } + } + }) } else { None }; - // Set EXTOOLS_DLL based on configuration or on bundle if available. - #[cfg(extools_bundle)] - let pathbuf; - - #[allow(unused_mut)] - let mut extools_dll = self.extools_dll.as_ref().map(std::path::Path::new); - - debug_output!(in self.seq, "[main] configured override: {:?}", extools_dll); - - #[cfg(extools_bundle)] { - if extools_dll.is_none() { - pathbuf = self::extools_bundle::extract()?; - extools_dll = Some(&pathbuf); - } - } - // Launch the subprocess. - self.launched = Some(Launched::new(self.seq.clone(), &self.dreamseeker_exe, ¶ms.dmb, port, extools_dll)?); + self.launched = Some(Launched::new(self.seq.clone(), &self.dreamseeker_exe, ¶ms.dmb, engine_params)?); } on AttachVsc(&mut self, params) { - self.extools = ExtoolsHolder::attach(self.seq.clone(), params.port.unwrap_or(extools::DEFAULT_PORT))?; + self.client = match self.engine { + DebugEngine::Extools => { + DebugClient::Extools(ExtoolsHolder::attach(self.seq.clone(), params.port.unwrap_or(extools::DEFAULT_PORT))?) + } + + DebugEngine::Auxtools => { + DebugClient::Auxtools(Auxtools::connect(self.seq.clone(), params.port)?) + } + }; } on Disconnect(&mut self, params) { let default_terminate = self.launched.is_some(); let terminate = params.terminateDebuggee.unwrap_or(default_terminate); - self.extools.disconnect(); + match &mut self.client { + DebugClient::Extools(extools) => { + extools.disconnect(); + } + + DebugClient::Auxtools(auxtools) => { + auxtools.disconnect(); + } + } if let Some(launched) = self.launched.take() { if terminate { @@ -394,34 +494,68 @@ handle_request! { } on ConfigurationDone(&mut self, ()) { - let extools = self.extools.get()?; + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; - let text = extools.get_source("stddef.dm".to_owned())?; - self.stddef_dm_info = Some(StddefDmInfo::new(text)); + let text = extools.get_source("stddef.dm".to_owned())?; + self.stddef_dm_info = Some(StddefDmInfo::new(text)); - extools.configuration_done(); + extools.configuration_done(); + } + + DebugClient::Auxtools(_) => {} + } } on Threads(&mut self, ()) { - let mut threads = Vec::new(); + match &mut self.client { + DebugClient::Extools(extools) => { - let extools = self.extools.get()?; - for (&k, v) in extools.get_all_threads().iter() { - threads.push(Thread { - id: k, - name: v.call_stack.last().unwrap().proc.clone(), - }); - } + let mut threads = Vec::new(); - if threads.is_empty() { - threads.push(Thread { - id: 0, - name: "Main".to_owned(), - }); - } + let extools = extools.get()?; + for (&k, v) in extools.get_all_threads().iter() { + threads.push(Thread { + id: k, + name: v.call_stack.last().unwrap().proc.clone(), + }); + } - ThreadsResponse { - threads, + if threads.is_empty() { + threads.push(Thread { + id: 0, + name: "Main".to_owned(), + }); + } + + ThreadsResponse { + threads, + } + }, + + DebugClient::Auxtools(auxtools) => { + let mut threads : Vec = auxtools.get_stacks()?.into_iter().map(|x| { + Thread { + id: x.id as i64, + name: x.name, + } + }).collect(); + + // If we tell DAP that there are no threads, Pause requests never get passed through! + if threads.is_empty() { + threads.push( + Thread { + id: 0, + name: "Main".to_owned(), + } + ); + } + + ThreadsResponse { + threads, + } + } } } @@ -438,72 +572,153 @@ handle_request! { } let inputs = params.breakpoints.unwrap_or_default(); - let mut breakpoints = Vec::new(); - - guard!(let Some(extools) = self.extools.as_ref() else { - for sbp in inputs { - breakpoints.push(Breakpoint { - message: Some("Debugging hooks not available".to_owned()), - line: Some(sbp.line), - verified: false, - .. Default::default() - }); - } - return Ok(SetBreakpointsResponse { breakpoints }); - }); - let saved = self.saved_breakpoints.entry(file_id).or_default(); let mut keep = HashSet::new(); - for sbp in inputs { - if let Some((typepath, name, override_id)) = self.db.location_to_proc_ref(file_id, sbp.line) { - // TODO: better discipline around format!("{}/{}") and so on - let proc = format!("{}/{}", typepath, name); - if let Some(offset) = extools.line_to_offset(&proc, override_id, sbp.line) { - let tup = (proc, override_id, offset); - if saved.insert(tup.clone()) { - extools.set_breakpoint(&tup.0, tup.1, tup.2); - } - keep.insert(tup); - breakpoints.push(Breakpoint { - line: Some(sbp.line), - verified: true, - column: Some(0), - .. Default::default() - }); - } else { - debug_output!(in self.seq, - "Couldn't find line {} in the following disassembly:\n{}", - sbp.line, - Self::format_disassembly(extools.bytecode(&proc, override_id))); + match &mut self.client { + DebugClient::Extools(extools) => { + let mut breakpoints = Vec::new(); - breakpoints.push(Breakpoint { - message: Some("Unable to determine offset in proc".to_owned()), - line: Some(sbp.line), - verified: false, - .. Default::default() - }); - } - } else { - breakpoints.push(Breakpoint { - message: Some("Unable to determine proc ref".to_owned()), - line: Some(sbp.line), - verified: false, - .. Default::default() + guard!(let Some(extools) = extools.as_ref() else { + for sbp in inputs { + breakpoints.push(Breakpoint { + message: Some("Debugging hooks not available".to_owned()), + line: Some(sbp.line), + verified: false, + .. Default::default() + }); + } + return Ok(SetBreakpointsResponse { breakpoints }); }); + + for sbp in inputs { + if let Some((typepath, name, override_id)) = self.db.location_to_proc_ref(file_id, sbp.line) { + // TODO: better discipline around format!("{}/{}") and so on + let proc = format!("{}/{}", typepath, name); + if let Some(offset) = extools.line_to_offset(&proc, override_id, sbp.line) { + let tup = (proc, override_id, offset); + if saved.insert(tup.clone()) { + extools.set_breakpoint(&tup.0, tup.1, tup.2); + } + keep.insert(tup); + breakpoints.push(Breakpoint { + line: Some(sbp.line), + verified: true, + column: Some(0), + .. Default::default() + }); + } else { + debug_output!(in self.seq, + "Couldn't find line {} in the following disassembly:\n{}", + sbp.line, + Self::format_disassembly(extools.bytecode(&proc, override_id))); + + breakpoints.push(Breakpoint { + message: Some("Unable to determine offset in proc".to_owned()), + line: Some(sbp.line), + verified: false, + .. Default::default() + }); + } + } else { + breakpoints.push(Breakpoint { + message: Some("Unable to determine proc ref".to_owned()), + line: Some(sbp.line), + verified: false, + .. Default::default() + }); + } + } + + saved.retain(|k| { + if !keep.contains(&k) { + extools.unset_breakpoint(&k.0, k.1, k.2); + false + } else { + true + } + }); + + SetBreakpointsResponse { breakpoints } + } + + DebugClient::Auxtools(auxtools) => { + let mut breakpoints = vec![]; + + for sbp in inputs { + if let Some((typepath, name, override_id)) = self.db.location_to_proc_ref(file_id, sbp.line) { + // TODO: better discipline around format!("{}/{}") and so on + let proc = format!("{}/{}", typepath, name); + + if let Some(offset) = auxtools.get_offset(proc.as_str(), override_id as u32, sbp.line as u32)? { + saved.insert((proc.clone(), override_id, offset as i64)); + keep.insert((proc.clone(), override_id, offset as i64)); + + let result = auxtools.set_breakpoint(&auxtools_types::InstructionRef { + proc: auxtools_types::ProcRef { + path: proc, + override_id: override_id as u32 + }, + offset + })?; + + breakpoints.push(match result { + auxtools_types::BreakpointSetResult::Success { line } => { + Breakpoint { + verified: true, + line: line.map(|x| x as i64), + .. Default::default() + } + }, + + auxtools_types::BreakpointSetResult::Failed => { + Breakpoint { + verified: false, + .. Default::default() + } + } + }); + } else { + // debug_output!(in self.seq, + // "Couldn't find line {} in the following disassembly:\n{}", + // sbp.line, + // Self::format_disassembly(extools.bytecode(&proc, override_id))); + + breakpoints.push(Breakpoint { + message: Some("Unable to determine offset in proc".to_owned()), + line: Some(sbp.line), + verified: false, + .. Default::default() + }); + } + } else { + breakpoints.push(Breakpoint { + message: Some("Unable to determine proc ref".to_owned()), + line: Some(sbp.line), + verified: false, + .. Default::default() + }); + } + } + + saved.retain(|k| { + if !keep.contains(&k) { + let _ = auxtools.unset_breakpoint(&auxtools_types::InstructionRef { + proc: auxtools_types::ProcRef { + path: k.0.clone(), + override_id: k.1 as u32 + }, + offset: k.2 as u32, + }); + false + } else { + true + } + }); + + SetBreakpointsResponse { breakpoints } } } - - saved.retain(|k| { - if !keep.contains(&k) { - extools.unset_breakpoint(&k.0, k.1, k.2); - false - } else { - true - } - }); - - SetBreakpointsResponse { breakpoints } } on SetFunctionBreakpoints(&mut self, params) { @@ -511,302 +726,511 @@ handle_request! { let inputs = params.breakpoints; let mut breakpoints = Vec::new(); - - guard!(let Some(extools) = self.extools.as_ref() else { - for _ in inputs { - breakpoints.push(Breakpoint { - message: Some("Debugging hooks not available".to_owned()), - verified: false, - .. Default::default() - }); - } - return Ok(SetFunctionBreakpointsResponse { breakpoints }); - }); - let saved = self.saved_breakpoints.entry(file_id).or_default(); let mut keep = HashSet::new(); - for sbp in inputs { - // parse function reference - let mut proc = &sbp.name[..]; - let mut override_id = 0; - if let Some(idx) = sbp.name.find('#') { - proc = &sbp.name[..idx]; - override_id = sbp.name[idx+1..].parse()?; + match &mut self.client { + DebugClient::Extools(extools) => { + guard!(let Some(extools) = extools.as_ref() else { + for _ in inputs { + breakpoints.push(Breakpoint { + message: Some("Debugging hooks not available".to_owned()), + verified: false, + .. Default::default() + }); + } + return Ok(SetFunctionBreakpointsResponse { breakpoints }); + }); + + for sbp in inputs { + // parse function reference + let mut proc = &sbp.name[..]; + let mut override_id = 0; + if let Some(idx) = sbp.name.find('#') { + proc = &sbp.name[..idx]; + override_id = sbp.name[idx+1..].parse()?; + } + + if let Some(proc_ref) = self.db.get_proc(proc, override_id) { + let offset = 0; + let tup = (proc.to_owned(), override_id, offset); + if saved.insert(tup.clone()) { + extools.set_breakpoint(&tup.0, tup.1, tup.2); + } + keep.insert(tup); + breakpoints.push(Breakpoint { + line: Some(proc_ref.location.line as i64), + verified: true, + column: Some(0), + .. Default::default() + }); + } else { + breakpoints.push(Breakpoint { + message: Some(format!("Unknown proc {}#{}", proc, override_id)), + verified: false, + .. Default::default() + }); + } + } + + saved.retain(|k| { + if !keep.contains(&k) { + extools.unset_breakpoint(&k.0, k.1, k.2); + false + } else { + true + } + }); + + SetFunctionBreakpointsResponse { breakpoints } } - if let Some(proc_ref) = self.db.get_proc(proc, override_id) { - let offset = 0; - let tup = (proc.to_owned(), override_id, offset); - if saved.insert(tup.clone()) { - extools.set_breakpoint(&tup.0, tup.1, tup.2); + + DebugClient::Auxtools(auxtools) => { + let mut breakpoints = vec![]; + + for sbp in inputs { + // parse function reference + let mut proc = &sbp.name[..]; + let mut override_id = 0; + if let Some(idx) = sbp.name.find('#') { + proc = &sbp.name[..idx]; + override_id = sbp.name[idx+1..].parse()?; + } + + let offset = 0; + let tup = (proc.to_owned(), override_id, offset); + + saved.insert(tup.clone()); + keep.insert(tup.clone()); + + let result = auxtools.set_breakpoint(&auxtools_types::InstructionRef { + proc: auxtools_types::ProcRef { + path: tup.0, + override_id: override_id as u32 + }, + offset: offset as u32, + })?; + + breakpoints.push(match result { + auxtools_types::BreakpointSetResult::Success { line } => { + Breakpoint { + verified: true, + line: line.map(|x| x as i64), + .. Default::default() + } + }, + + auxtools_types::BreakpointSetResult::Failed => { + Breakpoint { + verified: false, + .. Default::default() + } + } + }); } - keep.insert(tup); - breakpoints.push(Breakpoint { - line: Some(proc_ref.location.line as i64), - verified: true, - column: Some(0), - .. Default::default() - }); - } else { - breakpoints.push(Breakpoint { - message: Some(format!("Unknown proc {}#{}", proc, override_id)), - verified: false, - .. Default::default() + + saved.retain(|k| { + if !keep.contains(&k) { + let _ = auxtools.unset_breakpoint(&auxtools_types::InstructionRef { + proc: auxtools_types::ProcRef { + path: k.0.clone(), + override_id: k.1 as u32 + }, + offset: k.2 as u32, + }); + false + } else { + true + } }); + + SetFunctionBreakpointsResponse { breakpoints } } } - - saved.retain(|k| { - if !keep.contains(&k) { - extools.unset_breakpoint(&k.0, k.1, k.2); - false - } else { - true - } - }); - - SetFunctionBreakpointsResponse { breakpoints } } on StackTrace(&mut self, params) { - let extools = self.extools.get()?; - let thread = extools.get_thread(params.threadId)?; + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + let thread = extools.get_thread(params.threadId)?; - let len = thread.call_stack.len(); - let mut frames = Vec::with_capacity(len); - for (i, ex_frame) in thread.call_stack.into_iter().enumerate() { - let mut dap_frame = StackFrame { - name: ex_frame.proc.clone(), - id: (i * extools.get_all_threads().len()) as i64 + params.threadId, - instructionPointerReference: Some(format!("{}@{}#{}", ex_frame.proc, ex_frame.override_id, ex_frame.offset)), - .. Default::default() - }; + let len = thread.call_stack.len(); + let mut frames = Vec::with_capacity(len); + for (i, ex_frame) in thread.call_stack.into_iter().enumerate() { + let mut dap_frame = StackFrame { + name: ex_frame.proc.clone(), + id: (i * extools.get_all_threads().len()) as i64 + params.threadId, + instructionPointerReference: Some(format!("{}@{}#{}", ex_frame.proc, ex_frame.override_id, ex_frame.offset)), + .. Default::default() + }; - if i == 0 { - // Column must be nonzero for VSC to show the exception widget, - // but we don't usually have meaningful column information. - dap_frame.column = 1; - } + if i == 0 { + // Column must be nonzero for VSC to show the exception widget, + // but we don't usually have meaningful column information. + dap_frame.column = 1; + } + + if let Some(proc) = self.db.get_proc(&ex_frame.proc, ex_frame.override_id) { + if proc.location.is_builtins() { + // `stddef.dm` proc. + if let Some(stddef_dm_info) = self.stddef_dm_info.as_ref() { + if let Some(proc) = get_proc(&stddef_dm_info.objtree, &ex_frame.proc, ex_frame.override_id) { + dap_frame.source = Some(Source { + name: Some("stddef.dm".to_owned()), + sourceReference: Some(STDDEF_SOURCE_REFERENCE), + .. Default::default() + }); + dap_frame.line = i64::from(proc.location.line); + //dap_frame.column = i64::from(proc.location.column); + } + } + } else { + // Normal proc. + let path = self.db.files.get_path(proc.location.file); - if let Some(proc) = self.db.get_proc(&ex_frame.proc, ex_frame.override_id) { - if proc.location.is_builtins() { - // `stddef.dm` proc. - if let Some(stddef_dm_info) = self.stddef_dm_info.as_ref() { - if let Some(proc) = get_proc(&stddef_dm_info.objtree, &ex_frame.proc, ex_frame.override_id) { dap_frame.source = Some(Source { - name: Some("stddef.dm".to_owned()), - sourceReference: Some(STDDEF_SOURCE_REFERENCE), + name: Some(path.file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned()), + path: Some(self.db.root_dir.join(path).to_string_lossy().into_owned()), .. Default::default() }); dap_frame.line = i64::from(proc.location.line); //dap_frame.column = i64::from(proc.location.column); } } - } else { - // Normal proc. - let path = self.db.files.get_path(proc.location.file); - dap_frame.source = Some(Source { - name: Some(path.file_name() - .unwrap_or_default() - .to_string_lossy() - .into_owned()), - path: Some(self.db.root_dir.join(path).to_string_lossy().into_owned()), - .. Default::default() - }); - dap_frame.line = i64::from(proc.location.line); - //dap_frame.column = i64::from(proc.location.column); + if let Some(line) = extools.offset_to_line(&ex_frame.proc, ex_frame.override_id, ex_frame.offset) { + dap_frame.line = line; + } + + frames.push(dap_frame); + } + + StackTraceResponse { + totalFrames: Some(len as i64), + stackFrames: frames, } } - if let Some(line) = extools.offset_to_line(&ex_frame.proc, ex_frame.override_id, ex_frame.offset) { - dap_frame.line = line; + DebugClient::Auxtools(auxtools) => { + let (aux_frames, aux_frames_total) = auxtools.get_stack_frames( + params.threadId as u32, + params.startFrame.map(|x| x as u32), + params.levels.map(|x| x as u32))?; + + let mut frames = Vec::with_capacity(aux_frames.len()); + for (i, aux_frame) in aux_frames.iter().enumerate() { + let aux_proc = &aux_frame.instruction.proc; + let mut dap_frame = StackFrame { + name: aux_proc.path.to_owned(), + id: aux_frame.id as i64, + instructionPointerReference: Some(format!("{}@{}#{}", aux_proc.path, aux_proc.override_id, aux_frame.instruction.offset)), + .. Default::default() + }; + + if i == 0 { + // Column must be nonzero for VSC to show the exception widget, + // but we don't usually have meaningful column information. + dap_frame.column = 1; + } + + if let Some(proc) = self.db.get_proc(&aux_proc.path, aux_proc.override_id as usize) { + if proc.location.is_builtins() { + // `stddef.dm` proc. + if let Some(stddef_dm_info) = self.stddef_dm_info.as_ref() { + if let Some(proc) = get_proc(&stddef_dm_info.objtree, &aux_proc.path, aux_proc.override_id as usize) { + dap_frame.source = Some(Source { + name: Some("stddef.dm".to_owned()), + sourceReference: Some(STDDEF_SOURCE_REFERENCE), + .. Default::default() + }); + dap_frame.line = i64::from(proc.location.line); + //dap_frame.column = i64::from(proc.location.column); + } + } + } else { + // Normal proc. + let path = self.db.files.get_path(proc.location.file); + + dap_frame.source = Some(Source { + name: Some(path.file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned()), + path: Some(self.db.root_dir.join(path).to_string_lossy().into_owned()), + .. Default::default() + }); + dap_frame.line = i64::from(proc.location.line); + //dap_frame.column = i64::from(proc.location.column); + } + } + + if let Some(line) = aux_frame.line { + dap_frame.line = line as i64; + } + + frames.push(dap_frame); + } + + StackTraceResponse { + totalFrames: Some(aux_frames_total as i64), + stackFrames: frames, + } } - - frames.push(dap_frame); - } - - StackTraceResponse { - totalFrames: Some(len as i64), - stackFrames: frames, } } on Scopes(&mut self, ScopesArguments { frameId }) { - let extools = self.extools.get()?; - let frame_id = frameId as usize; + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + let frame_id = frameId as usize; - let threads = extools.get_all_threads(); - let thread_id = (frame_id % threads.len()) as i64; - let frame_no = frame_id / threads.len(); + let threads = extools.get_all_threads(); + let thread_id = (frame_id % threads.len()) as i64; + let frame_no = frame_id / threads.len(); - guard!(let Some(frame) = threads[&thread_id].call_stack.get(frame_no) else { - return Err(Box::new(GenericError2(format!("Stack frame out of range: {} (thread {}, depth {})", frameId, thread_id, frame_no)))); - }); + guard!(let Some(frame) = threads[&thread_id].call_stack.get(frame_no) else { + return Err(Box::new(GenericError2(format!("Stack frame out of range: {} (thread {}, depth {})", frameId, thread_id, frame_no)))); + }); - ScopesResponse { - scopes: vec![ - Scope { - name: "Locals".to_owned(), - presentationHint: Some("locals".to_owned()), - variablesReference: frameId * 2 + 2, - indexedVariables: Some(frame.locals.len() as i64), - .. Default::default() - }, - Scope { - name: "Arguments".to_owned(), - presentationHint: Some("arguments".to_owned()), - variablesReference: frameId * 2 + 1, - namedVariables: Some(2 + frame.args.len() as i64), - .. Default::default() - }, - Scope { - name: "Globals".to_owned(), - variablesReference: 0x0e_000001, - .. Default::default() - }, - ] + ScopesResponse { + scopes: vec![ + Scope { + name: "Locals".to_owned(), + presentationHint: Some("locals".to_owned()), + variablesReference: frameId * 2 + 2, + indexedVariables: Some(frame.locals.len() as i64), + .. Default::default() + }, + Scope { + name: "Arguments".to_owned(), + presentationHint: Some("arguments".to_owned()), + variablesReference: frameId * 2 + 1, + namedVariables: Some(2 + frame.args.len() as i64), + .. Default::default() + }, + Scope { + name: "Globals".to_owned(), + variablesReference: 0x0e_000001, + .. Default::default() + }, + ] + } + } + + DebugClient::Auxtools(auxtools) => { + let (arguments, locals, globals) = auxtools.get_scopes( frameId as u32 )?; + let mut scopes = vec![]; + + if let Some(arguments) = arguments { + scopes.push(Scope { + name: "Arguments".to_owned(), + variablesReference: arguments.0 as i64, + .. Default::default() + }); + } + + if let Some(locals) = locals { + scopes.push(Scope { + name: "Locals".to_owned(), + variablesReference: locals.0 as i64, + .. Default::default() + }); + } + + if let Some(globals) = globals { + scopes.push(Scope { + name: "Globals".to_owned(), + variablesReference: globals.0 as i64, + .. Default::default() + }); + } + + ScopesResponse { + scopes + } + } } } on Variables(&mut self, params) { - let extools = self.extools.get()?; + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; - if params.variablesReference >= 0x01_000000 { - let (var, ref_) = extools_types::ValueText::from_variables_reference(params.variablesReference); - let mut variables = Vec::new(); + if params.variablesReference >= 0x01_000000 { + let (var, ref_) = extools_types::ValueText::from_variables_reference(params.variablesReference); + let mut variables = Vec::new(); - if var.is_list { - // List reference - match extools.get_list_contents(ref_)? { - extools_types::ListContents::Linear(entries) => { - for (i, entry) in entries.iter().enumerate() { + if var.is_list { + // List reference + match extools.get_list_contents(ref_)? { + extools_types::ListContents::Linear(entries) => { + for (i, entry) in entries.iter().enumerate() { + variables.push(Variable { + name: format!("[{}]", 1 + i), + value: entry.to_string(), + variablesReference: entry.to_variables_reference(), + .. Default::default() + }); + } + } + extools_types::ListContents::Associative(entries) => { + for (i, (key, val)) in entries.iter().enumerate() { + variables.push(Variable { + name: format!("keys[{}]", 1 + i), + value: key.to_string(), + variablesReference: key.to_variables_reference(), + .. Default::default() + }); + variables.push(Variable { + name: format!("vals[{}]", 1 + i), + value: val.to_string(), + variablesReference: val.to_variables_reference(), + .. Default::default() + }); + } + } + } + } else if var.has_vars { + // Datum reference + let hashmap = extools.get_all_fields(ref_)?; + let mut entries: Vec<_> = hashmap.iter().collect(); + entries.sort_unstable_by_key(|tup| tup.0); + for (name, vt) in entries { variables.push(Variable { - name: format!("[{}]", 1 + i), - value: entry.to_string(), - variablesReference: entry.to_variables_reference(), + name: name.to_owned(), + value: vt.to_string(), + variablesReference: vt.to_variables_reference(), .. Default::default() - }); - } - } - extools_types::ListContents::Associative(entries) => { - for (i, (key, val)) in entries.iter().enumerate() { - variables.push(Variable { - name: format!("keys[{}]", 1 + i), - value: key.to_string(), - variablesReference: key.to_variables_reference(), - .. Default::default() - }); - variables.push(Variable { - name: format!("vals[{}]", 1 + i), - value: val.to_string(), - variablesReference: val.to_variables_reference(), - .. Default::default() - }); + }) } } + + return Ok(VariablesResponse { variables }); } - } else if var.has_vars { - // Datum reference - let hashmap = extools.get_all_fields(ref_)?; - let mut entries: Vec<_> = hashmap.iter().collect(); - entries.sort_unstable_by_key(|tup| tup.0); - for (name, vt) in entries { + + // Stack frame, arguments or locals + let frame_id = (params.variablesReference - 1) / 2; + let mod2 = params.variablesReference % 2; + + let (thread, frame_no) = extools.get_thread_by_frame_id(frame_id)?; + guard!(let Some(frame) = thread.call_stack.get(frame_no) else { + return Err(Box::new(GenericError("Stack frame out of range"))); + }); + + if mod2 == 1 { + // arguments + let mut variables = Vec::with_capacity(2 + frame.args.len()); + let mut seen = std::collections::HashMap::new(); + + seen.insert("src", 0); variables.push(Variable { - name: name.to_owned(), + name: "src".to_owned(), + value: frame.src.to_string(), + variablesReference: frame.src.to_variables_reference(), + .. Default::default() + }); + seen.insert("usr", 0); + variables.push(Variable { + name: "usr".to_owned(), + value: frame.usr.to_string(), + variablesReference: frame.usr.to_variables_reference(), + .. Default::default() + }); + + variables.extend(frame.args.iter().enumerate().map(|(i, vt)| Variable { + name: match frame.arg_names.get(i) { + Some(param) => { + match seen.entry(param).and_modify(|e| *e += 1).or_default() { + 0 => param.clone(), + n => format!("{} #{}", param, n), + } + } + None => format!("args[{}]", i + 1), + }, value: vt.to_string(), variablesReference: vt.to_variables_reference(), .. Default::default() - }) + })); + VariablesResponse { variables } + } else if mod2 == 0 { + // locals + let mut variables = Vec::with_capacity(1 + frame.locals.len()); + + variables.push(Variable { + name: ".".to_owned(), + value: frame.dot.to_string(), + variablesReference: frame.dot.to_variables_reference(), + .. Default::default() + }); + + // If VSC receives two Variables with the same name, it only + // displays the first one. Avert this by adding suffixes. + let mut seen = std::collections::HashMap::new(); + variables.extend(frame.locals.iter().enumerate().map(|(i, vt)| Variable { + name: match frame.local_names.get(i) { + Some(local) => { + match seen.entry(local).and_modify(|e| *e += 1).or_default() { + 0 => local.clone(), + n => format!("{} #{}", local, n), + } + } + None => i.to_string(), + }, + value: vt.to_string(), + variablesReference: vt.to_variables_reference(), + .. Default::default() + })); + VariablesResponse { variables } + } else { + return Err(Box::new(GenericError("Bad variables reference"))); } } - return Ok(VariablesResponse { variables }); - } + DebugClient::Auxtools(auxtools) => { + let aux_variables = auxtools.get_variables(auxtools_types::VariablesRef(params.variablesReference as i32))?; + let mut variables = vec![]; - // Stack frame, arguments or locals - let frame_id = (params.variablesReference - 1) / 2; - let mod2 = params.variablesReference % 2; + // TODO + // If VSC receives two Variables with the same name, it only + // displays the first one. Avert this by adding suffixes. - let (thread, frame_no) = extools.get_thread_by_frame_id(frame_id)?; - guard!(let Some(frame) = thread.call_stack.get(frame_no) else { - return Err(Box::new(GenericError("Stack frame out of range"))); - }); + for aux_var in aux_variables { + variables.push(Variable { + name: aux_var.name, + value: aux_var.value, + variablesReference: aux_var.variables.map(|x| x.0 as i64).unwrap_or(0), + .. Default::default() + }); + } - if mod2 == 1 { - // arguments - let mut variables = Vec::with_capacity(2 + frame.args.len()); - let mut seen = std::collections::HashMap::new(); - - seen.insert("src", 0); - variables.push(Variable { - name: "src".to_owned(), - value: frame.src.to_string(), - variablesReference: frame.src.to_variables_reference(), - .. Default::default() - }); - seen.insert("usr", 0); - variables.push(Variable { - name: "usr".to_owned(), - value: frame.usr.to_string(), - variablesReference: frame.usr.to_variables_reference(), - .. Default::default() - }); - - variables.extend(frame.args.iter().enumerate().map(|(i, vt)| Variable { - name: match frame.arg_names.get(i) { - Some(param) => { - match seen.entry(param).and_modify(|e| *e += 1).or_default() { - 0 => param.clone(), - n => format!("{} #{}", param, n), - } - } - None => format!("args[{}]", i + 1), - }, - value: vt.to_string(), - variablesReference: vt.to_variables_reference(), - .. Default::default() - })); - VariablesResponse { variables } - } else if mod2 == 0 { - // locals - let mut variables = Vec::with_capacity(1 + frame.locals.len()); - - variables.push(Variable { - name: ".".to_owned(), - value: frame.dot.to_string(), - variablesReference: frame.dot.to_variables_reference(), - .. Default::default() - }); - - // If VSC receives two Variables with the same name, it only - // displays the first one. Avert this by adding suffixes. - let mut seen = std::collections::HashMap::new(); - variables.extend(frame.locals.iter().enumerate().map(|(i, vt)| Variable { - name: match frame.local_names.get(i) { - Some(local) => { - match seen.entry(local).and_modify(|e| *e += 1).or_default() { - 0 => local.clone(), - n => format!("{} #{}", local, n), - } - } - None => i.to_string(), - }, - value: vt.to_string(), - variablesReference: vt.to_variables_reference(), - .. Default::default() - })); - VariablesResponse { variables } - } else { - return Err(Box::new(GenericError("Bad variables reference"))); + VariablesResponse { + variables + } + } } } on Continue(&mut self, _params) { - self.cull_thread_list(); - let extools = self.extools.get()?; - extools.continue_execution(); + self.notify_continue(); + + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + extools.continue_execution(); + } + + DebugClient::Auxtools(auxtools) => { + auxtools.continue_execution()?; + } + } + ContinueResponse { allThreadsContinued: Some(true), } @@ -814,41 +1238,97 @@ handle_request! { on StepIn(&mut self, params) { self.notify_continue(); - let extools = self.extools.get()?; - extools.step_in(params.threadId); + + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + extools.step_in(params.threadId); + } + + DebugClient::Auxtools(auxtools) => { + auxtools.step_into(params.threadId as u32)?; + } + } } on Next(&mut self, params) { self.notify_continue(); - let extools = self.extools.get()?; - extools.step_over(params.threadId); + + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + extools.step_over(params.threadId); + } + + DebugClient::Auxtools(auxtools) => { + auxtools.next(params.threadId as u32)?; + } + } } on StepOut(&mut self, params) { self.notify_continue(); - let extools = self.extools.get()?; - extools.step_out(params.threadId); + + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + extools.step_out(params.threadId); + } + + DebugClient::Auxtools(auxtools) => { + auxtools.step_out(params.threadId as u32)?; + } + } } on Pause(&mut self, _params) { - let extools = self.extools.get()?; - extools.pause(); + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + extools.pause(); + } + + DebugClient::Auxtools(auxtools) => { + auxtools.pause()?; + } + } } on SetExceptionBreakpoints(&mut self, params) { - let extools = self.extools.get()?; - extools.set_break_on_runtime(params.filters.iter().any(|x| x == EXCEPTION_FILTER_RUNTIMES)); + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + extools.set_break_on_runtime(params.filters.iter().any(|x| x == EXCEPTION_FILTER_RUNTIMES)); + } + + DebugClient::Auxtools(auxtools) => { + auxtools.set_catch_runtimes(params.filters.iter().any(|x| x == EXCEPTION_FILTER_RUNTIMES))?; + } + } } on ExceptionInfo(&mut self, _params) { - let extools = self.extools.get()?; - // VSC shows exceptionId, description, stackTrace in that order. - let message = extools.last_error_message(); - ExceptionInfoResponse { - exceptionId: message.unwrap_or_default().to_owned(), - description: None, - breakMode: ExceptionBreakMode::Always, - details: None, + match &mut self.client { + DebugClient::Extools(extools) => { + let extools = extools.get()?; + // VSC shows exceptionId, description, stackTrace in that order. + let message = extools.last_error_message(); + ExceptionInfoResponse { + exceptionId: message.unwrap_or_default().to_owned(), + description: None, + breakMode: ExceptionBreakMode::Always, + details: None, + } + } + + DebugClient::Auxtools(auxtools) => { + ExceptionInfoResponse { + exceptionId: auxtools.get_last_error_message(), + description: None, + breakMode: ExceptionBreakMode::Always, + details: None, + } + } } } @@ -876,26 +1356,34 @@ handle_request! { } on Disassemble(&mut self, params) { - guard!(let Some(captures) = MEMORY_REFERENCE_REGEX.captures(¶ms.memoryReference) else { - return Err(Box::new(GenericError("Invalid memory reference"))); - }); - let proc = &captures[1]; - let override_id: usize = captures[2].parse()?; - //let offset: i64 = captures[3].parse()?; + match &mut self.client { + DebugClient::Extools(extools) => { + guard!(let Some(captures) = MEMORY_REFERENCE_REGEX.captures(¶ms.memoryReference) else { + return Err(Box::new(GenericError("Invalid memory reference"))); + }); + let proc = &captures[1]; + let override_id: usize = captures[2].parse()?; + //let offset: i64 = captures[3].parse()?; - let extools = self.extools.get()?; - let mut result = Vec::new(); - for instr in extools.bytecode(proc, override_id) { - result.push(DisassembledInstruction { - address: format!("{}#{}@{}", proc, override_id, instr.offset), - instructionBytes: Some(instr.bytes.clone()), - instruction: format!("{} {}", instr.mnemonic, instr.comment), - .. Default::default() - }); - } + let extools = extools.get()?; + let mut result = Vec::new(); + for instr in extools.bytecode(proc, override_id) { + result.push(DisassembledInstruction { + address: format!("{}#{}@{}", proc, override_id, instr.offset), + instructionBytes: Some(instr.bytes.clone()), + instruction: format!("{} {}", instr.mnemonic, instr.comment), + .. Default::default() + }); + } - DisassembleResponse { - instructions: result + DisassembleResponse { + instructions: result + } + } + + DebugClient::Auxtools(_) => { + return Err(Box::new(GenericError("auxtools can't disassemble yet"))); + } } } } @@ -966,7 +1454,7 @@ impl SequenceNumber { output.push('\n'); self.issue_event(OutputEvent { output, - .. Default::default() + ..Default::default() }) } @@ -976,7 +1464,7 @@ impl SequenceNumber { self.issue_event(OutputEvent { output, category: Some("console".to_owned()), - .. Default::default() + ..Default::default() }) } @@ -990,7 +1478,9 @@ impl SequenceNumber { pub struct GenericError(&'static str); impl Error for GenericError { - fn description(&self) -> &str { self.0 } + fn description(&self) -> &str { + self.0 + } } impl std::fmt::Display for GenericError { @@ -1003,7 +1493,9 @@ impl std::fmt::Display for GenericError { pub struct GenericError2(String); impl Error for GenericError2 { - fn description(&self) -> &str { &self.0 } + fn description(&self) -> &str { + &self.0 + } } impl std::fmt::Display for GenericError2 { @@ -1030,7 +1522,6 @@ pub struct LaunchRequestArgumentsVsc { // provided by vscode dmb: String, - // other keys: __sessionId, name, preLaunchTask, request, type } @@ -1046,7 +1537,6 @@ impl Request for AttachVsc { pub struct AttachRequestArgumentsVsc { #[serde(flatten)] base: AttachRequestArguments, - port: Option, } @@ -1107,7 +1597,7 @@ const STDDEF_PROCS: &[&str] = &[ "/exception/New", "/regex/New", "/regex/Find", - "/regex/Replace" + "/regex/Replace", ]; const STDDEF_SOURCE_REFERENCE: i64 = 1; @@ -1121,11 +1611,9 @@ impl StddefDmInfo { fn new(text: String) -> StddefDmInfo { let context = dm::Context::default(); let pp = dm::preprocessor::Preprocessor::from_buffer(&context, "stddef.dm".into(), &text); - let parser = dm::parser::Parser::new(&context, dm::indents::IndentProcessor::new(&context, pp)); + let parser = + dm::parser::Parser::new(&context, dm::indents::IndentProcessor::new(&context, pp)); let objtree = parser.parse_object_tree_without_builtins(); - StddefDmInfo { - text, - objtree, - } + StddefDmInfo { text, objtree } } } diff --git a/src/langserver/main.rs b/src/langserver/main.rs index 15b139e0..270869d2 100644 --- a/src/langserver/main.rs +++ b/src/langserver/main.rs @@ -1900,7 +1900,7 @@ handle_method_call! { objtree: self.objtree.clone(), extools_dll: self.extools_dll.clone(), }; - let (port, handle) = debugger::start_server(params.dreamseeker_exe, db).map_err(invalid_request)?; + let (port, handle) = debugger::start_server(self.context.config().debugger.engine, params.dreamseeker_exe, db).map_err(invalid_request)?; self.threads.push(handle); extras::StartDebuggerResult { port } }