mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
There is no straightforward way for the Zig team to access the Solaris system headers; to do this, one has to create an Oracle account, accept their EULA to download the installer ISO, and finally install it on a machine or VM. We do not have to jump through hoops like this for any other OS that we support, and no one on the team has expressed willingness to do it. As a result, we cannot audit any Solaris contributions to std.c or other similarly sensitive parts of the standard library. The best we would be able to do is assume that Solaris and illumos are 100% compatible with no way to verify that assumption. But at that point, the solaris and illumos OS tags would be functionally identical anyway. For Solaris especially, any contributions that involve APIs introduced after the OS was made closed-source would also be inherently more risky than equivalent contributions for other proprietary OSs due to the case of Google LLC v. Oracle America, Inc., wherein Oracle clearly demonstrated its willingness to pursue legal action against entities that merely copy API declarations. Finally, Oracle laid off most of the Solaris team in 2017; the OS has been in maintenance mode since, presumably to be retired completely sometime in the 2030s. For these reasons, this commit removes all Oracle Solaris support. Anyone who still wishes to use Zig on Solaris can try their luck by simply using illumos instead of solaris in target triples - chances are it'll work. But there will be no effort from the Zig team to support this use case; we recommend that people move to illumos instead.
499 lines
17 KiB
Zig
499 lines
17 KiB
Zig
rwlock: std.Thread.RwLock,
|
|
|
|
modules: std.ArrayList(Module),
|
|
ranges: std.ArrayList(Module.Range),
|
|
|
|
unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn,
|
|
|
|
pub const init: SelfInfo = .{
|
|
.rwlock = .{},
|
|
.modules = .empty,
|
|
.ranges = .empty,
|
|
.unwind_cache = null,
|
|
};
|
|
pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
|
|
for (si.modules.items) |*mod| {
|
|
unwind: {
|
|
const u = &(mod.unwind orelse break :unwind catch break :unwind);
|
|
for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa);
|
|
}
|
|
loaded: {
|
|
const l = &(mod.loaded_elf orelse break :loaded catch break :loaded);
|
|
l.file.deinit(gpa);
|
|
}
|
|
}
|
|
|
|
si.modules.deinit(gpa);
|
|
si.ranges.deinit(gpa);
|
|
if (si.unwind_cache) |cache| gpa.free(cache);
|
|
}
|
|
|
|
pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
|
|
const module = try si.findModule(gpa, address, .exclusive);
|
|
defer si.rwlock.unlock();
|
|
|
|
const vaddr = address - module.load_offset;
|
|
|
|
const loaded_elf = try module.getLoadedElf(gpa);
|
|
if (loaded_elf.file.dwarf) |*dwarf| {
|
|
if (!loaded_elf.scanned_dwarf) {
|
|
dwarf.open(gpa, native_endian) catch |err| switch (err) {
|
|
error.InvalidDebugInfo,
|
|
error.MissingDebugInfo,
|
|
error.OutOfMemory,
|
|
=> |e| return e,
|
|
error.EndOfStream,
|
|
error.Overflow,
|
|
error.ReadFailed,
|
|
error.StreamTooLong,
|
|
=> return error.InvalidDebugInfo,
|
|
};
|
|
loaded_elf.scanned_dwarf = true;
|
|
}
|
|
if (dwarf.getSymbol(gpa, native_endian, vaddr)) |sym| {
|
|
return sym;
|
|
} else |err| switch (err) {
|
|
error.MissingDebugInfo => {},
|
|
|
|
error.InvalidDebugInfo,
|
|
error.OutOfMemory,
|
|
=> |e| return e,
|
|
|
|
error.ReadFailed,
|
|
error.EndOfStream,
|
|
error.Overflow,
|
|
error.StreamTooLong,
|
|
=> return error.InvalidDebugInfo,
|
|
}
|
|
}
|
|
// When DWARF is unavailable, fall back to searching the symtab.
|
|
return loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) {
|
|
error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,
|
|
error.BadSymtab => return error.InvalidDebugInfo,
|
|
error.OutOfMemory => |e| return e,
|
|
};
|
|
}
|
|
pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
|
|
const module = try si.findModule(gpa, address, .shared);
|
|
defer si.rwlock.unlockShared();
|
|
if (module.name.len == 0) return error.MissingDebugInfo;
|
|
return module.name;
|
|
}
|
|
|
|
pub const can_unwind: bool = s: {
|
|
// The DWARF code can't deal with ILP32 ABIs yet: https://github.com/ziglang/zig/issues/25447
|
|
switch (builtin.target.abi) {
|
|
.gnuabin32,
|
|
.muslabin32,
|
|
.gnux32,
|
|
.muslx32,
|
|
=> break :s false,
|
|
else => {},
|
|
}
|
|
|
|
// Notably, we are yet to support unwinding on ARM. There, unwinding is not done through
|
|
// `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format.
|
|
const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
|
|
// Not supported yet: arm
|
|
.haiku => &.{
|
|
.aarch64,
|
|
.m68k,
|
|
.riscv64,
|
|
.x86,
|
|
.x86_64,
|
|
},
|
|
// Not supported yet: arm/armeb/thumb/thumbeb, xtensa/xtensaeb
|
|
.linux => &.{
|
|
.aarch64,
|
|
.aarch64_be,
|
|
.arc,
|
|
.csky,
|
|
.loongarch64,
|
|
.m68k,
|
|
.mips,
|
|
.mipsel,
|
|
.mips64,
|
|
.mips64el,
|
|
.or1k,
|
|
.riscv32,
|
|
.riscv64,
|
|
.s390x,
|
|
.x86,
|
|
.x86_64,
|
|
},
|
|
.serenity => &.{
|
|
.aarch64,
|
|
.x86_64,
|
|
.riscv64,
|
|
},
|
|
|
|
.dragonfly => &.{
|
|
.x86_64,
|
|
},
|
|
// Not supported yet: arm
|
|
.freebsd => &.{
|
|
.aarch64,
|
|
.riscv64,
|
|
.x86_64,
|
|
},
|
|
// Not supported yet: arm/armeb, mips64/mips64el
|
|
.netbsd => &.{
|
|
.aarch64,
|
|
.aarch64_be,
|
|
.m68k,
|
|
.mips,
|
|
.mipsel,
|
|
.x86,
|
|
.x86_64,
|
|
},
|
|
// Not supported yet: arm
|
|
.openbsd => &.{
|
|
.aarch64,
|
|
.mips64,
|
|
.mips64el,
|
|
.riscv64,
|
|
.x86,
|
|
.x86_64,
|
|
},
|
|
|
|
.illumos => &.{
|
|
.x86,
|
|
.x86_64,
|
|
},
|
|
|
|
else => unreachable,
|
|
};
|
|
for (archs) |a| {
|
|
if (builtin.target.cpu.arch == a) break :s true;
|
|
}
|
|
break :s false;
|
|
};
|
|
comptime {
|
|
if (can_unwind) {
|
|
std.debug.assert(Dwarf.supportsUnwinding(&builtin.target));
|
|
}
|
|
}
|
|
pub const UnwindContext = Dwarf.SelfUnwinder;
|
|
pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
|
|
comptime assert(can_unwind);
|
|
|
|
{
|
|
si.rwlock.lockShared();
|
|
defer si.rwlock.unlockShared();
|
|
if (si.unwind_cache) |cache| {
|
|
if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| {
|
|
return context.next(gpa, entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
const module = try si.findModule(gpa, context.pc, .exclusive);
|
|
defer si.rwlock.unlock();
|
|
|
|
if (si.unwind_cache == null) {
|
|
si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048);
|
|
@memset(si.unwind_cache.?, .empty);
|
|
}
|
|
|
|
const unwind_sections = try module.getUnwindSections(gpa);
|
|
for (unwind_sections) |*unwind| {
|
|
if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| {
|
|
entry.populate(si.unwind_cache.?);
|
|
return context.next(gpa, &entry);
|
|
} else |err| switch (err) {
|
|
error.MissingDebugInfo => continue,
|
|
|
|
error.InvalidDebugInfo,
|
|
error.UnsupportedDebugInfo,
|
|
error.OutOfMemory,
|
|
=> |e| return e,
|
|
|
|
error.EndOfStream,
|
|
error.StreamTooLong,
|
|
error.ReadFailed,
|
|
error.Overflow,
|
|
error.InvalidOpcode,
|
|
error.InvalidOperation,
|
|
error.InvalidOperand,
|
|
=> return error.InvalidDebugInfo,
|
|
|
|
error.UnimplementedUserOpcode,
|
|
error.UnsupportedAddrSize,
|
|
=> return error.UnsupportedDebugInfo,
|
|
}
|
|
}
|
|
return error.MissingDebugInfo;
|
|
}
|
|
|
|
const Module = struct {
|
|
load_offset: usize,
|
|
name: []const u8,
|
|
build_id: ?[]const u8,
|
|
gnu_eh_frame: ?[]const u8,
|
|
|
|
/// `null` means unwind information has not yet been loaded.
|
|
unwind: ?(Error!UnwindSections),
|
|
|
|
/// `null` means the ELF file has not yet been loaded.
|
|
loaded_elf: ?(Error!LoadedElf),
|
|
|
|
const LoadedElf = struct {
|
|
file: std.debug.ElfFile,
|
|
scanned_dwarf: bool,
|
|
};
|
|
|
|
const UnwindSections = struct {
|
|
buf: [2]Dwarf.Unwind,
|
|
len: usize,
|
|
};
|
|
|
|
const Range = struct {
|
|
start: usize,
|
|
len: usize,
|
|
/// Index into `modules`
|
|
module_index: usize,
|
|
};
|
|
|
|
/// Assumes we already hold an exclusive lock.
|
|
fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind {
|
|
if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa);
|
|
const us = &(mod.unwind.? catch |err| return err);
|
|
return us.buf[0..us.len];
|
|
}
|
|
fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections {
|
|
var us: UnwindSections = .{
|
|
.buf = undefined,
|
|
.len = 0,
|
|
};
|
|
if (mod.gnu_eh_frame) |section_bytes| {
|
|
const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - mod.load_offset;
|
|
const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) {
|
|
error.ReadFailed => unreachable, // it's all fixed buffers
|
|
error.InvalidDebugInfo => |e| return e,
|
|
error.EndOfStream, error.Overflow => return error.InvalidDebugInfo,
|
|
error.UnsupportedAddrSize => return error.UnsupportedDebugInfo,
|
|
};
|
|
us.buf[us.len] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(mod.load_offset + header.eh_frame_vaddr))));
|
|
us.len += 1;
|
|
} else {
|
|
// There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
|
|
// section, but we'll have to load the binary to get at it.
|
|
const loaded = try mod.getLoadedElf(gpa);
|
|
// If both are present, we can't just pick one -- the info could be split between them.
|
|
// `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.
|
|
if (loaded.file.debug_frame) |*debug_frame| {
|
|
us.buf[us.len] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes);
|
|
us.len += 1;
|
|
}
|
|
if (loaded.file.eh_frame) |*eh_frame| {
|
|
us.buf[us.len] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
|
|
us.len += 1;
|
|
}
|
|
}
|
|
errdefer for (us.buf[0..us.len]) |*u| u.deinit(gpa);
|
|
for (us.buf[0..us.len]) |*u| u.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) {
|
|
error.ReadFailed => unreachable, // it's all fixed buffers
|
|
error.InvalidDebugInfo,
|
|
error.MissingDebugInfo,
|
|
error.OutOfMemory,
|
|
=> |e| return e,
|
|
error.EndOfStream,
|
|
error.Overflow,
|
|
error.StreamTooLong,
|
|
error.InvalidOperand,
|
|
error.InvalidOpcode,
|
|
error.InvalidOperation,
|
|
=> return error.InvalidDebugInfo,
|
|
error.UnsupportedAddrSize,
|
|
error.UnsupportedDwarfVersion,
|
|
error.UnimplementedUserOpcode,
|
|
=> return error.UnsupportedDebugInfo,
|
|
};
|
|
return us;
|
|
}
|
|
|
|
/// Assumes we already hold an exclusive lock.
|
|
fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf {
|
|
if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa);
|
|
return if (mod.loaded_elf.?) |*elf| elf else |err| err;
|
|
}
|
|
fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf {
|
|
const load_result = if (mod.name.len > 0) res: {
|
|
var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo;
|
|
defer file.close();
|
|
break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name));
|
|
} else res: {
|
|
const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
else => return error.ReadFailed,
|
|
};
|
|
defer gpa.free(path);
|
|
var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo;
|
|
defer file.close();
|
|
break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path));
|
|
};
|
|
|
|
var elf_file = load_result catch |err| switch (err) {
|
|
error.OutOfMemory,
|
|
error.Unexpected,
|
|
=> |e| return e,
|
|
|
|
error.Overflow,
|
|
error.TruncatedElfFile,
|
|
error.InvalidCompressedSection,
|
|
error.InvalidElfMagic,
|
|
error.InvalidElfVersion,
|
|
error.InvalidElfClass,
|
|
error.InvalidElfEndian,
|
|
=> return error.InvalidDebugInfo,
|
|
|
|
error.SystemResources,
|
|
error.MemoryMappingNotSupported,
|
|
error.AccessDenied,
|
|
error.LockedMemoryLimitExceeded,
|
|
error.ProcessFdQuotaExceeded,
|
|
error.SystemFdQuotaExceeded,
|
|
=> return error.ReadFailed,
|
|
};
|
|
errdefer elf_file.deinit(gpa);
|
|
|
|
if (elf_file.endian != native_endian) return error.InvalidDebugInfo;
|
|
if (elf_file.is_64 != (@sizeOf(usize) == 8)) return error.InvalidDebugInfo;
|
|
|
|
return .{
|
|
.file = elf_file,
|
|
.scanned_dwarf = false,
|
|
};
|
|
}
|
|
};
|
|
|
|
fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared, exclusive }) Error!*Module {
|
|
// With the requested lock, scan the module ranges looking for `address`.
|
|
switch (lock) {
|
|
.shared => si.rwlock.lockShared(),
|
|
.exclusive => si.rwlock.lock(),
|
|
}
|
|
for (si.ranges.items) |*range| {
|
|
if (address >= range.start and address < range.start + range.len) {
|
|
return &si.modules.items[range.module_index];
|
|
}
|
|
}
|
|
// The address wasn't in a known range. We will rebuild the module/range lists, since it's possible
|
|
// a new module was loaded. Upgrade to an exclusive lock if necessary.
|
|
switch (lock) {
|
|
.shared => {
|
|
si.rwlock.unlockShared();
|
|
si.rwlock.lock();
|
|
},
|
|
.exclusive => {},
|
|
}
|
|
// Rebuild module list with the exclusive lock.
|
|
{
|
|
errdefer si.rwlock.unlock();
|
|
for (si.modules.items) |*mod| {
|
|
unwind: {
|
|
const u = &(mod.unwind orelse break :unwind catch break :unwind);
|
|
for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa);
|
|
}
|
|
loaded: {
|
|
const l = &(mod.loaded_elf orelse break :loaded catch break :loaded);
|
|
l.file.deinit(gpa);
|
|
}
|
|
}
|
|
si.modules.clearRetainingCapacity();
|
|
si.ranges.clearRetainingCapacity();
|
|
var ctx: DlIterContext = .{ .si = si, .gpa = gpa };
|
|
try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback);
|
|
}
|
|
// Downgrade the lock back to shared if necessary.
|
|
switch (lock) {
|
|
.shared => {
|
|
si.rwlock.unlock();
|
|
si.rwlock.lockShared();
|
|
},
|
|
.exclusive => {},
|
|
}
|
|
// Scan the newly rebuilt module ranges.
|
|
for (si.ranges.items) |*range| {
|
|
if (address >= range.start and address < range.start + range.len) {
|
|
return &si.modules.items[range.module_index];
|
|
}
|
|
}
|
|
// Still nothing; unlock and error.
|
|
switch (lock) {
|
|
.shared => si.rwlock.unlockShared(),
|
|
.exclusive => si.rwlock.unlock(),
|
|
}
|
|
return error.MissingDebugInfo;
|
|
}
|
|
const DlIterContext = struct {
|
|
si: *SelfInfo,
|
|
gpa: Allocator,
|
|
|
|
fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
|
|
_ = size;
|
|
|
|
var build_id: ?[]const u8 = null;
|
|
var gnu_eh_frame: ?[]const u8 = null;
|
|
|
|
// Populate `build_id` and `gnu_eh_frame`
|
|
for (info.phdr[0..info.phnum]) |phdr| {
|
|
switch (phdr.p_type) {
|
|
std.elf.PT_NOTE => {
|
|
// Look for .note.gnu.build-id
|
|
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
|
var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
|
|
const name_size = r.takeInt(u32, native_endian) catch continue;
|
|
const desc_size = r.takeInt(u32, native_endian) catch continue;
|
|
const note_type = r.takeInt(u32, native_endian) catch continue;
|
|
const name = r.take(name_size) catch continue;
|
|
if (note_type != std.elf.NT_GNU_BUILD_ID) continue;
|
|
if (!std.mem.eql(u8, name, "GNU\x00")) continue;
|
|
const desc = r.take(desc_size) catch continue;
|
|
build_id = desc;
|
|
},
|
|
std.elf.PT_GNU_EH_FRAME => {
|
|
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
|
gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
const gpa = context.gpa;
|
|
const si = context.si;
|
|
|
|
const module_index = si.modules.items.len;
|
|
try si.modules.append(gpa, .{
|
|
.load_offset = info.addr,
|
|
// Android libc uses NULL instead of "" to mark the main program
|
|
.name = std.mem.sliceTo(info.name, 0) orelse "",
|
|
.build_id = build_id,
|
|
.gnu_eh_frame = gnu_eh_frame,
|
|
.unwind = null,
|
|
.loaded_elf = null,
|
|
});
|
|
|
|
for (info.phdr[0..info.phnum]) |phdr| {
|
|
if (phdr.p_type != std.elf.PT_LOAD) continue;
|
|
try context.si.ranges.append(gpa, .{
|
|
// Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000
|
|
.start = info.addr +% phdr.p_vaddr,
|
|
.len = phdr.p_memsz,
|
|
.module_index = module_index,
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const Dwarf = std.debug.Dwarf;
|
|
const Error = std.debug.SelfInfoError;
|
|
const assert = std.debug.assert;
|
|
|
|
const builtin = @import("builtin");
|
|
const native_endian = builtin.target.cpu.arch.endian();
|
|
|
|
const SelfInfo = @This();
|