Skip to content
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
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-11-arm
target: aarch64-pc-windows-msvc

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- name: Build
run: cargo build --target ${{ matrix.target }}

- name: Run tests
run: cargo test --target ${{ matrix.target }}
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

pub mod vlq;

#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
#[cfg(all(target_os = "windows", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod win64;

#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
#[cfg(all(target_os = "windows", any(target_arch = "x86_64", target_arch = "aarch64")))]
pub use win64::trace;

#[cfg(unix)]
Expand Down
95 changes: 92 additions & 3 deletions src/win64.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2025 Divy Srivastava <dj.srivastava23@gmail.com>
//
// https://github.com/littledivy/resym/blob/59db8fce68e8cf786b22dd37979afec70ddc363d/src/win64.rs
// Windows x86_64 and aarch64 stack unwinding support using RtlVirtualUnwind.

use crate::vlq::vlq_encode;

Expand All @@ -10,12 +10,14 @@ type DWORDLONG = u64;
type HMODULE = *mut u8;
type BOOL = i32;

#[cfg(target_arch = "x86_64")]
#[repr(C)]
struct M128A {
low: u64,
high: i64,
}

#[cfg(target_arch = "x86_64")]
#[repr(C, align(16))]
struct CONTEXT {
P1Home: DWORDLONG,
Expand Down Expand Up @@ -73,6 +75,87 @@ struct CONTEXT {
LastExceptionToRip: DWORDLONG,
LastExceptionFromRip: DWORDLONG,
}

#[cfg(target_arch = "x86_64")]
impl CONTEXT {
#[inline(always)]
fn ip(&self) -> DWORDLONG {
self.Rip
}

#[inline(always)]
fn sp(&self) -> DWORDLONG {
self.Rsp
}
}

#[cfg(target_arch = "aarch64")]
#[repr(C)]
struct ARM64_NT_NEON128 {
low: u64,
high: i64,
}

#[cfg(target_arch = "aarch64")]
#[repr(C, align(16))]
struct CONTEXT {
ContextFlags: DWORD,
Cpsr: DWORD,
X0: DWORDLONG,
X1: DWORDLONG,
X2: DWORDLONG,
X3: DWORDLONG,
X4: DWORDLONG,
X5: DWORDLONG,
X6: DWORDLONG,
X7: DWORDLONG,
X8: DWORDLONG,
X9: DWORDLONG,
X10: DWORDLONG,
X11: DWORDLONG,
X12: DWORDLONG,
X13: DWORDLONG,
X14: DWORDLONG,
X15: DWORDLONG,
X16: DWORDLONG,
X17: DWORDLONG,
X18: DWORDLONG,
X19: DWORDLONG,
X20: DWORDLONG,
X21: DWORDLONG,
X22: DWORDLONG,
X23: DWORDLONG,
X24: DWORDLONG,
X25: DWORDLONG,
X26: DWORDLONG,
X27: DWORDLONG,
X28: DWORDLONG,
Fp: DWORDLONG,
Lr: DWORDLONG,
Sp: DWORDLONG,
Pc: DWORDLONG,
V: [ARM64_NT_NEON128; 32],
Fpcr: DWORD,
Fpsr: DWORD,
Bcr: [DWORD; 8],
Bvr: [DWORDLONG; 8],
Wcr: [DWORD; 2],
Wvr: [DWORDLONG; 2],
}

#[cfg(target_arch = "aarch64")]
impl CONTEXT {
#[inline(always)]
fn ip(&self) -> DWORDLONG {
self.Pc
}

#[inline(always)]
fn sp(&self) -> DWORDLONG {
self.Sp
}
}

extern "system" {
fn GetModuleHandleExW(
dwFlags: DWORD,
Expand Down Expand Up @@ -105,7 +188,8 @@ pub fn trace() -> String {
RtlCaptureContext(&mut context);

loop {
let ip = context.Rip;
let ip = context.ip();
let sp = context.sp();
let mut base = 0;
let fn_entry =
RtlLookupFunctionEntry(ip, &mut base, std::ptr::null_mut());
Expand Down Expand Up @@ -137,7 +221,12 @@ pub fn trace() -> String {
&mut est_frame,
std::ptr::null_mut(),
);
if context.Rip == 0 {

// RtlVirtualUnwind indicates end of stack differently per arch:
// - x86_64: sets instruction pointer to 0
// - aarch64: leaves context unchanged (check if ip and sp are same)
let new_ip = context.ip();
if new_ip == 0 || (new_ip == ip && context.sp() == sp) {
break;
}
}
Expand Down
Loading