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

Macos support #32

Closed
wants to merge 6 commits into from
Closed
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
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
language: rust
cache: cargo

os:
- linux
# - osx

rust: stable

script:
- cd unwind && cargo build && (cargo run --example demo || true) && cargo run --example trace

env:
- RUST_BACKTRACE=pretty
8 changes: 7 additions & 1 deletion unwind/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ version = "0.1.0"
authors = ["main() <[email protected]>"]

[dependencies]
gimli = "0.17"
gimli = "0.18"
libc = "0.2"
fallible-iterator = "0.1"
log = "0.4"
backtrace = "0.3.15"

[target."cfg(target_os = \"macos\")".dependencies]
findshlibs = "0.4.0"
object = "0.11.0"
memmap = "0.7.0"

[build-dependencies]
gcc = "0.3.52"
Expand Down
2 changes: 1 addition & 1 deletion unwind/src/find_cfi/baremetal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn find_cfi_sections() -> Vec<EhRef> {
};
let eh_frame_end = &__ehframe_end as *const _ as u64;

cfi.push(EhRef {
cfi.push(EhRef::WithHeader {
text,
eh_frame_hdr,
eh_frame_end,
Expand Down
2 changes: 1 addition & 1 deletion unwind/src/find_cfi/ld.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ extern "C" fn callback(info: *const DlPhdrInfo, size: usize, data: *mut c_void)
.fold(0, |vaddr, x| cmp::max(vaddr, x.vaddr + x.memsz));
// This is an upper bound, not the exact address.
let eh_frame_end = (*info).addr + max_vaddr;
(*data).push(EhRef {
(*data).push(EhRef::WithHeader {
text: AddrRange { start: start_addr, end: start_addr + text.memsz },
eh_frame_hdr: AddrRange { start: eh_frame_hdr_start, end: eh_frame_hdr_start + eh_frame_hdr.memsz },
eh_frame_end,
Expand Down
66 changes: 66 additions & 0 deletions unwind/src/find_cfi/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
extern crate findshlibs;
extern crate object;
extern crate memmap;

use libc::{c_void, c_int, c_char};
use std::ffi::CStr;
use std::{slice, mem, cmp};
use range::AddrRange;
use super::EhRef;

use self::findshlibs::{SharedLibrary, Segment};
use self::object::{Object, ObjectSection};

extern "C" {
#[link_name = "\x01segment$start$__TEXT"]
static START_TEXT: usize;
#[link_name = "\x01segment$end$__TEXT"]
static STOP_TEXT: usize;

#[link_name = "\x01section$start$__DATA$__eh_frame"]
static START_EHFRAME: usize;
#[link_name = "\x01section$end$__DATA$__eh_frame"]
static STOP_EHFRAME: usize;
}

pub fn find_cfi_sections() -> Vec<EhRef> {
let mut cfi: Vec<EhRef> = Vec::new();

findshlibs::TargetSharedLibrary::each(|shlib| {
let text_seg = shlib.segments().find(|seg| seg.name() == CStr::from_bytes_with_nul(b"__TEXT\0").unwrap()).expect("No code in library???");

let text = AddrRange {
start: text_seg.actual_virtual_memory_address(shlib).0 as u64,
end: text_seg.actual_virtual_memory_address(shlib).0 as u64 + text_seg.len() as u64,
};

let file_bytes = unsafe { memmap::Mmap::map(&std::fs::File::open(shlib.name().to_str().unwrap()).unwrap()).unwrap() };

let object = match object::MachOFile::parse(&*file_bytes) {
Ok(object) => object,
Err(err) => {
if &file_bytes[0..4] == &[0xca, 0xfe, 0xba, 0xbe] {
//println!("can't parse fat mach-O file");
return;
}

println!("{}", shlib.name().to_str().unwrap());
println!("{:?}", err);
return;
}
};

// FIXME fix memory leak
let eh_frame: &'static [u8] = Box::leak(object.section_data_by_name(".eh_frame").unwrap().into_owned().into_boxed_slice());

cfi.push(EhRef::WithoutHeader {
text,
eh_frame: AddrRange { start: eh_frame.as_ptr() as u64, end: eh_frame.as_ptr() as u64 + eh_frame.len() as u64 },
});
});

println!("{:#?}", cfi);

trace!("CFI sections: {:?}", cfi);
cfi
}
22 changes: 16 additions & 6 deletions unwind/src/find_cfi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
use range::AddrRange;

#[derive(Debug)]
pub struct EhRef {
pub text: AddrRange,
pub eh_frame_hdr: AddrRange,
pub eh_frame_end: u64,
#[derive(Debug, Clone)]
pub enum EhRef {
WithHeader {
text: AddrRange,
eh_frame_hdr: AddrRange,
eh_frame_end: u64,
},
WithoutHeader {
text: AddrRange,
eh_frame: AddrRange,
},
}

#[cfg(unix)]
#[cfg(all(unix, not(target_os = "macos")))]
#[path = "ld.rs"]
mod imp;

#[cfg(target_os = "macos")]
#[path = "macos.rs"]
mod imp;

#[cfg(not(unix))]
#[path = "baremetal.rs"]
mod imp;
Expand Down
147 changes: 107 additions & 40 deletions unwind/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ extern crate gimli;
extern crate libc;
extern crate fallible_iterator;
#[macro_use] extern crate log;
extern crate backtrace;

use gimli::{UnwindSection, UnwindTable, UnwindTableRow, EhFrame, BaseAddresses, UninitializedUnwindContext, Pointer, Reader, EndianSlice, NativeEndian, CfaRule, RegisterRule, EhFrameHdr, ParsedEhFrameHdr, X86_64};
use fallible_iterator::FallibleIterator;

mod registers;
pub mod registers;
mod find_cfi;
mod range;
pub mod libunwind_shim;
Expand All @@ -31,6 +32,20 @@ pub struct StackFrame {
initial_address: u64,
}

impl StackFrame {
pub fn personality(&self) -> Option<u64> {
self.personality
}

pub fn lsda(&self) -> Option<u64> {
self.lsda
}

pub fn initial_address(&self) -> u64 {
self.initial_address
}
}

pub trait Unwinder: Default {
fn trace<F>(&mut self, f: F) where F: FnMut(&mut StackFrames);
}
Expand All @@ -39,45 +54,69 @@ type StaticReader = EndianSlice<'static, NativeEndian>;

struct ObjectRecord {
er: EhRef,
eh_frame_hdr: ParsedEhFrameHdr<StaticReader>,
eh_frame_hdr: Option<ParsedEhFrameHdr<StaticReader>>,
eh_frame: EhFrame<StaticReader>,
bases: BaseAddresses,
}

pub struct DwarfUnwinder {
cfi: Vec<ObjectRecord>,
ctx: Option<UninitializedUnwindContext<EhFrame<StaticReader>, StaticReader>>,
ctx: UninitializedUnwindContext<StaticReader>,
}

impl Default for DwarfUnwinder {
fn default() -> DwarfUnwinder {
let cfi = find_cfi::find_cfi_sections().into_iter().map(|er| {
unsafe {
// TODO: set_got()
let bases = BaseAddresses::default()
.set_eh_frame_hdr(er.eh_frame_hdr.start)
.set_text(er.text.start);
match er {
EhRef::WithHeader {
text,
eh_frame_hdr,
eh_frame_end,
} => unsafe {
// TODO: set_got()
let bases = BaseAddresses::default()
.set_eh_frame_hdr(eh_frame_hdr.start)
.set_text(text.start);

let eh_frame_hdr: &'static [u8] = std::slice::from_raw_parts(er.eh_frame_hdr.start as *const u8, er.eh_frame_hdr.len() as usize);
let eh_frame_hdr: &'static [u8] = std::slice::from_raw_parts(eh_frame_hdr.start as *const u8, eh_frame_hdr.len() as usize);

let eh_frame_hdr = EhFrameHdr::new(eh_frame_hdr, NativeEndian).parse(&bases, 8).unwrap();
let eh_frame_hdr = EhFrameHdr::new(eh_frame_hdr, NativeEndian).parse(&bases, 8).unwrap();

let eh_frame_addr = deref_ptr(eh_frame_hdr.eh_frame_ptr());
let eh_frame_sz = er.eh_frame_end.saturating_sub(eh_frame_addr);
let eh_frame_addr = deref_ptr(eh_frame_hdr.eh_frame_ptr());
let eh_frame_sz = eh_frame_end.saturating_sub(eh_frame_addr);

let eh_frame: &'static [u8] = std::slice::from_raw_parts(eh_frame_addr as *const u8, eh_frame_sz as usize);
trace!("eh_frame at {:p} sz {:x}", eh_frame_addr as *const u8, eh_frame_sz);
let eh_frame = EhFrame::new(eh_frame, NativeEndian);
let eh_frame: &'static [u8] = std::slice::from_raw_parts(eh_frame_addr as *const u8, eh_frame_sz as usize);
trace!("eh_frame at {:p} sz {:x}", eh_frame_addr as *const u8, eh_frame_sz);
let eh_frame = EhFrame::new(eh_frame, NativeEndian);

let bases = bases.set_eh_frame(eh_frame_addr);
let bases = bases.set_eh_frame(eh_frame_addr);

ObjectRecord { er, eh_frame_hdr, eh_frame, bases }
ObjectRecord { er, eh_frame_hdr: Some(eh_frame_hdr), eh_frame, bases }
}
EhRef::WithoutHeader {
text,
eh_frame,
} => unsafe {
// TODO: set_got()
let bases = BaseAddresses::default()
.set_text(text.start);

let eh_frame_addr = eh_frame.start as *const u8;
let eh_frame_sz = eh_frame.end as usize - eh_frame.start as usize;
let eh_frame: &'static [u8] = std::slice::from_raw_parts(eh_frame_addr, eh_frame_sz);
trace!("eh_frame at {:p} sz {:x}", eh_frame_addr as *const u8, eh_frame_sz);
let eh_frame = EhFrame::new(eh_frame, NativeEndian);

let bases = bases.set_eh_frame(eh_frame_addr as u64);

ObjectRecord { er, eh_frame_hdr: None, eh_frame, bases }
}
}
}).collect();

DwarfUnwinder {
cfi,
ctx: Some(UninitializedUnwindContext::new()),
ctx: UninitializedUnwindContext::new(),
}
}
}
Expand All @@ -96,45 +135,65 @@ struct UnwindInfo<R: Reader> {
personality: Option<Pointer>,
lsda: Option<Pointer>,
initial_address: u64,
ctx: UninitializedUnwindContext<EhFrame<StaticReader>, StaticReader>,
}

impl ObjectRecord {
fn unwind_info_for_address(&self,
ctx: UninitializedUnwindContext<EhFrame<StaticReader>, StaticReader>,
address: u64) -> gimli::Result<UnwindInfo<StaticReader>> {
fn unwind_info_for_address(
&self,
ctx: &mut UninitializedUnwindContext<StaticReader>,
address: u64,
) -> gimli::Result<UnwindInfo<StaticReader>> {
let &ObjectRecord {
ref eh_frame_hdr,
eh_frame: ref sel,
ref eh_frame,
ref bases,
..
} = self;

let fde = eh_frame_hdr.table().unwrap()
.lookup_and_parse(address, bases, sel.clone(), |offset| sel.cie_from_offset(bases, offset))?;

let fde;
let mut result_row = None;
let mut ctx = ctx.initialize(fde.cie()).unwrap();

{
let mut table = UnwindTable::new(&mut ctx, &fde);
while let Some(row) = table.next_row()? {
if row.contains(address) {
result_row = Some(row.clone());
break;
use gimli::UnwindSection;

let mut entries = eh_frame.entries(bases);
backtrace::resolve(address as *mut _, |s| {
println!("address {:016x}: {:?}", address, s.name());
let (text, eh_frame) = match self.er { EhRef::WithoutHeader { text, eh_frame, .. } => (text, eh_frame), _ => panic!() };
println!("bases {:?}", bases);
println!("text {:016x} .. {:016x}", text.start, text.end);
println!("eh_frame {:016x} .. {:016x}", eh_frame.start, eh_frame.end);
});
let mut i = 0;
while let Some(entry) = entries.next()? {
match entry {
gimli::CieOrFde::Cie(_) => {}
gimli::CieOrFde::Fde(partial) => {
let fde = partial.parse(EhFrame::cie_from_offset).unwrap();
println!("fde {:016x} .. {:016x}", fde.initial_address(), fde.initial_address() + fde.len());
//println!("{:?}", fde);
}
}
i += 1;
if i > 10 {
break;
}
}

fde = eh_frame.fde_for_address(bases, address, EhFrame::cie_from_offset).unwrap();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It fails here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works on linux (after I hack it to use EhRef::WithoutHeader for linux too).

result_row = Some(eh_frame.unwind_info_for_address(
bases,
ctx,
address,
EhFrame::cie_from_offset,
)?);

match result_row {
Some(row) => Ok(UnwindInfo {
row,
ctx: ctx.reset(),
personality: fde.personality(),
lsda: fde.lsda(),
initial_address: fde.initial_address(),
}),
None => Err(gimli::Error::NoUnwindInfoForAddress)
None => panic!(),
}
}
}
Expand Down Expand Up @@ -196,11 +255,19 @@ impl<'a> FallibleIterator for StackFrames<'a> {
caller -= 1; // THIS IS NECESSARY
debug!("caller is 0x{:x}", caller);

let rec = self.unwinder.cfi.iter().filter(|x| x.er.text.contains(caller)).next().ok_or(gimli::Error::NoUnwindInfoForAddress)?;
println!("{:?}, {:?}", caller, self.unwinder.cfi.iter().map(|x| x.er.clone()).collect::<Vec<_>>());

let rec = self.unwinder.cfi
.iter()
.filter(|x| match x.er {
EhRef::WithoutHeader { text, .. } | EhRef::WithHeader { text, .. } => text.contains(caller),
})
.next()
.ok_or(gimli::Error::NoUnwindInfoForAddress)?;

println!("found");

let ctx = self.unwinder.ctx.take().unwrap_or_else(UninitializedUnwindContext::new);
let UnwindInfo { row, personality, lsda, initial_address, ctx } = rec.unwind_info_for_address(ctx, caller)?;
self.unwinder.ctx = Some(ctx);
let UnwindInfo { row, personality, lsda, initial_address } = rec.unwind_info_for_address(&mut self.unwinder.ctx, caller)?;

trace!("ok: {:?} (0x{:x} - 0x{:x})", row.cfa(), row.start_address(), row.end_address());
let cfa = match *row.cfa() {
Expand Down
1 change: 0 additions & 1 deletion unwind/src/unwind_helper.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really broke using nanoas editor.

asm (
".global unwind_trampoline \n"
"unwind_trampoline: \n"
Expand Down