Skip to content

Commit b471e5f

Browse files
Add AIX support in gimli symbolizer
From bzEq/add-aix-support in #508
2 parents a390aa7 + ecb214b commit b471e5f

File tree

7 files changed

+330
-12
lines changed

7 files changed

+330
-12
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ libc = { version = "0.2.146", default-features = false }
4646
[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object]
4747
version = "0.32.0"
4848
default-features = false
49-
features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive']
49+
features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive']
5050

5151
[target.'cfg(windows)'.dependencies]
5252
winapi = { version = "0.3.9", optional = true }

crates/as-if-std/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ addr2line = { version = "0.21.0", optional = true, default-features = false }
2424
version = "0.32.0"
2525
default-features = false
2626
optional = true
27-
features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive']
27+
features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive']
2828

2929
[build-dependencies]
3030
# Dependency of the `backtrace` crate

src/symbolize/gimli.rs

+39-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ cfg_if::cfg_if! {
4141
target_os = "openbsd",
4242
target_os = "solaris",
4343
target_os = "illumos",
44+
target_os = "aix",
4445
))] {
4546
#[path = "gimli/mmap_unix.rs"]
4647
mod mmap;
@@ -116,8 +117,17 @@ impl<'data> Context<'data> {
116117
dwp: Option<Object<'data>>,
117118
) -> Option<Context<'data>> {
118119
let mut sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
119-
let data = object.section(stash, id.name()).unwrap_or(&[]);
120-
Ok(EndianSlice::new(data, Endian))
120+
if cfg!(not(target_os = "aix")) {
121+
let data = object.section(stash, id.name()).unwrap_or(&[]);
122+
Ok(EndianSlice::new(data, Endian))
123+
} else {
124+
if let Some(name) = id.xcoff_name() {
125+
let data = object.section(stash, name).unwrap_or(&[]);
126+
Ok(EndianSlice::new(data, Endian))
127+
} else {
128+
Ok(EndianSlice::new(&[], Endian))
129+
}
130+
}
121131
})
122132
.ok()?;
123133

