384 lines
12 KiB
Rust
384 lines
12 KiB
Rust
use std::fmt::Write;
|
|
|
|
use runtime::{abi::SyscallFunction, abi_lib::SyscallRegister, rt::io::{OpenOptions, SeekFrom}};
|
|
|
|
use crate::tracer::{CommandTracer, SyscallTrace};
|
|
|
|
enum Arg {
|
|
Fd,
|
|
OptFd,
|
|
OptPid,
|
|
InByteSlice,
|
|
OutByteSlice,
|
|
OpenOptions,
|
|
Hex,
|
|
Dec,
|
|
Oct,
|
|
Bool,
|
|
ThinPointer,
|
|
U64Dec,
|
|
Seek,
|
|
Todo,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum Result {
|
|
ResUnit,
|
|
ResFd,
|
|
ResPid,
|
|
ResDec,
|
|
ResHex,
|
|
ResU32,
|
|
None,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct Formatter {
|
|
fun: &'static str,
|
|
args: &'static [Arg],
|
|
res: Result
|
|
}
|
|
|
|
impl Formatter {
|
|
const EMPTY: Self = Self {
|
|
fun: "",
|
|
args: &[],
|
|
res: Result::None
|
|
};
|
|
}
|
|
|
|
macro_rules! formatter_list {
|
|
(
|
|
[
|
|
$($syscall:ident => $name:ident ($($arg:ident),* $(,)?) $(-> $result:ident)?),+ $(,)?
|
|
]
|
|
) => {
|
|
const FORMATTERS: [Option<Formatter>; SYSCALL_MAX] = const {
|
|
let mut array = [None; SYSCALL_MAX];
|
|
|
|
$(
|
|
array[SyscallFunction::$syscall as usize] = Some(Formatter {
|
|
fun: stringify!($name),
|
|
args: &[$(Arg::$arg),*],
|
|
$(res: Result::$result,)?
|
|
..Formatter::EMPTY
|
|
});
|
|
)+
|
|
|
|
array
|
|
};
|
|
};
|
|
}
|
|
|
|
// TODO somehow sync this up with abi definition
|
|
const SYSCALL_MAX: usize = 256;
|
|
formatter_list!([
|
|
// System
|
|
GetRandom => get_random(Todo),
|
|
GetClock => get_clock(Todo) -> ResUnit,
|
|
Mount => mount(Todo) -> ResUnit,
|
|
Unmount => unmount(Todo) -> ResUnit,
|
|
LoadModule => load_module(OutByteSlice) -> ResUnit,
|
|
FilesystemControl => filesystem_control(OptFd, Hex, Todo) -> ResDec,
|
|
GetSystemInfo => get_system_info(Hex, Todo) -> ResDec,
|
|
|
|
// Memory management
|
|
MapMemory => map_memory(ThinPointer, Hex, Todo) -> ResHex,
|
|
UnmapMemory => unmap_memory(ThinPointer, Hex) -> ResUnit,
|
|
|
|
// Process/thread
|
|
SpawnThread => spawn_thread(Todo) -> ResU32,
|
|
WaitThread => wait_thread(Dec, Todo) -> ResUnit,
|
|
GetThreadOption => get_thread_option(Hex, Todo) -> ResDec,
|
|
SetThreadOption => set_thread_option(Hex, Todo) -> ResUnit,
|
|
GetProcessOption => get_process_option(OptPid, Hex, Todo) -> ResDec,
|
|
SetProcessOption => set_process_option(OptPid, Hex, Todo) -> ResUnit,
|
|
|
|
Nanosleep => nanosleep(Todo) -> ResUnit,
|
|
ExitSignal => exit_signal(Todo),
|
|
SendSignal => send_signal(Dec, Todo) -> ResUnit,
|
|
|
|
Mutex => mutex(ThinPointer, Todo) -> ResUnit,
|
|
StartSession => start_session() -> ResUnit,
|
|
|
|
// I/O
|
|
Open => open(OptFd, OutByteSlice, OpenOptions, Oct) -> ResFd,
|
|
Close => close(Fd) -> ResUnit,
|
|
Read => read(Fd, InByteSlice) -> ResDec,
|
|
Write => write(Fd, OutByteSlice) -> ResDec,
|
|
Seek => seek(Fd, Seek, Todo) -> ResUnit,
|
|
Truncate => truncate(Fd, U64Dec) -> ResUnit,
|
|
Fsync => fsync(Fd, Todo) -> ResUnit,
|
|
ReadAt => read_at(Fd, U64Dec, InByteSlice) -> ResDec,
|
|
WriteAt => write_at(Fd, U64Dec, OutByteSlice) -> ResDec,
|
|
GetFileOption => get_file_option(Fd, Hex, Todo) -> ResDec,
|
|
SetFileOption => set_file_option(Fd, Hex, Todo) -> ResUnit,
|
|
OpenDirectory => open_directory(OptFd, OutByteSlice) -> ResFd,
|
|
ReadDirectoryEntries => read_directory_entries(Fd, Todo) -> ResDec,
|
|
CreateDirectory => create_directory(OptFd, OutByteSlice, Oct) -> ResUnit,
|
|
CreateSymlink => create_symlink(OptFd, OutByteSlice, OutByteSlice) -> ResUnit,
|
|
ReadLink => read_link(OptFd, OutByteSlice, InByteSlice) -> ResDec,
|
|
Remove => remove(OptFd, OutByteSlice, Todo) -> ResUnit,
|
|
Rename => rename(Todo) -> ResUnit,
|
|
CloneFd => clone_fd(Fd, OptFd) -> ResFd,
|
|
UpdateMetadata => update_metadata(OptFd, OutByteSlice, Todo) -> ResUnit,
|
|
GetMetadata => get_metadata(OptFd, OutByteSlice, Todo, Bool) -> ResUnit,
|
|
DeviceRequest => device_request(Fd, Hex, Todo) -> ResDec,
|
|
|
|
// Misc I/O
|
|
CreateTimer => create_timer(Todo) -> ResFd,
|
|
CreatePid => create_pid(Todo) -> ResFd,
|
|
CreatePty => create_pty(Todo) -> ResFd,
|
|
CreateSharedMemory => create_shared_memory(Hex) -> ResFd,
|
|
CreatePipe => create_pipe(Todo) -> ResFd,
|
|
PollChannelWait => poll_channel_wait(Fd, Todo) -> ResUnit,
|
|
PollChannelControl => poll_channel_control(Fd, Todo) -> ResUnit,
|
|
|
|
// Network
|
|
CreateSocket => create_socket(Todo) -> ResFd,
|
|
Bind => bind(Fd, Todo) -> ResUnit,
|
|
Listen => listen(Fd) -> ResUnit,
|
|
Connect => connect(Fd, Todo) -> ResUnit,
|
|
Accept => accept(Fd, Todo) -> ResFd,
|
|
Shutdown => shutdown(Fd, Todo) -> ResUnit,
|
|
SendTo => send_to(Fd, OutByteSlice, Todo) -> ResDec,
|
|
ReceiveFrom => receive_from(Fd, InByteSlice, Todo) -> ResDec,
|
|
GetSocketOption => get_socket_option(Fd, Hex, Todo) -> ResDec,
|
|
SetSocketOption => set_socket_option(Fd, Hex, Todo) -> ResUnit,
|
|
SendMessage => send_message(Fd, Todo) -> ResDec,
|
|
ReceiveMessage => receive_message(Fd, Todo) -> ResDec,
|
|
|
|
// C compat
|
|
Fork => fork() -> ResPid,
|
|
Execve => execve(Todo) -> ResUnit,
|
|
|
|
// Debugging
|
|
DebugTrace => debug_trace(Dec, OutByteSlice),
|
|
DebugControl => debug_control(Dec, Hex, Todo) -> ResDec,
|
|
]);
|
|
|
|
impl Formatter {
|
|
fn print(&self, tracer: &mut CommandTracer, args: &[usize], result: usize) {
|
|
print!("{}(", self.fun);
|
|
let mut arg_index = 0;
|
|
for (i, arg) in self.args.iter().enumerate() {
|
|
if i != 0 {
|
|
print!(", ");
|
|
}
|
|
|
|
let Some((arg, len)) = arg.format(tracer, &args[arg_index..], result) else {
|
|
print!("<???>");
|
|
break;
|
|
};
|
|
arg_index += len;
|
|
|
|
print!("{arg}")
|
|
}
|
|
print!(")");
|
|
if let Some(result) = self.res.format(result) {
|
|
print!(" -> {result}");
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
|
|
const MAX_BYTES: usize = 32;
|
|
|
|
fn format_byte_slice(bytes: &[u8], real_len: usize, allow_ascii: bool) -> String {
|
|
let mut result = String::new();
|
|
if bytes.is_empty() {
|
|
return "<empty>".into();
|
|
}
|
|
if bytes[0].is_ascii_graphic() && allow_ascii {
|
|
// Format as ASCII string
|
|
write!(result, "\"").ok();
|
|
for &byte in bytes {
|
|
let ch = byte.escape_ascii();
|
|
write!(result, "{ch}").ok();
|
|
}
|
|
if bytes.len() < real_len {
|
|
write!(result, "...\" +{}", real_len - bytes.len()).ok();
|
|
} else {
|
|
write!(result, "\"").ok();
|
|
}
|
|
} else {
|
|
write!(result, "[").ok();
|
|
for (i, &byte) in bytes.iter().enumerate() {
|
|
if i != 0 {
|
|
write!(result, ", ").ok();
|
|
}
|
|
write!(result, "{byte:#04X}").ok();
|
|
}
|
|
if bytes.len() < real_len {
|
|
write!(result, "... +{}", real_len - bytes.len()).ok();
|
|
}
|
|
write!(result, "]").ok();
|
|
}
|
|
result
|
|
}
|
|
|
|
fn format_open_options(opts: OpenOptions) -> String {
|
|
const OPTS: &[(&str, OpenOptions)] = &[
|
|
("READ", OpenOptions::READ),
|
|
("WRITE", OpenOptions::WRITE),
|
|
("APPEND", OpenOptions::APPEND),
|
|
("CREATE", OpenOptions::CREATE),
|
|
("CREATE_EXCL", OpenOptions::CREATE_EXCL),
|
|
("TRUNCATE", OpenOptions::TRUNCATE),
|
|
];
|
|
let mut result = String::new();
|
|
|
|
let mut i = 0;
|
|
for &(name, opt) in OPTS {
|
|
if opts.contains(opt) {
|
|
if i != 0 {
|
|
write!(result, "|").ok();
|
|
}
|
|
write!(result, "{name}").ok();
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
if result.is_empty() {
|
|
"<empty>".into()
|
|
} else {
|
|
result
|
|
}
|
|
}
|
|
|
|
impl Arg {
|
|
fn format(
|
|
&self,
|
|
tracer: &mut CommandTracer,
|
|
args: &[usize],
|
|
result: usize,
|
|
) -> Option<(String, usize)> {
|
|
match self {
|
|
Arg::Dec => Some((format!("{}", args[0]), 1)),
|
|
Arg::Hex => Some((format!("{:#x}", args[0]), 1)),
|
|
Arg::Oct => Some((format!("{:#o}", args[0]), 1)),
|
|
#[cfg(any(rust_analyzer, target_pointer_width = "64"))]
|
|
Arg::U64Dec => {
|
|
Some((format!("{}", args[0]), 1))
|
|
}
|
|
Arg::Bool => {
|
|
let result = if args[0] == 0 {
|
|
"false".into()
|
|
} else {
|
|
"true".into()
|
|
};
|
|
Some((result, 1))
|
|
}
|
|
Arg::Fd => Some((format!("{}", args[0]), 1)),
|
|
Arg::OptPid => {
|
|
let result = if args[0] == usize::MAX {
|
|
"<self>".into()
|
|
} else {
|
|
format!("#{}", args[0])
|
|
};
|
|
Some((result, 1))
|
|
}
|
|
Arg::OptFd => Some((
|
|
if args[0] == usize::MAX {
|
|
"<none>".into()
|
|
} else {
|
|
format!("{}", args[0])
|
|
},
|
|
1,
|
|
)),
|
|
Arg::OutByteSlice => {
|
|
let mut len = args[1];
|
|
if len > MAX_BYTES {
|
|
len = MAX_BYTES;
|
|
}
|
|
let mut buffer = vec![0; len];
|
|
if tracer.read_memory(args[0], &mut buffer).is_ok() {
|
|
Some((format_byte_slice(&buffer, args[1], true), 2))
|
|
} else {
|
|
Some((format!("<{} bytes @ {:#x}>", args[1], args[0]), 2))
|
|
}
|
|
}
|
|
Arg::InByteSlice => {
|
|
let mut len = result;
|
|
if len > MAX_BYTES {
|
|
len = MAX_BYTES;
|
|
}
|
|
let mut buffer = vec![0; len];
|
|
if tracer.read_memory(args[0], &mut buffer).is_ok() {
|
|
Some((format_byte_slice(&buffer, result, true), 2))
|
|
} else {
|
|
Some((
|
|
format!("<{}/{} bytes @ {:#x}>", result, args[1], args[0]),
|
|
2,
|
|
))
|
|
}
|
|
}
|
|
Arg::OpenOptions => {
|
|
let opts = unsafe { OpenOptions::from_raw(args[0] as u32) };
|
|
Some((format_open_options(opts), 1))
|
|
}
|
|
Arg::ThinPointer => {
|
|
let result = if args[0] == 0 {
|
|
"<null>".into()
|
|
} else {
|
|
format!("{:#x}", args[0])
|
|
};
|
|
Some((result, 1))
|
|
}
|
|
Arg::Seek => {
|
|
// TODO this will get encoded as two words in i686
|
|
let seek = SeekFrom::from_syscall_register(args[0]);
|
|
let result = match seek {
|
|
SeekFrom::Start(pos) => format!("={pos}"),
|
|
SeekFrom::End(pos) => format!("=END{:+}", -pos),
|
|
SeekFrom::Current(pos) => format!("{pos:+}"),
|
|
};
|
|
Some((result, 1))
|
|
}
|
|
Arg::Todo => {
|
|
Some(("...".into(), 1))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Result {
|
|
fn format(&self, result: usize) -> Option<String> {
|
|
Some(match self {
|
|
Self::ResDec | Self::ResFd | Self::ResU32 | Self::ResPid => {
|
|
if result & (1 << 63) != 0 {
|
|
"<error>".into()
|
|
} else {
|
|
format!("{result}")
|
|
}
|
|
}
|
|
Self::ResHex => {
|
|
if result & (1 << 63) != 0 {
|
|
"<error>".into()
|
|
} else {
|
|
format!("{result:#x}")
|
|
}
|
|
}
|
|
Self::ResUnit => {
|
|
if result == 0 {
|
|
"<ok>"
|
|
} else {
|
|
"<error>"
|
|
}.into()
|
|
}
|
|
_ => return None
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn format_syscall(tracer: &mut CommandTracer, syscall: &SyscallTrace) {
|
|
// Ignore mutex calls for now, they're too noisy
|
|
if syscall.function == SyscallFunction::Mutex as usize {
|
|
return;
|
|
}
|
|
|
|
if let Some(Some(formatter)) = FORMATTERS.get(syscall.function) {
|
|
formatter.print(tracer, &syscall.args, syscall.result);
|
|
}
|
|
}
|