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:
William Wallace 2020-11-28 01:34:52 +00:00 committed by GitHub
parent 650daa0eb0
commit 2737e5d352
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1669 additions and 472 deletions

11
Cargo.lock generated
View file

@ -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",

View file

@ -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 {

View file

@ -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" }

View file

@ -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> {

View 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)
}
}

View 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)
}

View 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>,
}

View file

@ -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

View file

@ -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

View file

@ -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 }
}