Skip to content

Commit

Permalink
native extension profiling for windows
Browse files Browse the repository at this point in the history
Add support for profiling native extensions on Windows using
functions provided in dbghelp.dll for symoblication and the
StackWalk64 api for unwinding native frames
  • Loading branch information
benfred committed Jan 28, 2019
1 parent c17720b commit d3292e8
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 59 deletions.
3 changes: 1 addition & 2 deletions remoteprocess/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ Features:
- Getting a stack trace for a thread in the target process
- Resolve symbols for an address in the other process

This crate provides implementations for Linux, OSX and Windows. Note that currently the stack trace
functionality is currently limited to 64-bit Linux and OSX.
This crate provides implementations for Linux, OSX and Windows.

## Usage

Expand Down
7 changes: 6 additions & 1 deletion remoteprocess/examples/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ fn get_backtrace(pid: remoteprocess::Pid) -> Result<(), remoteprocess::Error> {
let unwinder = process.unwinder()?;
for (i, thread) in process.threads()?.iter().enumerate() {
let thread = *thread;
#[cfg(unix)]
println!("Thread {} ({})", i, thread);

// TODO: normalize this using GetThreadId or something
#[cfg(windows)]
println!("Thread {}", i);

/* TODO: cross pross thread status
let threadid = get_thread_identifier_info(thread)?;
let threadstatus = get_thread_basic_info(thread)?;
Expand Down Expand Up @@ -49,7 +54,7 @@ fn main() {
std::process::id()
};

if let Err(e) = get_backtrace(pid as i32) {
if let Err(e) = get_backtrace(pid as remoteprocess::Pid) {
println!("Failed to get backtrace {:?}", e);
}
}
2 changes: 1 addition & 1 deletion remoteprocess/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ impl From<osx::compact_unwind::Error> for Error {
}
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct StackFrame {
pub line: Option<u64>,
pub filename: Option<String>,
Expand Down
61 changes: 40 additions & 21 deletions remoteprocess/src/windows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,46 @@
use winapi::um::processthreadsapi::OpenProcess;
use winapi::um::winnt::{PROCESS_QUERY_INFORMATION, WCHAR, HANDLE, PROCESS_VM_READ, PROCESS_SUSPEND_RESUME};
use winapi::shared::minwindef::{FALSE, DWORD, MAX_PATH, ULONG};
use winapi::um::handleapi::{INVALID_HANDLE_VALUE, CloseHandle};
use winapi::um::processthreadsapi::{OpenProcess, GetThreadId};
use winapi::um::winnt::{ACCESS_MASK, MAXIMUM_ALLOWED, PROCESS_QUERY_INFORMATION,
PROCESS_VM_READ, PROCESS_SUSPEND_RESUME, THREAD_QUERY_INFORMATION, THREAD_GET_CONTEXT,
WCHAR, HANDLE};
use winapi::shared::minwindef::{FALSE, DWORD, MAX_PATH, ULONG, TRUE};
use winapi::um::handleapi::{CloseHandle};
use winapi::um::winbase::QueryFullProcessImageNameW;
use std::ffi::OsString;
use std::os::windows::ffi::{OsStringExt};
use winapi::shared::ntdef::NTSTATUS;

pub use read_process_memory::{Pid, ProcessHandle};
pub use Pid as Tid;
pub use ProcessHandle as Tid;

use super::Error;

mod unwinder;
pub use self::unwinder::Unwinder;

pub struct Process {
pub pid: Pid,
pub handle: ProcessHandle
}

// using these undocumented api's seems to be the best way to suspend/resume a process
// on windows (using the toolhelp32snapshot api to get threads doesn't seem practical tbh)
// https://j00ru.vexillium.org/2009/08/suspending-processes-in-windows/
#[link(name="ntdll")]
extern "system" {
// using these undocumented api's seems to be the best way to suspend/resume a process
// on windows (using the toolhelp32snapshot api to get threads doesn't seem practical tbh)
// https://j00ru.vexillium.org/2009/08/suspending-processes-in-windows/
fn RtlNtStatusToDosError(status: NTSTATUS) -> ULONG;
fn NtSuspendProcess(process: HANDLE) -> NTSTATUS;
fn NtResumeProcess(process: HANDLE) -> NTSTATUS;

// Use NtGetNextThread to get process threads. This limits us to Windows Vista and above,
fn NtGetNextThread(process: HANDLE, thread: HANDLE, access: ACCESS_MASK, attritubes: ULONG, flags: ULONG, new_thread: *mut HANDLE) -> NTSTATUS;
}

impl Process {
pub fn new(pid: Pid) -> Result<Process, Error> {
// we can't just use try_into_prcess_handle here because we need some additional permissions
unsafe {
let handle = OpenProcess(PROCESS_VM_READ | PROCESS_SUSPEND_RESUME | PROCESS_QUERY_INFORMATION, FALSE, pid);
let handle = OpenProcess(PROCESS_VM_READ | PROCESS_SUSPEND_RESUME | PROCESS_QUERY_INFORMATION
| THREAD_QUERY_INFORMATION | THREAD_GET_CONTEXT, FALSE, pid);
if handle == (0 as std::os::windows::io::RawHandle) {
return Err(Error::from(std::io::Error::last_os_error()));
}
Expand All @@ -58,27 +67,35 @@ impl Process {
}

pub fn cwd(&self) -> Result<String, Error> {
let exe = self.exe()?;
if let Some(parent) = std::path::Path::new(&exe).parent() {
return Ok(parent.to_string_lossy().into_owned());
}
Ok("/".to_owned())

// TODO: get the CWD.
// seems a little involved: http://wj32.org/wp/2009/01/24/howto-get-the-command-line-of-processes/
// steps:
// 1) NtQueryInformationProcess to get PebBaseAddress, which ProcessParameters
// is at some constant offset (+10 on 32 bit etc)
// 2) ReadProcessMemory to get RTL_USER_PROCESS_PARAMETERS struct
// 3) get CWD from the struct (has UNICODE_DATA object with ptr + length to CWD)

let exe = self.exe()?;
if let Some(parent) = std::path::Path::new(&exe).parent() {
return Ok(parent.to_string_lossy().into_owned());
}
Ok("/".to_owned())
}

pub fn threads(&self) -> Result<Vec<Tid>, Error> {
// TODO: lookup threads of the process
// the documented way is with toolhelp32snapshot, but that isn't practical
// (since it returns all threads for all systems and is insanely slow)
// NtGetNextThread / NtGetNextProcess seem to be the way to go
Ok(Vec::new())
let mut ret = Vec::new();
unsafe {
let mut current_thread: HANDLE = std::mem::zeroed();
while NtGetNextThread(self.handle, current_thread, MAXIMUM_ALLOWED, 0, 0,
&mut current_thread as *mut HANDLE) == 0 {
ret.push(current_thread);
}
}
Ok(ret)
}

pub fn unwinder(&self) -> Result<unwinder::Unwinder, Error> {
unwinder::Unwinder::new(self.handle)
}
}

Expand All @@ -88,7 +105,6 @@ impl Drop for Process {
}
}


pub struct Lock {
process: ProcessHandle
}
Expand Down Expand Up @@ -117,3 +133,6 @@ impl Drop for Lock {
}
}

pub fn get_thread_id(tid: Tid) -> u32 {
unsafe { GetThreadId(tid) }
}
Loading

0 comments on commit d3292e8

Please sign in to comment.