├── .gitignore ├── examples ├── test_bindgen.sh ├── test.hpp ├── inherit_test.rs └── test.rs ├── Cargo.toml ├── src ├── method_helpers.rs ├── parsers.rs ├── vtable.rs ├── lib.rs └── vtable │ └── dwarf.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/test_bindgen.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | bindgen --no-layout-tests test.hpp -o test.rs 3 | -------------------------------------------------------------------------------- /examples/test.hpp: -------------------------------------------------------------------------------- 1 | struct non_virtual { 2 | int value; 3 | int x(); 4 | non_virtual(int v); 5 | }; 6 | 7 | struct base { 8 | int value; 9 | virtual int x(); 10 | base(int v); 11 | }; 12 | 13 | struct derived : public base { 14 | virtual int x(); 15 | derived(int v); 16 | }; 17 | 18 | int call_x_on(base* x); 19 | -------------------------------------------------------------------------------- /examples/inherit_test.rs: -------------------------------------------------------------------------------- 1 | use cpp_inherit::*; 2 | 3 | include!("test.rs"); 4 | 5 | #[inherit_from(base)] 6 | #[derive(Debug)] 7 | struct Test {} 8 | 9 | #[inherit_from_impl(base, "test.hpp")] 10 | impl Test { 11 | fn new() -> Self { 12 | Self { 13 | _base: base { 14 | vtable_: Test::VTABLE_ as _, 15 | value: 3, 16 | }, 17 | } 18 | } 19 | 20 | #[overridden] 21 | fn x(&self) -> i32 { 22 | 99 23 | } 24 | } 25 | 26 | fn main() { 27 | let test = Test::new(); 28 | dbg!(test.value); 29 | dbg!(test.x()); 30 | } 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp-inherit" 3 | version = "0.1.1" 4 | authors = ["jam1garner <8260240+jam1garner@users.noreply.github.com>"] 5 | edition = "2018" 6 | license = "MIT" 7 | readme = "README.md" 8 | description = "Macros for enabling you to subclass a Rust struct from a C++ class" 9 | documentation = "https://docs.rs/cpp-inherit" 10 | repository = "https://github.com/jam1garner/cpp-inherit" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "1.0.35", features = ["full", "parsing", "extra-traits"]} 17 | quote = "1.0.7" 18 | object = "0.20" 19 | typed-arena = "2.0" 20 | gimli = "0.22" 21 | memmap = "0.7" 22 | -------------------------------------------------------------------------------- /src/method_helpers.rs: -------------------------------------------------------------------------------- 1 | use syn::{Attribute, ImplItemMethod}; 2 | 3 | fn is_override_attr(attr: &Attribute) -> bool { 4 | attr.path 5 | .get_ident() 6 | .map(|ident| ident.to_string() == "overridden") 7 | .unwrap_or(false) 8 | } 9 | 10 | pub fn filter_overrides(method: &mut ImplItemMethod) -> Option<&mut ImplItemMethod> { 11 | if method.attrs.iter().any(is_override_attr) { 12 | Some(method) 13 | } else { 14 | None 15 | } 16 | } 17 | 18 | pub fn remove_override_attr(method: &mut &mut ImplItemMethod) { 19 | method.attrs.retain(|attr| !is_override_attr(attr)); 20 | } 21 | 22 | pub fn make_extern_c(method: &mut &mut ImplItemMethod) { 23 | method.sig.abi.replace(syn::parse_quote!(extern "C")); 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpp-inherit 2 | A macro for inheriting Rust structures from C++ classes. Nothing of value lies here. 3 | 4 | ### Example 5 | 6 | ```rust 7 | use cpp_inherit::*; 8 | 9 | #[inherit_from(BaseType)] 10 | #[derive(Debug)] 11 | struct RustType {} 12 | 13 | #[inherit_from_impl(BaseType, "test.hpp")] 14 | impl RustType { 15 | fn new() -> Self { 16 | Self { 17 | _base: BaseType { vtable_: RustType::VTABLE_ as _, value: 3 } 18 | } 19 | } 20 | 21 | #[overridden] fn x(&self) -> i32 { 22 | 99 23 | } 24 | } 25 | 26 | // Now you can pass RustType as a BaseType, access any BaseType fields, call any BaseType methods (virtual or not), from either C++ or Rust 27 | ``` 28 | 29 | [Rest of example usage here](https://github.com/jam1garner/cpp-inherit-test) 30 | -------------------------------------------------------------------------------- /src/parsers.rs: -------------------------------------------------------------------------------- 1 | use syn::{Field, Ident, LitStr, Token}; 2 | 3 | pub struct InheritImplAttr { 4 | pub class: Ident, 5 | _comma: Token![,], 6 | pub header: LitStr, 7 | } 8 | 9 | impl syn::parse::Parse for InheritImplAttr { 10 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 11 | Ok(Self { 12 | class: input.parse()?, 13 | _comma: input.parse()?, 14 | header: input.parse()?, 15 | }) 16 | } 17 | } 18 | 19 | pub struct NamedField(pub Field); 20 | 21 | impl syn::parse::Parse for NamedField { 22 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 23 | Ok(Self(Field::parse_named(input)?)) 24 | } 25 | } 26 | 27 | impl Into for NamedField { 28 | fn into(self) -> Field { 29 | self.0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/test.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen */ 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct non_virtual { 6 | pub value: ::std::os::raw::c_int, 7 | } 8 | extern "C" { 9 | #[link_name = "\u{1}_ZN11non_virtual1xEv"] 10 | pub fn non_virtual_x(this: *mut non_virtual) -> ::std::os::raw::c_int; 11 | } 12 | extern "C" { 13 | #[link_name = "\u{1}_ZN11non_virtualC1Ei"] 14 | pub fn non_virtual_non_virtual(this: *mut non_virtual, v: ::std::os::raw::c_int); 15 | } 16 | impl non_virtual { 17 | #[inline] 18 | pub unsafe fn x(&mut self) -> ::std::os::raw::c_int { 19 | non_virtual_x(self) 20 | } 21 | #[inline] 22 | pub unsafe fn new(v: ::std::os::raw::c_int) -> Self { 23 | let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit(); 24 | non_virtual_non_virtual(__bindgen_tmp.as_mut_ptr(), v); 25 | __bindgen_tmp.assume_init() 26 | } 27 | } 28 | #[repr(C)] 29 | pub struct base__bindgen_vtable(::std::os::raw::c_void); 30 | #[repr(C)] 31 | #[derive(Debug, Copy, Clone)] 32 | pub struct base { 33 | pub vtable_: *const base__bindgen_vtable, 34 | pub value: ::std::os::raw::c_int, 35 | } 36 | extern "C" { 37 | #[link_name = "\u{1}_ZN4baseC1Ei"] 38 | pub fn base_base(this: *mut base, v: ::std::os::raw::c_int); 39 | } 40 | impl base { 41 | #[inline] 42 | pub unsafe fn new(v: ::std::os::raw::c_int) -> Self { 43 | let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit(); 44 | base_base(__bindgen_tmp.as_mut_ptr(), v); 45 | __bindgen_tmp.assume_init() 46 | } 47 | } 48 | extern "C" { 49 | #[link_name = "\u{1}_ZN4base1xEv"] 50 | pub fn base_x(this: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int; 51 | } 52 | #[repr(C)] 53 | #[derive(Debug, Copy, Clone)] 54 | pub struct derived { 55 | pub _base: base, 56 | } 57 | extern "C" { 58 | #[link_name = "\u{1}_ZN7derivedC1Ei"] 59 | pub fn derived_derived(this: *mut derived, v: ::std::os::raw::c_int); 60 | } 61 | impl derived { 62 | #[inline] 63 | pub unsafe fn new(v: ::std::os::raw::c_int) -> Self { 64 | let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit(); 65 | derived_derived(__bindgen_tmp.as_mut_ptr(), v); 66 | __bindgen_tmp.assume_init() 67 | } 68 | } 69 | extern "C" { 70 | #[link_name = "\u{1}_ZN7derived1xEv"] 71 | pub fn derived_x(this: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int; 72 | } 73 | extern "C" { 74 | #[link_name = "\u{1}_Z9call_x_onP4base"] 75 | pub fn call_x_on(x: *mut base) -> ::std::os::raw::c_int; 76 | } 77 | -------------------------------------------------------------------------------- /src/vtable.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::env; 3 | use std::io::prelude::*; 4 | 5 | use quote::{format_ident, quote, ToTokens}; 6 | use std::process::{Command, Stdio}; 7 | use syn::{Ident, Path, Type}; 8 | 9 | mod dwarf; 10 | use dwarf::VTableElement; 11 | 12 | pub fn generate_vtable_const(methods: Vec, ty: &Type) -> impl ToTokens { 13 | let method_count = methods.len(); 14 | quote!( 15 | impl #ty { 16 | // One constant to do a static borrow to ensure it's effectively a static 17 | const _VTABLE_BORROW_FDKSLASDASD: &'static [*const (); #method_count] = &[ 18 | #( 19 | #methods as *const (), 20 | )* 21 | ]; 22 | 23 | // One constant to convert to a pointer to reduce casting 24 | // 25 | // TODO: is it possible to get the bindgen vtable type? if so then no casting would be 26 | // needed... 27 | const VTABLE_: *const [*const (); #method_count] = #ty::_VTABLE_BORROW_FDKSLASDASD as *const _; 28 | } 29 | ) 30 | } 31 | 32 | pub fn get_vtable_info(header: &str, class: &str) -> HashMap> { 33 | let header_path = env::current_dir().unwrap().join("src").join(header); 34 | let out_path = std::path::Path::new(&env::var("OUT_DIR").unwrap()).join(class); 35 | // Compile the header to an unstripped object file to 36 | let mut gcc = Command::new("g++") 37 | .args(&[ 38 | // I don't really know why some of these can't be removed but probably best to leave 39 | // these be 40 | "-femit-class-debug-always", 41 | "-fno-eliminate-unused-debug-types", 42 | "-fno-eliminate-unused-debug-symbols", 43 | "-g3", 44 | "-gdwarf-4", 45 | "-x", 46 | "c++", 47 | "-c", 48 | ]) 49 | .arg("-o") 50 | .arg(&out_path) 51 | .arg(&header_path) 52 | .stdin(Stdio::piped()) 53 | .stderr(Stdio::piped()) 54 | .spawn() 55 | .expect("Failed to start g++"); 56 | if !gcc.wait().unwrap().success() { 57 | let mut x = String::new(); 58 | gcc.stderr.unwrap().read_to_string(&mut x).unwrap(); 59 | panic!("g++ error:\n{}", x); 60 | } 61 | 62 | dwarf::get_vtables_from_file(&out_path) 63 | } 64 | 65 | pub fn get_binding_symbol(symbol: &str) -> Ident { 66 | format_ident!("__cpp_inherit_internal_{}", symbol) 67 | } 68 | 69 | pub fn generate_binding(symbol: &str) -> impl ToTokens { 70 | let ident = get_binding_symbol(symbol); 71 | 72 | quote!( 73 | extern "C" { 74 | #[link_name = #symbol] 75 | fn #ident(); 76 | } 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use proc_macro::TokenStream; 4 | use quote::quote; 5 | use syn::{Fields, ImplItem, Path}; 6 | 7 | mod method_helpers; 8 | use method_helpers::{filter_overrides, make_extern_c, remove_override_attr}; 9 | 10 | mod parsers; 11 | use parsers::{InheritImplAttr, NamedField}; 12 | 13 | mod vtable; 14 | use vtable::generate_vtable_const; 15 | 16 | #[proc_macro_attribute] 17 | pub fn inherit_from(attr: TokenStream, item: TokenStream) -> TokenStream { 18 | let mut struct_def = syn::parse_macro_input!(item as syn::ItemStruct); 19 | let ty = syn::parse_macro_input!(attr as syn::Type); 20 | 21 | let fields = match struct_def.fields { 22 | Fields::Named(ref mut fields) => &mut fields.named, 23 | Fields::Unit => { 24 | struct_def.fields = Fields::Named(syn::parse_quote!({})); 25 | if let Fields::Named(ref mut fields) = struct_def.fields { 26 | &mut fields.named 27 | } else { 28 | unreachable!() 29 | } 30 | } 31 | _ => panic!("Tuple-type structs cannot inherit from classes"), 32 | }; 33 | 34 | let base_field: NamedField = syn::parse_quote!( 35 | _base: #ty 36 | ); 37 | 38 | fields.insert(0, base_field.0); 39 | 40 | let struct_name = &struct_def.ident; 41 | 42 | struct_def.attrs.push(syn::parse_quote! { 43 | #[repr(C)] 44 | }); 45 | 46 | quote!( 47 | #struct_def 48 | 49 | impl ::core::ops::Deref for #struct_name { 50 | type Target = #ty; 51 | 52 | fn deref(&self) -> &Self::Target { 53 | &self._base 54 | } 55 | } 56 | 57 | impl ::core::ops::DerefMut for #struct_name { 58 | fn deref_mut(&mut self) -> &mut Self::Target { 59 | &mut self._base 60 | } 61 | } 62 | ) 63 | .into() 64 | } 65 | 66 | fn into_path_segment(ident: &&syn::Ident) -> syn::PathSegment { 67 | (*ident).clone().into() 68 | } 69 | 70 | #[proc_macro_attribute] 71 | pub fn inherit_from_impl(attr: TokenStream, item: TokenStream) -> TokenStream { 72 | let mut impl_block = syn::parse_macro_input!(item as syn::ItemImpl); 73 | let InheritImplAttr { class, header, .. } = syn::parse_macro_input!(attr as InheritImplAttr); 74 | 75 | let header = header.value(); 76 | 77 | // List of methods with #[overridden] attrbiute 78 | let mut override_items = impl_block 79 | .items 80 | .iter_mut() 81 | .filter_map(|item| { 82 | if let ImplItem::Method(ref mut method) = item { 83 | filter_overrides(method) 84 | } else { 85 | None 86 | } 87 | }) 88 | .collect::>(); 89 | 90 | // Make all override methods `extern "C"` 91 | override_items.iter_mut().for_each(make_extern_c); 92 | 93 | // Remove fake overridden attributes 94 | override_items.iter_mut().for_each(remove_override_attr); 95 | 96 | let vtable_info = vtable::get_vtable_info(&header, &class.to_string()); 97 | 98 | // List of method override names 99 | let override_list = override_items 100 | .into_iter() 101 | .map(|method| method.sig.ident.clone()) 102 | .collect::>(); 103 | 104 | let type_ident = match *impl_block.self_ty { 105 | syn::Type::Path(ref path) => path.path.get_ident().expect("Class type must be an ident"), 106 | _ => panic!("Class type must be an ident"), // Error about how class type must be ident 107 | }; 108 | 109 | match vtable_info.get(&class.to_string()) { 110 | Some(base_type_vtable) => { 111 | // Generate a vtable before overrides 112 | let base_vtable: Vec> = vec![None; base_type_vtable.len()]; 113 | 114 | let mut vtable = base_vtable; 115 | 116 | // Apply each override to the base vtable 117 | for o in override_list { 118 | match base_type_vtable.binary_search_by_key(&&o.to_string(), |entry| &entry.name) { 119 | Ok(index) => { 120 | vtable[index] = Some(Path { 121 | leading_colon: None, 122 | // $class::$method 123 | segments: [&type_ident, &o].iter().map(into_path_segment).collect(), 124 | }); 125 | } 126 | Err(..) => panic!("Cannot override a virtual method that doesn't exist in the original vtable"), 127 | } 128 | } 129 | 130 | let mut bindings_to_gen = vec![]; 131 | 132 | let vtable = vtable 133 | .into_iter() 134 | .enumerate() 135 | .map(|(i, x)| { 136 | x.unwrap_or_else(|| { 137 | bindings_to_gen.push(base_type_vtable[i].default.deref()); 138 | 139 | vtable::get_binding_symbol(&base_type_vtable[i].default).into() 140 | }) 141 | }) 142 | .collect(); 143 | 144 | let self_type = &impl_block.self_ty; 145 | 146 | let vtable_const = generate_vtable_const(vtable, self_type); 147 | 148 | let bindings = bindings_to_gen.into_iter().map(vtable::generate_binding); 149 | 150 | quote!( 151 | #impl_block 152 | 153 | #vtable_const 154 | 155 | #( 156 | #bindings 157 | )* 158 | ) 159 | .into() 160 | } 161 | None => panic!("Class does not exist in header"), // add compiler error for class not existing in header 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/vtable/dwarf.rs: -------------------------------------------------------------------------------- 1 | use object::{Object, ObjectSection}; 2 | use std::{ 3 | borrow::{Borrow, Cow}, 4 | collections::HashMap, 5 | fs, 6 | path::Path, 7 | }; 8 | use typed_arena::Arena; 9 | type RelocationMap = HashMap; 10 | 11 | trait Reader: gimli::Reader + Send + Sync {} 12 | 13 | impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where 14 | Endian: gimli::Endianity + Send + Sync 15 | { 16 | } 17 | 18 | pub fn get_vtables_from_file(path: &Path) -> HashMap> { 19 | let file = fs::File::open(&path).unwrap(); 20 | let mmap = unsafe { memmap::Mmap::map(&file).unwrap() }; 21 | let object = object::File::parse(&*mmap).unwrap(); 22 | let endian = if object.is_little_endian() { 23 | gimli::RunTimeEndian::Little 24 | } else { 25 | gimli::RunTimeEndian::Big 26 | }; 27 | 28 | dump_file(&object, endian).unwrap() 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | struct Relocate<'a, R: gimli::Reader> { 33 | relocations: &'a RelocationMap, 34 | section: R, 35 | reader: R, 36 | } 37 | 38 | impl<'a, R: gimli::Reader> Relocate<'a, R> { 39 | fn relocate(&self, offset: usize, value: u64) -> u64 { 40 | if let Some(relocation) = self.relocations.get(&offset) { 41 | match relocation.kind() { 42 | object::RelocationKind::Absolute => { 43 | if relocation.has_implicit_addend() { 44 | // Use the explicit addend too, because it may have the symbol value. 45 | return value.wrapping_add(relocation.addend() as u64); 46 | } else { 47 | return relocation.addend() as u64; 48 | } 49 | } 50 | _ => {} 51 | } 52 | }; 53 | value 54 | } 55 | } 56 | 57 | impl<'a, R: gimli::Reader> gimli::Reader for Relocate<'a, R> { 58 | type Endian = R::Endian; 59 | type Offset = R::Offset; 60 | 61 | fn read_address(&mut self, address_size: u8) -> gimli::Result { 62 | let offset = self.reader.offset_from(&self.section); 63 | let value = self.reader.read_address(address_size)?; 64 | Ok(self.relocate(offset, value)) 65 | } 66 | 67 | fn read_length(&mut self, format: gimli::Format) -> gimli::Result { 68 | let offset = self.reader.offset_from(&self.section); 69 | let value = self.reader.read_length(format)?; 70 | ::from_u64(self.relocate(offset, value as u64)) 71 | } 72 | 73 | fn read_offset(&mut self, format: gimli::Format) -> gimli::Result { 74 | let offset = self.reader.offset_from(&self.section); 75 | let value = self.reader.read_offset(format)?; 76 | ::from_u64(self.relocate(offset, value as u64)) 77 | } 78 | 79 | fn read_sized_offset(&mut self, size: u8) -> gimli::Result { 80 | let offset = self.reader.offset_from(&self.section); 81 | let value = self.reader.read_sized_offset(size)?; 82 | ::from_u64(self.relocate(offset, value as u64)) 83 | } 84 | 85 | #[inline] 86 | fn split(&mut self, len: Self::Offset) -> gimli::Result { 87 | let mut other = self.clone(); 88 | other.reader.truncate(len)?; 89 | self.reader.skip(len)?; 90 | Ok(other) 91 | } 92 | 93 | // All remaining methods simply delegate to `self.reader`. 94 | 95 | #[inline] 96 | fn endian(&self) -> Self::Endian { 97 | self.reader.endian() 98 | } 99 | 100 | #[inline] 101 | fn len(&self) -> Self::Offset { 102 | self.reader.len() 103 | } 104 | 105 | #[inline] 106 | fn empty(&mut self) { 107 | self.reader.empty() 108 | } 109 | 110 | #[inline] 111 | fn truncate(&mut self, len: Self::Offset) -> gimli::Result<()> { 112 | self.reader.truncate(len) 113 | } 114 | 115 | #[inline] 116 | fn offset_from(&self, base: &Self) -> Self::Offset { 117 | self.reader.offset_from(&base.reader) 118 | } 119 | 120 | #[inline] 121 | fn offset_id(&self) -> gimli::ReaderOffsetId { 122 | self.reader.offset_id() 123 | } 124 | 125 | #[inline] 126 | fn lookup_offset_id(&self, id: gimli::ReaderOffsetId) -> Option { 127 | self.reader.lookup_offset_id(id) 128 | } 129 | 130 | #[inline] 131 | fn find(&self, byte: u8) -> gimli::Result { 132 | self.reader.find(byte) 133 | } 134 | 135 | #[inline] 136 | fn skip(&mut self, len: Self::Offset) -> gimli::Result<()> { 137 | self.reader.skip(len) 138 | } 139 | 140 | #[inline] 141 | fn to_slice(&self) -> gimli::Result> { 142 | self.reader.to_slice() 143 | } 144 | 145 | #[inline] 146 | fn to_string(&self) -> gimli::Result> { 147 | self.reader.to_string() 148 | } 149 | 150 | #[inline] 151 | fn to_string_lossy(&self) -> gimli::Result> { 152 | self.reader.to_string_lossy() 153 | } 154 | 155 | #[inline] 156 | fn read_slice(&mut self, buf: &mut [u8]) -> gimli::Result<()> { 157 | self.reader.read_slice(buf) 158 | } 159 | } 160 | 161 | impl<'a, R: Reader> Reader for Relocate<'a, R> {} 162 | 163 | fn add_relocations( 164 | relocations: &mut RelocationMap, 165 | file: &object::File, 166 | section: &object::Section, 167 | ) { 168 | for (offset64, mut relocation) in section.relocations() { 169 | let offset = offset64 as usize; 170 | if offset as u64 != offset64 { 171 | continue; 172 | } 173 | let offset = offset as usize; 174 | match relocation.kind() { 175 | object::RelocationKind::Absolute => { 176 | match relocation.target() { 177 | object::RelocationTarget::Symbol(symbol_idx) => { 178 | match file.symbol_by_index(symbol_idx) { 179 | Ok(symbol) => { 180 | let addend = 181 | symbol.address().wrapping_add(relocation.addend() as u64); 182 | relocation.set_addend(addend as i64); 183 | } 184 | Err(_) => { 185 | eprintln!( 186 | "Relocation with invalid symbol for section {} at offset 0x{:08x}", 187 | section.name().unwrap(), 188 | offset 189 | ); 190 | } 191 | } 192 | } 193 | object::RelocationTarget::Section(_section_idx) => {} 194 | } 195 | if relocations.insert(offset, relocation).is_some() { 196 | eprintln!( 197 | "Multiple relocations for section {} at offset 0x{:08x}", 198 | section.name().unwrap(), 199 | offset 200 | ); 201 | } 202 | } 203 | _ => { 204 | println!( 205 | "Unsupported relocation for section {} at offset 0x{:08x}", 206 | section.name().unwrap(), 207 | offset 208 | ); 209 | } 210 | } 211 | } 212 | } 213 | 214 | #[derive(Debug)] 215 | pub struct VTableElement { 216 | pub default: String, 217 | pub name: String, 218 | pub pos: u64, 219 | } 220 | 221 | fn get_structure_vtable<'abbrev, 'unit, 'tree, R: gimli::Reader>( 222 | node: gimli::EntriesTreeNode<'abbrev, 'unit, 'tree, R>, 223 | unit: &gimli::Unit, 224 | dwarf: &gimli::Dwarf, 225 | ) -> Result, gimli::Error> { 226 | let mut vtable = Vec::new(); 227 | let mut children = node.children(); 228 | while let Some(node) = children.next()? { 229 | let entry = node.entry(); 230 | if entry.tag() == gimli::DW_TAG_subprogram { 231 | if let (Some(name_val), Some(default_val), Some(pos_expr)) = ( 232 | entry.attr_value(gimli::DW_AT_name)?, 233 | entry.attr_value(gimli::DW_AT_linkage_name)?, 234 | entry 235 | .attr_value(gimli::DW_AT_vtable_elem_location)? 236 | .and_then(|x| x.exprloc_value()), 237 | ) { 238 | let name_bytes = dwarf.attr_string(&unit, name_val)?; 239 | let name = gimli::Reader::to_string(&name_bytes)?.to_string(); 240 | let default_bytes = dwarf.attr_string(&unit, default_val)?; 241 | let default = gimli::Reader::to_string(&default_bytes)?.to_string(); 242 | let mut pos_eval = pos_expr.evaluation(unit.encoding()); 243 | let pos_res = pos_eval.evaluate()?; 244 | if !matches!(pos_res, gimli::EvaluationResult::Complete) { 245 | unimplemented!("{:?}", pos_res); 246 | } 247 | let pos_pieces = pos_eval.result(); 248 | let pos_piece = &pos_pieces[0]; 249 | match pos_piece.location { 250 | gimli::Location::Address { address: pos } => { 251 | vtable.push(VTableElement { name, default, pos }) 252 | } 253 | _ => unimplemented!("{:?}", pos_piece), 254 | } 255 | } 256 | } 257 | } 258 | Ok(vtable) 259 | } 260 | 261 | fn walk_node<'abbrev, 'unit, 'tree, R: gimli::Reader>( 262 | node: gimli::EntriesTreeNode<'abbrev, 'unit, 'tree, R>, 263 | unit: &gimli::Unit, 264 | dwarf: &gimli::Dwarf, 265 | vtables: &mut HashMap>, 266 | ) -> Result<(), gimli::Error> { 267 | let entry = node.entry(); 268 | 269 | if entry.tag() == gimli::DW_TAG_structure_type { 270 | let name_val = if let Some(name_val) = entry.attr_value(gimli::DW_AT_name)? { 271 | name_val 272 | } else { 273 | return Ok(()); 274 | }; 275 | let name_bytes = dwarf.attr_string(&unit, name_val)?; 276 | let name = gimli::Reader::to_string(&name_bytes)?.to_string(); 277 | 278 | let vtable = get_structure_vtable(node, unit, dwarf)?; 279 | 280 | vtables.insert(name, vtable); 281 | } else { 282 | let mut children = node.children(); 283 | while let Some(node) = children.next()? { 284 | walk_node(node, unit, dwarf, vtables)?; 285 | } 286 | } 287 | 288 | Ok(()) 289 | } 290 | 291 | fn dump_file( 292 | object: &object::File, 293 | endian: gimli::RunTimeEndian, 294 | ) -> Result>, gimli::Error> { 295 | let arena = (Arena::new(), Arena::new()); 296 | 297 | // Load a section and return as `Cow<[u8]>`. 298 | let mut load_section = |id: gimli::SectionId| -> Result<_, object::read::Error> { 299 | let mut relocations = RelocationMap::default(); 300 | let name = id.name(); 301 | let data = match object.section_by_name(&name) { 302 | Some(ref section) => { 303 | add_relocations(&mut relocations, object, section); 304 | section.uncompressed_data()? 305 | } 306 | // Use a non-zero capacity so that `ReaderOffsetId`s are unique. 307 | None => Cow::Owned(Vec::with_capacity(1)), 308 | }; 309 | let data_ref = (*arena.0.alloc(data)).borrow(); 310 | let reader = gimli::EndianSlice::new(data_ref, endian); 311 | let section = reader; 312 | let relocations = (*arena.1.alloc(relocations)).borrow(); 313 | Ok(Relocate { 314 | relocations, 315 | section, 316 | reader, 317 | }) 318 | }; 319 | 320 | let no_relocations = (*arena.1.alloc(RelocationMap::default())).borrow(); 321 | let no_reader = Relocate { 322 | relocations: no_relocations, 323 | section: Default::default(), 324 | reader: Default::default(), 325 | }; 326 | 327 | let mut vtables = HashMap::new(); 328 | 329 | let dwarf = gimli::Dwarf::load(&mut load_section, |_| Ok(no_reader.clone())).unwrap(); 330 | 331 | // Iterate over the compilation units. 332 | let mut iter = dwarf.units(); 333 | while let Some(header) = iter.next()? { 334 | /*println!( 335 | "Unit at <.debug_info+0x{:x}>", 336 | &header.offset().0 337 | );*/ 338 | let unit = dwarf.unit(header)?; 339 | 340 | let mut tree = unit.entries_tree(None)?; 341 | let root = tree.root()?; 342 | walk_node(root, &unit, &dwarf, &mut vtables)?; 343 | } 344 | 345 | Ok(vtables) 346 | } 347 | --------------------------------------------------------------------------------