mirror of
https://github.com/SpaceManiac/SpacemanDMM.git
synced 2025-12-23 05:36:47 +00:00
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 ```
This commit is contained in:
parent
650daa0eb0
commit
2737e5d352
11 changed files with 1669 additions and 472 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<Severity> for WarningLevel {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for DebugEngine {
|
||||
fn default() -> Self {
|
||||
Self::Extools
|
||||
}
|
||||
}
|
||||
|
||||
/// Config parse error
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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<String, git2::Error> {
|
||||
|
|
|
|||
453
src/langserver/debugger/auxtools.rs
Normal file
453
src/langserver/debugger/auxtools.rs
Normal file
|
|
@ -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<TcpStream>),
|
||||
|
||||
Connected(TcpStream),
|
||||
|
||||
// The server has finished being used
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
pub struct Auxtools {
|
||||
seq: Arc<SequenceNumber>,
|
||||
responses: mpsc::Receiver<Response>,
|
||||
_thread: JoinHandle<()>,
|
||||
stream: StreamState,
|
||||
last_error: Arc<RwLock<String>>,
|
||||
}
|
||||
|
||||
pub struct AuxtoolsThread {
|
||||
seq: Arc<SequenceNumber>,
|
||||
responses: mpsc::Sender<Response>,
|
||||
last_error: Arc<RwLock<String>>,
|
||||
}
|
||||
|
||||
impl Auxtools {
|
||||
pub fn connect(seq: Arc<SequenceNumber>, port: Option<u16>) -> std::io::Result<Self> {
|
||||
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<SequenceNumber>) -> 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<Response, Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<Option<u32>, Box<dyn std::error::Error>> {
|
||||
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<Option<u32>, Box<dyn std::error::Error>> {
|
||||
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<BreakpointSetResult, Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<Vec<Stack>, Box<dyn std::error::Error>> {
|
||||
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<u32>,
|
||||
count: Option<u32>,
|
||||
) -> Result<(Vec<StackFrame>, u32), Box<dyn std::error::Error>> {
|
||||
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<VariablesRef>, Option<VariablesRef>, Option<VariablesRef>), Box<dyn std::error::Error>> {
|
||||
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<Vec<Variable>, Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
self.send_or_disconnect(Request::CatchRuntimes { should_catch })
|
||||
}
|
||||
}
|
||||
|
||||
impl AuxtoolsThread {
|
||||
fn spawn_listener(
|
||||
self,
|
||||
listener: TcpListener,
|
||||
connection_sender: mpsc::Sender<TcpStream>,
|
||||
) -> 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<bool, Box<dyn std::error::Error>> {
|
||||
let response = bincode::deserialize::<Response>(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)
|
||||
}
|
||||
}
|
||||
24
src/langserver/debugger/auxtools_bundle.rs
Normal file
24
src/langserver/debugger/auxtools_bundle.rs
Normal file
|
|
@ -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<PathBuf> {
|
||||
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)
|
||||
}
|
||||
145
src/langserver/debugger/auxtools_types.rs
Normal file
145
src/langserver/debugger/auxtools_types.rs
Normal file
|
|
@ -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<u32>,
|
||||
count: Option<u32>,
|
||||
},
|
||||
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<u32>,
|
||||
},
|
||||
Offset {
|
||||
offset: Option<u32>,
|
||||
},
|
||||
Stacks {
|
||||
stacks: Vec<Stack>,
|
||||
},
|
||||
StackFrames {
|
||||
frames: Vec<StackFrame>,
|
||||
total_count: u32,
|
||||
},
|
||||
Scopes {
|
||||
arguments: Option<VariablesRef>,
|
||||
locals: Option<VariablesRef>,
|
||||
globals: Option<VariablesRef>,
|
||||
},
|
||||
Variables {
|
||||
vars: Vec<Variable>,
|
||||
},
|
||||
|
||||
// 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<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum BreakpointSetResult {
|
||||
Success { line: Option<u32> },
|
||||
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<VariablesRef>,
|
||||
}
|
||||
|
|
@ -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<EvaluateResponse, Box<dyn std::error::Error>> {
|
||||
pub fn evaluate(
|
||||
&mut self,
|
||||
params: EvaluateArguments,
|
||||
) -> Result<EvaluateResponse, Box<dyn std::error::Error>> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<Mutex<State>>,
|
||||
}
|
||||
|
||||
pub enum EngineParams {
|
||||
Extools {
|
||||
port: u16,
|
||||
dll: Option<std::path::PathBuf>
|
||||
},
|
||||
Auxtools {
|
||||
port: u16,
|
||||
dll: Option<std::path::PathBuf>
|
||||
}
|
||||
}
|
||||
|
||||
impl Launched {
|
||||
pub fn new(seq: Arc<SequenceNumber>, dreamseeker_exe: &str, dmb: &str, port: Option<u16>, extools_dll: Option<&std::path::Path>) -> std::io::Result<Launched> {
|
||||
pub fn new(
|
||||
seq: Arc<SequenceNumber>,
|
||||
dreamseeker_exe: &str,
|
||||
dmb: &str,
|
||||
params: Option<EngineParams>,
|
||||
) -> std::io::Result<Launched> {
|
||||
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<()> {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue