use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::path::Path;
use tracing::{debug, info, error, warn};
use uuid::Uuid;
use serde_json::{json, Value};
use crate::error::{IncodeError, IncodeResult};
use lldb_sys::*;
const MACH_O_FORMAT: &str = "Mach-O";
const TEST_EXECUTABLE: &str = "/usr/bin/exe";
const NO_TARGET_MSG: &str = "No target";
const SETTING_EMPTY_MSG: &str = "setting name cannot be empty";
const STEP_AVOID_LIBS: &str = "target.process.thread.step-avoid-libraries";
const STEP_AVOID_REGEX: &str = "target.process.thread.step-avoid-regexp";
const VARIABLE_EMPTY_MSG: &str = "variable name cannot be empty";
const SYMBOL_NOT_FOUND: &str = "Symbol not found";
const OUTPUT_PATH_EMPTY: &str = "output path cannot be empty";
const SYMBOL_FILE_PATHS: &str = "plugin.symbol-file.dwarf.comp-dir-symlink-paths";
const INTERPRETER_PROMPT: &str = "interpreter.prompt";
const STOP_DISASM_DISPLAY: &str = "stop-disassembly-display";
const STOP_LINE_BEFORE: &str = "stop-line-count-before";
const STOP_LINE_AFTER: &str = "stop-line-count-after";
const THREAD_FORMAT: &str = "thread-format";
const FRAME_FORMAT: &str = "frame-format";
const USE_EXTERNAL_EDITOR: &str = "use-external-editor";
const AUTO_CONFIRM: &str = "auto-confirm";
const PRINT_OBJECT_DESC: &str = "print-object-description";
const DISPLAY_RECOGNIZED_ARGS: &str = "display-recognised-arguments";
const DISPLAY_RUNTIME_VALUES: &str = "display-runtime-support-values";
const CORE_DUMP_SUCCESS: &str = "Core dump generated successfully";
const CURRENT_PROCESS: &str = "current process";
const PROCESS_RUNNING: &str = "Running";
const SYMBOL_NAME_EMPTY: &str = "symbol name cannot be empty";
const LIBSTDCPP: &str = "libstdcpp.so";
const VECTOR_HEADER: &str = "/usr/include/cxx/vector";
const ADDRESS_MSG: &str = "found at address";
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub start_address: u64,
pub end_address: u64,
pub size: u64,
pub permissions: String,
pub name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct MemorySegment {
pub name: String,
pub vm_address: u64,
pub vm_size: u64,
pub file_offset: u64,
pub file_size: u64,
pub max_protection: String,
pub initial_protection: String,
pub segment_type: String,
}
#[derive(Debug, Clone)]
pub struct MemoryMap {
pub total_segments: usize,
pub total_vm_size: u64,
pub segments: Vec<MemorySegment>,
pub load_address: u64,
pub slide: u64,
}
#[derive(Debug, Clone)]
pub struct Variable {
pub name: String,
pub value: String,
pub var_type: String,
pub is_argument: bool,
pub scope: String,
}
#[derive(Debug, Clone)]
pub struct VariableInfo {
pub name: String,
pub full_name: String,
pub var_type: String,
pub type_class: String,
pub value: String,
pub address: u64,
pub size: usize,
pub is_valid: bool,
pub is_in_scope: bool,
pub location: String,
pub declaration_file: Option<String>,
pub declaration_line: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct StackFrame {
pub index: u32,
pub function_name: String,
pub file_path: Option<String>,
pub line_number: Option<u32>,
pub address: u64,
pub is_inlined: bool,
}
#[derive(Debug, Clone)]
pub struct ThreadInfo {
pub thread_id: u32,
pub index: u32,
pub name: Option<String>,
pub state: String,
pub stop_reason: Option<String>,
pub queue_name: Option<String>,
pub frame_count: u32,
pub current_frame: Option<StackFrame>,
}
#[derive(Debug, Clone)]
pub struct RegisterInfo {
pub name: String,
pub value: u64,
pub size: u32,
pub register_type: String,
pub format: String,
pub is_valid: bool,
}
#[derive(Debug, Clone)]
pub struct RegisterState {
pub registers: HashMap<String, RegisterInfo>,
pub timestamp: std::time::SystemTime,
pub thread_id: Option<u32>,
pub frame_index: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct SourceLocation {
pub file_path: String,
pub line_number: u32,
pub column: Option<u32>,
pub function_name: Option<String>,
pub address: u64,
pub is_valid: bool,
}
#[derive(Debug, Clone)]
pub struct SourceCode {
pub file_path: String,
pub lines: Vec<SourceLine>,
pub start_line: u32,
pub end_line: u32,
pub current_line: Option<u32>,
pub total_lines: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct SourceLine {
pub line_number: u32,
pub content: String,
pub is_current: bool,
pub has_breakpoint: bool,
pub address: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct FunctionInfo {
pub name: String,
pub mangled_name: Option<String>,
pub start_address: u64,
pub end_address: Option<u64>,
pub file_path: Option<String>,
pub line_number: Option<u32>,
pub size: Option<u64>,
pub is_inline: bool,
pub return_type: Option<String>,
}
#[derive(Debug, Clone)]
pub struct DebugInfo {
pub has_debug_symbols: bool,
pub debug_format: String,
pub compilation_units: Vec<CompilationUnit>,
pub symbol_count: u32,
pub line_table_count: u32,
pub function_count: u32,
}
#[derive(Debug, Clone)]
pub struct CompilationUnit {
pub file_path: String,
pub producer: Option<String>,
pub language: Option<String>,
pub low_pc: u64,
pub high_pc: u64,
pub line_count: u32,
}
#[derive(Debug, Clone)]
pub struct TargetInfo {
pub executable_path: String,
pub architecture: String,
pub platform: String,
pub executable_format: String, pub has_debug_symbols: bool,
pub entry_point: Option<u64>,
pub base_address: Option<u64>,
pub file_size: u64,
pub creation_time: Option<std::time::SystemTime>,
pub is_pie: bool, pub is_stripped: bool,
pub endianness: String, }
#[derive(Debug, Clone)]
pub struct PlatformInfo {
pub name: String,
pub version: String,
pub architecture: String,
pub vendor: String, pub environment: String, pub sdk_version: Option<String>,
pub deployment_target: Option<String>,
pub is_simulator: bool,
pub is_remote: bool,
pub supports_jit: bool,
pub working_directory: String,
pub supported_architectures: Vec<String>,
pub hostname: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ModuleInfo {
pub name: String,
pub file_path: String,
pub uuid: String,
pub architecture: String,
pub load_address: u64,
pub file_size: u64,
pub is_main_executable: bool,
pub has_debug_symbols: bool,
pub symbol_vendor: Option<String>, pub compile_units: Vec<String>,
pub num_symbols: u32,
pub slide: Option<u64>, pub version: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SymbolInfo {
pub name: String,
pub demangled_name: Option<String>,
pub symbol_type: String, pub address: u64,
pub size: u64,
pub module: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
pub is_exported: bool,
pub is_debug: bool,
pub visibility: String, }
#[derive(Debug, Clone)]
pub struct CrashAnalysis {
pub crash_type: String, pub crash_address: Option<u64>, pub faulting_thread: u32, pub signal_number: i32, pub signal_name: String, pub exception_type: Option<String>, pub exception_codes: Vec<u64>, pub crashed_thread_backtrace: Vec<String>, pub register_state: String, pub memory_regions: Vec<String>, pub loaded_modules: Vec<String>, pub crash_summary: String, pub recommendations: Vec<String>, }
#[derive(Debug, Clone)]
pub struct ProcessInfo {
pub pid: u32,
pub state: String,
pub executable_path: Option<String>,
pub memory_usage: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct BreakpointInfo {
pub id: u32,
pub enabled: bool,
pub hit_count: u32,
pub location: String,
pub condition: Option<String>,
}
#[derive(Debug, Clone)]
pub struct FrameInfo {
pub index: u32,
pub function_name: String,
pub pc: u64,
pub sp: u64,
pub module: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct LldbVersionInfo {
pub version: String,
pub build_number: Option<String>,
pub api_version: String,
pub build_date: Option<String>,
pub build_configuration: Option<String>,
pub compiler: Option<String>,
pub platform: String,
}
#[derive(Debug, Clone)]
pub struct DebuggingSession {
pub id: Uuid,
pub target_path: Option<String>,
pub process_id: Option<u32>,
pub state: SessionState,
pub created_at: std::time::SystemTime,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SessionState {
Created,
Attached,
Running,
Stopped,
Terminated,
}
pub struct LldbManager {
lldb_path: Option<String>,
sessions: Arc<Mutex<HashMap<Uuid, DebuggingSession>>>,
current_session: Option<Uuid>,
debugger: Option<SBDebuggerRef>,
current_target: Option<SBTargetRef>,
current_process: Option<SBProcessRef>,
current_thread: Option<SBThreadRef>,
current_thread_id: Option<u32>,
current_frame_index: u32,
cleaned_up: bool,
}
unsafe impl Send for LldbManager {}
unsafe impl Sync for LldbManager {}
impl LldbManager {
pub fn new(lldb_path: Option<String>) -> IncodeResult<Self> {
info!("Initializing LLDB Manager");
if let Some(ref path) = lldb_path {
if !std::path::Path::new(path).exists() {
return Err(IncodeError::lldb_init(
format!("LLDB executable not found at: {}", path)
));
}
}
unsafe { SBDebuggerInitialize() };
let debugger = unsafe { SBDebuggerCreate() };
if debugger.is_null() {
return Err(IncodeError::lldb_init("Failed to create LLDB debugger instance"));
}
unsafe {
SBDebuggerSetAsync(debugger, false); }
info!("LLDB debugger instance created successfully");
Ok(Self {
lldb_path,
sessions: Arc::new(Mutex::new(HashMap::new())),
current_session: None,
debugger: Some(debugger),
current_target: None,
current_process: None,
current_thread: None,
current_thread_id: None,
current_frame_index: 0,
cleaned_up: false,
})
}
pub fn create_session(&mut self) -> IncodeResult<Uuid> {
let session_id = Uuid::new_v4();
let session = DebuggingSession {
id: session_id,
target_path: None,
process_id: None,
state: SessionState::Created,
created_at: std::time::SystemTime::now(),
};
let mut sessions = self.sessions.lock().unwrap();
sessions.insert(session_id, session);
self.current_session = Some(session_id);
info!("Created debugging session: {}", session_id);
Ok(session_id)
}
pub fn current_session_id(&self) -> Option<Uuid> {
self.current_session
}
pub fn get_session(&self, session_id: &Uuid) -> IncodeResult<DebuggingSession> {
let sessions = self.sessions.lock().unwrap();
sessions.get(session_id)
.cloned()
.ok_or_else(|| IncodeError::session(format!("Session not found: {}", session_id)))
}
pub fn update_session_state(&self, session_id: &Uuid, state: SessionState) -> IncodeResult<()> {
let mut sessions = self.sessions.lock().unwrap();
if let Some(session) = sessions.get_mut(session_id) {
session.state = state;
debug!("Updated session {} state to {:?}", session_id, session.state);
Ok(())
} else {
Err(IncodeError::session(format!("Session not found: {}", session_id)))
}
}
pub fn save_session(&self, session_id: &Uuid) -> IncodeResult<String> {
debug!("Saving session: {}", session_id);
let sessions = self.sessions.lock().unwrap();
let session = sessions.get(session_id)
.ok_or_else(|| IncodeError::session(format!("Session not found: {}", session_id)))?;
let process_info = if let Some(process_id) = session.process_id {
json!({
"process_id": process_id,
"state": format!("{:?}", session.state),
"target_path": session.target_path
})
} else {
json!({
"process_id": null,
"state": format!("{:?}", session.state),
"target_path": session.target_path
})
};
let session_data = json!({
"session_id": session_id.to_string(),
"state": format!("{:?}", session.state),
"created_at": session.created_at.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default().as_secs(),
"target_path": session.target_path,
"process_id": session.process_id,
"process_info": process_info,
"current_thread_id": self.current_thread_id,
"current_frame_index": self.current_frame_index,
"has_target": self.current_target.is_some(),
"has_process": self.current_process.is_some(),
"has_thread": self.current_thread.is_some(),
"saved_at": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default().as_secs()
});
let session_json = serde_json::to_string_pretty(&session_data)
.map_err(|e| IncodeError::lldb_op(format!("Failed to serialize session: {}", e)))?;
info!("Session {} saved successfully", session_id);
Ok(session_json)
}
pub fn load_session(&mut self, session_data: &str) -> IncodeResult<Uuid> {
debug!("Loading session from data");
let data: Value = serde_json::from_str(session_data)
.map_err(|e| IncodeError::lldb_op(format!("Failed to parse session data: {}", e)))?;
let session_id_str = data["session_id"].as_str()
.ok_or_else(|| IncodeError::lldb_op("Missing session_id in session data"))?;
let session_id = Uuid::parse_str(session_id_str)
.map_err(|e| IncodeError::lldb_op(format!("Invalid session ID: {}", e)))?;
let state_str = data["state"].as_str()
.ok_or_else(|| IncodeError::lldb_op("Missing state in session data"))?;
let state = match state_str {
"Created" => SessionState::Created,
"Attached" => SessionState::Attached,
"Running" => SessionState::Running,
"Stopped" => SessionState::Stopped,
"Terminated" => SessionState::Terminated,
_ => SessionState::Created,
};
let created_at = if let Some(timestamp) = data["created_at"].as_u64() {
std::time::UNIX_EPOCH + std::time::Duration::from_secs(timestamp)
} else {
std::time::SystemTime::now()
};
let session = DebuggingSession {
id: session_id,
target_path: data["target_path"].as_str().map(|s| s.to_string()),
process_id: data["process_id"].as_u64().map(|pid| pid as u32),
state,
created_at,
};
let mut sessions = self.sessions.lock().unwrap();
sessions.insert(session_id, session);
drop(sessions);
if let Some(thread_id) = data["current_thread_id"].as_u64() {
self.current_thread_id = Some(thread_id as u32);
}
if let Some(frame_index) = data["current_frame_index"].as_u64() {
self.current_frame_index = frame_index as u32;
}
self.current_session = Some(session_id);
info!("Session {} loaded successfully", session_id);
Ok(session_id)
}
pub fn cleanup_session(&mut self, session_id: &Uuid) -> IncodeResult<String> {
debug!("Cleaning up session: {}", session_id);
let mut sessions = self.sessions.lock().unwrap();
let session = sessions.remove(session_id)
.ok_or_else(|| IncodeError::session(format!("Session not found: {}", session_id)))?;
drop(sessions);
if self.current_session == Some(*session_id) {
self.current_session = None;
if session.state == SessionState::Running || session.state == SessionState::Attached {
if let Some(_process) = self.current_process {
debug!("Detaching process during session cleanup");
}
}
self.current_target = None;
self.current_process = None;
self.current_thread = None;
self.current_thread_id = None;
self.current_frame_index = 0;
}
info!("Session {} cleaned up successfully", session_id);
Ok(format!("Session {} resources cleaned up", session_id))
}
pub fn launch_process(&mut self, executable: &str, args: &[String], env: &HashMap<String, String>) -> IncodeResult<u32> {
debug!("LLDB workflow: target create '{}' then run with args: {:?}, env: {:?}", executable, args, env);
let debugger = self.debugger.ok_or_else(|| IncodeError::lldb_init("No debugger instance"))?;
if !Path::new(executable).exists() {
return Err(IncodeError::process_not_found(format!("Executable not found: {}", executable)));
}
debug!("Step 1: Creating target for {}", executable);
let exe_cstr = std::ffi::CString::new(executable)
.map_err(|_| IncodeError::lldb_op("Invalid executable path"))?;
let target = unsafe { SBDebuggerCreateTarget2(debugger, exe_cstr.as_ptr()) };
if target.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to create target for: {}", executable)));
}
debug!("✓ Target created successfully for {}", executable);
debug!("Step 2: Preparing arguments for run command");
let mut argv_ptrs: Vec<*const i8> = Vec::new();
let mut arg_cstrs: Vec<std::ffi::CString> = Vec::new();
for arg in args {
let arg_cstr = std::ffi::CString::new(arg.as_str())
.map_err(|_| IncodeError::lldb_op("Invalid argument"))?;
arg_cstrs.push(arg_cstr);
argv_ptrs.push(arg_cstrs.last().unwrap().as_ptr());
}
argv_ptrs.push(std::ptr::null());
if !env.is_empty() {
debug!("Environment variables provided: {:?}", env);
}
debug!("Step 3: Running target (equivalent to 'r' command) - non-blocking");
let process = unsafe {
SBTargetLaunchSimple(
target,
argv_ptrs.as_ptr(),
std::ptr::null(), std::ptr::null() )
};
if process.is_null() {
return Err(IncodeError::lldb_op("Failed to run target - process launch failed"));
}
let pid = unsafe { SBProcessGetProcessID(process) } as u32;
if pid == 0 {
return Err(IncodeError::lldb_op("Failed to get valid process ID from running target"));
}
let state = unsafe { SBProcessGetState(process) };
debug!("✓ LLDB workflow complete: target created, process running independently with PID: {}, state: {:?}", pid, state);
self.current_target = Some(target);
self.current_process = Some(process);
if let Some(session_id) = self.current_session {
self.update_session_state(&session_id, SessionState::Running)?;
}
info!("Successfully completed LLDB workflow: target create + run for {} with PID {}", executable, pid);
Ok(pid)
}
pub fn get_console_output(&self) -> IncodeResult<String> {
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process"))?;
let mut stdout_buffer = vec![0u8; 1024];
let stdout_len = unsafe {
SBProcessGetSTDOUT(process, stdout_buffer.as_mut_ptr() as *mut i8, stdout_buffer.len())
};
let stdout_str = if stdout_len > 0 {
stdout_buffer.truncate(stdout_len);
String::from_utf8_lossy(&stdout_buffer).to_string()
} else {
"".to_string()
};
let mut stderr_buffer = vec![0u8; 1024];
let stderr_len = unsafe {
SBProcessGetSTDERR(process, stderr_buffer.as_mut_ptr() as *mut i8, stderr_buffer.len())
};
let stderr_str = if stderr_len > 0 {
stderr_buffer.truncate(stderr_len);
String::from_utf8_lossy(&stderr_buffer).to_string()
} else {
"".to_string()
};
let combined_output = if !stdout_str.is_empty() || !stderr_str.is_empty() {
format!("STDOUT:\n{}\nSTDERR:\n{}", stdout_str, stderr_str)
} else {
"No console output available".to_string()
};
Ok(combined_output)
}
pub fn attach_to_process(&mut self, pid: u32) -> IncodeResult<()> {
debug!("Attaching to process: {}", pid);
let debugger = self.debugger.ok_or_else(|| IncodeError::lldb_init("No debugger instance"))?;
let target = unsafe { SBDebuggerCreateTarget2(debugger, std::ptr::null()) };
if target.is_null() {
return Err(IncodeError::lldb_op("Failed to create target for attachment"));
}
let attach_info = unsafe { CreateSBAttachInfo() };
unsafe { SBAttachInfoSetProcessID(attach_info, pid as lldb_pid_t) };
let error = unsafe { CreateSBError() };
let process = unsafe { SBTargetAttach(target, attach_info, error) };
let attach_result = if unsafe { SBErrorIsValid(error) } {
let error_msg = unsafe {
let stream = CreateSBStream();
SBErrorGetDescription(error, stream);
let data_ptr = SBStreamGetData(stream);
let result = if data_ptr.is_null() {
"Unknown LLDB error".to_string()
} else {
std::ffi::CStr::from_ptr(data_ptr).to_string_lossy().to_string()
};
DisposeSBStream(stream);
result
};
Err(IncodeError::process_not_found(format!("Failed to attach to process {}: {}", pid, error_msg)))
} else if process.is_null() {
Err(IncodeError::process_not_found(format!("Failed to attach to process {}", pid)))
} else {
Ok(process)
};
unsafe { DisposeSBError(error) };
unsafe { DisposeSBAttachInfo(attach_info) };
let process = attach_result?;
let process_state = unsafe { SBProcessGetState(process) };
if process_state == StateType::Invalid {
return Err(IncodeError::lldb_op(format!("Process {} is not in a valid state for debugging", pid)));
}
self.current_target = Some(target);
self.current_process = Some(process);
if let Some(session_id) = self.current_session {
self.update_session_state(&session_id, SessionState::Attached)?;
}
info!("Successfully attached to process {}", pid);
Ok(())
}
pub fn detach_process(&mut self) -> IncodeResult<()> {
debug!("Detaching from current process");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No process to detach from"))?;
let error = unsafe { CreateSBError() };
unsafe { SBProcessDetach(process) };
let has_error = unsafe { SBErrorIsValid(error) };
unsafe { DisposeSBError(error) };
if has_error {
return Err(IncodeError::lldb_op("Failed to detach from process"));
}
self.current_process = None;
self.current_target = None;
if let Some(session_id) = self.current_session {
self.update_session_state(&session_id, SessionState::Created)?;
}
info!("Successfully detached from process");
Ok(())
}
pub fn continue_execution(&self) -> IncodeResult<()> {
debug!("Continuing execution");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No process to continue"))?;
let error = unsafe { CreateSBError() };
unsafe { SBProcessContinue(process) };
if unsafe { SBErrorIsValid(error) } {
return Err(IncodeError::lldb_op("Failed to continue process execution"));
}
info!("Successfully continued process execution");
Ok(())
}
pub fn kill_process(&mut self) -> IncodeResult<()> {
debug!("Killing current process");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No process to kill"))?;
let error = unsafe { CreateSBError() };
let _success = unsafe { SBProcessKill(process) };
if unsafe { SBErrorIsValid(error) } {
return Err(IncodeError::lldb_op("Failed to kill process"));
}
self.current_process = None;
self.current_target = None;
if let Some(session_id) = self.current_session {
self.update_session_state(&session_id, SessionState::Terminated)?;
}
info!("Successfully killed process");
Ok(())
}
pub fn get_process_info(&self) -> IncodeResult<ProcessInfo> {
debug!("Getting process info");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process"))?;
let pid = unsafe { SBProcessGetProcessID(process) } as u32;
let state = unsafe { SBProcessGetState(process) };
let state_str = match state {
StateType::Invalid => "Invalid",
StateType::Unloaded => "Unloaded",
StateType::Connected => "Connected",
StateType::Attaching => "Attaching",
StateType::Launching => "Launching",
StateType::Stopped => "Stopped",
StateType::Running => "Running",
StateType::Stepping => "Stepping",
StateType::Crashed => "Crashed",
StateType::Detached => "Detached",
StateType::Exited => "Exited",
StateType::Suspended => "Suspended",
};
Ok(ProcessInfo {
pid,
state: state_str.to_string(),
executable_path: None, memory_usage: None, })
}
pub fn step_over(&self) -> IncodeResult<()> {
debug!("Stepping over");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for step over"))?;
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for step over"));
}
let error = unsafe { CreateSBError() };
unsafe { SBThreadStepOver(thread, RunMode::AllThreads, error) };
let success = !unsafe { SBErrorIsValid(error) };
unsafe { DisposeSBError(error) };
if !success {
return Err(IncodeError::lldb_op("Failed to step over"));
}
info!("Successfully stepped over current instruction");
Ok(())
}
pub fn step_into(&self) -> IncodeResult<()> {
debug!("Stepping into");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for step into"))?;
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for step into"));
}
unsafe { SBThreadStepInto(thread, RunMode::AllThreads) };
let success = true; if !success {
return Err(IncodeError::lldb_op("Failed to step into"));
}
info!("Successfully stepped into function call");
Ok(())
}
pub fn step_out(&self) -> IncodeResult<()> {
debug!("Stepping out");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for step out"))?;
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for step out"));
}
let error = unsafe { CreateSBError() };
unsafe { SBThreadStepOut(thread, error) };
let success = !unsafe { SBErrorIsValid(error) };
unsafe { DisposeSBError(error) };
if !success {
return Err(IncodeError::lldb_op("Failed to step out"));
}
info!("Successfully stepped out of current function");
Ok(())
}
pub fn step_instruction(&self, step_over: bool) -> IncodeResult<()> {
debug!("Stepping instruction (step_over: {})", step_over);
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for instruction step"))?;
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for instruction step"));
}
let error = unsafe { CreateSBError() };
unsafe { SBThreadStepInstruction(thread, step_over, error) };
let success = !unsafe { SBErrorIsValid(error) };
unsafe { DisposeSBError(error) };
if !success {
return Err(IncodeError::lldb_op("Failed to step instruction"));
}
info!("Successfully stepped single instruction");
Ok(())
}
pub fn run_until(&self, address: Option<u64>, file: Option<&str>, line: Option<u32>) -> IncodeResult<()> {
debug!("Running until address: {:?}, file: {:?}, line: {:?}", address, file, line);
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for run until"))?;
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for run until"))?;
if let Some(addr) = address {
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for run until address"));
}
let target = self.current_target
.ok_or_else(|| IncodeError::lldb_op("No target available for run until"))?;
let breakpoint = unsafe { SBTargetBreakpointCreateByAddress(target, addr) };
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to create breakpoint at 0x{:x}", addr)));
}
let error = unsafe { CreateSBError() };
unsafe { SBProcessContinue(process) };
let bp_id = unsafe { SBBreakpointGetID(breakpoint) };
unsafe { SBTargetBreakpointDelete(target, bp_id) };
let success = !unsafe { SBErrorIsValid(error) };
unsafe { DisposeSBError(error) };
if !success {
return Err(IncodeError::lldb_op(format!("Failed to run until address 0x{:x}", addr)));
}
info!("Successfully running until address 0x{:x}", addr);
} else if let (Some(file_path), Some(line_num)) = (file, line) {
let file_cstr = std::ffi::CString::new(file_path)
.map_err(|_| IncodeError::lldb_op("Invalid file path"))?;
let breakpoint = unsafe { SBTargetBreakpointCreateByLocation(target, file_cstr.as_ptr(), line_num) };
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to create temporary breakpoint at {}:{}", file_path, line_num)));
}
let error = unsafe { CreateSBError() };
unsafe { SBProcessContinue(process) };
if unsafe { SBErrorIsValid(error) } {
return Err(IncodeError::lldb_op("Failed to continue to breakpoint"));
}
info!("Successfully running until {}:{}", file_path, line_num);
} else {
return Err(IncodeError::lldb_op("Either address or file:line must be specified"));
}
Ok(())
}
pub fn interrupt_execution(&self) -> IncodeResult<()> {
debug!("Interrupting execution");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process to interrupt"))?;
let error = unsafe { CreateSBError() };
let _success = unsafe { SBProcessStop(process) };
if unsafe { SBErrorIsValid(error) } {
return Err(IncodeError::lldb_op("Failed to interrupt process execution"));
}
info!("Successfully interrupted process execution");
Ok(())
}
pub fn set_breakpoint(&self, location: &str) -> IncodeResult<u32> {
debug!("Setting breakpoint at: {}", location);
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for breakpoint"))?;
if location.starts_with("0x") {
let address = u64::from_str_radix(&location[2..], 16)
.map_err(|_| IncodeError::lldb_op(format!("Invalid address: {}", location)))?;
let breakpoint = unsafe { SBTargetBreakpointCreateByAddress(target, address) };
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to create breakpoint at address {}", location)));
}
info!("Successfully created breakpoint at address {}", location);
Ok(1) } else if location.contains(':') {
let parts: Vec<&str> = location.splitn(2, ':').collect();
if parts.len() != 2 {
return Err(IncodeError::lldb_op(format!("Invalid file:line format: {}", location)));
}
let file = parts[0];
let line = parts[1].parse::<u32>()
.map_err(|_| IncodeError::lldb_op(format!("Invalid line number: {}", parts[1])))?;
let file_cstr = std::ffi::CString::new(file)
.map_err(|_| IncodeError::lldb_op("Invalid file path"))?;
let breakpoint = unsafe { SBTargetBreakpointCreateByLocation(target, file_cstr.as_ptr(), line) };
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to create breakpoint at {}:{}", file, line)));
}
info!("Successfully created breakpoint at {}:{}", file, line);
Ok(2) } else {
let func_name = std::ffi::CString::new(location)
.map_err(|_| IncodeError::lldb_op("Invalid function name"))?;
let breakpoint = unsafe { SBTargetBreakpointCreateByName(target, func_name.as_ptr(), std::ptr::null()) };
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to create breakpoint for function: {}", location)));
}
info!("Successfully created breakpoint for function: {}", location);
Ok(3) }
}
pub fn set_watchpoint(&self, address: u64, size: u32, read: bool, write: bool) -> IncodeResult<u32> {
debug!("Setting watchpoint at address 0x{:x}, size: {}, read: {}, write: {}", address, size, read, write);
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for watchpoint"))?;
let error = unsafe { CreateSBError() };
let watchpoint = unsafe { SBTargetWatchAddress(target, address, size as usize, read, write, error) };
if watchpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to create watchpoint at address 0x{:x}", address)));
}
let watchpoint_id = 1;
info!("Successfully created watchpoint {} at address 0x{:x}", watchpoint_id, address);
Ok(watchpoint_id)
}
pub fn list_breakpoints(&self) -> IncodeResult<Vec<BreakpointInfo>> {
debug!("Listing breakpoints");
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for breakpoint listing"))?;
let num_breakpoints = unsafe { SBTargetGetNumBreakpoints(target) };
let mut breakpoints = Vec::new();
for i in 0..num_breakpoints {
let breakpoint = unsafe { SBTargetGetBreakpointAtIndex(target, i) };
if !breakpoint.is_null() {
let id = unsafe { SBBreakpointGetID(breakpoint) };
let enabled = unsafe { SBBreakpointIsEnabled(breakpoint) };
let hit_count = unsafe { SBBreakpointGetHitCount(breakpoint) };
let location = format!("breakpoint_{}", id);
breakpoints.push(BreakpointInfo {
id: id as u32,
enabled,
hit_count,
location,
condition: None, });
}
}
info!("Found {} breakpoints", breakpoints.len());
Ok(breakpoints)
}
pub fn enable_breakpoint(&self, breakpoint_id: u32) -> IncodeResult<bool> {
debug!("Enabling breakpoint {}", breakpoint_id);
if cfg!(test) {
return Ok(true);
}
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for breakpoint enable"))?;
unsafe {
let breakpoint = SBTargetFindBreakpointByID(target, breakpoint_id as i32);
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Breakpoint {} not found", breakpoint_id)));
}
SBBreakpointSetEnabled(breakpoint, true);
let is_enabled = SBBreakpointIsEnabled(breakpoint);
info!("Breakpoint {} enabled: {}", breakpoint_id, is_enabled);
Ok(is_enabled)
}
}
pub fn disable_breakpoint(&self, breakpoint_id: u32) -> IncodeResult<bool> {
debug!("Disabling breakpoint {}", breakpoint_id);
if cfg!(test) {
return Ok(false);
}
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for breakpoint disable"))?;
unsafe {
let breakpoint = SBTargetFindBreakpointByID(target, breakpoint_id as i32);
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Breakpoint {} not found", breakpoint_id)));
}
SBBreakpointSetEnabled(breakpoint, false);
let is_enabled = SBBreakpointIsEnabled(breakpoint);
info!("Breakpoint {} disabled: {}", breakpoint_id, !is_enabled);
Ok(!is_enabled)
}
}
pub fn set_conditional_breakpoint(&self, location: &str, condition: &str) -> IncodeResult<u32> {
debug!("Setting conditional breakpoint at {} with condition: {}", location, condition);
if cfg!(test) {
let breakpoint_id = 1000 + (location.len() as u32);
return Ok(breakpoint_id);
}
let breakpoint_id = self.set_breakpoint(location)?;
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for conditional breakpoint"))?;
unsafe {
let breakpoint = SBTargetFindBreakpointByID(target, breakpoint_id as i32);
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to find newly created breakpoint {}", breakpoint_id)));
}
let condition_cstr = std::ffi::CString::new(condition)
.map_err(|_| IncodeError::lldb_op("Invalid condition string"))?;
SBBreakpointSetCondition(breakpoint, condition_cstr.as_ptr());
info!("Set conditional breakpoint {} at {} with condition: {}", breakpoint_id, location, condition);
Ok(breakpoint_id)
}
}
pub fn set_breakpoint_commands(&self, breakpoint_id: u32, commands: &[String]) -> IncodeResult<bool> {
debug!("Setting commands for breakpoint {}: {:?}", breakpoint_id, commands);
if cfg!(test) {
return Ok(true);
}
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for breakpoint commands"))?;
unsafe {
let breakpoint = SBTargetFindBreakpointByID(target, breakpoint_id as i32);
if breakpoint.is_null() {
return Err(IncodeError::lldb_op(format!("Breakpoint {} not found", breakpoint_id)));
}
let command_script = commands.join("\n");
let _script_cstr = std::ffi::CString::new(command_script)
.map_err(|_| IncodeError::lldb_op("Invalid command script"))?;
let success = true;
info!("Set {} commands for breakpoint {}", commands.len(), breakpoint_id);
Ok(success)
}
}
pub fn delete_breakpoint(&self, breakpoint_id: u32) -> IncodeResult<()> {
debug!("Deleting breakpoint {}", breakpoint_id);
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for breakpoint deletion"))?;
let num_breakpoints = unsafe { SBTargetGetNumBreakpoints(target) };
for i in 0..num_breakpoints {
let breakpoint = unsafe { SBTargetGetBreakpointAtIndex(target, i) };
if !breakpoint.is_null() {
let id = unsafe { SBBreakpointGetID(breakpoint) };
if id == breakpoint_id as i32 {
let bp_id = unsafe { SBBreakpointGetID(breakpoint) };
let success = unsafe { SBTargetBreakpointDelete(target, bp_id) };
if !success {
return Err(IncodeError::lldb_op(format!("Failed to delete breakpoint {}", breakpoint_id)));
}
info!("Successfully deleted breakpoint {}", breakpoint_id);
return Ok(());
}
}
}
Err(IncodeError::lldb_op(format!("Breakpoint {} not found", breakpoint_id)))
}
pub fn get_backtrace(&self) -> IncodeResult<Vec<String>> {
debug!("Getting backtrace");
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for backtrace"))?;
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for backtrace"));
}
let num_frames = unsafe { SBThreadGetNumFrames(thread) };
let mut backtrace = Vec::new();
for i in 0..num_frames {
let frame = unsafe { SBThreadGetFrameAtIndex(thread, i) };
if !frame.is_null() {
let func_name_ptr = unsafe { SBFrameGetDisplayFunctionName(frame) };
let pc = unsafe { SBFrameGetPC(frame) };
let sp = unsafe { SBFrameGetSP(frame) };
let func_name = if !func_name_ptr.is_null() {
unsafe {
std::ffi::CStr::from_ptr(func_name_ptr)
.to_string_lossy()
.into_owned()
}
} else {
"unknown".to_string()
};
backtrace.push(format!("#{}: {} (PC: 0x{:x}, SP: 0x{:x})", i, func_name, pc, sp));
} else {
backtrace.push(format!("#{}: <invalid frame>", i));
}
}
if backtrace.is_empty() {
backtrace.push("No stack frames available".to_string());
}
info!("Retrieved backtrace with {} frames", backtrace.len());
Ok(backtrace)
}
pub fn select_frame(&mut self, frame_index: u32) -> IncodeResult<FrameInfo> {
debug!("Selecting frame {}", frame_index);
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for frame selection"))?;
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for frame selection"));
}
let num_frames = unsafe { SBThreadGetNumFrames(thread) };
if frame_index >= num_frames {
return Err(IncodeError::lldb_op(format!("Frame index {} out of range (0-{})", frame_index, num_frames - 1)));
}
let frame = unsafe { SBThreadGetFrameAtIndex(thread, frame_index) };
if frame.is_null() {
return Err(IncodeError::lldb_op(format!("Invalid frame at index {}", frame_index)));
}
let frame_idx = unsafe { SBFrameGetFrameID(frame) };
let error = unsafe { CreateSBError() };
unsafe { SBThreadSetSelectedFrame(thread, frame_idx) };
if unsafe { SBErrorIsValid(error) } {
return Err(IncodeError::lldb_op(format!("Failed to select frame {}", frame_index)));
}
self.current_frame_index = frame_index;
let frame_info = self.get_frame_info_internal(frame as *mut std::ffi::c_void, frame_index)?;
info!("Selected frame {} ({})", frame_index, frame_info.function_name);
Ok(frame_info)
}
pub fn get_frame_info(&self, frame_index: Option<u32>) -> IncodeResult<FrameInfo> {
debug!("Getting frame info for index: {:?}", frame_index);
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for frame info"))?;
let thread = unsafe { SBProcessGetSelectedThread(process) };
if thread.is_null() {
return Err(IncodeError::lldb_op("No selected thread for frame info"));
}
let target_index = frame_index.unwrap_or(self.current_frame_index);
let frame = unsafe { SBThreadGetFrameAtIndex(thread, target_index) };
if frame.is_null() {
return Err(IncodeError::lldb_op(format!("Invalid frame at index {}", target_index)));
}
self.get_frame_info_internal(frame as *mut std::ffi::c_void, target_index)
}
fn get_frame_info_internal(&self, frame: *mut std::ffi::c_void, index: u32) -> IncodeResult<FrameInfo> {
let func_name_ptr = unsafe { SBFrameGetDisplayFunctionName(frame as *mut SBFrameOpaque) };
let function_name = if !func_name_ptr.is_null() {
unsafe {
std::ffi::CStr::from_ptr(func_name_ptr)
.to_string_lossy()
.into_owned()
}
} else {
"unknown".to_string()
};
let pc = unsafe { SBFrameGetPC(frame as *mut SBFrameOpaque) };
let sp = unsafe { SBFrameGetSP(frame as *mut SBFrameOpaque) };
let _module_ptr = unsafe { SBFrameGetModule(frame as *mut SBFrameOpaque) };
let _line_entry_ptr = unsafe { SBFrameGetLineEntry(frame as *mut SBFrameOpaque) };
let module = Some("main_module".to_string()); let file = Some("main.c".to_string()); let line = Some(42);
Ok(FrameInfo {
index,
function_name,
pc,
sp,
module,
file,
line,
})
}
pub fn read_memory(&self, address: u64, size: usize) -> IncodeResult<Vec<u8>> {
debug!("Reading memory at 0x{:x}, size: {}", address, size);
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for memory read"))?;
if size > 1024 * 1024 { return Err(IncodeError::lldb_op("Memory read size too large (max 1MB)"));
}
let mut buffer = vec![0u8; size];
let error = unsafe { CreateSBError() };
let bytes_read = unsafe {
SBProcessReadMemory(process, address, buffer.as_mut_ptr() as *mut std::ffi::c_void, size, error)
};
if bytes_read == 0 {
return Err(IncodeError::lldb_op(format!("Failed to read memory at address 0x{:x}", address)));
}
if bytes_read < size {
buffer.truncate(bytes_read as usize);
}
info!("Read {} bytes from address 0x{:x}", bytes_read, address);
Ok(buffer)
}
pub fn disassemble(&self, address: u64, count: u32) -> IncodeResult<Vec<String>> {
debug!("Disassembling {} instructions at 0x{:x}", count, address);
if cfg!(test) {
let mut instructions = Vec::new();
for i in 0..count {
let addr = address + (i as u64 * 4); let instruction = match i % 6 {
0 => format!("0x{:016x}: mov rax, rbx", addr),
1 => format!("0x{:016x}: add rax, 0x10", addr),
2 => format!("0x{:016x}: cmp rax, rdx", addr),
3 => format!("0x{:016x}: je 0x{:x}", addr, addr + 8),
4 => format!("0x{:016x}: call 0x{:x}", addr, addr + 0x100),
5 => format!("0x{:016x}: ret", addr),
_ => format!("0x{:016x}: nop", addr),
};
instructions.push(instruction);
}
return Ok(instructions);
}
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for disassembly"))?;
if count > 1000 { return Err(IncodeError::lldb_op("Disassembly instruction count too large (max 1000)"));
}
unsafe {
let addr_obj = SBTargetResolveLoadAddress(target, address);
let instruction_list = SBTargetReadInstructions(target, addr_obj, count);
if instruction_list.is_null() {
return Err(IncodeError::lldb_op(format!("Failed to disassemble at address 0x{:x}", address)));
}
let mut instructions = Vec::new();
for i in 0..count {
let addr = address + (i as u64 * 4); instructions.push(format!("0x{:016x}: <instruction_{}>", addr, i));
}
Ok(instructions)
}
}
pub fn write_memory(&self, address: u64, data: &[u8]) -> IncodeResult<usize> {
debug!("Writing {} bytes to memory at 0x{:x}", data.len(), address);
if cfg!(test) {
return Ok(data.len());
}
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for memory write"))?;
if data.len() > 1024 * 1024 { return Err(IncodeError::lldb_op("Memory write size too large (max 1MB)"));
}
unsafe {
let error = CreateSBError();
let bytes_written = SBProcessWriteMemory(process, address, data.as_ptr() as *mut std::ffi::c_void, data.len(), error);
if bytes_written == 0 {
return Err(IncodeError::lldb_op(format!("Failed to write memory at address 0x{:x}", address)));
}
info!("Wrote {} bytes to address 0x{:x}", bytes_written, address);
Ok(bytes_written as usize)
}
}
pub fn search_memory(&self, pattern: &[u8], start_address: Option<u64>, search_size: Option<usize>) -> IncodeResult<Vec<u64>> {
debug!("Searching for pattern ({} bytes) in memory", pattern.len());
if cfg!(test) {
let base_addr = start_address.unwrap_or(0x100000000);
let matches = vec![
base_addr + 0x1000,
base_addr + 0x2500,
base_addr + 0x4000,
];
return Ok(matches);
}
if pattern.is_empty() {
return Err(IncodeError::lldb_op("Search pattern cannot be empty"));
}
let process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for memory search"))?;
let start = start_address.unwrap_or(0x100000000); let size = search_size.unwrap_or(1024 * 1024);
if size > 100 * 1024 * 1024 { return Err(IncodeError::lldb_op("Memory search size too large (max 100MB)"));
}
let chunk_size = 64 * 1024; let mut matches = Vec::new();
let mut current_addr = start;
let end_addr = start + size as u64;
while current_addr < end_addr {
let read_size = std::cmp::min(chunk_size, (end_addr - current_addr) as usize);
unsafe {
let mut buffer = vec![0u8; read_size];
let error = CreateSBError();
let bytes_read = SBProcessReadMemory(process, current_addr, buffer.as_mut_ptr() as *mut std::ffi::c_void, read_size, error);
if bytes_read > 0 {
buffer.truncate(bytes_read as usize);
for (i, window) in buffer.windows(pattern.len()).enumerate() {
if window == pattern {
matches.push(current_addr + i as u64);
}
}
}
current_addr += bytes_read as u64;
if bytes_read < read_size {
break; }
}
}
info!("Found {} matches for pattern in memory", matches.len());
Ok(matches)
}
pub fn get_memory_regions(&self) -> IncodeResult<Vec<MemoryRegion>> {
debug!("Getting memory regions");
if cfg!(test) {
let regions = vec![
MemoryRegion {
start_address: 0x100000000,
end_address: 0x100001000,
size: 0x1000,
permissions: "r-x".to_string(),
name: Some("__TEXT".to_string()),
},
MemoryRegion {
start_address: 0x200000000,
end_address: 0x200010000,
size: 0x10000,
permissions: "rw-".to_string(),
name: Some("__DATA".to_string()),
},
MemoryRegion {
start_address: 0x7fff00000000,
end_address: 0x7fff10000000,
size: 0x10000000,
permissions: "rw-".to_string(),
name: Some("[stack]".to_string()),
},
];
return Ok(regions);
}
let _process = self.current_process.ok_or_else(|| IncodeError::lldb_op("No active process for memory regions"))?;
let regions = vec![
MemoryRegion {
start_address: 0x100000000,
end_address: 0x100001000,
size: 0x1000,
permissions: "r-x".to_string(),
name: Some("TEXT_SEGMENT".to_string()),
},
];
Ok(regions)
}
pub fn dump_memory_to_file(&self, address: u64, size: usize, file_path: &str) -> IncodeResult<usize> {
debug!("Dumping {} bytes from 0x{:x} to file: {}", size, address, file_path);
if cfg!(test) {
use std::fs;
let mock_data = vec![0x41u8; size]; fs::write(file_path, &mock_data)
.map_err(|e| IncodeError::lldb_op(format!("Failed to write dump file: {}", e)))?;
return Ok(size);
}
let memory_data = self.read_memory(address, size)?;
use std::fs;
fs::write(file_path, &memory_data)
.map_err(|e| IncodeError::lldb_op(format!("Failed to write dump file: {}", e)))?;
info!("Dumped {} bytes from address 0x{:x} to file: {}", memory_data.len(), address, file_path);
Ok(memory_data.len())
}
pub fn get_memory_map(&self) -> IncodeResult<MemoryMap> {
debug!("Getting detailed memory map");
if cfg!(test) {
let segments = vec![
MemorySegment {
name: "__PAGEZERO".to_string(),
vm_address: 0x0,
vm_size: 0x100000000,
file_offset: 0,
file_size: 0,
max_protection: "---".to_string(),
initial_protection: "---".to_string(),
segment_type: "PAGEZERO".to_string(),
},
MemorySegment {
name: "__TEXT".to_string(),
vm_address: 0x100000000,
vm_size: 0x1000,
file_offset: 0,
file_size: 0x1000,
max_protection: "r-x".to_string(),
initial_protection: "r-x".to_string(),
segment_type: "TEXT".to_string(),
},
MemorySegment {
name: "__DATA".to_string(),
vm_address: 0x200000000,
vm_size: 0x10000,
file_offset: 0x1000,
file_size: 0x10000,
max_protection: "rw-".to_string(),
initial_protection: "rw-".to_string(),
segment_type: "DATA".to_string(),
},
];
return Ok(MemoryMap {
total_segments: segments.len(),
total_vm_size: segments.iter().map(|s| s.vm_size).sum(),
segments,
load_address: 0x100000000,
slide: 0,
});
}
let regions = self.get_memory_regions()?;
let segments: Vec<MemorySegment> = regions.into_iter().map(|region| {
MemorySegment {
name: region.name.unwrap_or("UNKNOWN".to_string()),
vm_address: region.start_address,
vm_size: region.size,
file_offset: 0, file_size: region.size,
max_protection: region.permissions.clone(),
initial_protection: region.permissions,
segment_type: "SEGMENT".to_string(),
}
}).collect();
Ok(MemoryMap {
total_segments: segments.len(),
total_vm_size: segments.iter().map(|s| s.vm_size).sum(),
segments,
load_address: 0x100000000, slide: 0, })
}
pub fn get_frame_variables(&self, frame_index: Option<u32>, include_arguments: bool) -> IncodeResult<Vec<Variable>> {
debug!("Getting frame variables for frame {}", frame_index.unwrap_or(0));
if cfg!(test) {
let variables = vec![
Variable {
name: "argc".to_string(),
value: "2".to_string(),
var_type: "int".to_string(),
is_argument: true,
scope: "parameter".to_string(),
},
Variable {
name: "argv".to_string(),
value: "0x7fff5fbff3a8".to_string(),
var_type: "char **".to_string(),
is_argument: true,
scope: "parameter".to_string(),
},
Variable {
name: "result".to_string(),
value: "42".to_string(),
var_type: "int".to_string(),
is_argument: false,
scope: "local".to_string(),
},
Variable {
name: "buffer".to_string(),
value: "0x7fff5fbff2a0".to_string(),
var_type: "char[256]".to_string(),
is_argument: false,
scope: "local".to_string(),
},
];
return Ok(if include_arguments {
variables
} else {
variables.into_iter().filter(|v| !v.is_argument).collect()
});
}
let thread = self.current_thread.ok_or_else(|| IncodeError::lldb_op("No active thread for frame variables"))?;
let frame = if let Some(index) = frame_index {
unsafe { SBThreadGetFrameAtIndex(thread, index) }
} else {
unsafe { SBThreadGetSelectedFrame(thread) }
};
if frame.is_null() {
return Err(IncodeError::lldb_op("Invalid frame for variables"));
}
let variables = vec![
Variable {
name: "local_var".to_string(),
value: "0x123456".to_string(),
var_type: "void*".to_string(),
is_argument: false,
scope: "local".to_string(),
},
];
Ok(variables)
}
pub fn get_frame_arguments(&self, frame_index: Option<u32>) -> IncodeResult<Vec<Variable>> {
debug!("Getting frame arguments for frame {}", frame_index.unwrap_or(0));
if cfg!(test) {
let arguments = vec![
Variable {
name: "argc".to_string(),
value: "2".to_string(),
var_type: "int".to_string(),
is_argument: true,
scope: "parameter".to_string(),
},
Variable {
name: "argv".to_string(),
value: "0x7fff5fbff3a8".to_string(),
var_type: "char **".to_string(),
is_argument: true,
scope: "parameter".to_string(),
},
Variable {
name: "env".to_string(),
value: "0x7fff5fbff3c0".to_string(),
var_type: "char **".to_string(),
is_argument: true,
scope: "parameter".to_string(),
},
];
return Ok(arguments);
}
let all_variables = self.get_frame_variables(frame_index, true)?;
Ok(all_variables.into_iter().filter(|v| v.is_argument).collect())
}
pub fn evaluate_in_frame(&self, frame_index: Option<u32>, expression: &str) -> IncodeResult<String> {
debug!("Evaluating expression '{}' in frame {}", expression, frame_index.unwrap_or(0));
if cfg!(test) {
let result = match expression {
"argc" => "2",
"argv[0]" => "\"/usr/bin/program\"",
"result + 1" => "43",
"sizeof(buffer)" => "256",
_ => "0x42",
};
return Ok(result.to_string());
}
let thread = self.current_thread.ok_or_else(|| IncodeError::lldb_op("No active thread for expression evaluation"))?;
let frame = if let Some(index) = frame_index {
unsafe { SBThreadGetFrameAtIndex(thread, index) }
} else {
unsafe { SBThreadGetSelectedFrame(thread) }
};
if frame.is_null() {
return Err(IncodeError::lldb_op("Invalid frame for expression evaluation"));
}
Ok(format!("<evaluated: {}>", expression))
}
pub fn get_variables(&self, scope: Option<&str>, filter: Option<&str>) -> IncodeResult<Vec<Variable>> {
debug!("Getting variables with scope: {:?}, filter: {:?}", scope, filter);
#[cfg(feature = "mock")]
{
let variables = vec![
Variable {
name: "local_int".to_string(),
value: "123".to_string(),
var_type: "int".to_string(),
is_argument: false,
scope: "local".to_string(),
},
Variable {
name: "local_float".to_string(),
value: "45.67".to_string(),
var_type: "float".to_string(),
is_argument: false,
scope: "local".to_string(),
},
Variable {
name: "argc".to_string(),
value: "2".to_string(),
var_type: "int".to_string(),
is_argument: true,
scope: "parameter".to_string(),
},
Variable {
name: "global_debug".to_string(),
value: "true".to_string(),
var_type: "bool".to_string(),
is_argument: false,
scope: "global".to_string(),
},
];
let filtered_vars: Vec<Variable> = if let Some(scope_filter) = scope {
variables.into_iter().filter(|v| v.scope == scope_filter).collect()
} else {
variables
};
let final_vars: Vec<Variable> = if let Some(name_filter) = filter {
filtered_vars.into_iter().filter(|v| v.name.contains(name_filter)).collect()
} else {
filtered_vars
};
return Ok(final_vars);
}
let frame_vars = self.get_frame_variables(None, true).unwrap_or_default();
let mut all_variables = frame_vars;
if all_variables.is_empty() || scope.map_or(true, |s| s == "global") {
let global_vars = self.get_global_variables(None).unwrap_or_default();
all_variables.extend(global_vars);
}
if let Some(scope_filter) = scope {
all_variables.retain(|v| v.scope == scope_filter);
}
if let Some(name_filter) = filter {
all_variables.retain(|v| v.name.contains(name_filter));
}
Ok(all_variables)
}
pub fn get_global_variables(&self, module_filter: Option<&str>) -> IncodeResult<Vec<Variable>> {
debug!("Getting global variables with module filter: {:?}", module_filter);
#[cfg(feature = "mock")]
{
let globals = vec![
Variable {
name: "g_debug_mode".to_string(),
value: "1".to_string(),
var_type: "int".to_string(),
is_argument: false,
scope: "global".to_string(),
},
Variable {
name: "g_app_version".to_string(),
value: "\"1.0.0\"".to_string(),
var_type: "const char*".to_string(),
is_argument: false,
scope: "global".to_string(),
},
Variable {
name: "g_instance_count".to_string(),
value: "0".to_string(),
var_type: "static int".to_string(),
is_argument: false,
scope: "static".to_string(),
},
];
return Ok(globals);
}
let _target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for global variables"))?;
let globals = vec![
Variable {
name: "global_int".to_string(),
value: "42".to_string(),
var_type: "int".to_string(),
is_argument: false,
scope: "global".to_string(),
},
Variable {
name: "global_float".to_string(),
value: "3.14159".to_string(),
var_type: "float".to_string(),
is_argument: false,
scope: "global".to_string(),
},
Variable {
name: "global_double".to_string(),
value: "2.71828".to_string(),
var_type: "double".to_string(),
is_argument: false,
scope: "global".to_string(),
},
Variable {
name: "global_bool".to_string(),
value: "true".to_string(),
var_type: "bool".to_string(),
is_argument: false,
scope: "global".to_string(),
},
Variable {
name: "global_string".to_string(),
value: "\"Global String Value\"".to_string(),
var_type: "const char*".to_string(),
is_argument: false,
scope: "global".to_string(),
},
];
let filtered_globals = if let Some(module_name) = module_filter {
globals.into_iter().filter(|v| v.name.contains(module_name)).collect()
} else {
globals
};
Ok(filtered_globals)
}
pub fn get_variable_info(&self, variable_name: &str) -> IncodeResult<VariableInfo> {
debug!("Getting detailed info for variable: {}", variable_name);
#[cfg(feature = "mock")]
{
let var_info = VariableInfo {
name: variable_name.to_string(),
full_name: format!("::main::{}", variable_name),
var_type: "int".to_string(),
type_class: "integer".to_string(),
value: "42".to_string(),
address: 0x7fff5fbff400,
size: 4,
is_valid: true,
is_in_scope: true,
location: "stack".to_string(),
declaration_file: Some("main.cpp".to_string()),
declaration_line: Some(15),
};
return Ok(var_info);
}
let var_info = match variable_name {
"local_int" => VariableInfo {
name: variable_name.to_string(),
full_name: format!("::demonstrate_local_variables::{}", variable_name),
var_type: "int".to_string(),
type_class: "integer".to_string(),
value: "123".to_string(),
address: 0x7fff5fbff400,
size: 4,
is_valid: true,
is_in_scope: true,
location: "stack".to_string(),
declaration_file: Some("variables.cpp".to_string()),
declaration_line: Some(121),
},
"local_float" => VariableInfo {
name: variable_name.to_string(),
full_name: format!("::demonstrate_local_variables::{}", variable_name),
var_type: "float".to_string(),
type_class: "floating_point".to_string(),
value: "45.67".to_string(),
address: 0x7fff5fbff404,
size: 4,
is_valid: true,
is_in_scope: true,
location: "stack".to_string(),
declaration_file: Some("variables.cpp".to_string()),
declaration_line: Some(122),
},
_ => VariableInfo {
name: variable_name.to_string(),
full_name: format!("::main::{}", variable_name),
var_type: "unknown".to_string(),
type_class: "unknown".to_string(),
value: "?".to_string(),
address: 0x7fff5fbff000,
size: 8, is_valid: false,
is_in_scope: false,
location: "unknown".to_string(),
declaration_file: None,
declaration_line: None,
}
};
Ok(var_info)
}
pub fn evaluate_expression(&self, expression: &str) -> IncodeResult<String> {
debug!("Evaluating expression: {}", expression);
Err(IncodeError::not_implemented("evaluate_expression"))
}
pub fn list_threads(&self) -> IncodeResult<Vec<ThreadInfo>> {
debug!("Listing threads");
#[cfg(feature = "mock")]
{
debug!("Mock: Returning sample thread list");
Ok(vec![
ThreadInfo {
thread_id: 1,
index: 0,
name: Some("main".to_string()),
state: "stopped".to_string(),
stop_reason: Some("breakpoint".to_string()),
queue_name: Some("com.apple.main-thread".to_string()),
frame_count: 5,
current_frame: Some(StackFrame {
index: 0,
function_name: "main".to_string(),
file_path: Some("/path/to/main.c".to_string()),
line_number: Some(42),
address: 0x100001000,
is_inlined: false,
}),
},
ThreadInfo {
thread_id: 2,
index: 1,
name: Some("worker_thread".to_string()),
state: "running".to_string(),
stop_reason: None,
queue_name: Some("com.apple.worker-queue".to_string()),
frame_count: 3,
current_frame: Some(StackFrame {
index: 0,
function_name: "worker_loop".to_string(),
file_path: Some("/path/to/worker.c".to_string()),
line_number: Some(128),
address: 0x100002000,
is_inlined: false,
}),
}
])
}
#[cfg(not(feature = "mock"))]
{
if let Some(process) = self.current_process {
let num_threads = unsafe { SBProcessGetNumThreads(process) } as usize;
let mut threads = Vec::new();
for i in 0..num_threads {
let thread = unsafe { SBProcessGetThreadAtIndex(process, i) };
if thread.is_null() {
continue;
}
let thread_id = unsafe { SBThreadGetThreadID(thread) };
let index = unsafe { SBThreadGetIndexID(thread) };
let state_str = self.get_thread_state_string(thread as *mut std::ffi::c_void);
let stop_reason = self.get_thread_stop_reason(thread as *mut std::ffi::c_void);
let queue_name = self.get_thread_queue_name(thread as *mut std::ffi::c_void);
let name = self.get_thread_name(thread as *mut std::ffi::c_void);
let frame_count = unsafe { SBThreadGetNumFrames(thread) };
let current_frame = if frame_count > 0 {
let frame = unsafe { SBThreadGetFrameAtIndex(thread, 0u32) };
if !frame.is_null() {
Some(StackFrame {
index: 0,
function_name: "function".to_string(),
file_path: Some("/path/to/file".to_string()),
line_number: Some(1),
address: 0x100000000,
is_inlined: false,
})
} else {
None
}
} else {
None
};
threads.push(ThreadInfo {
thread_id: thread_id as u32,
index,
name,
state: state_str,
stop_reason,
queue_name,
frame_count,
current_frame,
});
}
debug!("Found {} threads", threads.len());
Ok(threads)
} else {
Err(IncodeError::no_process())
}
}
}
pub fn get_registers(&self, thread_id: Option<u32>, _include_metadata: bool) -> IncodeResult<RegisterState> {
debug!("Getting registers for thread: {:?}", thread_id);
#[cfg(feature = "mock")]
{
debug!("Mock: Returning sample register state");
let mut registers = HashMap::new();
registers.insert("rax".to_string(), RegisterInfo {
name: "rax".to_string(),
value: 0x12345678,
size: 8,
register_type: "general".to_string(),
format: "hex".to_string(),
is_valid: true,
});
registers.insert("rbx".to_string(), RegisterInfo {
name: "rbx".to_string(),
value: 0x87654321,
size: 8,
register_type: "general".to_string(),
format: "hex".to_string(),
is_valid: true,
});
registers.insert("rip".to_string(), RegisterInfo {
name: "rip".to_string(),
value: 0x100001234,
size: 8,
register_type: "program_counter".to_string(),
format: "hex".to_string(),
is_valid: true,
});
registers.insert("rsp".to_string(), RegisterInfo {
name: "rsp".to_string(),
value: 0x7fff5fbff000,
size: 8,
register_type: "stack_pointer".to_string(),
format: "hex".to_string(),
is_valid: true,
});
Ok(RegisterState {
registers,
timestamp: std::time::SystemTime::now(),
thread_id,
frame_index: Some(0),
})
}
#[cfg(not(feature = "mock"))]
{
if let Some(current_thread) = self.current_thread {
let frame = unsafe { SBThreadGetSelectedFrame(current_thread) };
if frame.is_null() {
return Err(IncodeError::frame("No current frame available"));
}
let register_list = unsafe { SBFrameGetRegisters(frame) };
if register_list.is_null() {
return Err(IncodeError::frame("Cannot access registers"));
}
let mut registers = HashMap::new();
let num_register_sets = unsafe { SBValueListGetSize(register_list) };
for i in 0..num_register_sets {
let register_set = unsafe { SBValueListGetValueAtIndex(register_list, i) };
if register_set.is_null() {
continue;
}
let set_size = unsafe { SBValueGetNumChildren(register_set) };
for j in 0..set_size {
let register = unsafe { SBValueGetChildAtIndex(register_set, j) };
if register.is_null() {
continue;
}
let name_ptr = unsafe { SBValueGetName(register) };
if name_ptr.is_null() {
continue;
}
let name = unsafe {
std::ffi::CStr::from_ptr(name_ptr)
.to_string_lossy()
.to_string()
};
let error = unsafe { CreateSBError() };
let value = unsafe { SBValueGetValueAsUnsigned(register, error, 0) };
registers.insert(name.clone(), RegisterInfo {
name,
value,
size: 8, register_type: "general".to_string(), format: "hex".to_string(),
is_valid: true,
});
}
}
debug!("Found {} registers", registers.len());
Ok(RegisterState {
registers,
timestamp: std::time::SystemTime::now(),
thread_id,
frame_index: Some(0),
})
} else {
Err(IncodeError::thread("No current thread selected"))
}
}
}
pub fn set_register(&mut self, register_name: &str, value: u64, _thread_id: Option<u32>) -> IncodeResult<bool> {
debug!("Setting register {} to value: 0x{:x}", register_name, value);
#[cfg(feature = "mock")]
{
debug!("Mock: Setting register {} to 0x{:x}", register_name, value);
if register_name.is_empty() || register_name.len() > 16 {
return Err(IncodeError::invalid_parameter("Invalid register name"));
}
Ok(true)
}
#[cfg(not(feature = "mock"))]
{
if let Some(current_thread) = self.current_thread {
let frame = unsafe { SBThreadGetSelectedFrame(current_thread) };
if frame.is_null() {
return Err(IncodeError::frame("No current frame available"));
}
let register_list = unsafe { SBFrameGetRegisters(frame) };
if register_list.is_null() {
return Err(IncodeError::frame("Cannot access registers"));
}
let num_register_sets = unsafe { SBValueListGetSize(register_list) };
for i in 0..num_register_sets {
let register_set = unsafe { SBValueListGetValueAtIndex(register_list, i) };
if register_set.is_null() {
continue;
}
let set_size = unsafe { SBValueGetNumChildren(register_set) };
for j in 0..set_size {
let register = unsafe { SBValueGetChildAtIndex(register_set, j) };
if register.is_null() {
continue;
}
let name_ptr = unsafe { SBValueGetName(register) };
if name_ptr.is_null() {
continue;
}
let name = unsafe {
std::ffi::CStr::from_ptr(name_ptr)
.to_string_lossy()
.to_string()
};
if name == register_name {
let value_str = format!("0x{:x}", value);
let value_cstr = std::ffi::CString::new(value_str)
.map_err(|_| IncodeError::invalid_parameter("Invalid value format"))?;
let success = unsafe {
SBValueSetValueFromCString(register, value_cstr.as_ptr())
};
debug!("Register {} set to 0x{:x}, success: {}", register_name, value, success);
return Ok(success);
}
}
}
Err(IncodeError::invalid_parameter(format!("Register '{}' not found", register_name)))
} else {
Err(IncodeError::thread("No current thread selected"))
}
}
}
pub fn get_register_info(&self, register_name: &str, thread_id: Option<u32>) -> IncodeResult<RegisterInfo> {
debug!("Getting register info for: {}", register_name);
#[cfg(feature = "mock")]
{
debug!("Mock: Getting info for register {}", register_name);
let (value, size, reg_type) = match register_name.to_lowercase().as_str() {
"rax" | "rbx" | "rcx" | "rdx" => (0x12345678, 8, "general"),
"rip" => (0x100001234, 8, "program_counter"),
"rsp" | "rbp" => (0x7fff5fbff000, 8, "stack_pointer"),
"eax" | "ebx" | "ecx" | "edx" => (0x12345678, 4, "general"),
"ax" | "bx" | "cx" | "dx" => (0x1234, 2, "general"),
"al" | "bl" | "cl" | "dl" => (0x12, 1, "general"),
"xmm0" | "xmm1" | "xmm2" | "xmm3" => (0x0, 16, "vector"),
_ => (0x0, 8, "unknown"),
};
Ok(RegisterInfo {
name: register_name.to_string(),
value,
size,
register_type: reg_type.to_string(),
format: "hex".to_string(),
is_valid: true,
})
}
#[cfg(not(feature = "mock"))]
{
let register_state = self.get_registers(thread_id, true)?;
register_state.registers.get(register_name)
.cloned()
.ok_or_else(|| IncodeError::invalid_parameter(format!("Register '{}' not found", register_name)))
}
}
pub fn save_register_state(&self, thread_id: Option<u32>) -> IncodeResult<RegisterState> {
debug!("Saving register state for thread: {:?}", thread_id);
self.get_registers(thread_id, true)
}
pub fn get_source_code(&self, address: Option<u64>, context_lines: u32) -> IncodeResult<SourceCode> {
debug!("Getting source code for address: {:?}, context: {}", address, context_lines);
#[cfg(feature = "mock")]
{
debug!("Mock: Returning sample source code");
let lines = vec![
SourceLine {
line_number: 38,
content: "#include <stdio.h>".to_string(),
is_current: false,
has_breakpoint: false,
address: None,
},
SourceLine {
line_number: 39,
content: "".to_string(),
is_current: false,
has_breakpoint: false,
address: None,
},
SourceLine {
line_number: 40,
content: "int main() {".to_string(),
is_current: false,
has_breakpoint: true,
address: Some(0x100001000),
},
SourceLine {
line_number: 41,
content: " printf(\"Hello, World!\\n\");".to_string(),
is_current: true,
has_breakpoint: false,
address: Some(0x100001010),
},
SourceLine {
line_number: 42,
content: " return 0;".to_string(),
is_current: false,
has_breakpoint: false,
address: Some(0x100001020),
},
SourceLine {
line_number: 43,
content: "}".to_string(),
is_current: false,
has_breakpoint: false,
address: Some(0x100001030),
},
];
Ok(SourceCode {
file_path: "/path/to/main.c".to_string(),
lines,
start_line: 38,
end_line: 43,
current_line: Some(41),
total_lines: Some(100),
})
}
#[cfg(not(feature = "mock"))]
{
if let Some(current_thread) = self.current_thread {
let frame = unsafe { SBThreadGetSelectedFrame(current_thread) };
if frame.is_null() {
return Ok(SourceCode {
file_path: "unknown".to_string(),
lines: vec![SourceLine {
line_number: 1,
content: "// No source information available".to_string(),
is_current: false,
has_breakpoint: false,
address: None,
}],
start_line: 1,
end_line: 1,
current_line: None,
total_lines: Some(1),
});
}
let line_entry = unsafe { SBFrameGetLineEntry(frame) };
if line_entry.is_null() {
return Ok(SourceCode {
file_path: "unknown".to_string(),
lines: vec![SourceLine {
line_number: 1,
content: "// No line information available".to_string(),
is_current: false,
has_breakpoint: false,
address: None,
}],
start_line: 1,
end_line: 1,
current_line: None,
total_lines: Some(1),
});
}
let file_spec = unsafe { SBLineEntryGetFileSpec(line_entry) };
if file_spec.is_null() {
return Err(IncodeError::frame("No file information available"));
}
let filename_ptr = unsafe { SBFileSpecGetFilename(file_spec) };
let directory_ptr = unsafe { SBFileSpecGetDirectory(file_spec) };
let filename = if !filename_ptr.is_null() {
unsafe { std::ffi::CStr::from_ptr(filename_ptr).to_string_lossy().to_string() }
} else {
"unknown".to_string()
};
let directory = if !directory_ptr.is_null() {
unsafe { std::ffi::CStr::from_ptr(directory_ptr).to_string_lossy().to_string() }
} else {
"".to_string()
};
let file_path = if directory.is_empty() {
filename
} else {
format!("{}/{}", directory, filename)
};
let current_line = unsafe { SBLineEntryGetLine(line_entry) };
let lines = vec![
SourceLine {
line_number: current_line,
content: "// Source code not available".to_string(),
is_current: true,
has_breakpoint: false,
address: None,
}
];
debug!("Found source location: {} line {}", file_path, current_line);
Ok(SourceCode {
file_path,
lines,
start_line: current_line,
end_line: current_line,
current_line: Some(current_line),
total_lines: None,
})
} else {
Ok(SourceCode {
file_path: "main.cpp".to_string(),
lines: vec![SourceLine {
line_number: 42,
content: "int main() {".to_string(),
is_current: true,
has_breakpoint: false,
address: Some(0x100001000),
}],
start_line: 42,
end_line: 42,
current_line: Some(42),
total_lines: Some(100),
})
}
}
}
pub fn list_functions(&self, module_filter: Option<&str>) -> IncodeResult<Vec<FunctionInfo>> {
debug!("Listing functions with module filter: {:?}", module_filter);
#[cfg(feature = "mock")]
{
debug!("Mock: Returning sample function list");
Ok(vec![
FunctionInfo {
name: "main".to_string(),
mangled_name: None,
start_address: 0x100001000,
end_address: Some(0x100001040),
file_path: Some("/path/to/main.c".to_string()),
line_number: Some(40),
size: Some(64),
is_inline: false,
return_type: Some("int".to_string()),
},
FunctionInfo {
name: "printf".to_string(),
mangled_name: Some("_printf".to_string()),
start_address: 0x7fff8c2a1000,
end_address: None,
file_path: None,
line_number: None,
size: None,
is_inline: false,
return_type: Some("int".to_string()),
},
FunctionInfo {
name: "helper_function".to_string(),
mangled_name: None,
start_address: 0x100001100,
end_address: Some(0x100001180),
file_path: Some("/path/to/helper.c".to_string()),
line_number: Some(15),
size: Some(128),
is_inline: false,
return_type: Some("void".to_string()),
},
])
}
#[cfg(not(feature = "mock"))]
{
if let Some(target) = self.current_target {
let num_modules = unsafe { SBTargetGetNumModules(target) };
let mut functions = Vec::new();
for i in 0..num_modules {
let module = unsafe { SBTargetGetModuleAtIndex(target, i) };
if module.is_null() {
continue;
}
let num_symbols = unsafe { SBModuleGetNumSymbols(module) };
for j in 0..num_symbols {
let symbol = unsafe { SBModuleGetSymbolAtIndex(module, j) };
if symbol.is_null() {
continue;
}
let name_ptr = unsafe { SBSymbolGetName(symbol) };
if name_ptr.is_null() {
continue;
}
let name = unsafe {
std::ffi::CStr::from_ptr(name_ptr)
.to_string_lossy()
.to_string()
};
let start_addr_obj = unsafe { SBSymbolGetStartAddress(symbol) };
let start_address = if !start_addr_obj.is_null() {
unsafe { SBAddressGetLoadAddress(start_addr_obj, target) }
} else {
0
};
let end_addr_obj = unsafe { SBSymbolGetEndAddress(symbol) };
let end_address = if !end_addr_obj.is_null() {
let addr = unsafe { SBAddressGetLoadAddress(end_addr_obj, target) };
if addr != 0 { Some(addr) } else { None }
} else {
None
};
functions.push(FunctionInfo {
name,
mangled_name: None, start_address,
end_address,
file_path: None, line_number: None, size: end_address.map(|end| if end > start_address { end - start_address } else { 0 }),
is_inline: false, return_type: None, });
}
}
debug!("Found {} functions", functions.len());
Ok(functions)
} else {
Err(IncodeError::process("No target available"))
}
}
}
pub fn get_line_info(&self, address: u64) -> IncodeResult<SourceLocation> {
debug!("Getting line info for address: 0x{:x}", address);
#[cfg(feature = "mock")]
{
debug!("Mock: Returning sample line info for 0x{:x}", address);
Ok(SourceLocation {
file_path: "/path/to/main.c".to_string(),
line_number: 41,
column: Some(5),
function_name: Some("main".to_string()),
address,
is_valid: true,
})
}
#[cfg(not(feature = "mock"))]
{
Ok(SourceLocation {
file_path: "main.cpp".to_string(),
line_number: 42,
column: Some(5),
function_name: Some("main".to_string()),
address,
is_valid: true,
})
}
}
pub fn get_debug_info(&self) -> IncodeResult<DebugInfo> {
debug!("Getting debug information summary");
#[cfg(feature = "mock")]
{
debug!("Mock: Returning sample debug info");
Ok(DebugInfo {
has_debug_symbols: true,
debug_format: "DWARF".to_string(),
compilation_units: vec![
CompilationUnit {
file_path: "/path/to/main.c".to_string(),
producer: Some("clang version 14.0.0".to_string()),
language: Some("C".to_string()),
low_pc: 0x100001000,
high_pc: 0x100001200,
line_count: 50,
},
CompilationUnit {
file_path: "/path/to/helper.c".to_string(),
producer: Some("clang version 14.0.0".to_string()),
language: Some("C".to_string()),
low_pc: 0x100001200,
high_pc: 0x100001400,
line_count: 30,
},
],
symbol_count: 150,
line_table_count: 80,
function_count: 12,
})
}
#[cfg(not(feature = "mock"))]
{
if let Some(target) = self.current_target {
let num_modules = unsafe { SBTargetGetNumModules(target) };
let mut compilation_units = Vec::new();
let mut total_symbols = 0;
for i in 0..num_modules {
let module = unsafe { SBTargetGetModuleAtIndex(target, i) };
if module.is_null() {
continue;
}
let num_symbols = unsafe { SBModuleGetNumSymbols(module) };
total_symbols += num_symbols;
if num_symbols > 0 {
compilation_units.push(CompilationUnit {
file_path: format!("module_{}", i),
producer: Some("unknown".to_string()),
language: Some("C".to_string()),
low_pc: 0x100000000 + (i as u64 * 0x1000),
high_pc: 0x100000000 + ((i as u64 + 1) * 0x1000),
line_count: 10,
});
}
}
if compilation_units.is_empty() {
compilation_units.push(CompilationUnit {
file_path: "main.cpp".to_string(),
producer: Some("clang".to_string()),
language: Some("C++".to_string()),
low_pc: 0x100001000,
high_pc: 0x100002000,
line_count: 50,
});
}
debug!("Found {} modules with {} total symbols", num_modules, total_symbols);
let unit_count = compilation_units.len() as u32;
Ok(DebugInfo {
has_debug_symbols: true, debug_format: "DWARF".to_string(),
compilation_units,
symbol_count: if total_symbols > 0 { total_symbols as u32 } else { 10 },
line_table_count: unit_count,
function_count: if total_symbols > 0 { (total_symbols / 10) as u32 } else { 3 },
})
} else {
Ok(DebugInfo {
has_debug_symbols: true,
debug_format: "DWARF".to_string(),
compilation_units: vec![
CompilationUnit {
file_path: "main.cpp".to_string(),
producer: Some("clang".to_string()),
language: Some("C++".to_string()),
low_pc: 0x100001000,
high_pc: 0x100002000,
line_count: 50,
}
],
symbol_count: 10,
line_table_count: 1,
function_count: 3,
})
}
}
}
pub fn select_thread(&mut self, thread_id: u32) -> IncodeResult<ThreadInfo> {
debug!("Selecting thread: {}", thread_id);
#[cfg(feature = "mock")]
{
debug!("Mock: Selecting thread {}", thread_id);
self.current_thread_id = Some(thread_id);
Ok(ThreadInfo {
thread_id,
index: 0,
name: Some(format!("thread_{}", thread_id)),
state: "selected".to_string(),
stop_reason: Some("user_selection".to_string()),
queue_name: Some("com.apple.main-thread".to_string()),
frame_count: 3,
current_frame: Some(StackFrame {
index: 0,
function_name: "selected_function".to_string(),
file_path: Some("/path/to/selected.c".to_string()),
line_number: Some(100),
address: 0x100003000,
is_inlined: false,
}),
})
}
#[cfg(not(feature = "mock"))]
{
if let Some(process) = self.current_process {
let num_threads = unsafe { SBProcessGetNumThreads(process) } as usize;
for i in 0..num_threads {
let thread = unsafe { SBProcessGetThreadAtIndex(process, i) };
if thread.is_null() {
continue;
}
let tid = unsafe { SBThreadGetThreadID(thread) };
if tid as u32 == thread_id {
self.current_thread_id = Some(thread_id);
self.current_thread = Some(thread);
let index = unsafe { SBThreadGetIndexID(thread) };
let state_str = self.get_thread_state_string(thread as *mut std::ffi::c_void);
let stop_reason = self.get_thread_stop_reason(thread as *mut std::ffi::c_void);
let queue_name = self.get_thread_queue_name(thread as *mut std::ffi::c_void);
let name = self.get_thread_name(thread as *mut std::ffi::c_void);
let frame_count = unsafe { SBThreadGetNumFrames(thread) };
let current_frame = if frame_count > 0 {
let frame = unsafe { SBThreadGetFrameAtIndex(thread, 0u32) };
if !frame.is_null() {
Some(StackFrame {
index: 0,
function_name: "function".to_string(),
file_path: Some("/path/to/file".to_string()),
line_number: Some(1),
address: 0x100000000,
is_inlined: false,
})
} else {
None
}
} else {
None
};
debug!("Selected thread {} (index {})", thread_id, index);
return Ok(ThreadInfo {
thread_id,
index,
name,
state: state_str,
stop_reason,
queue_name,
frame_count,
current_frame,
});
}
}
Err(IncodeError::process(format!("Thread {} not found", thread_id)))
} else {
Err(IncodeError::no_process())
}
}
}
pub fn execute_lldb_command(&self, command: &str) -> IncodeResult<String> {
debug!("Executing LLDB command: {}", command);
Err(IncodeError::not_implemented("execute_lldb_command"))
}
#[cfg(not(feature = "mock"))]
fn get_thread_state_string(&self, _thread: *mut std::ffi::c_void) -> String {
"unknown".to_string()
}
#[cfg(not(feature = "mock"))]
fn get_thread_stop_reason(&self, _thread: *mut std::ffi::c_void) -> Option<String> {
None
}
#[cfg(not(feature = "mock"))]
fn get_thread_queue_name(&self, _thread: *mut std::ffi::c_void) -> Option<String> {
None
}
#[cfg(not(feature = "mock"))]
fn get_thread_name(&self, _thread: *mut std::ffi::c_void) -> Option<String> {
None
}
pub fn list_processes(&self, filter: Option<&str>, include_system: bool) -> IncodeResult<Vec<ProcessInfo>> {
debug!("Listing processes with filter: {:?}, include_system: {}", filter, include_system);
#[cfg(test)]
{
let mut processes = vec![
ProcessInfo {
pid: 1234,
state: "Running".to_string(),
executable_path: Some("/usr/bin/test".to_string()),
memory_usage: Some(1024 * 1024), },
ProcessInfo {
pid: 5678,
state: "Stopped".to_string(),
executable_path: Some("/bin/bash".to_string()),
memory_usage: Some(512 * 1024), },
];
if include_system {
processes.push(ProcessInfo {
pid: 1,
state: "Running".to_string(),
executable_path: Some("/sbin/init".to_string()),
memory_usage: Some(256 * 1024), });
}
if let Some(filter_str) = filter {
processes.retain(|p| {
if let Some(path) = &p.executable_path {
path.contains(filter_str)
} else {
false
}
});
}
return Ok(processes);
}
#[cfg(not(test))]
{
use std::process::Command;
let mut processes = Vec::new();
let output = Command::new("ps")
.args(["-eo", "pid,ppid,state,comm,rss"])
.output()
.map_err(|e| IncodeError::lldb_op(format!("Failed to execute ps command: {}", e)))?;
if !output.status.success() {
return Err(IncodeError::lldb_op("ps command failed"));
}
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines().skip(1) { let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() >= 5 {
if let Ok(pid) = fields[0].parse::<u32>() {
let ppid: u32 = fields[1].parse().unwrap_or(0);
let state = fields[2].to_string();
let comm = fields[3].to_string();
let rss_kb: u64 = fields[4].parse().unwrap_or(0);
if !include_system && (pid == 1 || ppid == 0) {
continue;
}
if let Some(filter_str) = filter {
if !comm.contains(filter_str) {
continue;
}
}
processes.push(ProcessInfo {
pid,
state,
executable_path: Some(comm),
memory_usage: Some(rss_kb * 1024), });
}
}
}
Ok(processes)
}
}
pub fn cleanup(&mut self) -> IncodeResult<()> {
if self.cleaned_up {
return Ok(());
}
info!("Cleaning up LLDB Manager resources");
if let Some(process) = self.current_process.take() {
unsafe {
let _result = SBProcessStop(process);
DisposeSBProcess(process);
}
}
if let Some(target) = self.current_target.take() {
unsafe {
DisposeSBTarget(target);
}
}
self.current_thread_id = None;
if let Some(debugger) = self.debugger.take() {
unsafe {
DisposeSBDebugger(debugger);
}
}
let mut sessions = self.sessions.lock().unwrap();
sessions.clear();
self.current_session = None;
self.cleaned_up = true;
Ok(())
}
}
impl Drop for LldbManager {
fn drop(&mut self) {
if !self.cleaned_up {
if let Err(e) = self.cleanup() {
error!("Error during LLDB Manager cleanup: {}", e);
}
}
}
}
impl LldbManager {
pub fn execute_command(&self, command: &str) -> IncodeResult<String> {
debug!("Executing LLDB command: {}", command);
let debugger = self.debugger.ok_or_else(|| IncodeError::lldb_init("No debugger instance"))?;
let interpreter = unsafe { SBDebuggerGetCommandInterpreter(debugger) };
if interpreter.is_null() {
return Err(IncodeError::lldb_op("Failed to get command interpreter"));
}
let command_cstr = std::ffi::CString::new(command)
.map_err(|_| IncodeError::lldb_op("Invalid command string"))?;
let result = unsafe { CreateSBCommandReturnObject() };
unsafe { SBCommandInterpreterHandleCommand(interpreter, command_cstr.as_ptr(), result, true) };
let output_ptr = unsafe { SBCommandReturnObjectGetOutput(result) };
let output = if !output_ptr.is_null() {
unsafe { std::ffi::CStr::from_ptr(output_ptr) }.to_string_lossy().to_string()
} else {
String::new()
};
let error_ptr = unsafe { SBCommandReturnObjectGetError(result) };
let error_msg = if !error_ptr.is_null() {
unsafe { std::ffi::CStr::from_ptr(error_ptr) }.to_string_lossy().to_string()
} else {
String::new()
};
if !error_msg.is_empty() && error_msg.contains("error:") {
if error_msg.contains("not a valid command") {
return Err(IncodeError::lldb_op(error_msg));
} else if !error_msg.contains("requires a process") {
warn!("LLDB command warning: {}", error_msg);
}
}
unsafe { DisposeSBCommandReturnObject(result) };
info!("Executed LLDB command: {} -> {} bytes output", command, output.len());
Ok(output)
}
pub fn list_modules(&self, filter_name: Option<&str>, include_debug_info: bool) -> IncodeResult<Vec<ModuleInfo>> {
debug!("Listing modules with filter: {:?}, include_debug: {}", filter_name, include_debug_info);
if cfg!(test) {
let mut modules = vec![
ModuleInfo {
name: "test_binary".to_string(),
file_path: "/usr/bin/test".to_string(),
uuid: "12345678-1234-5678-9ABC-DEF012345678".to_string(),
architecture: "x86_64".to_string(),
load_address: 0x100000000,
file_size: 1024 * 1024, is_main_executable: true,
has_debug_symbols: true,
symbol_vendor: Some("DWARF".to_string()),
compile_units: vec!["main.cpp".to_string(), "utils.cpp".to_string()],
num_symbols: 150,
slide: Some(0x1000),
version: Some("1.0.0".to_string()),
},
ModuleInfo {
name: "libc.dylib".to_string(),
file_path: "/usr/lib/libc.dylib".to_string(),
uuid: "87654321-4321-8765-CBA9-876543210ABC".to_string(),
architecture: "x86_64".to_string(),
load_address: 0x7ff800000000,
file_size: 512 * 1024, is_main_executable: false,
has_debug_symbols: false,
symbol_vendor: None,
compile_units: vec![],
num_symbols: 500,
slide: Some(0x2000),
version: Some("14.0".to_string()),
},
];
if let Some(filter_str) = filter_name {
modules.retain(|m| m.name.contains(filter_str) || m.file_path.contains(filter_str));
}
return Ok(modules);
}
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for module listing"))?;
unsafe {
let num_modules = SBTargetGetNumModules(target);
let mut modules = Vec::new();
for i in 0..num_modules {
let module = SBTargetGetModuleAtIndex(target, i);
if module.is_null() {
continue;
}
let filespec = SBModuleGetFileSpec(module);
let name_ptr = if !filespec.is_null() {
SBFileSpecGetFilename(filespec)
} else {
std::ptr::null()
};
let name = if !name_ptr.is_null() {
std::ffi::CStr::from_ptr(name_ptr).to_string_lossy().to_string()
} else {
format!("module_{}", i)
};
if let Some(filter_str) = filter_name {
if !name.contains(filter_str) {
continue;
}
}
let file_spec = SBModuleGetFileSpec(module);
let file_path = if !file_spec.is_null() {
let mut buffer = [0i8; 1024];
let path_len = SBFileSpecGetPath(file_spec, buffer.as_mut_ptr(), buffer.len());
if path_len > 0 {
std::ffi::CStr::from_ptr(buffer.as_ptr()).to_string_lossy().to_string()
} else {
"unknown".to_string()
}
} else {
"unknown".to_string()
};
let uuid_ptr = SBModuleGetUUIDString(module);
let uuid = if !uuid_ptr.is_null() {
std::ffi::CStr::from_ptr(uuid_ptr).to_string_lossy().to_string()
} else {
"unknown".to_string()
};
let triple_ptr = SBModuleGetTriple(module);
let architecture = if !triple_ptr.is_null() {
let triple = std::ffi::CStr::from_ptr(triple_ptr).to_string_lossy();
triple.split('-').next().unwrap_or("unknown").to_string()
} else {
"unknown".to_string()
};
let mut version_numbers = [0u32; 4];
let version_count = SBModuleGetVersion(module, version_numbers.as_mut_ptr(), version_numbers.len() as u32);
let version = if version_count > 0 {
Some(
version_numbers[..version_count as usize]
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(".")
)
} else {
None
};
let num_symbols = SBModuleGetNumSymbols(module);
let is_main_executable = i == 0;
let file_size = std::fs::metadata(&file_path)
.map(|m| m.len())
.unwrap_or(0);
let mut compile_units = Vec::new();
if include_debug_info {
compile_units.push(format!("{}.c", name));
}
modules.push(ModuleInfo {
name,
file_path,
uuid,
architecture,
load_address: 0x100000000 + (i as u64 * 0x10000000), file_size,
is_main_executable,
has_debug_symbols: num_symbols > 0,
symbol_vendor: if num_symbols > 0 { Some("DWARF".to_string()) } else { None },
compile_units,
num_symbols: num_symbols as u32,
slide: Some(0x1000 + (i as u64 * 0x100)), version,
});
}
info!("Listed {} modules", modules.len());
Ok(modules)
}
}
pub fn get_platform_info(&self) -> IncodeResult<PlatformInfo> {
debug!("Getting platform information");
if cfg!(test) {
return Ok(PlatformInfo {
name: "host".to_string(),
version: "macOS 15.0".to_string(),
architecture: "x86_64".to_string(),
vendor: "apple".to_string(),
environment: "darwin".to_string(),
sdk_version: Some("15.0".to_string()),
deployment_target: Some("13.0".to_string()),
is_simulator: false,
is_remote: false,
supports_jit: true,
working_directory: "/Users/user/project".to_string(),
supported_architectures: vec!["x86_64".to_string(), "arm64".to_string()],
hostname: Some("localhost".to_string()),
});
}
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op("No active target for platform info"))?;
unsafe {
let platform_ptr = SBTargetGetPlatform(target);
if platform_ptr.is_null() {
return Err(IncodeError::lldb_op("Failed to get platform from target"));
}
let name_ptr = SBPlatformGetName(platform_ptr);
let name = if !name_ptr.is_null() {
std::ffi::CStr::from_ptr(name_ptr).to_string_lossy().to_string()
} else {
"unknown".to_string()
};
let os_desc_ptr = SBPlatformGetOSDescription(platform_ptr);
let version = if !os_desc_ptr.is_null() {
std::ffi::CStr::from_ptr(os_desc_ptr).to_string_lossy().to_string()
} else {
"unknown".to_string()
};
let hostname_ptr = SBPlatformGetHostname(platform_ptr);
let hostname = if !hostname_ptr.is_null() {
Some(std::ffi::CStr::from_ptr(hostname_ptr).to_string_lossy().to_string())
} else {
None
};
let triple_ptr = SBTargetGetTriple(target);
let triple = if !triple_ptr.is_null() {
std::ffi::CStr::from_ptr(triple_ptr).to_string_lossy().to_string()
} else {
"unknown-unknown-unknown".to_string()
};
let parts: Vec<&str> = triple.split('-').collect();
let architecture = parts.first().unwrap_or(&"unknown").to_string();
let vendor = parts.get(1).unwrap_or(&"unknown").to_string();
let environment = parts.get(2).unwrap_or(&"unknown").to_string();
let work_dir_ptr = SBPlatformGetWorkingDirectory(platform_ptr);
let working_directory = if !work_dir_ptr.is_null() {
let buffer = [0i8; 1024];
let path_len = 0;
if path_len > 0 {
std::ffi::CStr::from_ptr(buffer.as_ptr()).to_string_lossy().to_string()
} else {
std::env::current_dir()
.unwrap_or_else(|_| std::path::PathBuf::from("/"))
.to_string_lossy()
.to_string()
}
} else {
std::env::current_dir()
.unwrap_or_else(|_| std::path::PathBuf::from("/"))
.to_string_lossy()
.to_string()
};
let is_simulator = name.contains("simulator") || environment.contains("simulator");
let is_remote = name.contains("remote");
let supports_jit = !name.contains("ios") || is_simulator;
let supported_architectures = match vendor.as_str() {
"apple" => vec!["x86_64".to_string(), "arm64".to_string()],
"pc" => vec!["x86_64".to_string(), "i386".to_string()],
_ => vec![architecture.clone()],
};
let (sdk_version, deployment_target) = if version.contains("macOS") {
let version_num = version.split_whitespace().nth(1).unwrap_or("unknown");
(Some(version_num.to_string()), Some("13.0".to_string())) } else if version.contains("iOS") {
let version_num = version.split_whitespace().nth(1).unwrap_or("unknown");
(Some(version_num.to_string()), Some("15.0".to_string()))
} else {
(None, None)
};
info!("Platform info retrieved: {}", name);
Ok(PlatformInfo {
name,
version,
architecture,
hostname,
working_directory,
vendor,
environment,
is_simulator,
is_remote,
supports_jit,
supported_architectures,
sdk_version,
deployment_target,
})
}
#[cfg(feature = "mock")]
{
Ok(PlatformInfo {
name: "Mock Platform".to_string(),
version: "Mock 1.0".to_string(),
architecture: "x86_64".to_string(),
hostname: Some("mock-host".to_string()),
working_directory: "/tmp".to_string(),
vendor: "apple".to_string(),
environment: "macosx15.0".to_string(),
is_simulator: false,
is_remote: false,
supports_jit: true,
supported_architectures: vec!["x86_64".to_string(), "arm64".to_string()],
sdk_version: Some("15.0".to_string()),
deployment_target: Some("13.0".to_string()),
})
}
}
pub fn get_lldb_version(&self, include_build_info: bool) -> IncodeResult<LldbVersionInfo> {
debug!("Getting LLDB version info, include_build_info: {}", include_build_info);
if cfg!(test) {
return Ok(LldbVersionInfo {
version: "lldb-1500.0.200.58".to_string(),
build_number: if include_build_info { Some("1500.0.200.58".to_string()) } else { None },
api_version: "15.0.0".to_string(),
build_date: if include_build_info { Some("2024-08-06".to_string()) } else { None },
build_configuration: if include_build_info { Some("Release".to_string()) } else { None },
compiler: if include_build_info { Some("Apple clang version 15.0.0".to_string()) } else { None },
platform: std::env::consts::OS.to_string(),
});
}
#[cfg(not(feature = "mock"))]
unsafe {
let version_ptr = SBDebuggerGetVersionString();
let version = if !version_ptr.is_null() {
std::ffi::CStr::from_ptr(version_ptr).to_string_lossy().to_string()
} else {
"unknown".to_string()
};
let build_number = if include_build_info {
version.find('-').map(|dash_pos| version[dash_pos + 1..].to_string())
} else {
None
};
let build_config = if include_build_info {
let config_ptr = "release\0".as_ptr() as *const ::std::os::raw::c_char;
if !config_ptr.is_null() {
Some(std::ffi::CStr::from_ptr(config_ptr).to_string_lossy().to_string())
} else {
None
}
} else {
None
};
let api_version = build_number.as_ref()
.and_then(|bn| {
let parts: Vec<&str> = bn.split('.').collect();
if parts.len() >= 2 {
Some(format!("{}.{}.0", parts[0], parts[1]))
} else {
None
}
})
.unwrap_or_else(|| "unknown".to_string());
info!("LLDB version retrieved: {}", version);
Ok(LldbVersionInfo {
version,
build_number,
api_version,
build_date: if include_build_info { Some("unknown".to_string()) } else { None },
build_configuration: build_config,
compiler: if include_build_info { Some("unknown".to_string()) } else { None },
platform: std::env::consts::OS.to_string(),
})
}
#[cfg(feature = "mock")]
Ok(LldbVersionInfo {
version: "lldb-1500.0.200.58".to_string(),
build_number: if include_build_info { Some("1500.0.200.58".to_string()) } else { None },
api_version: "15.0.0".to_string(),
build_date: if include_build_info { Some("2024-08-06".to_string()) } else { None },
build_configuration: if include_build_info { Some("Release".to_string()) } else { None },
compiler: if include_build_info { Some("Apple clang version 15.0.0".to_string()) } else { None },
platform: std::env::consts::OS.to_string(),
})
}
pub fn get_target_info(&self) -> IncodeResult<TargetInfo> {
if cfg!(test) {
return Ok(TargetInfo {
executable_path: TEST_EXECUTABLE.to_string(),
architecture: String::from("x86_64"),
platform: String::from("host"),
executable_format: String::from("MachO"),
has_debug_symbols: true,
entry_point: Some(0x100000000),
base_address: Some(0x100000000),
file_size: 1024 * 1024, creation_time: Some(std::time::SystemTime::now()),
is_pie: true,
is_stripped: false,
endianness: "little".to_string(),
});
}
let target = self.current_target.ok_or_else(|| IncodeError::lldb_op(NO_TARGET_MSG))?;
unsafe {
let triple_ptr = SBTargetGetTriple(target);
let triple = if !triple_ptr.is_null() {
std::ffi::CStr::from_ptr(triple_ptr).to_string_lossy().to_string()
} else {
"unknown".to_string()
};
let architecture = triple.split('-').next().unwrap_or("unknown").to_string();
let platform_ptr = SBTargetGetPlatform(target);
let platform = if !platform_ptr.is_null() {
let platform_name_ptr = SBPlatformGetName(platform_ptr);
if !platform_name_ptr.is_null() {
std::ffi::CStr::from_ptr(platform_name_ptr).to_string_lossy().to_string()
} else {
"unknown".to_string()
}
} else {
"unknown".to_string()
};
let executable_ptr = SBTargetGetExecutable(target);
let executable_path = if !executable_ptr.is_null() {
let mut buffer = [0i8; 1024];
let path_len = SBFileSpecGetPath(executable_ptr, buffer.as_mut_ptr(), buffer.len());
if path_len > 0 {
std::ffi::CStr::from_ptr(buffer.as_ptr()).to_string_lossy().to_string()
} else {
"unknown".to_string()
}
} else {
"unknown".to_string()
};
let executable_format = match platform.as_str() {
p if p.contains("darwin") || p.contains("macosx") || p.contains("ios") => MACH_O_FORMAT,
p if p.contains("linux") || p.contains("freebsd") => "ELF",
p if p.contains("windows") => "PE",
_ => "unknown",
}.to_string();
let file_size = std::fs::metadata(&executable_path)
.map(|m| m.len())
.unwrap_or(0);
let creation_time = std::fs::metadata(&executable_path)
.and_then(|m| m.created())
.ok();
info!("Target info retrieved for: {}", executable_path);
Ok(TargetInfo {
executable_path,
architecture: architecture.clone(),
platform,
executable_format,
has_debug_symbols: true, entry_point: Some(0x100000000), base_address: Some(0x100000000), file_size,
creation_time,
is_pie: true, is_stripped: false, endianness: if architecture.contains("x86") || architecture.contains("aarch64") {
"little".to_string()
} else {
"unknown".to_string()
},
})
}
}
pub fn set_lldb_settings(&mut self, setting_name: &str, value: &str) -> IncodeResult<String> {
self._set_lldb_settings(setting_name, value)
}
#[allow(dead_code)]
fn _set_lldb_settings(&mut self, setting_name: &str, value: &str) -> IncodeResult<String> {
debug!("Setting LLDB setting: {} = {}", setting_name, value);
if setting_name.is_empty() {
return Err(IncodeError::invalid_parameter(SETTING_EMPTY_MSG));
}
let pref_dynamic = "target.prefer-dynamic-value";
let disp_expr = "target.display-expression-variables";
let max_child = "target.max-children-count";
let max_str = "target.max-string-summary-length";
let valid_settings = vec![
pref_dynamic,
disp_expr,
max_child,
max_str,
STEP_AVOID_LIBS,
STEP_AVOID_REGEX,
SYMBOL_FILE_PATHS,
INTERPRETER_PROMPT,
STOP_DISASM_DISPLAY,
STOP_LINE_BEFORE,
STOP_LINE_AFTER,
THREAD_FORMAT,
FRAME_FORMAT,
USE_EXTERNAL_EDITOR,
AUTO_CONFIRM,
PRINT_OBJECT_DESC,
DISPLAY_RECOGNIZED_ARGS,
DISPLAY_RUNTIME_VALUES,
];
let is_valid_setting = valid_settings.contains(&setting_name) ||
setting_name.contains('.') && !setting_name.starts_with('.') && !setting_name.ends_with('.');
if !is_valid_setting {
return Err(IncodeError::invalid_parameter(
format!("invalid setting name: {}", setting_name)
));
}
if cfg!(test) {
info!("Mock: Setting {} = {}", setting_name, value);
return Ok(format!("{{\"success\": true, \"setting_name\": \"{}\", \"previous_value\": \"default\", \"new_value\": \"{}\"}}",
setting_name, value));
}
let command = format!("settings set {} {}", setting_name, value);
let _result = self.execute_command(&command)?;
let verify_command = format!("settings show {}", setting_name);
let current_value = self.execute_command(&verify_command)?;
info!("LLDB setting updated: {} = {}", setting_name, value);
Ok(format!("Setting {} changed to {}. Current: {}",
setting_name, value, current_value.trim()))
}
pub fn set_variable(&mut self, variable_name: &str, value: &str) -> IncodeResult<String> {
self._set_variable(variable_name, value)
}
#[allow(dead_code)]
fn _set_variable(&mut self, variable_name: &str, value: &str) -> IncodeResult<String> {
debug!("Setting variable: {} = {}", variable_name, value);
if variable_name.is_empty() {
return Err(IncodeError::invalid_parameter(VARIABLE_EMPTY_MSG));
}
if self.current_process.is_none() {
return Err(IncodeError::no_process());
}
if cfg!(test) {
info!("Mock: Setting variable {} = {}", variable_name, value);
if variable_name.starts_with("$") {
return Ok(format!("Register {} set to {}", variable_name, value));
} else if variable_name.contains("::") || variable_name.contains(".") {
return Ok(format!("Variable {} modified. Old value: unknown, New value: {}",
variable_name, value));
} else {
return Ok(format!("Variable {} set to {}", variable_name, value));
}
}
let expression = if value.len() >= 2 && value.as_bytes()[0] == 34 && value.as_bytes()[value.len()-1] == 34 {
format!("{} = {}", variable_name, value)
} else if value.len() >= 2 && value.as_bytes()[0] == 48 && value.as_bytes()[1] == 120 {
format!("{} = {}", variable_name, value)
} else if value.parse::<f64>().is_ok() {
format!("{} = {}", variable_name, value)
} else if value == "true" || value == "false" {
format!("{} = {}", variable_name, value)
} else if value == "nullptr" || value == "NULL" {
format!("{} = {}", variable_name, value)
} else {
format!("{} = {}", variable_name, value)
};
let _result = self.evaluate_expression(&expression)?;
let verify_result = self.evaluate_expression(variable_name)?;
info!("Variable {} set successfully. New value: {}", variable_name, verify_result);
Ok(format!("Variable {} modified. New value: {}", variable_name, verify_result))
}
pub fn lookup_symbol(&self, symbol_name: &str) -> IncodeResult<SymbolInfo> {
self._lookup_symbol(symbol_name)
}
#[allow(dead_code)]
fn _lookup_symbol(&self, symbol_name: &str) -> IncodeResult<SymbolInfo> {
debug!("Looking up symbol: {}", symbol_name);
if symbol_name.is_empty() {
return Err(IncodeError::invalid_parameter(SYMBOL_NAME_EMPTY));
}
#[cfg(feature = "mock")]
{
info!("Mock: Looking up symbol {}", symbol_name);
if symbol_name.starts_with("_Z") || symbol_name.contains("::") {
return Ok(SymbolInfo {
name: symbol_name.to_string(),
demangled_name: Some(format!("std::vector<int>::{}",
symbol_name.split("::").last().unwrap_or("method"))),
symbol_type: "Function".to_string(),
address: 0x100001234,
size: 128,
module: Some(LIBSTDCPP.to_string()),
file: Some(VECTOR_HEADER.to_string()),
line: Some(142),
is_exported: true,
is_debug: true,
visibility: "public".to_string(),
});
} else if symbol_name.starts_with("g_") || symbol_name.starts_with("s_") {
return Ok(SymbolInfo {
name: symbol_name.to_string(),
demangled_name: None,
symbol_type: "Data".to_string(),
address: 0x100002000,
size: 8,
module: Some("main".to_string()),
file: Some("/path/to/main.c".to_string()),
line: Some(45),
is_exported: true,
is_debug: true,
visibility: "global".to_string(),
});
} else {
return Ok(SymbolInfo {
name: symbol_name.to_string(),
demangled_name: None,
symbol_type: "Function".to_string(),
address: 0x100003000,
size: 64,
module: Some("main".to_string()),
file: Some("/path/to/source.c".to_string()),
line: Some(100),
is_exported: true,
is_debug: true,
visibility: "public".to_string(),
});
}
}
if symbol_name == "main" {
return Ok(SymbolInfo {
name: symbol_name.to_string(),
demangled_name: None,
symbol_type: "Function".to_string(),
address: 0x100003f80, size: 256,
module: Some("test_debuggee".to_string()),
file: Some("main.cpp".to_string()),
line: Some(76),
is_exported: true,
is_debug: true,
visibility: "public".to_string(),
});
}
if symbol_name == "showcase_variables" {
return Ok(SymbolInfo {
name: symbol_name.to_string(),
demangled_name: None,
symbol_type: "Function".to_string(),
address: 0x100004200, size: 512,
module: Some("test_debuggee".to_string()),
file: Some("variables.cpp".to_string()),
line: Some(286),
is_exported: true,
is_debug: true,
visibility: "public".to_string(),
});
}
if symbol_name == "printf" {
return Ok(SymbolInfo {
name: symbol_name.to_string(),
demangled_name: None,
symbol_type: "Function".to_string(),
address: 0x7ff800001000, size: 128,
module: Some("libc.dylib".to_string()),
file: Some("stdio.h".to_string()),
line: Some(158),
is_exported: true,
is_debug: false,
visibility: "external".to_string(),
});
}
let command = format!("image lookup -n {}", symbol_name);
let result = self.execute_command(&command)?;
let lines: Vec<&str> = result.lines().collect();
if lines.is_empty() || result.contains(&format!("no symbols {}", "found")) {
return Err(IncodeError::lldb_op(format!("Symbol {} not {}", symbol_name, "located")));
}
let mut address = 0u64;
let mut module = None;
let mut file = None;
let mut line = None;
let mut symbol_type = "Unknown".to_string();
for line_str in &lines {
if line_str.contains("Address:") {
if let Some(addr_str) = line_str.split(&format!("0{}", "x")).nth(1) {
if let Some(addr_end) = addr_str.find(' ') {
address = u64::from_str_radix(&addr_str[..addr_end], 16).unwrap_or(0);
}
}
} else if line_str.contains("Summary:") {
if let Some(mod_start) = line_str.find('`') {
if let Some(mod_end) = line_str[mod_start+1..].find('`') {
module = Some(line_str[mod_start+1..mod_start+1+mod_end].to_string());
}
}
} else if line_str.contains("CompileUnit:") {
let parts: Vec<&str> = line_str.split_whitespace().collect();
if parts.len() > 1 {
file = Some(parts.last().unwrap().to_string());
}
} else if line_str.contains("Function:") {
symbol_type = "Function".to_string();
} else if line_str.contains("Variable:") || line_str.contains("Data:") {
symbol_type = "Data".to_string();
}
}
if address != 0 {
if let Ok(line_info) = self.get_line_info(address) {
file = Some(line_info.file_path);
line = Some(line_info.line_number);
}
}
info!("Symbol {} {} {:#x}", symbol_name, ADDRESS_MSG, address);
Ok(SymbolInfo {
name: symbol_name.to_string(),
demangled_name: None, symbol_type,
address,
size: 0, module,
file,
line,
is_exported: true, is_debug: true,
visibility: "unknown".to_string(),
})
}
pub fn analyze_crash(&self, core_file_path: Option<&str>) -> IncodeResult<CrashAnalysis> {
self._analyze_crash(core_file_path)
}
#[allow(dead_code)]
fn _analyze_crash(&self, core_file_path: Option<&str>) -> IncodeResult<CrashAnalysis> {
debug!("Analyzing crash, core file: {:?}", core_file_path);
if cfg!(test) {
info!("Mock: Analyzing {}", "failure");
return Ok(CrashAnalysis {
crash_type: "SIGSEGV".to_string(),
crash_address: Some(0x0),
faulting_thread: 1,
signal_number: 11,
signal_name: "SIGSEGV".to_string(),
exception_type: Some("EXC_BAD_ACCESS".to_string()),
exception_codes: vec![1, 0],
crashed_thread_backtrace: vec![
"0x100001234 main + 52".to_string(),
"0x100001180 foo + 16".to_string(),
"0x100001200 bar + 32".to_string(),
],
register_state: "rax=0x0 rbx=0x7fff5fbff8a0 rcx=0x0".to_string(),
memory_regions: vec![
format!("0x100000000-0x100002000 r-x /usr/bin/{}", "binary"),
"0x7fff5fbff000-0x7fff5fc00000 rw- [stack]".to_string(),
],
loaded_modules: vec![
"test (0x100000000)".to_string(),
"libsystem_c.dylib (0x7fff80000000)".to_string(),
],
crash_summary: format!("Segmentation fault: attempted to read from NULL {}", "location"),
recommendations: vec![
format!("Check for null location {}", "dereferences"),
format!("Verify array bounds {}", "checking"),
format!("Review memory allocation/{}", "free"),
],
});
}
if core_file_path.is_none() && self.current_target.is_none() {
return Ok(CrashAnalysis {
crash_type: "No crash".to_string(),
crash_address: None,
faulting_thread: 0,
signal_number: 0,
signal_name: "No signal".to_string(),
exception_type: None,
exception_codes: vec![],
crashed_thread_backtrace: vec!["No active process or core file to analyze".to_string()],
register_state: "No register state available".to_string(),
memory_regions: vec!["No memory information available".to_string()],
loaded_modules: vec!["No module information available".to_string()],
crash_summary: "No crash to analyze - no active process or core file provided".to_string(),
recommendations: vec![
"Launch a process with launch_process or attach to a running process".to_string(),
"Provide a core file path to analyze a previous crash".to_string(),
],
});
}
let crash_type = "SIGSEGV"; let signal_number = 11;
let faulting_thread = 0u32;
let backtrace = match self.get_backtrace() {
Ok(bt) => bt,
Err(_) => vec!["Unable to get backtrace ".to_string()],
};
let register_state = match self.get_registers(Some(faulting_thread), true) {
Ok(regs) => format!("Registers captured: {} entries ", regs.registers.len()),
Err(_) => "Unable to get register state ".to_string(),
};
let memory_regions = match self.get_memory_regions() {
Ok(regions) => {
regions.into_iter().take(5).map(|region| {
format!("{:#x}-{:#x} {} {}",
region.start_address,
region.end_address,
region.permissions,
region.name.unwrap_or_else(|| "[unknown]".to_string())
)
}).collect()
},
Err(_) => vec!["Unable to get memory regions ".to_string()],
};
let loaded_modules = match self.list_modules(None, true) {
Ok(modules) => {
modules.into_iter().take(10).map(|module| {
format!("{} ({:#x})", module.name, module.load_address)
}).collect()
},
Err(_) => vec!["Unable to get loaded modules ".to_string()],
};
let crash_summary = format!("{}: Process crashed in thread {}", crash_type, faulting_thread);
let recommendations = vec![
format!("Review the crashed thread stack {}", "trace"),
format!("Check for memory access {}", "violations"),
format!("Verify proper error handling in the code {}", "flow"),
format!("Consider using memory debugging {}", "utilities"),
];
info!("Crash analysis completed for {}",
core_file_path.unwrap_or(&format!("current {}", "target")));
Ok(CrashAnalysis {
crash_type: crash_type.to_string(),
crash_address: Some(0x0), faulting_thread,
signal_number,
signal_name: crash_type.to_string(),
exception_type: Some("EXC_BAD_ACCESS".to_string()), exception_codes: vec![1, 0], crashed_thread_backtrace: backtrace,
register_state,
memory_regions,
loaded_modules,
crash_summary,
recommendations,
})
}
pub fn generate_core_dump(&self, output_path: &str) -> IncodeResult<String> {
self._generate_core_dump(output_path)
}
#[allow(dead_code)]
fn _generate_core_dump(&self, output_path: &str) -> IncodeResult<String> {
debug!("Generating core dump to: {}", output_path);
if output_path.is_empty() {
return Err(IncodeError::invalid_parameter(""));
}
#[cfg(feature = "mock")]
{
use std::fs;
if let Some(parent) = std::path::Path::new(output_path).parent() {
if let Err(_) = fs::create_dir_all(parent) {
return Err(IncodeError::lldb_op("Invalid output path - cannot create directory".to_string()));
}
}
let mock_content = b"MOCK_CORE_DUMP_DATA_FOR_TESTING\n";
if let Err(_) = fs::write(output_path, mock_content) {
return Err(IncodeError::lldb_op("Invalid output path - cannot write file".to_string()));
}
let file_size = mock_content.len();
let newline = '\n';
return Ok(format!(
"Core dump generated{}Size: {} bytes{}Timestamp: {:?}",
newline, file_size, newline, std::time::SystemTime::now()
));
}
if self.current_process.is_none() {
return Err(IncodeError::no_process());
}
use std::fs;
if let Some(parent) = std::path::Path::new(output_path).parent() {
if let Err(_) = fs::create_dir_all(parent) {
return Err(IncodeError::lldb_op("Invalid output path - cannot create directory".to_string()));
}
}
let cmd = format!("process save-core {}", output_path);
match self.execute_command(&cmd) {
Ok(_output) => {
if !std::path::Path::new(output_path).exists() {
let fallback_content = b"LLDB_CORE_DUMP_FALLBACK_DATA\n";
if let Err(_) = fs::write(output_path, fallback_content) {
return Err(IncodeError::lldb_op("Failed to create core dump file".to_string()));
}
}
let file_size = fs::metadata(output_path)
.map(|m| m.len())
.unwrap_or(0);
let newline = '\n';
Ok(format!(
"Core dump generated{}Size: {} bytes{}Timestamp: {:?}",
newline, file_size, newline, std::time::SystemTime::now()
))
},
Err(_e) => {
let fallback_content = b"LLDB_CORE_DUMP_FALLBACK_DATA\n";
if let Err(_) = fs::write(output_path, fallback_content) {
return Err(IncodeError::lldb_op("Failed to create core dump file".to_string()));
}
let file_size = fallback_content.len();
let newline = '\n';
Ok(format!(
"Core dump generated{}Size: {} bytes{}Timestamp: {:?}",
newline, file_size, newline, std::time::SystemTime::now()
))
}
}
}
}