Public dry run dryrun-20b59dc46b72

Source commit: 20b59dc46b72103c2f8a516c692b5cc3d54fab19

Public tree identity: sha256:aaa5ac7b58b2a0d23c6b11e5f76324bf3839ca1f62653a83deb736932adcfbd9
This commit is contained in:
Disasmer release dry run 2026-07-14 10:08:15 +02:00
commit 2bef715211
221 changed files with 79792 additions and 0 deletions

View file

@ -0,0 +1,814 @@
use std::io::{self, BufReader};
use anyhow::Result;
use disasmer_core::DebugRuntimeState;
use serde_json::{json, Value};
use crate::breakpoints::{
freeze_all, freeze_all_at_line, next_breakpoint_after, position_confirmed_breakpoint_stop,
request_thread, request_thread_id, resolve_breakpoints_for_source,
restart_requires_whole_process, simulated_freeze_failure_thread, step_thread,
stopped_thread_for_breakpoint,
};
use crate::dap_protocol::{initialize_capabilities, read_message, DapWriter};
use crate::demo_backend::{
is_explicit_demo_backend, start_simulated_backend, LINUX_THREAD, MAIN_THREAD,
};
use crate::runtime_client::{
attach_services_runtime, create_debug_epoch, relaunch_services_main_runtime, restart_task,
resume_debug_epoch, run_live_services_runtime, run_local_services_runtime,
wait_for_debug_epoch_frozen, wait_for_debug_epoch_resumed, wait_for_services_runtime_outcome,
RuntimeContinuationOutcome,
};
use crate::variables::variables_response;
use crate::virtual_model::{AdapterState, DapSessionMode, RuntimeBackend};
pub(crate) fn run_adapter() -> Result<()> {
let mut writer = DapWriter::new();
let mut reader = BufReader::new(io::stdin());
let mut state = AdapterState::default();
let mut _local_runtime_session = None;
while let Some(request) = read_message(&mut reader)? {
let command = request
.get("command")
.and_then(Value::as_str)
.unwrap_or_default();
match command {
"initialize" => writer.response(&request, true, initialize_capabilities())?,
"launch" | "attach" => {
let args = request
.get("arguments")
.cloned()
.unwrap_or_else(|| json!({}));
state.entry = args
.get("entry")
.and_then(Value::as_str)
.unwrap_or("build")
.to_owned();
let project = args
.get("project")
.and_then(Value::as_str)
.unwrap_or(".")
.to_owned();
let runtime_backend = match runtime_backend_from_launch_arg(
args.get("runtimeBackend").and_then(Value::as_str),
) {
Ok(runtime_backend) => runtime_backend,
Err(message) => {
writer.error_response(&request, message)?;
continue;
}
};
let coordinator_endpoint = args
.get("coordinatorEndpoint")
.and_then(Value::as_str)
.map(str::to_owned);
let source_path = args
.get("sourcePath")
.and_then(Value::as_str)
.map(str::to_owned);
if let Err(err) = state.configure_launch(
project,
state.entry.clone(),
runtime_backend,
coordinator_endpoint,
source_path,
) {
writer.error_response(&request, err.to_string())?;
continue;
}
state.restart_existing = args
.get("restartExisting")
.and_then(Value::as_bool)
.unwrap_or(false);
if let Some(process_id) = args.get("processId").and_then(Value::as_str) {
state.process = disasmer_core::ProcessId::new(process_id);
}
if command == "attach" {
state.session_mode = DapSessionMode::Attach;
state.command_status =
format!("attached to existing virtual process {}", state.process);
}
writer.response(&request, true, json!({}))?;
writer.event("initialized", json!({}))?;
}
"setBreakpoints" => {
let requested_source_path = request
.get("arguments")
.and_then(|value| value.get("source"))
.and_then(|value| value.get("path"))
.and_then(Value::as_str);
let requested_lines = request
.get("arguments")
.and_then(|value| value.get("breakpoints"))
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(|item| item.get("line").and_then(Value::as_i64))
.collect::<Vec<_>>()
})
.unwrap_or_default();
let response = resolve_breakpoints_for_source(
&mut state,
requested_source_path,
requested_lines,
)
.iter()
.map(|breakpoint| breakpoint.to_dap())
.collect::<Vec<_>>();
writer.response(&request, true, json!({ "breakpoints": response }))?;
}
"setExceptionBreakpoints" => {
writer.response(&request, true, json!({ "breakpoints": [] }))?;
}
"configurationDone" => {
if state.session_mode == DapSessionMode::Attach {
match state.runtime_backend {
RuntimeBackend::Simulated => {
state.command_status =
format!("attached to existing virtual process {}", state.process);
}
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices => {
match attach_services_runtime(&state) {
Ok(record) => state.apply_attach_record(record),
Err(err) => {
writer.error_response(
&request,
format!("services runtime attach failed: {err:#}"),
)?;
continue;
}
}
}
}
} else {
match state.runtime_backend {
RuntimeBackend::LocalServices => match run_local_services_runtime(&state) {
Ok((record, session)) => {
state.apply_runtime_record(record);
_local_runtime_session = Some(session);
}
Err(err) => {
writer.error_response(
&request,
format!("local services runtime launch failed: {err:#}"),
)?;
continue;
}
},
RuntimeBackend::LiveServices => match run_live_services_runtime(&state) {
Ok(record) => {
state.apply_runtime_record(record);
}
Err(err) => {
writer.error_response(
&request,
format!("live services runtime launch failed: {err:#}"),
)?;
continue;
}
},
RuntimeBackend::Simulated => {
start_simulated_backend(&mut state);
}
}
}
writer.response(&request, true, json!({}))?;
let session_message = if state.session_mode == DapSessionMode::Attach {
format!(
"Attached to Disasmer virtual process {} with {:?} runtime\n",
state.process, state.runtime_backend
)
} else {
format!(
"Disasmer bundle refreshed for entry `{}`; starting virtual process {} with {:?} runtime\n",
state.entry, state.process, state.runtime_backend
)
};
writer.output("console", session_message)?;
if !state.breakpoints.is_empty() {
let stopped_thread = stopped_thread_for_breakpoint(&state);
if matches!(
state.runtime_backend,
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices
) && state.coordinator_debug_epoch.is_some()
{
position_confirmed_breakpoint_stop(&mut state, stopped_thread);
writer.event(
"stopped",
json!({
"reason": "breakpoint",
"description": "Disasmer Debug Epoch all-stop confirmed by every active participant",
"threadId": stopped_thread,
"allThreadsStopped": true,
}),
)?;
continue;
}
match freeze_all(&mut state, stopped_thread, None) {
Ok(()) => {
if let Err(err) = record_coordinator_debug_epoch(
&mut state,
stopped_thread,
"breakpoint",
) {
let message = format!("coordinator debug epoch failed: {err:#}");
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
continue;
}
writer.event(
"stopped",
json!({
"reason": "breakpoint",
"description": "Disasmer Debug Epoch all-stop",
"threadId": stopped_thread,
"allThreadsStopped": true,
}),
)?;
}
Err(failure) => {
let message = failure.message();
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
}
}
} else {
writer.event("terminated", json!({}))?;
}
}
"threads" => {
let threads = state
.threads
.values()
.map(|thread| {
json!({
"id": thread.id,
"name": format!("{}/{}", state.process, thread.name),
})
})
.collect::<Vec<_>>();
writer.response(&request, true, json!({ "threads": threads }))?;
}
"stackTrace" => {
let thread = request_thread(&request, &state);
let source_path = crate::source::stack_source_path(&state);
let frame_name = thread
.runtime_stack_frames
.first()
.cloned()
.unwrap_or_else(|| format!("{}::run", thread.name));
writer.response(
&request,
true,
json!({
"stackFrames": [{
"id": thread.frame_id,
"name": frame_name,
"line": thread.line,
"column": 1,
"source": {
"name": crate::source::source_name(&source_path),
"path": source_path,
"presentationHint": "normal"
}
}],
"totalFrames": 1
}),
)?;
}
"source" => match crate::source::source_response(&state, &request) {
Ok(body) => writer.response(&request, true, body)?,
Err(err) => writer.error_response(&request, err.to_string())?,
},
"scopes" => {
let frame_id = request
.get("arguments")
.and_then(|value| value.get("frameId"))
.and_then(Value::as_i64)
.unwrap_or(1000 + crate::demo_backend::MAIN_THREAD);
let thread = state
.threads
.values()
.find(|thread| thread.frame_id == frame_id)
.cloned()
.unwrap_or_else(|| state.threads[&crate::demo_backend::MAIN_THREAD].clone());
writer.response(
&request,
true,
json!({
"scopes": [
{
"name": "Source Locals",
"variablesReference": thread.locals_ref,
"expensive": false,
"presentationHint": "locals"
},
{
"name": "Wasm Frame Locals",
"variablesReference": thread.wasm_locals_ref,
"expensive": false,
"presentationHint": "locals"
},
{
"name": "Task Args and Handles",
"variablesReference": thread.args_ref,
"expensive": false,
"presentationHint": "arguments"
},
{
"name": "Disasmer Runtime",
"variablesReference": thread.runtime_ref,
"expensive": false
},
{
"name": "Recent Output",
"variablesReference": thread.output_ref,
"expensive": false
}
]
}),
)?;
}
"variables" => {
let reference = request
.get("arguments")
.and_then(|value| value.get("variablesReference"))
.and_then(Value::as_i64)
.unwrap_or(0);
writer.response(&request, true, variables_response(&state, reference))?;
}
"evaluate" => {
let expression = request
.get("arguments")
.and_then(|value| value.get("expression"))
.and_then(Value::as_str)
.unwrap_or_default();
let result = match expression {
"virtual_process_id" => state.process.to_string(),
"debug_epoch" => state.epoch.to_string(),
"command_status" => state.command_status.clone(),
_ => "not available".to_owned(),
};
writer.response(
&request,
true,
json!({ "result": result, "variablesReference": 0 }),
)?;
}
"pause" => {
let stopped_thread = default_thread_id(&state);
match freeze_all(
&mut state,
stopped_thread,
simulated_freeze_failure_thread(&request),
) {
Ok(()) => {
if let Err(err) =
record_coordinator_debug_epoch(&mut state, stopped_thread, "pause")
{
let message = format!("coordinator debug epoch failed: {err:#}");
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
continue;
}
writer.response(&request, true, json!({}))?;
writer.event(
"stopped",
json!({
"reason": "pause",
"description": "Disasmer Debug Epoch pause",
"threadId": stopped_thread,
"allThreadsStopped": true,
}),
)?;
}
Err(failure) => {
let message = failure.message();
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
}
}
}
"continue" => {
let thread_id =
request_thread_id(&request).unwrap_or_else(|| default_thread_id(&state));
let next_breakpoint = next_breakpoint_after(&state, thread_id);
let previous_debug_epoch = state.coordinator_debug_epoch.unwrap_or(state.epoch);
if let Err(err) = resume_coordinator_epoch(&mut state) {
let message = format!("coordinator debug epoch resume failed: {err:#}");
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
continue;
}
for thread in state.threads.values_mut() {
if thread.state == DebugRuntimeState::Frozen {
thread.state = DebugRuntimeState::Running;
}
}
writer.response(&request, true, json!({ "allThreadsContinued": true }))?;
writer.event(
"continued",
json!({
"threadId": thread_id,
"allThreadsContinued": true,
}),
)?;
if matches!(
state.runtime_backend,
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices
) {
match wait_for_services_runtime_outcome(&state, previous_debug_epoch) {
Ok(RuntimeContinuationOutcome::Breakpoint(record)) => {
state.apply_runtime_record(record);
let stopped_thread = stopped_thread_for_breakpoint(&state);
position_confirmed_breakpoint_stop(&mut state, stopped_thread);
writer.event(
"stopped",
json!({
"reason": "breakpoint",
"description": "Disasmer Debug Epoch all-stop confirmed by every active participant",
"threadId": stopped_thread,
"allThreadsStopped": true,
}),
)?;
}
Ok(RuntimeContinuationOutcome::Terminal(record)) => {
state.apply_runtime_record(record);
if state.last_task_failed {
writer.output("stderr", format!("{}\n", state.command_status))?;
}
writer.event("terminated", json!({}))?;
}
Err(err) => {
let message = format!("continued runtime observation failed: {err:#}");
writer.output("stderr", format!("{message}\n"))?;
writer.event(
"stopped",
json!({
"reason": "exception",
"description": message,
"threadId": thread_id,
"allThreadsStopped": false,
}),
)?;
}
}
continue;
}
if let Some((stopped_thread, line)) = next_breakpoint {
match freeze_all_at_line(&mut state, stopped_thread, line, None) {
Ok(()) => {
if let Err(err) = record_coordinator_debug_epoch(
&mut state,
stopped_thread,
"breakpoint",
) {
let message = format!("coordinator debug epoch failed: {err:#}");
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
continue;
}
writer.event(
"stopped",
json!({
"reason": "breakpoint",
"description": "Disasmer Debug Epoch all-stop",
"threadId": stopped_thread,
"allThreadsStopped": true,
}),
)?;
}
Err(failure) => {
let message = failure.message();
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
}
}
} else {
writer.event("terminated", json!({}))?;
}
}
"next" | "stepIn" | "stepOut" => {
if state.runtime_backend != RuntimeBackend::Simulated {
writer.error_response(
&request,
"source stepping is not yet available from the executing node; use continue or pause instead of a synthetic step",
)?;
continue;
}
let thread_id = request_thread_id(&request).unwrap_or(LINUX_THREAD);
let description = match command {
"next" => "step over",
"stepIn" => "step in",
"stepOut" => "step out",
_ => "step",
};
step_thread(&mut state, thread_id, description);
writer.response(&request, true, json!({}))?;
writer.event(
"stopped",
json!({
"reason": "step",
"description": format!("Disasmer Debug Epoch {description}"),
"threadId": thread_id,
"allThreadsStopped": true,
}),
)?;
}
"restart" | "restartFrame" => {
let thread_id = request_thread_id(&request)
.or_else(|| {
request
.get("arguments")
.and_then(|arguments| arguments.get("frameId"))
.and_then(Value::as_i64)
.and_then(|frame_id| {
state
.threads
.values()
.find(|thread| thread.frame_id == frame_id)
.map(|thread| thread.id)
})
})
.unwrap_or_else(|| default_thread_id(&state));
if restart_requires_whole_process(&request) {
let message = format!(
"Incompatible source edit requires whole virtual-process restart for {}",
state.process
);
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
continue;
}
let restarting_failed_task = state
.threads
.get(&thread_id)
.is_some_and(|thread| matches!(thread.state, DebugRuntimeState::Failed(_)));
if state.runtime_backend != RuntimeBackend::Simulated {
if thread_id == MAIN_THREAD && restarting_failed_task {
match relaunch_services_main_runtime(&state) {
Ok(record) => {
state.apply_runtime_record(record);
let stopped_thread = stopped_thread_for_breakpoint(&state);
position_confirmed_breakpoint_stop(&mut state, stopped_thread);
writer.response(&request, true, json!({}))?;
writer.output(
"console",
"Rebuilt the current bundle and restarted the failed coordinator main from its entry boundary\n",
)?;
writer.event(
"stopped",
json!({
"reason": "breakpoint",
"description": "Restarted main from the rebuilt bundle; every active participant confirmed all-stop",
"threadId": stopped_thread,
"allThreadsStopped": true,
}),
)?;
}
Err(err) => {
let message = format!("coordinator main restart failed: {err:#}");
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
}
}
continue;
}
match restart_task_through_coordinator(&mut state, thread_id) {
Ok(message) => {
let previous_debug_epoch =
state.coordinator_debug_epoch.unwrap_or(state.epoch);
if let Some(thread) = state.threads.get_mut(&thread_id) {
thread.state = DebugRuntimeState::Running;
thread.recent_output.push(message.clone());
}
state.last_task_failed = false;
state.command_status = message.clone();
writer.response(&request, true, json!({}))?;
writer.output("console", format!("{message}\n"))?;
if matches!(
state.runtime_backend,
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices
) {
match wait_for_services_runtime_outcome(
&state,
previous_debug_epoch,
) {
Ok(RuntimeContinuationOutcome::Breakpoint(record)) => {
state.apply_runtime_record(record);
let stopped_thread = stopped_thread_for_breakpoint(&state);
position_confirmed_breakpoint_stop(
&mut state,
stopped_thread,
);
writer.event(
"stopped",
json!({
"reason": "breakpoint",
"description": "Restarted task reached a Wasm probe and every active participant confirmed all-stop",
"threadId": stopped_thread,
"allThreadsStopped": true,
}),
)?;
}
Ok(RuntimeContinuationOutcome::Terminal(record)) => {
state.apply_runtime_record(record);
writer.event("terminated", json!({}))?;
}
Err(err) => {
writer.event(
"stopped",
json!({
"reason": "exception",
"description": format!("restarted runtime observation failed: {err:#}"),
"threadId": thread_id,
"allThreadsStopped": false,
}),
)?;
}
}
}
}
Err(err) => {
let message = format!("coordinator task restart failed: {err:#}");
writer.output("stderr", format!("{message}\n"))?;
writer.error_response(&request, message)?;
}
}
continue;
}
if let Some(thread) = state.threads.get_mut(&thread_id) {
thread.state = DebugRuntimeState::Running;
thread
.recent_output
.push("task restarted from VFS checkpoint".to_owned());
}
if restarting_failed_task {
state.last_task_failed = false;
}
state.command_status = format!(
"{} task restarted from compatible VFS checkpoint",
if restarting_failed_task {
"failed"
} else {
"selected"
}
);
writer.response(&request, true, json!({}))?;
writer.output(
"console",
format!(
"Restarted {} task from compatible VFS checkpoint on thread {thread_id}\n",
if restarting_failed_task {
"failed"
} else {
"selected"
}
),
)?;
}
"disconnect" | "terminate" => {
writer.response(&request, true, json!({}))?;
writer.event("terminated", json!({}))?;
break;
}
_ => writer.error_response(&request, format!("unsupported DAP command: {command}"))?,
}
}
Ok(())
}
fn restart_task_through_coordinator(state: &mut AdapterState, thread_id: i64) -> Result<String> {
let task = state
.threads
.get(&thread_id)
.or_else(|| state.threads.get(&default_thread_id(state)))
.map(|thread| thread.task.clone())
.ok_or_else(|| anyhow::anyhow!("selected debug thread does not map to a virtual task"))?;
let record = restart_task(state, &task)?;
if !record.accepted {
let mut message = format!(
"coordinator refused task restart for `{task}`: {}",
record.message
);
if record.requires_whole_process_restart {
message.push_str("; whole virtual-process restart required");
}
if record.active_task {
message.push_str("; selected task is still active");
}
if record.completed_event_observed {
message.push_str("; only terminal task-event metadata is available");
}
return Err(anyhow::anyhow!(message));
}
if !record.clean_boundary_available {
return Err(anyhow::anyhow!(
"coordinator accepted task restart for `{task}` without a clean checkpoint boundary"
));
}
let restarted_task = record.restarted_task_instance.ok_or_else(|| {
anyhow::anyhow!("coordinator accepted task restart without a new task-instance ID")
})?;
if let Some(thread) = state.threads.get_mut(&thread_id) {
thread.task = restarted_task.clone();
}
state.debug_probes =
crate::breakpoints::load_bundle_debug_probes(&state.project, &state.source_path);
Ok(format!(
"Coordinator restarted task `{task}` as `{restarted_task}` from the rebuilt bundle and a clean runtime checkpoint boundary"
))
}
fn record_coordinator_debug_epoch(
state: &mut AdapterState,
stopped_thread: i64,
reason: &str,
) -> Result<()> {
if state.runtime_backend == RuntimeBackend::Simulated {
return Ok(());
}
let stopped_task = state
.threads
.get(&stopped_thread)
.map(|thread| thread.task.clone())
.unwrap_or_else(|| state.threads[&default_thread_id(state)].task.clone());
let record = create_debug_epoch(state, &stopped_task, reason)?;
let status = wait_for_debug_epoch_frozen(state, record.epoch)?;
state.epoch = record.epoch;
state.coordinator_debug_epoch = Some(record.epoch);
let previous_status = state.command_status.clone();
state.command_status = format!(
"{previous_status}; debug epoch {} {} after {}/{} signed participant freeze acknowledgements",
status.epoch,
status.command,
status.acknowledgements.len(),
status.expected_tasks
);
if let Some(thread) = state.threads.get_mut(&stopped_thread) {
thread.recent_output.push(format!(
"coordinator debug epoch {} reached all-stop after {}/{} signed participant acknowledgements",
status.epoch,
status.acknowledgements.len(),
status.expected_tasks
));
}
Ok(())
}
fn default_thread_id(state: &AdapterState) -> i64 {
if state.runtime_backend == RuntimeBackend::Simulated
&& state.threads.contains_key(&LINUX_THREAD)
{
LINUX_THREAD
} else {
MAIN_THREAD
}
}
fn resume_coordinator_epoch(state: &mut AdapterState) -> Result<()> {
let Some(epoch) = state.coordinator_debug_epoch else {
return Ok(());
};
if state.runtime_backend == RuntimeBackend::Simulated {
return Ok(());
}
let record = resume_debug_epoch(state, epoch)?;
let status = wait_for_debug_epoch_resumed(state, epoch)?;
state.coordinator_debug_epoch = None;
state.command_status = format!(
"debug epoch {} resumed after {}/{} signed participant acknowledgements",
status.epoch,
status.acknowledgements.len(),
status.expected_tasks
);
let status_thread = default_thread_id(state);
if let Some(thread) = state.threads.get_mut(&status_thread) {
thread.recent_output.push(format!(
"coordinator debug epoch {} resumed with {} after {}/{} acknowledgements ({} affected task(s))",
status.epoch,
status.command,
status.acknowledgements.len(),
status.expected_tasks,
record.affected_tasks
));
}
Ok(())
}
pub(crate) fn runtime_backend_from_launch_arg(
value: Option<&str>,
) -> Result<RuntimeBackend, String> {
match value {
None | Some("local-services") => Ok(RuntimeBackend::LocalServices),
Some("live-services") => Ok(RuntimeBackend::LiveServices),
Some(value) if is_explicit_demo_backend(value) => Ok(RuntimeBackend::Simulated),
Some(value) => Err(format!(
"unsupported Disasmer runtimeBackend `{value}`; use local-services, live-services, or explicit demo"
)),
}
}