#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone)]
pub struct X86DapRequest {
pub seq: u32,
pub command: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct X86DapResponse {
pub seq: u32,
pub request_seq: u32,
pub success: bool,
pub command: String,
pub message: Option<String>,
pub body: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct X86DapEvent {
pub event: String,
pub body: serde_json::Value,
}
#[derive(Debug, Clone)]
pub enum X86DapMessage {
Request(X86DapRequest),
Response(X86DapResponse),
Event(X86DapEvent),
}
#[derive(Debug, Clone)]
pub struct X86DebugConfig {
pub program: String,
pub args: Vec<String>,
pub cwd: Option<String>,
pub env: HashMap<String, String>,
pub stop_on_entry: bool,
pub remote_target: Option<String>,
pub attach_pid: Option<u32>,
pub symbol_file: Option<String>,
pub source_map: Vec<(String, String)>,
pub architecture: X86DebugArch,
pub disassembly_flavor: X86AsmFlavor,
pub external_console: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DebugArch {
X86,
X86_64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86AsmFlavor {
Intel,
ATT,
}
impl Default for X86DebugConfig {
fn default() -> Self {
Self {
program: String::new(),
args: Vec::new(),
cwd: None,
env: HashMap::new(),
stop_on_entry: false,
remote_target: None,
attach_pid: None,
symbol_file: None,
source_map: Vec::new(),
architecture: X86DebugArch::X86_64,
disassembly_flavor: X86AsmFlavor::Intel,
external_console: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86Breakpoint {
pub id: u32,
pub source: Option<X86Source>,
pub line: Option<u32>,
pub column: Option<u32>,
pub function_name: Option<String>,
pub enabled: bool,
pub condition: Option<String>,
pub hit_condition: Option<String>,
pub log_message: Option<String>,
pub resolved_addresses: Vec<u64>,
pub verified: bool,
pub kind: X86BreakpointKind,
pub hw_register: Option<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86BreakpointKind {
Software,
Hardware,
Function,
Instruction,
ReadWatch,
WriteWatch,
AccessWatch,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct X86Source {
pub name: Option<String>,
pub path: Option<String>,
pub source_reference: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86BreakpointReason {
Changed,
New,
Removed,
}
#[derive(Debug, Clone)]
pub struct X86ExceptionBreakpoint {
pub filter: String,
pub label: String,
pub description: String,
pub default_enabled: bool,
pub enabled: bool,
pub condition: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86StackFrame {
pub id: u32,
pub name: String,
pub source: Option<X86Source>,
pub line: u32,
pub column: u32,
pub instruction_pointer: u64,
pub stack_pointer: u64,
pub frame_pointer: u64,
pub module_id: Option<u32>,
pub inline_frames: Vec<X86InlineFrame>,
pub presentation_hint: Option<X86FrameHint>,
}
#[derive(Debug, Clone)]
pub struct X86InlineFrame {
pub name: String,
pub source: Option<X86Source>,
pub line: u32,
pub column: u32,
pub call_stack_depth: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86FrameHint {
Normal,
Label,
Subtle,
}
#[derive(Debug, Clone)]
pub struct X86Variable {
pub name: String,
pub value: String,
pub type_name: Option<String>,
pub variable_reference: u32,
pub named_variables: Option<u32>,
pub indexed_variables: Option<u32>,
pub memory_reference: Option<String>,
pub is_register: bool,
}
#[derive(Debug, Clone)]
pub struct X86Scope {
pub id: u32,
pub name: String,
pub source: Option<X86Source>,
pub line: u32,
pub column: u32,
pub expensive: bool,
pub presentation_hint: Option<X86ScopeHint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ScopeHint {
Arguments,
Locals,
Registers,
}
#[derive(Debug, Clone)]
pub struct X86Register {
pub name: String,
pub value: String,
pub bit_size: u32,
pub id: u32,
pub presentation_hint: Option<X86RegisterHint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RegisterHint {
GeneralPurpose,
Segment,
Flags,
FloatingPoint,
Vector,
Debug,
}
#[derive(Debug)]
pub struct X86ExpressionEvaluator {
pub expression: String,
pub frame_id: Option<u32>,
pub result: Option<String>,
pub result_type: Option<String>,
pub result_children: u32,
pub error: Option<String>,
}
impl X86ExpressionEvaluator {
pub fn new(expression: &str, frame_id: Option<u32>) -> Self {
Self {
expression: expression.to_string(),
frame_id,
result: None,
result_type: None,
result_children: 0,
error: None,
}
}
pub fn evaluate(&mut self, registers: &HashMap<String, u64>, locals: &HashMap<String, String>) {
let expr = self.expression.trim();
if expr.starts_with('$') {
let reg_name = &expr[1..].to_lowercase();
if let Some(val) = registers.get(reg_name.as_str()) {
self.result = Some(format!("{:#x}", val));
self.result_type = Some("uint64".to_string());
return;
}
}
if let Some(val) = locals.get(expr) {
self.result = Some(val.clone());
self.result_type = Some("string".to_string());
return;
}
self.error = Some(format!("Cannot evaluate '{}'", expr));
}
pub fn evaluate_dwarf_expr(
&mut self,
expr_bytes: &[u8],
registers: &HashMap<String, u64>,
memory: &HashMap<u64, u8>,
) {
let mut stack: Vec<u64> = Vec::new();
let mut pos = 0;
while pos < expr_bytes.len() {
let op = expr_bytes[pos];
pos += 1;
match op {
0x30..=0x4f => {
stack.push((op - 0x30) as u64);
}
0x70..=0x8f => {
let reg_idx = op - 0x70;
let offset = self.read_sleb128(expr_bytes, &mut pos);
let reg_name = format!("r{}", reg_idx);
let base = registers.get(®_name).copied().unwrap_or(0);
stack.push(base.wrapping_add(offset as u64));
}
0x06 => {
if let Some(addr) = stack.pop() {
let mut val: u64 = 0;
for i in 0..8 {
val |= (*memory.get(&(addr + i)).unwrap_or(&0) as u64) << (i * 8);
}
stack.push(val);
}
}
0x22 => {
let b = stack.pop().unwrap_or(0);
let a = stack.pop().unwrap_or(0);
stack.push(a.wrapping_add(b));
}
0x1c => {
let b = stack.pop().unwrap_or(0);
let a = stack.pop().unwrap_or(0);
stack.push(a.wrapping_sub(b));
}
0x03 => {
let addr = if pos + 8 <= expr_bytes.len() {
u64::from_le_bytes([
expr_bytes[pos],
expr_bytes[pos + 1],
expr_bytes[pos + 2],
expr_bytes[pos + 3],
expr_bytes[pos + 4],
expr_bytes[pos + 5],
expr_bytes[pos + 6],
expr_bytes[pos + 7],
])
} else {
0
};
pos += 8;
stack.push(addr);
}
0x50..=0x6f => {
let reg_idx = op - 0x50;
let reg_name = format!("r{}", reg_idx);
let val = registers.get(®_name).copied().unwrap_or(0);
self.result = Some(format!("{:#x}", val));
self.result_type = Some("register".to_string());
return;
}
_ => {
self.error = Some(format!("unsupported DWARF op: {:#x}", op));
return;
}
}
}
if let Some(top) = stack.last() {
self.result = Some(format!("{:#x}", top));
self.result_type = Some("uint64".to_string());
}
}
fn read_sleb128(&self, data: &[u8], pos: &mut usize) -> i64 {
let mut result: i64 = 0;
let mut shift = 0u32;
loop {
if *pos >= data.len() {
break;
}
let byte = data[*pos];
*pos += 1;
result |= ((byte & 0x7f) as i64) << shift;
shift += 7;
if byte & 0x80 == 0 {
if shift < 64 && (byte & 0x40) != 0 {
result |= -(1i64 << shift);
}
break;
}
}
result
}
}
#[derive(Debug, Clone)]
pub struct X86DisassembledInstruction {
pub address: u64,
pub bytes: Vec<u8>,
pub instruction: String,
pub symbol: Option<String>,
pub source_line: Option<u32>,
pub source_file: Option<String>,
}
#[derive(Debug)]
pub struct X86DisassemblyView {
pub instructions: BTreeMap<u64, X86DisassembledInstruction>,
pub base_address: u64,
pub instruction_count: u32,
pub offset: u32,
pub flavor: X86AsmFlavor,
}
impl Default for X86DisassemblyView {
fn default() -> Self {
Self {
instructions: BTreeMap::new(),
base_address: 0,
instruction_count: 20,
offset: 0,
flavor: X86AsmFlavor::Intel,
}
}
}
impl X86DisassemblyView {
pub fn new() -> Self {
Self::default()
}
pub fn disassemble_range(&mut self, memory: &[u8], start_address: u64) {
self.instructions.clear();
let mut offset = 0;
let mut addr = start_address;
while offset < memory.len() && self.instructions.len() < 200 {
let inst_len = 1usize; if offset + inst_len <= memory.len() {
self.instructions.insert(
addr,
X86DisassembledInstruction {
address: addr,
bytes: memory[offset..offset + inst_len].to_vec(),
instruction: "nop".to_string(),
symbol: None,
source_line: None,
source_file: None,
},
);
}
offset += inst_len;
addr += inst_len as u64;
}
}
pub fn get_instruction(&self, address: u64) -> Option<&X86DisassembledInstruction> {
self.instructions.get(&address)
}
pub fn get_instructions_range(&self, start: u64, end: u64) -> Vec<&X86DisassembledInstruction> {
self.instructions
.range(start..end)
.map(|(_, inst)| inst)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct X86MemoryRegion {
pub address: u64,
pub size: u64,
pub readable: bool,
pub writable: bool,
pub executable: bool,
pub name: Option<String>,
}
#[derive(Debug)]
pub struct X86MemoryView {
pub buffer: HashMap<u64, Vec<u8>>,
pub regions: Vec<X86MemoryRegion>,
pub bytes_per_line: u32,
}
impl Default for X86MemoryView {
fn default() -> Self {
Self {
buffer: HashMap::new(),
regions: Vec::new(),
bytes_per_line: 16,
}
}
}
impl X86MemoryView {
pub fn new() -> Self {
Self::default()
}
pub fn read_memory(&self, address: u64, size: usize) -> Vec<u8> {
for (start, data) in &self.buffer {
let end = *start + data.len() as u64;
if address >= *start && address + size as u64 <= end {
let offset = (address - *start) as usize;
return data[offset..offset + size].to_vec();
}
}
vec![0u8; size]
}
pub fn write_memory(&mut self, address: u64, data: &[u8]) {
let keys: Vec<u64> = self.buffer.keys().copied().collect();
for start in keys {
let end = start + self.buffer[&start].len() as u64;
if address >= start && address + data.len() as u64 <= end {
let offset = (address - start) as usize;
if let Some(buf) = self.buffer.get_mut(&start) {
buf[offset..offset + data.len()].copy_from_slice(data);
}
return;
}
}
self.buffer.insert(address, data.to_vec());
}
pub fn add_region(&mut self, region: X86MemoryRegion) {
self.regions.push(region);
}
pub fn hex_dump(&self, address: u64, size: usize) -> String {
let data = self.read_memory(address, size);
let mut out = String::new();
for (i, chunk) in data.chunks(self.bytes_per_line as usize).enumerate() {
let addr = address + (i * self.bytes_per_line as usize) as u64;
out.push_str(&format!("{:#010x}: ", addr));
for byte in chunk {
out.push_str(&format!("{:02x} ", byte));
}
for _ in chunk.len()..self.bytes_per_line as usize {
out.push_str(" ");
}
out.push_str(" |");
for byte in chunk {
let c = if byte.is_ascii_graphic() || *byte == b' ' {
*byte as char
} else {
'.'
};
out.push(c);
}
out.push_str("|\n");
}
out
}
}
#[derive(Debug)]
pub struct X86RegisterView {
pub registers: HashMap<String, u64>,
pub descriptions: Vec<X86Register>,
pub show_float: bool,
pub show_vector: bool,
pub show_segments: bool,
}
impl Default for X86RegisterView {
fn default() -> Self {
let mut rv = Self {
registers: HashMap::new(),
descriptions: Vec::new(),
show_float: true,
show_vector: true,
show_segments: false,
};
for reg in &[
"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15", "rip", "rflags",
] {
rv.registers.insert(reg.to_string(), 0);
rv.descriptions.push(X86Register {
name: reg.to_string(),
value: "0".to_string(),
bit_size: 64,
id: rv.descriptions.len() as u32,
presentation_hint: Some(X86RegisterHint::GeneralPurpose),
});
}
rv.descriptions.push(X86Register {
name: "rflags".to_string(),
value: "0".to_string(),
bit_size: 64,
id: rv.descriptions.len() as u32,
presentation_hint: Some(X86RegisterHint::Flags),
});
rv
}
}
impl X86RegisterView {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, name: &str) -> Option<u64> {
self.registers.get(&name.to_lowercase()).copied()
}
pub fn set(&mut self, name: &str, value: u64) {
let key = name.to_lowercase();
self.registers.insert(key, value);
}
pub fn gp_registers(&self) -> Vec<(&str, u64)> {
let gp_names = [
"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15",
];
gp_names
.iter()
.filter_map(|n| self.registers.get(*n).map(|v| (*n, *v)))
.collect()
}
pub fn format_flags(&self) -> String {
let rflags = self.get("rflags").unwrap_or(0);
let mut flags = String::new();
let flag_names = [
('C', 0),
('P', 2),
('A', 4),
('Z', 6),
('S', 7),
('T', 8),
('I', 9),
('D', 10),
('O', 11),
];
for (name, bit) in flag_names {
let state = if rflags & (1 << bit) != 0 {
name.to_uppercase().to_string()
} else {
name.to_lowercase().to_string()
};
flags.push_str(&state);
flags.push(' ');
}
flags.trim().to_string()
}
}
#[derive(Debug, Clone)]
pub struct X86Module {
pub id: u32,
pub name: String,
pub path: Option<String>,
pub base_address: u64,
pub size: u64,
pub symbols_loaded: bool,
pub symbol_file: Option<String>,
pub address_range: (u64, u64),
}
#[derive(Debug, Clone)]
pub struct X86Symbol {
pub name: String,
pub address: u64,
pub size: u64,
pub kind: X86SymbolKind,
pub module_id: u32,
pub source_file: Option<String>,
pub source_line: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86SymbolKind {
Function,
Data,
Label,
Thunk,
Import,
Export,
Unknown,
}
#[derive(Debug)]
pub struct X86ModuleManager {
pub modules: HashMap<u32, X86Module>,
pub symbols_by_name: HashMap<String, Vec<X86Symbol>>,
pub symbols_by_address: BTreeMap<u64, X86Symbol>,
next_module_id: u32,
next_symbol_id: u32,
}
impl Default for X86ModuleManager {
fn default() -> Self {
Self {
modules: HashMap::new(),
symbols_by_name: HashMap::new(),
symbols_by_address: BTreeMap::new(),
next_module_id: 1,
next_symbol_id: 1,
}
}
}
impl X86ModuleManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_module(&mut self, name: &str, base: u64, size: u64) -> u32 {
let id = self.next_module_id;
self.next_module_id += 1;
self.modules.insert(
id,
X86Module {
id,
name: name.to_string(),
path: None,
base_address: base,
size,
symbols_loaded: false,
symbol_file: None,
address_range: (base, base + size),
},
);
id
}
pub fn remove_module(&mut self, id: u32) {
self.modules.remove(&id);
}
pub fn add_symbol(
&mut self,
name: &str,
address: u64,
size: u64,
kind: X86SymbolKind,
module_id: u32,
) -> u32 {
let symbol = X86Symbol {
name: name.to_string(),
address,
size,
kind,
module_id,
source_file: None,
source_line: None,
};
let id = self.next_symbol_id;
self.next_symbol_id += 1;
self.symbols_by_name
.entry(name.to_string())
.or_default()
.push(symbol.clone());
self.symbols_by_address.insert(address, symbol);
id
}
pub fn resolve_by_name(&self, name: &str) -> Option<&X86Symbol> {
self.symbols_by_name.get(name).and_then(|v| v.first())
}
pub fn resolve_by_address(&self, address: u64) -> Option<&X86Symbol> {
self.symbols_by_address
.range(..=address)
.next_back()
.filter(|&(addr, sym)| address >= *addr && address < *addr + sym.size)
.map(|(_, sym)| sym)
}
pub fn find_module_by_address(&self, address: u64) -> Option<&X86Module> {
self.modules
.values()
.find(|m| address >= m.address_range.0 && address < m.address_range.1)
}
pub fn symbols_in_module(&self, module_id: u32) -> Vec<&X86Symbol> {
self.symbols_by_address
.values()
.filter(|s| s.module_id == module_id)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct X86SourceFile {
pub id: u32,
pub name: String,
pub path: String,
pub content: Option<String>,
pub checksums: Vec<X86SourceChecksum>,
}
#[derive(Debug, Clone)]
pub struct X86SourceChecksum {
pub algorithm: X86ChecksumAlgorithm,
pub checksum: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ChecksumAlgorithm {
MD5,
SHA1,
SHA256,
Timestamp,
}
#[derive(Debug)]
pub struct X86SourceMapper {
pub sources: HashMap<u32, X86SourceFile>,
pub path_mappings: Vec<(String, String)>,
next_source_id: u32,
}
impl Default for X86SourceMapper {
fn default() -> Self {
Self {
sources: HashMap::new(),
path_mappings: Vec::new(),
next_source_id: 1,
}
}
}
impl X86SourceMapper {
pub fn new() -> Self {
Self::default()
}
pub fn add_mapping(&mut self, remote_prefix: &str, local_prefix: &str) {
self.path_mappings
.push((remote_prefix.to_string(), local_prefix.to_string()));
}
pub fn map_path(&self, remote_path: &str) -> String {
for (remote_prefix, local_prefix) in &self.path_mappings {
if remote_path.starts_with(remote_prefix) {
return format!("{}{}", local_prefix, &remote_path[remote_prefix.len()..]);
}
}
remote_path.to_string()
}
pub fn register_source(&mut self, name: &str, path: &str) -> u32 {
let id = self.next_source_id;
self.next_source_id += 1;
self.sources.insert(
id,
X86SourceFile {
id,
name: name.to_string(),
path: path.to_string(),
content: None,
checksums: Vec::new(),
},
);
id
}
pub fn get_source(&self, id: u32) -> Option<&X86SourceFile> {
self.sources.get(&id)
}
pub fn find_by_path(&self, path: &str) -> Option<&X86SourceFile> {
self.sources.values().find(|s| s.path == path)
}
pub fn set_content(&mut self, id: u32, content: &str) {
if let Some(source) = self.sources.get_mut(&id) {
source.content = Some(content.to_string());
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86StepGranularity {
Instruction,
Statement,
Line,
NextLine,
StepOut,
ReverseInstruction,
ReverseLine,
}
#[derive(Debug)]
pub struct X86SteppingState {
pub granularity: X86StepGranularity,
pub stop_on_entry: bool,
pub stepping: bool,
pub step_out_return_address: Option<u64>,
pub supports_reverse: bool,
}
impl Default for X86SteppingState {
fn default() -> Self {
Self {
granularity: X86StepGranularity::Line,
stop_on_entry: false,
stepping: false,
step_out_return_address: None,
supports_reverse: false,
}
}
}
impl X86SteppingState {
pub fn new() -> Self {
Self::default()
}
pub fn start_step(&mut self, granularity: X86StepGranularity) {
self.granularity = granularity;
self.stepping = true;
}
pub fn complete_step(&mut self) {
self.stepping = false;
}
pub fn is_stepping(&self) -> bool {
self.stepping
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DebugState {
Uninitialized,
Initialized,
Running,
Stopped,
Paused,
Stepping,
Exited,
Terminated,
}
impl Default for X86DebugState {
fn default() -> Self {
X86DebugState::Uninitialized
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86StopReason {
Step,
Breakpoint,
Exception,
Pause,
Entry,
Goto,
FunctionBreakpoint,
DataBreakpoint,
InstructionBreakpoint,
Unknown,
}
impl X86StopReason {
pub fn to_str(&self) -> &'static str {
match self {
Self::Step => "step",
Self::Breakpoint => "breakpoint",
Self::Exception => "exception",
Self::Pause => "pause",
Self::Entry => "entry",
Self::Goto => "goto",
Self::FunctionBreakpoint => "function breakpoint",
Self::DataBreakpoint => "data breakpoint",
Self::InstructionBreakpoint => "instruction breakpoint",
Self::Unknown => "unknown",
}
}
}
#[derive(Debug)]
pub struct X86DebugAdapter {
pub config: X86DebugConfig,
pub state: X86DebugState,
pub breakpoints: HashMap<u32, X86Breakpoint>,
pub exception_breakpoints: Vec<X86ExceptionBreakpoint>,
pub frames: Vec<X86StackFrame>,
pub threads: Vec<X86DebugThread>,
pub current_thread_id: u32,
pub modules: X86ModuleManager,
pub sources: X86SourceMapper,
pub disassembly: X86DisassemblyView,
pub memory: X86MemoryView,
pub registers: X86RegisterView,
pub stepping: X86SteppingState,
pub scopes: Vec<X86Scope>,
next_breakpoint_id: u32,
next_frame_id: u32,
next_thread_id: u32,
seq: u32,
session_start: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct X86DebugThread {
pub id: u32,
pub name: String,
}
impl Default for X86DebugAdapter {
fn default() -> Self {
Self {
config: X86DebugConfig::default(),
state: X86DebugState::Uninitialized,
breakpoints: HashMap::new(),
exception_breakpoints: Self::default_exception_breakpoints(),
frames: Vec::new(),
threads: Vec::new(),
current_thread_id: 1,
modules: X86ModuleManager::new(),
sources: X86SourceMapper::new(),
disassembly: X86DisassemblyView::new(),
memory: X86MemoryView::new(),
registers: X86RegisterView::new(),
stepping: X86SteppingState::new(),
scopes: Vec::new(),
next_breakpoint_id: 1,
next_frame_id: 1,
next_thread_id: 1,
seq: 1,
session_start: None,
}
}
}
impl X86DebugAdapter {
pub fn new() -> Self {
Self::default()
}
fn default_exception_breakpoints() -> Vec<X86ExceptionBreakpoint> {
vec![
X86ExceptionBreakpoint {
filter: "cpp_throw".to_string(),
label: "C++ Exceptions".to_string(),
description: "Break on C++ throw".to_string(),
default_enabled: false,
enabled: false,
condition: None,
},
X86ExceptionBreakpoint {
filter: "cpp_catch".to_string(),
label: "C++ Catch".to_string(),
description: "Break on C++ catch".to_string(),
default_enabled: false,
enabled: false,
condition: None,
},
X86ExceptionBreakpoint {
filter: "seh".to_string(),
label: "SEH Exceptions".to_string(),
description: "Break on Windows SEH exceptions".to_string(),
default_enabled: false,
enabled: false,
condition: None,
},
X86ExceptionBreakpoint {
filter: "signal".to_string(),
label: "Signals".to_string(),
description: "Break on POSIX signals".to_string(),
default_enabled: false,
enabled: false,
condition: None,
},
]
}
pub fn initialize(&mut self, _client_id: &str, _client_name: &str) -> serde_json::Value {
self.state = X86DebugState::Initialized;
serde_json::json!({
"supportsConfigurationDoneRequest": true,
"supportsFunctionBreakpoints": true,
"supportsConditionalBreakpoints": true,
"supportsHitConditionalBreakpoints": true,
"supportsLogPoints": true,
"supportsEvaluateForHovers": true,
"supportsStepBack": false,
"supportsSetVariable": true,
"supportsRestartFrame": false,
"supportsGotoTargetsRequest": false,
"supportsStepInTargetsRequest": false,
"supportsCompletionsRequest": true,
"supportsModulesRequest": true,
"supportsExceptionOptions": true,
"supportsValueFormattingOptions": true,
"supportsExceptionInfoRequest": true,
"supportTerminateDebuggee": true,
"supportsDelayedStackTraceLoading": true,
"supportsDisassembleRequest": true,
"supportsReadMemoryRequest": true,
"supportsWriteMemoryRequest": true,
"supportsInstructionBreakpoints": true,
"supportsDataBreakpoints": true,
"exceptionBreakpointFilters": self.exception_breakpoints.iter().map(|eb| {
serde_json::json!({
"filter": eb.filter,
"label": eb.label,
"description": eb.description,
"default": eb.default_enabled,
})
}).collect::<Vec<_>>(),
})
}
pub fn launch(&mut self, config: X86DebugConfig) -> Result<(), String> {
self.config = config;
self.state = X86DebugState::Running;
self.session_start = Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64,
);
self.threads.push(X86DebugThread {
id: self.next_thread_id,
name: "Main Thread".to_string(),
});
self.current_thread_id = self.next_thread_id;
self.next_thread_id += 1;
if self.config.stop_on_entry {
self.state = X86DebugState::Stopped;
}
Ok(())
}
pub fn attach(&mut self, pid: u32) -> Result<(), String> {
self.config.attach_pid = Some(pid);
self.state = X86DebugState::Running;
self.session_start = Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64,
);
self.threads.push(X86DebugThread {
id: self.next_thread_id,
name: "Main Thread".to_string(),
});
self.current_thread_id = self.next_thread_id;
self.next_thread_id += 1;
self.state = X86DebugState::Stopped;
Ok(())
}
pub fn configuration_done(&mut self) {
self.state = X86DebugState::Running;
}
pub fn disconnect(&mut self) {
self.state = X86DebugState::Terminated;
}
pub fn set_breakpoint(
&mut self,
source: X86Source,
line: u32,
column: Option<u32>,
condition: Option<&str>,
hit_condition: Option<&str>,
log_message: Option<&str>,
) -> u32 {
let id = self.next_breakpoint_id;
self.next_breakpoint_id += 1;
let bp = X86Breakpoint {
id,
source: Some(source),
line: Some(line),
column,
function_name: None,
enabled: true,
condition: condition.map(String::from),
hit_condition: hit_condition.map(String::from),
log_message: log_message.map(String::from),
resolved_addresses: vec![0x400000 + id as u64 * 16], verified: true,
kind: X86BreakpointKind::Software,
hw_register: None,
};
self.breakpoints.insert(id, bp);
id
}
pub fn set_function_breakpoint(&mut self, name: &str, condition: Option<&str>) -> u32 {
let id = self.next_breakpoint_id;
self.next_breakpoint_id += 1;
let bp = X86Breakpoint {
id,
source: None,
line: None,
column: None,
function_name: Some(name.to_string()),
enabled: true,
condition: condition.map(String::from),
hit_condition: None,
log_message: None,
resolved_addresses: vec![0x401000 + id as u64 * 16], verified: true,
kind: X86BreakpointKind::Function,
hw_register: None,
};
self.breakpoints.insert(id, bp);
id
}
pub fn set_instruction_breakpoint(&mut self, address: u64) -> u32 {
let id = self.next_breakpoint_id;
self.next_breakpoint_id += 1;
let hw_reg = if id <= 4 { Some(id as u8 - 1) } else { None };
let bp = X86Breakpoint {
id,
source: None,
line: None,
column: None,
function_name: None,
enabled: true,
condition: None,
hit_condition: None,
log_message: None,
resolved_addresses: vec![address],
verified: hw_reg.is_some(),
kind: X86BreakpointKind::Instruction,
hw_register: hw_reg,
};
self.breakpoints.insert(id, bp);
id
}
pub fn set_data_breakpoint(
&mut self,
address: u64,
size: usize,
access_type: X86BreakpointKind,
) -> u32 {
let id = self.next_breakpoint_id;
self.next_breakpoint_id += 1;
let hw_reg = if id <= 4 { Some(id as u8 - 1) } else { None };
let bp = X86Breakpoint {
id,
source: None,
line: None,
column: None,
function_name: None,
enabled: true,
condition: None,
hit_condition: None,
log_message: None,
resolved_addresses: vec![address],
verified: hw_reg.is_some(),
kind: access_type,
hw_register: hw_reg,
};
self.breakpoints.insert(id, bp);
id
}
pub fn remove_breakpoint(&mut self, id: u32) -> bool {
self.breakpoints.remove(&id).is_some()
}
pub fn set_breakpoint_enabled(&mut self, id: u32, enabled: bool) -> bool {
if let Some(bp) = self.breakpoints.get_mut(&id) {
bp.enabled = enabled;
true
} else {
false
}
}
pub fn set_exception_breakpoints(&mut self, filters: &[String]) {
for eb in &mut self.exception_breakpoints {
eb.enabled = filters.contains(&eb.filter);
}
}
pub fn stack_trace(
&mut self,
thread_id: u32,
start_frame: u32,
levels: u32,
) -> Vec<X86StackFrame> {
self.frames.clear();
self.next_frame_id = 1;
let count = levels.min(20);
for i in 0..count {
let frame = X86StackFrame {
id: self.next_frame_id,
name: format!("frame_{}", i),
source: Some(X86Source {
name: Some(format!("file_{}.cpp", i % 3)),
path: Some(format!("/src/file_{}.cpp", i % 3)),
source_reference: Some(i),
}),
line: 42 + i * 3,
column: 10,
instruction_pointer: 0x400100 + (i as u64 * 0x40),
stack_pointer: 0x7ffffff0 - (i as u64 * 0x100),
frame_pointer: 0x7fffffe0 - (i as u64 * 0x100),
module_id: Some(1),
inline_frames: if i == 0 {
vec![X86InlineFrame {
name: "inline_helper".to_string(),
source: Some(X86Source {
name: Some("header.hpp".to_string()),
path: Some("/src/header.hpp".to_string()),
source_reference: None,
}),
line: 15,
column: 5,
call_stack_depth: 1,
}]
} else {
Vec::new()
},
presentation_hint: None,
};
self.frames.push(frame);
self.next_frame_id += 1;
}
self.frames.clone()
}
pub fn scopes(&mut self, frame_id: u32) -> Vec<X86Scope> {
self.scopes = vec![
X86Scope {
id: 100 + frame_id,
name: "Locals".to_string(),
source: None,
line: 0,
column: 0,
expensive: false,
presentation_hint: Some(X86ScopeHint::Locals),
},
X86Scope {
id: 200 + frame_id,
name: "Arguments".to_string(),
source: None,
line: 0,
column: 0,
expensive: false,
presentation_hint: Some(X86ScopeHint::Arguments),
},
X86Scope {
id: 300 + frame_id,
name: "Registers".to_string(),
source: None,
line: 0,
column: 0,
expensive: false,
presentation_hint: Some(X86ScopeHint::Registers),
},
];
self.scopes.clone()
}
pub fn variables(&self, scope_id: u32) -> Vec<X86Variable> {
let hint = scope_id / 100;
match hint {
1 => {
vec![
X86Variable {
name: "i".to_string(),
value: "42".to_string(),
type_name: Some("int".to_string()),
variable_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
is_register: false,
},
X86Variable {
name: "buf".to_string(),
value: "[...]".to_string(),
type_name: Some("char[256]".to_string()),
variable_reference: scope_id * 1000 + 1,
named_variables: None,
indexed_variables: Some(256),
memory_reference: Some("0x7fffffe0".to_string()),
is_register: false,
},
]
}
2 => {
vec![
X86Variable {
name: "argc".to_string(),
value: "1".to_string(),
type_name: Some("int".to_string()),
variable_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
is_register: false,
},
X86Variable {
name: "argv".to_string(),
value: "0x7ffffff0".to_string(),
type_name: Some("char**".to_string()),
variable_reference: scope_id * 1000 + 2,
named_variables: None,
indexed_variables: None,
memory_reference: Some("0x7ffffff0".to_string()),
is_register: false,
},
]
}
3 => {
self.registers
.gp_registers()
.iter()
.map(|(name, val)| X86Variable {
name: name.to_string(),
value: format!("{:#x}", val),
type_name: Some("uint64".to_string()),
variable_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
is_register: true,
})
.collect()
}
_ => Vec::new(),
}
}
pub fn evaluate(
&mut self,
expression: &str,
frame_id: Option<u32>,
) -> Result<X86Variable, String> {
let mut locals = HashMap::new();
locals.insert("i".to_string(), "42".to_string());
locals.insert("argc".to_string(), "1".to_string());
let mut registers = HashMap::new();
for (name, val) in self.registers.gp_registers() {
registers.insert(name.to_string(), val);
}
let mut eval = X86ExpressionEvaluator::new(expression, frame_id);
eval.evaluate(®isters, &locals);
if let Some(err) = &eval.error {
return Err(err.clone());
}
Ok(X86Variable {
name: expression.to_string(),
value: eval.result.unwrap_or_else(|| "?".to_string()),
type_name: eval.result_type,
variable_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
is_register: false,
})
}
pub fn continue_execution(&mut self) {
self.state = X86DebugState::Running;
}
pub fn step(&mut self, granularity: X86StepGranularity) {
self.stepping.start_step(granularity);
self.state = X86DebugState::Stepping;
}
pub fn pause(&mut self) {
self.state = X86DebugState::Stopped;
}
pub fn reverse_continue(&mut self) {
self.state = X86DebugState::Running;
}
pub fn stopped(
&mut self,
reason: X86StopReason,
description: Option<&str>,
thread_id: Option<u32>,
) {
self.state = X86DebugState::Stopped;
self.current_thread_id = thread_id.unwrap_or(1);
}
pub fn disassemble(
&mut self,
memory: &[u8],
start: u64,
offset: u32,
count: u32,
) -> Vec<X86DisassembledInstruction> {
self.disassembly.disassemble_range(memory, start);
self.disassembly
.get_instructions_range(start, start + count as u64)
.into_iter()
.cloned()
.collect()
}
pub fn read_memory(&self, address: u64, size: usize) -> Vec<u8> {
self.memory.read_memory(address, size)
}
pub fn write_memory(&mut self, address: u64, data: &[u8]) {
self.memory.write_memory(address, data);
}
pub fn memory_hex_dump(&self, address: u64, size: usize) -> String {
self.memory.hex_dump(address, size)
}
pub fn modules_list(&self) -> Vec<&X86Module> {
self.modules.modules.values().collect()
}
pub fn add_module(&mut self, name: &str, base: u64, size: u64) -> u32 {
self.modules.add_module(name, base, size)
}
pub fn stats(&self) -> X86DebugStats {
X86DebugStats {
session_state: self.state,
breakpoint_count: self.breakpoints.len() as u64,
enabled_breakpoints: self.breakpoints.values().filter(|b| b.enabled).count() as u64,
module_count: self.modules.modules.len() as u64,
source_count: self.sources.sources.len() as u64,
thread_count: self.threads.len() as u64,
total_steps: 0,
expression_evals: 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86DebugStats {
pub session_state: X86DebugState,
pub breakpoint_count: u64,
pub enabled_breakpoints: u64,
pub module_count: u64,
pub source_count: u64,
pub thread_count: u64,
pub total_steps: u64,
pub expression_evals: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug_adapter_new() {
let da = X86DebugAdapter::new();
assert_eq!(da.state, X86DebugState::Uninitialized);
assert!(da.breakpoints.is_empty());
assert_eq!(da.exception_breakpoints.len(), 4);
}
#[test]
fn test_debug_adapter_initialize() {
let mut da = X86DebugAdapter::new();
let caps = da.initialize("test_client", "Test IDE");
assert_eq!(da.state, X86DebugState::Initialized);
assert!(caps
.get("supportsConfigurationDoneRequest")
.unwrap()
.as_bool()
.unwrap());
}
#[test]
fn test_set_source_breakpoint() {
let mut da = X86DebugAdapter::new();
let id = da.set_breakpoint(
X86Source {
name: Some("test.cpp".into()),
path: Some("/tmp/test.cpp".into()),
source_reference: None,
},
42,
None,
None,
None,
None,
);
assert_eq!(id, 1);
let bp = da.breakpoints.get(&1).unwrap();
assert!(bp.verified);
assert_eq!(bp.kind, X86BreakpointKind::Software);
}
#[test]
fn test_set_function_breakpoint() {
let mut da = X86DebugAdapter::new();
let id = da.set_function_breakpoint("main", None);
assert_eq!(id, 1);
let bp = da.breakpoints.get(&1).unwrap();
assert_eq!(bp.function_name, Some("main".into()));
}
#[test]
fn test_set_conditional_breakpoint() {
let mut da = X86DebugAdapter::new();
let id = da.set_breakpoint(
X86Source {
name: Some("test.cpp".into()),
path: Some("/tmp/test.cpp".into()),
source_reference: None,
},
100,
None,
Some("i > 10"),
None,
None,
);
let bp = da.breakpoints.get(&id).unwrap();
assert_eq!(bp.condition, Some("i > 10".into()));
}
#[test]
fn test_set_log_point() {
let mut da = X86DebugAdapter::new();
let id = da.set_breakpoint(
X86Source {
name: Some("test.cpp".into()),
path: Some("/tmp/test.cpp".into()),
source_reference: None,
},
200,
None,
None,
None,
Some("Entering loop with i={i}"),
);
let bp = da.breakpoints.get(&id).unwrap();
assert_eq!(bp.log_message, Some("Entering loop with i={i}".into()));
}
#[test]
fn test_remove_breakpoint() {
let mut da = X86DebugAdapter::new();
da.set_function_breakpoint("main", None);
assert!(da.remove_breakpoint(1));
assert!(!da.remove_breakpoint(99));
}
#[test]
fn test_set_breakpoint_enabled() {
let mut da = X86DebugAdapter::new();
da.set_function_breakpoint("main", None);
assert!(da.set_breakpoint_enabled(1, false));
assert!(!da.breakpoints.get(&1).unwrap().enabled);
}
#[test]
fn test_exception_breakpoints() {
let mut da = X86DebugAdapter::new();
da.set_exception_breakpoints(&["cpp_throw".into(), "signal".into()]);
assert!(
da.exception_breakpoints
.iter()
.find(|eb| eb.filter == "cpp_throw")
.unwrap()
.enabled
);
assert!(
!da.exception_breakpoints
.iter()
.find(|eb| eb.filter == "cpp_catch")
.unwrap()
.enabled
);
}
#[test]
fn test_launch() {
let mut da = X86DebugAdapter::new();
let config = X86DebugConfig {
program: "/bin/ls".into(),
stop_on_entry: true,
..Default::default()
};
da.launch(config).unwrap();
assert_eq!(da.state, X86DebugState::Stopped);
assert_eq!(da.threads.len(), 1);
}
#[test]
fn test_stack_trace() {
let mut da = X86DebugAdapter::new();
let frames = da.stack_trace(1, 0, 10);
assert!(!frames.is_empty());
assert!(!frames[0].inline_frames.is_empty());
}
#[test]
fn test_scopes() {
let mut da = X86DebugAdapter::new();
let scopes = da.scopes(1);
assert_eq!(scopes.len(), 3);
assert_eq!(scopes[0].name, "Locals");
assert_eq!(scopes[1].name, "Arguments");
assert_eq!(scopes[2].name, "Registers");
}
#[test]
fn test_variables_locals() {
let da = X86DebugAdapter::new();
let vars = da.variables(101); assert!(!vars.is_empty());
assert_eq!(vars[0].name, "i");
}
#[test]
fn test_variables_arguments() {
let da = X86DebugAdapter::new();
let vars = da.variables(201);
assert_eq!(vars[0].name, "argc");
}
#[test]
fn test_variables_registers() {
let da = X86DebugAdapter::new();
let vars = da.variables(301);
assert!(vars.iter().any(|v| v.name == "rax"));
assert!(vars.iter().any(|v| v.is_register));
}
#[test]
fn test_evaluate_register() {
let mut da = X86DebugAdapter::new();
da.registers.set("rax", 0xDEADBEEF);
let result = da.evaluate("$rax", Some(1)).unwrap();
assert!(result.value.contains("deadbeef"));
}
#[test]
fn test_evaluate_local() {
let mut da = X86DebugAdapter::new();
let result = da.evaluate("i", Some(1)).unwrap();
assert_eq!(result.value, "42");
}
#[test]
fn test_evaluate_unknown() {
let mut da = X86DebugAdapter::new();
let result = da.evaluate("nonexistent", Some(1));
assert!(result.is_err());
}
#[test]
fn test_stepping() {
let mut da = X86DebugAdapter::new();
da.step(X86StepGranularity::Line);
assert!(da.stepping.is_stepping());
assert_eq!(da.state, X86DebugState::Stepping);
da.stepping.complete_step();
assert!(!da.stepping.is_stepping());
}
#[test]
fn test_disassembly_view() {
let mut view = X86DisassemblyView::new();
let mem = vec![0x90u8; 64]; view.disassemble_range(&mem, 0x400000);
assert!(!view.instructions.is_empty());
let inst = view.get_instruction(0x400000).unwrap();
assert_eq!(inst.address, 0x400000);
}
#[test]
fn test_memory_view_read_write() {
let mut mem = X86MemoryView::new();
mem.write_memory(0x1000, &[0x01, 0x02, 0x03, 0x04]);
let data = mem.read_memory(0x1000, 4);
assert_eq!(data, vec![0x01, 0x02, 0x03, 0x04]);
}
#[test]
fn test_memory_hex_dump() {
let mut mem = X86MemoryView::new();
mem.write_memory(0x1000, &[0xde, 0xad, 0xbe, 0xef]);
let dump = mem.hex_dump(0x1000, 4);
assert!(dump.contains("deadbeef") || dump.contains("DEADBEEF"));
}
#[test]
fn test_register_view_gp() {
let rv = X86RegisterView::new();
let gps = rv.gp_registers();
assert!(gps.iter().any(|(n, _)| *n == "rax"));
assert!(gps.iter().any(|(n, _)| *n == "r15"));
}
#[test]
fn test_register_view_flags() {
let mut rv = X86RegisterView::new();
rv.set("rflags", 0x246); let flags = rv.format_flags();
assert!(flags.contains("P")); assert!(flags.contains("Z")); }
#[test]
fn test_module_manager_add_find() {
let mut mm = X86ModuleManager::new();
let id = mm.add_module("test.so", 0x400000, 65536);
mm.add_symbol("main", 0x401000, 128, X86SymbolKind::Function, id);
let sym = mm.resolve_by_name("main").unwrap();
assert_eq!(sym.address, 0x401000);
let sym2 = mm.resolve_by_address(0x401050);
assert!(sym2.is_some());
let m = mm.find_module_by_address(0x401000);
assert!(m.is_some());
}
#[test]
fn test_source_mapper_map_path() {
let mut sm = X86SourceMapper::new();
sm.add_mapping("/build/", "/home/user/project/");
let mapped = sm.map_path("/build/src/main.cpp");
assert_eq!(mapped, "/home/user/project/src/main.cpp");
}
#[test]
fn test_source_mapper_register() {
let mut sm = X86SourceMapper::new();
let id = sm.register_source("main.cpp", "/src/main.cpp");
let source = sm.get_source(id).unwrap();
assert_eq!(source.name, "main.cpp");
}
#[test]
fn test_dwarf_expression_evaluator() {
let mut eval = X86ExpressionEvaluator::new("test", None);
let registers: HashMap<String, u64> = [("r1".to_string(), 0x1000u64)].into();
let memory: HashMap<u64, u8> = [
(0x1000, 0x78),
(0x1001, 0x56),
(0x1002, 0x34),
(0x1003, 0x12),
]
.into();
let expr = vec![0x71, 0x00, 0x06];
eval.evaluate_dwarf_expr(&expr, ®isters, &memory);
assert!(eval.result.is_some());
}
#[test]
fn test_stop_reason_to_str() {
assert_eq!(X86StopReason::Breakpoint.to_str(), "breakpoint");
assert_eq!(X86StopReason::Step.to_str(), "step");
assert_eq!(X86StopReason::Exception.to_str(), "exception");
}
#[test]
fn test_debug_stats() {
let da = X86DebugAdapter::new();
let stats = da.stats();
assert_eq!(stats.session_state, X86DebugState::Uninitialized);
assert_eq!(stats.breakpoint_count, 0);
}
#[test]
fn test_step_granularity_variants() {
assert_ne!(X86StepGranularity::Instruction, X86StepGranularity::Line,);
assert_ne!(
X86StepGranularity::StepOut,
X86StepGranularity::ReverseInstruction,
);
}
}