#![allow(non_snake_case)]
use std::convert::TryInto;
use std::mem;
use std::os::raw::*;
use std::ptr;
use std::slice;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use owoify::OwOifiable;
use goblin::elf;
use goblin::strtab;
use elf::dynamic::dyn64;
use elf::dynamic::*;
use elf::program_header::program_header64::ProgramHeader;
use elf::program_header::*;
use elf::reloc::reloc64;
use elf::sym::sym64;
static BORING_WRITE: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
static STDOUT_MACHINERY: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
static STDERR_MACHINERY: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
static UWU_STDOUT: AtomicBool = AtomicBool::new(true);
static UWU_STDERR: AtomicBool = AtomicBool::new(false);
const STDIO_NAMES: &[&str] = &[
"std::io::stdio::print_to",
"std::io::stdio::_print",
"std::io::stdio::_eprint",
];
fn grab_bt(machinery: &AtomicPtr<c_void>) -> (*mut c_void, backtrace::Backtrace) {
let mut cached_print_machinery = machinery.load(Ordering::Relaxed);
if cached_print_machinery.is_null() {
let bt = backtrace::Backtrace::new();
for fra in bt.frames() {
for sym in fra.symbols() {
let pretty = format!("{}", sym.name().unwrap());
if STDIO_NAMES.iter().any(|n| pretty.starts_with(n)) {
let print_addr = fra.symbol_address();
machinery.compare_and_swap(ptr::null_mut(), print_addr, Ordering::Relaxed);
cached_print_machinery = print_addr;
}
}
}
(cached_print_machinery, bt)
} else {
(
cached_print_machinery,
backtrace::Backtrace::new_unresolved(),
)
}
}
extern "C" fn write_uwu(fd: c_int, buf: *const c_void, count: usize) -> isize {
let write = BORING_WRITE.load(Ordering::Relaxed);
if write.is_null() {
std::process::abort();
}
let write: CWrite = unsafe { mem::transmute(write) };
let is_uwuable = match fd {
1 => UWU_STDOUT.load(Ordering::Relaxed),
2 => UWU_STDERR.load(Ordering::Relaxed),
_ => false,
};
if !is_uwuable {
return write(fd, buf, count);
}
let (cached_print_machinery, bt) = grab_bt(if fd == 1 {
&STDOUT_MACHINERY
} else if fd == 2 {
&STDERR_MACHINERY
} else {
std::process::abort();
});
let should_uwu = !cached_print_machinery.is_null()
&& bt
.frames()
.iter()
.any(|f| f.symbol_address() == cached_print_machinery);
if !should_uwu {
write(fd, buf, count)
} else {
let s: &[u8] = unsafe { slice::from_raw_parts(buf as *const u8, count) };
let s = std::str::from_utf8(s);
if let Ok(s) = s {
let uwu = s.to_string().owoify();
let uwu = uwu.as_bytes();
let mut writing = &uwu[..];
while !writing.is_empty() {
let wrote = write(fd, writing.as_ptr() as *const c_void, writing.len());
if wrote < 0 {
return wrote;
}
writing = &writing[wrote as usize..];
}
count as isize
} else {
write(fd, buf, count)
}
}
}
const PAGE_SIZE: usize = 0x1000;
pub fn install() -> Option<()> {
let write = find_write()?;
let writeaddr = unsafe { *write as *mut () };
let v = BORING_WRITE.compare_and_swap(ptr::null_mut(), writeaddr, Ordering::Relaxed);
if v.is_null() {
let write_page = (write as usize) & !(PAGE_SIZE - 1);
unsafe {
libc::mprotect(
write_page as *mut c_void,
PAGE_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
)
};
unsafe { *write = write_uwu };
Some(())
} else {
Some(())
}
}
pub fn uwu_stdout(should: bool) {
UWU_STDOUT.store(should, Ordering::Relaxed);
}
pub fn uwu_stderr(should: bool) {
UWU_STDERR.store(should, Ordering::Relaxed);
}
type CWrite = extern "C" fn(fd: c_int, buf: *const c_void, count: usize) -> isize;
macro_rules! auxval {
($name:ident, $cons:ident) => {
fn $name() -> usize {
unsafe { libc::getauxval(libc::$cons) as usize }
}
};
}
auxval!(ph_entries, AT_PHNUM);
auxval!(phdr_base, AT_PHDR);
#[cfg(target_pointer_width = "64")]
unsafe fn get_headers() -> &'static [ProgramHeader] {
ProgramHeader::from_raw_parts(phdr_base() as *const ProgramHeader, ph_entries())
}
fn find_write() -> Option<*mut CWrite> {
let headers = unsafe { get_headers() };
let phdr = headers.iter().find(|h| h.p_type == PT_PHDR)?;
let prog_base = phdr_base() - phdr.p_vaddr as usize;
let dynamic = unsafe { dyn64::from_phdrs(prog_base, headers) }?;
let mut rela = None;
let mut relasz = None;
let mut strtab = None;
let mut strtabsz = None;
let mut symtab = None;
for dynentry in dynamic {
let v = Some(dynentry.d_val);
match dynentry.d_tag {
DT_RELA => rela = v,
DT_RELASZ => relasz = v,
DT_STRTAB => strtab = v,
DT_STRSZ => strtabsz = v,
DT_SYMTAB => symtab = v,
_ => (),
}
}
let symtab = symtab? as *const sym64::Sym;
let rela = unsafe { reloc64::from_raw_rela(rela? as *const reloc64::Rela, relasz? as usize) };
let strtab = unsafe {
strtab::Strtab::from_raw(
strtab.unwrap() as *const u8,
strtabsz.unwrap() as usize,
0x0, )
};
let mut write = None;
for rel in rela.iter() {
let ridx = rel.r_info >> 32;
let sym = unsafe { *symtab.offset(ridx.try_into().ok()?) };
let name = strtab.get(sym.st_name as usize); if let Some(Ok(v)) = name {
if v != "write" {
continue;
}
} else {
continue;
}
write = unsafe {
Some(mem::transmute(
prog_base.checked_add(rel.r_offset as usize)?,
))
};
}
write
}
#[cfg(doctest)]
mod doctest {
use doc_comment::doctest;
doctest!("../README.md");
}