Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get thread activity from the OS #123

Merged
merged 1 commit into from
Jul 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions remoteprocess/examples/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn get_backtrace(pid: remoteprocess::Pid) -> Result<(), remoteprocess::Error> {
// Create a stack unwind object, and use it to get the stack for each thread
let unwinder = process.unwinder()?;
for thread in process.threads()?.iter() {
println!("Thread 0x{:0x} - {}", thread.id()?, if thread.active()? { "running" } else { "idle" });
println!("Thread {} - {}", thread.id()?, if thread.active()? { "running" } else { "idle" });

// lock the thread to get a consistent snapshot (unwinding will fail otherwise)
// Note: the thread will appear idle when locked, so wee are calling
Expand All @@ -24,8 +24,8 @@ fn get_backtrace(pid: remoteprocess::Pid) -> Result<(), remoteprocess::Error> {

// Lookup the current stack frame containing a filename/function/linenumber etc
// for the current address
unwinder.symbolicate(ip, &mut |sf| {
println!("{}", sf);
unwinder.symbolicate(ip, true, &mut |sf| {
println!("\t{}", sf);
})?;
}
}
Expand Down
2 changes: 1 addition & 1 deletion remoteprocess/src/dylib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ macro_rules! dlsym {
static $name: self::dylib::Symbol<unsafe extern fn($($t),*) -> $ret> =
self::dylib::Symbol {
name: concat!(stringify!($name), "\0"),
addr: ::std::sync::atomic::ATOMIC_USIZE_INIT,
addr: ::std::sync::atomic::AtomicUsize::new(0),
_marker: ::std::marker::PhantomData,
};
)*)
Expand Down
7 changes: 3 additions & 4 deletions remoteprocess/src/linux/gimli_unwinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use proc_maps;

use gimli::{EhFrame, BaseAddresses, Pointer, NativeEndian, EhFrameHdr};
use goblin::elf::program_header::*;
use gimli;

use gimli::EndianRcSlice;
type RcReader = EndianRcSlice<NativeEndian>;
Expand Down Expand Up @@ -166,11 +165,11 @@ impl Unwinder {
Ok(Cursor{registers: thread.registers()?, parent: self, initial_frame: true})
}

pub fn symbolicate(&self, addr: u64, callback: &mut FnMut(&StackFrame)) -> Result<(), Error> {
pub fn symbolicate(&self, addr: u64, line_info: bool, callback: &mut FnMut(&StackFrame)) -> Result<(), Error> {
let binary = match self.get_binary(addr) {
Some(binary) => binary,
None => {
return Err(gimli::Error::NoUnwindInfoForAddress.into());
return Err(Error::NoBinaryForAddress(addr));
}
};
if binary.filename != "[vdso]" {
Expand All @@ -180,7 +179,7 @@ impl Unwinder {
*symbols = Some(SymbolData::new(&binary.filename, binary.offset));
}
match symbols.as_ref() {
Some(Ok(symbols)) => symbols.symbolicate(addr, callback),
Some(Ok(symbols)) => symbols.symbolicate(addr, line_info, callback),
_ => {
// we probably failed to load the symbols (maybe goblin v0.15 dependency causing error
// in gimli/object crate). Rather than fail add a stub
Expand Down
13 changes: 8 additions & 5 deletions remoteprocess/src/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ pub mod libunwind;
mod gimli_unwinder;
#[cfg(unwind)]
mod symbolication;
use libc::{c_void, pid_t};
use libc::pid_t;
#[cfg(unwind)]
use libc::c_void;

use nix::{self, sys::wait, sys::ptrace, {sched::{setns, CloneFlags}}};
use std::io::Read;
Expand All @@ -25,6 +27,7 @@ pub use self::libunwind::{LibUnwind};
use read_process_memory::{CopyAddress};

pub type Pid = pid_t;
pub type Tid = pid_t;

pub struct Process {
pub pid: Pid,
Expand Down Expand Up @@ -106,8 +109,8 @@ impl super::ProcessMemory for Process {
}

impl Thread {
pub fn new(threadid: i32) -> Thread{
Thread{tid: nix::unistd::Pid::from_raw(threadid)}
pub fn new(threadid: i32) -> Result<Thread, Error> {
Ok(Thread{tid: nix::unistd::Pid::from_raw(threadid)})
}

pub fn lock(&self) -> Result<ThreadLock, Error> {
Expand All @@ -128,8 +131,8 @@ impl Thread {
}
}

pub fn id(&self) -> Result<u64, Error> {
Ok(self.tid.as_raw() as u64)
pub fn id(&self) -> Result<Tid, Error> {
Ok(self.tid.as_raw())
}

pub fn active(&self) -> Result<bool, Error> {
Expand Down
51 changes: 28 additions & 23 deletions remoteprocess/src/linux/symbolication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,36 +51,41 @@ impl SymbolData {
Ok(SymbolData{ctx, offset, dynamic_symbols, symbols, filename: filename.to_owned()})
}

pub fn symbolicate(&self, addr: u64, callback: &mut FnMut(&StackFrame)) -> Result<(), Error> {
pub fn symbolicate(&self, addr: u64, line_info: bool, callback: &mut FnMut(&StackFrame)) -> Result<(), Error> {
let mut ret = StackFrame{line:None, filename: None, function: None, addr, module: self.filename.clone()};

// get the address before relocations
let offset = addr - self.offset;
let mut has_debug_info = false;

// addr2line0.8 uses an older version of gimli (0.0.19) than we are using here (0.0.21),
// this means we can't use the type of the error returned ourselves here since the
// type alias is private. hack by re-mapping the error
let error_handler = |e| Error::Other(format!("addr2line error: {:?}", e));

// if we have debugging info, get the appropiate stack frames for the adresss
let mut frames = self.ctx.find_frames(offset).map_err(error_handler)?;
while let Some(frame) = frames.next().map_err(error_handler)? {
has_debug_info = true;
if let Some(func) = frame.function {
ret.function = Some(func.raw_name().map_err(error_handler)?.to_string());
}
if let Some(loc) = frame.location {
ret.line = loc.line;
if let Some(file) = loc.file.as_ref() {
ret.filename = Some(file.to_string());

// if we are being asked for line information, sue gimli addr2line to look up the debug info
// (this is slow, and not necessary all the time which is why we are skipping)
if line_info {
let mut has_debug_info = false;

// addr2line0.8 uses an older version of gimli (0.0.19) than we are using here (0.0.21),
// this means we can't use the type of the error returned ourselves here since the
// type alias is private. hack by re-mapping the error
let error_handler = |e| Error::Other(format!("addr2line error: {:?}", e));

// if we have debugging info, get the appropiate stack frames for the adresss
let mut frames = self.ctx.find_frames(offset).map_err(error_handler)?;
while let Some(frame) = frames.next().map_err(error_handler)? {
has_debug_info = true;
if let Some(func) = frame.function {
ret.function = Some(func.raw_name().map_err(error_handler)?.to_string());
}
if let Some(loc) = frame.location {
ret.line = loc.line;
if let Some(file) = loc.file.as_ref() {
ret.filename = Some(file.to_string());
}
}
callback(&ret);
}
callback(&ret);
}

if has_debug_info {
return Ok(())
if has_debug_info {
return Ok(())
}
}

// otherwise try getting the function name from the symbols
Expand Down
13 changes: 11 additions & 2 deletions remoteprocess/src/osx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub use self::unwinder::Unwinder;
use libproc::libproc::proc_pid::{pidpath, pidinfo, PIDInfo, PidInfoFlavor};

pub type Pid = pid_t;
pub type Tid = u32;

pub struct Process {
pub pid: Pid,
Expand All @@ -35,7 +36,7 @@ pub struct Process {

#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub struct Thread {
pub tid: u32
pub tid: Tid
}

impl Process {
Expand Down Expand Up @@ -95,7 +96,15 @@ use self::mach_thread_bindings::{thread_info, thread_basic_info, thread_identifi


impl Thread {
pub fn id(&self) -> Result<u64, Error> {
pub fn new(tid: Tid) -> Result<Thread, Error> {
Ok(Thread{tid})
}

pub fn id(&self) -> Result<Tid, Error> {
Ok(self.tid)
}

pub fn thread_handle(&self) -> Result<u64, Error> {
let thread_id = self.get_thread_identifier_info()?;
Ok(thread_id.thread_handle)
}
Expand Down
2 changes: 1 addition & 1 deletion remoteprocess/src/osx/unwinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl Unwinder {
Ok(Cursor{registers: thread.registers()?, parent: self, initial_frame: true})
}

pub fn symbolicate(&self, addr: u64, callback: &mut FnMut(&StackFrame)) -> Result<(), Error> {
pub fn symbolicate(&self, addr: u64, _line_info: bool, callback: &mut FnMut(&StackFrame)) -> Result<(), Error> {
// Get the symbols for the current address
let symbol = unsafe { self.cs.resolve(addr) };

Expand Down
115 changes: 115 additions & 0 deletions remoteprocess/src/windows/generate_syscall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
""" Generates the syscall.rs code """
import argparse
import os
import sys
import json

# We need to map to major/minor/build to the names used in the syscall table
# https://www.gaijin.at/en/infos/windows-version-numbers
OS_VERSION_LOOKUP = {
("Windows Vista", "SP0"): (6, 0, 6000),
("Windows Vista", "SP1"): (6, 0, 6001),
("Windows Vista", "SP2"): (6, 0, 6002),
("Windows 7", "SP0"): (6, 1, 7600),
("Windows 7", "SP1"): (6, 1, 7601),
("Windows 8", "8.0"): (6, 2, 9200),
("Windows 8", "8.1"): (6, 3, 9600),
("Windows 10", "1507"): (10, 0, 10240),
("Windows 10", "1511"): (10, 0, 10586),
("Windows 10", "1607"): (10, 0, 14393),
("Windows 10", "1703"): (10, 0, 15063),
("Windows 10", "1709"): (10, 0, 16299),
("Windows 10", "1803"): (10, 0, 17134),
("Windows 10", "1809"): (10, 0, 17763),
("Windows 10", "1903"): (10, 0, 18362),
}

# I'm not supporting anything prior to vista here
UNSUPPORTED_OS = {"Windows XP", "Windows Server 2003"}

# these seem to have the same NT syscall as the consumer versions
SERVER_OS = {"Windows Server 2008", "Windows Server 2012"}

# how different are the syscall tables here?
OS_SERVER_LOOKUP = {
("Windows Server 2008", "SP0"): (6, 0, 6001),
("Windows Server 2008", "SP2"): (6, 0, 6002),
("Windows Server 2008", "R2"): (6, 1, 7600),
("Windows Server 2012", "SP0"): (6, 2, 9200),
("Windows Server 2012", "R2"): (6, 3, 9600),
("Windows Server 2008", "R2 SP1"): (6, 1, 7601),
}


def generate(inputpath, arch="x64", namefilter=lambda s: not s.startswith("NtWait")):
nt = json.load(open(os.path.join(inputpath, arch, "json", "nt-per-system.json")))
win32k = json.load(open(os.path.join(inputpath, arch, "json", "win32k-per-system.json")))
outputpath = os.path.dirname(os.path.realpath(__file__))
outputfilename = os.path.join(outputpath, f"syscalls_{arch}.rs")

syscallnames = set()
matches = []

for ((osname, osversion), (major, minor, build)) in OS_VERSION_LOOKUP.items():
syscalls = nt[osname][osversion]
syscalls.update(win32k[osname][osversion])
for syscallname, syscallnumber in syscalls.items():
if namefilter and namefilter(syscallname):
continue

matches.append((major, minor, build, syscallnumber, syscallname))
syscallnames.add(syscallname)

matches.sort()
syscallnames = sorted(syscallnames)

print(outputfilename)
with open(outputfilename, "w") as o:
o.write(f"// Automatically generated by '{__file__}' - do not edit\n")
if namefilter:
o.write("// Note that this is a subset of the syscalls, run again with no namefilter\n")
o.write("// for the complete list (which takes forever to compile)\n")
o.write("\n")
o.write("#[allow(non_camel_case_types)]\n\n")

o.write("pub enum Syscall {\n")
for syscall in syscallnames:
o.write(f" {syscall},\n")
o.write("}\n\n")

o.write("pub fn lookup_syscall(major: u32, minor: u32, build: u32, syscall: u32)")
o.write(" -> Option<Syscall> {\n")
o.write(" match (major, minor, build, syscall) {\n")
for major, minor, build, syscallnumber, syscallname in matches:
o.write(f" ({major}, {minor}, {build}, {syscallnumber})")
o.write(f" => Some(Syscall::{syscallname}),\n")
o.write(" _ => None\n")
o.write(" }\n")
o.write("}\n\n")


if __name__ == "__main__":

if sys.platform.startswith("win"):
default_syscall_path = os.path.join(os.getenv("userprofile"), "code", "windows-syscalls")
else:
default_syscall_path = os.path.join(os.getenv("HOME"), "code", "windows-syscalls")

parser = argparse.ArgumentParser(
description="generates syscall.rs", formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
"--syscallpath",
type=str,
default=default_syscall_path,
dest="syscallpath",
help="path to syscall repo https://github.com/j00ru/windows-syscalls",
)
args = parser.parse_args()

if not os.path.isdir(args.syscallpath):
print(f"Directory '{args.syscallpath}' doesn't exist!")
print("Pass a valid path to the j00ru/windows-syscalls in with --syscallpath <pathname>")
sys.exit(1)

generate(args.syscallpath)
Loading