@@ -192,6 +202,9 @@ cfg_if::cfg_if! {
192202
))] {
193203
mod macho;
194204
use self::macho::{handle_split_dwarf, Object};
205+
} else if #[cfg(target_os = "aix")] {
206+
mod xcoff;
207+
use self::xcoff::{handle_split_dwarf, Object};
195208
} else {
196209
mod elf;
197210
use self::elf::{handle_split_dwarf, Object};
@@ -234,6 +247,9 @@ cfg_if::cfg_if! {
234247
} else if #[cfg(target_os = "haiku")] {
235248
mod libs_haiku;
236249
use libs_haiku::native_libraries;
250+
} else if #[cfg(target_os = "aix")] {
251+
mod libs_aix;
252+
use libs_aix::native_libraries;
237253
} else {
238254
// Everything else should doesn't know how to load native libraries.
239255
fn native_libraries() -> Vec<Library> {
@@ -261,6 +277,13 @@ struct Cache {
261277

262278
struct Library {
263279
name: OsString,
280+
#[cfg(target_os = "aix")]
281+
/// On AIX, the library mmapped can be a member of a big-archive file.
282+
/// For example, with a big-archive named libfoo.a containing libbar.so,
283+
/// one can use `dlopen("libfoo.a(libbar.so)", RTLD_MEMBER | RTLD_LAZY)`
284+
/// to use the `libbar.so` library. In this case, only `libbar.so` is
285+
/// mmapped, not the whole `libfoo.a`.
286+
member_name: OsString,
264287
/// Segments of this library loaded into memory, and where they're loaded.
265288
segments: Vec<LibrarySegment>,
266289
/// The "bias" of this library, typically where it's loaded into memory.
@@ -280,6 +303,19 @@ struct LibrarySegment {
280303
len: usize,
281304
}
282305

306+
#[cfg(target_os = "aix")]
307+
fn create_mapping(lib: &Library) -> Option<Mapping> {
308+
let name = &lib.name;
309+
let member_name = &lib.member_name;
310+
Mapping::new(name.as_ref(), member_name)
311+
}
312+
313+
#[cfg(not(target_os = "aix"))]
314+
fn create_mapping(lib: &Library) -> Option<Mapping> {
315+
let name = &lib.name;
316+
Mapping::new(name.as_ref())
317+
}
318+
283319
// unsafe because this is required to be externally synchronized
284320
pub unsafe fn clear_symbol_cache() {
285321
Cache::with_global(|cache| cache.mappings.clear());
@@ -360,8 +396,7 @@ impl Cache {
360396
// When the mapping is not in the cache, create a new mapping,
361397
// insert it into the front of the cache, and evict the oldest cache
362398
// entry if necessary.
363-
let name = &self.libraries[lib].name;
364-
let mapping = Mapping::new(name.as_ref())?;
399+
let mapping = create_mapping(&self.libraries[lib])?;
365400

366401
if self.mappings.len() == MAPPINGS_CACHE_SIZE {
367402
self.mappings.pop();

src/symbolize/gimli/libs_aix.rs

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use super::mystd::borrow::ToOwned;
2+
use super::mystd::env;
3+
use super::mystd::ffi::{CStr, OsStr};
4+
use super::mystd::io::Error;
5+
use super::mystd::os::unix::prelude::*;
6+
use super::xcoff;
7+
use super::{Library, LibrarySegment, Vec};
8+
use alloc::vec;
9+
use core::mem;
10+
11+
const EXE_IMAGE_BASE: u64 = 0x100000000;
12+
13+
/// On AIX, we use `loadquery` with `L_GETINFO` flag to query libraries mmapped.
14+
/// See https://www.ibm.com/docs/en/aix/7.2?topic=l-loadquery-subroutine for
15+
/// detailed information of `loadquery`.
16+
pub(super) fn native_libraries() -> Vec<Library> {
17+
let mut ret = Vec::new();
18+
unsafe {
19+
let mut buffer = vec![mem::zeroed::<libc::ld_info>(); 64];
20+
loop {
21+
if libc::loadquery(
22+
libc::L_GETINFO,
23+
buffer.as_mut_ptr() as *mut libc::c_char,
24+
(mem::size_of::<libc::ld_info>() * buffer.len()) as u32,
25+
) != -1
26+
{
27+
break;
28+
} else {
29+
match Error::last_os_error().raw_os_error() {
30+
Some(libc::ENOMEM) => {
31+
buffer.resize(buffer.len() * 2, mem::zeroed::<libc::ld_info>());
32+
}
33+
Some(_) => {
34+
// If other error occurs, return empty libraries.
35+
return Vec::new();
36+
}
37+
_ => unreachable!(),
38+
}
39+
}
40+
}
41+
let mut current = buffer.as_mut_ptr();
42+
loop {
43+
let text_base = (*current).ldinfo_textorg as usize;
44+
let filename_ptr: *const libc::c_char = &(*current).ldinfo_filename[0];
45+
let bytes = CStr::from_ptr(filename_ptr).to_bytes();
46+
let member_name_ptr = filename_ptr.offset((bytes.len() + 1) as isize);
47+
let mut filename = OsStr::from_bytes(bytes).to_owned();
48+
if text_base == EXE_IMAGE_BASE as usize {
49+
if let Ok(exe) = env::current_exe() {
50+
filename = exe.into_os_string();
51+
}
52+
}
53+
let bytes = CStr::from_ptr(member_name_ptr).to_bytes();
54+
let member_name = OsStr::from_bytes(bytes).to_owned();
55+
if let Some(image) = xcoff::parse_image(filename.as_ref(), &member_name) {
56+
ret.push(Library {
57+
name: filename,
58+
member_name,
59+
segments: vec![LibrarySegment {
60+
stated_virtual_memory_address: image.base as usize,
61+
len: image.size,
62+
}],
63+
bias: (text_base + image.offset).wrapping_sub(image.base as usize),
64+
});
65+
}
66+
if (*current).ldinfo_next == 0 {
67+
break;
68+
}
69+
current = (current as *mut libc::c_char).offset((*current).ldinfo_next as isize)
70+
as *mut libc::ld_info;
71+
}
72+
}
73+
return ret;
74+
}

src/symbolize/gimli/xcoff.rs

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use super::mystd::ffi::{OsStr, OsString};
2+
use super::mystd::os::unix::ffi::OsStrExt;
3+
use super::mystd::str;
4+
use super::{gimli, Context, Endian, EndianSlice, Mapping, Path, Stash, Vec};
5+
use alloc::sync::Arc;
6+
use core::ops::Deref;
7+
use object::read::archive::ArchiveFile;
8+
use object::read::xcoff::{FileHeader, SectionHeader, XcoffFile, XcoffSymbol};
9+
use object::Object as _;
10+
use object::ObjectSection as _;
11+
use object::ObjectSymbol as _;
12+
use object::SymbolFlags;
13+
14+
#[cfg(target_pointer_width = "32")]
15+
type Xcoff = object::xcoff::FileHeader32;
16+
#[cfg(target_pointer_width = "64")]
17+
type Xcoff = object::xcoff::FileHeader64;
18+
19+
impl Mapping {
20+
pub fn new(path: &Path, member_name: &OsString) -> Option<Mapping> {
21+
let map = super::mmap(path)?;
22+
Mapping::mk(map, |data, stash| {
23+
if member_name.is_empty() {
24+
Context::new(stash, Object::parse(data)?, None, None)
25+
} else {
26+
let archive = ArchiveFile::parse(data).ok()?;
27+
for member in archive
28+
.members()
29+
.filter_map(|m| m.ok())
30+
.filter(|m| OsStr::from_bytes(m.name()) == member_name)
31+
{
32+
let member_data = member.data(data).ok()?;
33+
if let Some(obj) = Object::parse(member_data) {
34+
return Context::new(stash, obj, None, None);
35+
}
36+
}
37+
None
38+
}
39+
})
40+
}
41+
}
42+
43+
struct ParsedSym<'a> {
44+
address: u64,
45+
size: u64,
46+
name: &'a str,
47+
}
48+
49+
pub struct Object<'a> {
50+
syms: Vec<ParsedSym<'a>>,
51+
file: XcoffFile<'a, Xcoff>,
52+
}
53+
54+
pub struct Image {
55+
pub offset: usize,
56+
pub base: u64,
57+
pub size: usize,
58+
}
59+
60+
pub fn parse_xcoff(data: &[u8]) -> Option<Image> {
61+
let mut offset = 0;
62+
let header = Xcoff::parse(data, &mut offset).ok()?;
63+
let _ = header.aux_header(data, &mut offset).ok()?;
64+
let sections = header.sections(data, &mut offset).ok()?;
65+
if let Some(section) = sections.iter().find(|s| {
66+
if let Ok(name) = str::from_utf8(&s.s_name()[0..5]) {
67+
name == ".text"
68+
} else {
69+
false
70+
}
71+
}) {
72+
Some(Image {
73+
offset: section.s_scnptr() as usize,
74+
base: section.s_paddr() as u64,
75+
size: section.s_size() as usize,
76+
})
77+
} else {
78+
None
79+
}
80+
}
81+
82+
pub fn parse_image(path: &Path, member_name: &OsString) -> Option<Image> {
83+
let map = super::mmap(path)?;
84+
let data = map.deref();
85+
if member_name.is_empty() {
86+
return parse_xcoff(data);
87+
} else {
88+
let archive = ArchiveFile::parse(data).ok()?;
89+
for member in archive
90+
.members()
91+
.filter_map(|m| m.ok())
92+
.filter(|m| OsStr::from_bytes(m.name()) == member_name)
93+
{
94+
let member_data = member.data(data).ok()?;
95+
if let Some(image) = parse_xcoff(member_data) {
96+
return Some(image);
97+
}
98+
}
99+
None
100+
}
101+
}
102+
103+
impl<'a> Object<'a> {
104+
fn get_concrete_size(file: &XcoffFile<'a, Xcoff>, sym: &XcoffSymbol<'a, '_, Xcoff>) -> u64 {
105+
match sym.flags() {
106+
SymbolFlags::Xcoff {
107+
n_sclass: _,
108+
x_smtyp: _,
109+
x_smclas: _,
110+
containing_csect: Some(index),
111+
} => {
112+
if let Ok(tgt_sym) = file.symbol_by_index(index) {
113+
Self::get_concrete_size(file, &tgt_sym)
114+
} else {
115+
0
116+
}
117+
}
118+
_ => sym.size(),
119+
}
120+
}
121+
122+
fn parse(data: &'a [u8]) -> Option<Object<'a>> {
123+
let file = XcoffFile::parse(data).ok()?;
124+
let mut syms = file
125+
.symbols()
126+
.filter_map(|sym| {
127+
let name = sym.name().map_or("", |v| v);
128+
let address = sym.address();
129+
let size = Self::get_concrete_size(&file, &sym);
130+
if name == ".text" || name == ".data" {
131+
// We don't want to include ".text" and ".data" symbols.
132+
// If they are included, since their ranges cover other
133+
// symbols, when searching a symbol for a given address,
134+
// ".text" or ".data" is returned. That's not what we expect.
135+
None
136+
} else {
137+
Some(ParsedSym {
138+
address,
139+
size,
140+
name,
141+
})
142+
}
143+
})
144+
.collect::<Vec<_>>();
145+
syms.sort_by_key(|s| s.address);
146+
Some(Object { syms, file })
147+
}
148+
149+
pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> {
150+
Some(self.file.section_by_name(name)?.data().ok()?)
151+
}
152+
153+
pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
154+
// Symbols, except ".text" and ".data", are sorted and are not overlapped each other,
155+
// so we can just perform a binary search here.
156+
let i = match self.syms.binary_search_by_key(&addr, |sym| sym.address) {
157+
Ok(i) => i,
158+
Err(i) => i.checked_sub(1)?,
159+
};
160+
let sym = self.syms.get(i)?;
161+
if (sym.address..sym.address + sym.size).contains(&addr) {
162+
// On AIX, for a function call, for example, `foo()`, we have
163+
// two symbols `foo` and `.foo`. `foo` references the function
164+
// descriptor and `.foo` references the function entry.
165+
// See https://www.ibm.com/docs/en/xl-fortran-aix/16.1.0?topic=calls-linkage-convention-function
166+
// for more information.
167+
// We trim the prefix `.` here, so that the rust demangler can work
168+
// properly.
169+
Some(sym.name.trim_start_matches(".").as_bytes())
170+
} else {
171+
None
172+
}
173+
}
174+
175+
pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
176+
None
177+
}
178+
}
179+
180+
pub(super) fn handle_split_dwarf<'data>(
181+
_package: Option<&gimli::DwarfPackage<EndianSlice<'data, Endian>>>,
182+
_stash: &'data Stash,
183+
_load: addr2line::SplitDwarfLoad<EndianSlice<'data, Endian>>,
184+
) -> Option<Arc<gimli::Dwarf<EndianSlice<'data, Endian>>>> {
185+
None
186+
}

tests/accuracy/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ fn doit() {
3131
dir.push("dylib_dep.dll");
3232
} else if cfg!(target_os = "macos") {
3333
dir.push("libdylib_dep.dylib");
34+
} else if cfg!(target_os = "aix") {
35+
dir.push("libdylib_dep.a");
3436
} else {
3537
dir.push("libdylib_dep.so");
3638
}

0 commit comments

Comments
 (0)