const std = @import("../../std.zig");
const builtin = @import("builtin");
const mem = std.mem;
const assert = std.debug.assert;
const fs = std.fs;
const elf = std.elf;
const native_endian = builtin.cpu.arch.endian();
const NativeTargetInfo = @This();
const Target = std.Target;
const Allocator = std.mem.Allocator;
const CrossTarget = std.zig.CrossTarget;
const windows = std.zig.system.windows;
const darwin = std.zig.system.darwin;
const linux = std.zig.system.linux;
target: Target,
dynamic_linker: DynamicLinker = DynamicLinker{},
pub const DynamicLinker = Target.DynamicLinker;
pub const DetectError = error{
FileSystem,
SystemResources,
SymLinkLoop,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
DeviceBusy,
OSVersionDetectionFail,
Unexpected,
};
pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo {
var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
if (cross_target.os_tag == null) {
switch (builtin.target.os.tag) {
.linux => {
const uts = std.os.uname();
const release = mem.sliceTo(&uts.release, 0);
if (std.builtin.Version.parse(release)) |ver| {
os.version_range.linux.range.min = ver;
os.version_range.linux.range.max = ver;
} else |err| switch (err) {
error.Overflow => {},
error.InvalidCharacter => {},
error.InvalidVersion => {},
}
},
.solaris => {
const uts = std.os.uname();
const release = mem.sliceTo(&uts.release, 0);
if (std.builtin.Version.parse(release)) |ver| {
os.version_range.semver.min = ver;
os.version_range.semver.max = ver;
} else |err| switch (err) {
error.Overflow => {},
error.InvalidCharacter => {},
error.InvalidVersion => {},
}
},
.windows => {
const detected_version = windows.detectRuntimeVersion();
os.version_range.windows.min = detected_version;
os.version_range.windows.max = detected_version;
},
.macos => try darwin.macos.detect(&os),
.freebsd, .netbsd, .dragonfly => {
const key = switch (builtin.target.os.tag) {
.freebsd => "kern.osreldate",
.netbsd, .dragonfly => "kern.osrevision",
else => unreachable,
};
var value: u32 = undefined;
var len: usize = @sizeOf(@TypeOf(value));
std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.PermissionDenied => unreachable,
error.SystemResources => unreachable,
error.UnknownName => unreachable,
error.Unexpected => return error.OSVersionDetectionFail,
};
switch (builtin.target.os.tag) {
.freebsd => {
const major = value / 100_000;
const minor1 = value % 100_000 / 10_000;
const minor2 = value % 10_000 / 1_000;
const patch = value % 1_000;
os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
os.version_range.semver.max = os.version_range.semver.min;
},
.netbsd => {
const major = value / 100_000_000;
const minor = value % 100_000_000 / 1_000_000;
const patch = value % 10_000 / 100;
os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
os.version_range.semver.max = os.version_range.semver.min;
},
.dragonfly => {
const major = value / 100_000;
const minor = value % 100_000 / 100;
const patch = value % 100;
os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
os.version_range.semver.max = os.version_range.semver.min;
},
else => unreachable,
}
},
.openbsd => {
const mib: [2]c_int = [_]c_int{
std.os.CTL.KERN,
std.os.KERN.OSRELEASE,
};
var buf: [64]u8 = undefined;
var len: usize = buf.len;
std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.PermissionDenied => unreachable,
error.SystemResources => unreachable,
error.UnknownName => unreachable,
error.Unexpected => return error.OSVersionDetectionFail,
};
if (std.builtin.Version.parse(buf[0 .. len - 1])) |ver| {
os.version_range.semver.min = ver;
os.version_range.semver.max = ver;
} else |_| {
return error.OSVersionDetectionFail;
}
},
else => {
},
}
}
if (cross_target.os_version_min) |min| switch (min) {
.none => {},
.semver => |semver| switch (cross_target.getOsTag()) {
.linux => os.version_range.linux.range.min = semver,
else => os.version_range.semver.min = semver,
},
.windows => |win_ver| os.version_range.windows.min = win_ver,
};
if (cross_target.os_version_max) |max| switch (max) {
.none => {},
.semver => |semver| switch (cross_target.getOsTag()) {
.linux => os.version_range.linux.range.max = semver,
else => os.version_range.semver.max = semver,
},
.windows => |win_ver| os.version_range.windows.max = win_ver,
};
if (cross_target.glibc_version) |glibc| {
assert(cross_target.isGnuLibC());
os.version_range.linux.glibc = glibc;
}
const cpu_arch = cross_target.getCpuArch();
var cpu = switch (cross_target.cpu_model) {
.native => detectNativeCpuAndFeatures(cpu_arch, os, cross_target),
.baseline => Target.Cpu.baseline(cpu_arch),
.determined_by_cpu_arch => if (cross_target.cpu_arch == null)
detectNativeCpuAndFeatures(cpu_arch, os, cross_target)
else
Target.Cpu.baseline(cpu_arch),
.explicit => |model| model.toCpu(cpu_arch),
} orelse backup_cpu_detection: {
break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
};
var result = try detectAbiAndDynamicLinker(cpu, os, cross_target);
switch (cpu_arch) {
.x86 => {
if (!std.Target.x86.featureSetHasAny(cross_target.cpu_features_add, .{
.@"16bit_mode", .@"32bit_mode",
})) {
switch (result.target.abi) {
.code16 => result.target.cpu.features.addFeature(
@enumToInt(std.Target.x86.Feature.@"16bit_mode"),
),
else => result.target.cpu.features.addFeature(
@enumToInt(std.Target.x86.Feature.@"32bit_mode"),
),
}
}
},
.arm, .armeb => {
},
.thumb, .thumbeb => {
result.target.cpu.features.addFeature(
@enumToInt(std.Target.arm.Feature.thumb_mode),
);
},
else => {},
}
cross_target.updateCpuFeatures(&result.target.cpu.features);
return result;
}
fn detectAbiAndDynamicLinker(
cpu: Target.Cpu,
os: Target.Os,
cross_target: CrossTarget,
) DetectError!NativeTargetInfo {
const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
const is_linux = builtin.target.os.tag == .linux;
const have_all_info = cross_target.dynamic_linker.get() != null and
cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu());
const os_is_non_native = cross_target.os_tag != null;
if (!native_target_has_ld or have_all_info or os_is_non_native) {
return defaultAbiAndDynamicLinker(cpu, os, cross_target);
}
if (cross_target.abi) |abi| {
if (abi.isMusl()) {
return defaultAbiAndDynamicLinker(cpu, os, cross_target);
}
}
const all_abis = comptime blk: {
assert(@enumToInt(Target.Abi.none) == 0);
const fields = std.meta.fields(Target.Abi)[1..];
var array: [fields.len]Target.Abi = undefined;
inline for (fields, 0..) |field, i| {
array[i] = @field(Target.Abi, field.name);
}
break :blk array;
};
var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
var ld_info_list_len: usize = 0;
const ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch);
for (all_abis) |abi| {
const target: Target = .{
.cpu = cpu,
.os = os,
.abi = abi,
.ofmt = ofmt,
};
const ld = target.standardDynamicLinkerPath();
if (ld.get() == null) continue;
ld_info_list_buffer[ld_info_list_len] = .{
.ld = ld,
.abi = abi,
};
ld_info_list_len += 1;
}
const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
const elf_file = blk: {
var file_name: []const u8 = "/usr/bin/env";
var buffer: [258]u8 = undefined;
while (true) {
const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
error.NoSpaceLeft => unreachable,
error.NameTooLong => unreachable,
error.PathAlreadyExists => unreachable,
error.SharingViolation => unreachable,
error.InvalidUtf8 => unreachable,
error.BadPathName => unreachable,
error.PipeBusy => unreachable,
error.FileLocksNotSupported => unreachable,
error.WouldBlock => unreachable,
error.FileBusy => unreachable,
error.IsDir,
error.NotDir,
error.InvalidHandle,
error.AccessDenied,
error.NoDevice,
error.FileNotFound,
error.FileTooBig,
error.Unexpected,
=> |e| {
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
return defaultAbiAndDynamicLinker(cpu, os, cross_target);
},
else => |e| return e,
};
errdefer file.close();
const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) {
error.UnexpectedEndOfFile,
error.UnableToReadElfFile,
=> break :blk file,
else => |e| return e,
};
const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file;
const line = buffer[0..newline];
if (!mem.startsWith(u8, line, "#!")) break :blk file;
var it = mem.tokenize(u8, line[2..], " ");
file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target);
file.close();
}
};
defer elf_file.close();
return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
error.FileSystem,
error.SystemResources,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
=> |e| return e,
error.UnableToReadElfFile,
error.InvalidElfClass,
error.InvalidElfVersion,
error.InvalidElfEndian,
error.InvalidElfFile,
error.InvalidElfMagic,
error.Unexpected,
error.UnexpectedEndOfFile,
error.NameTooLong,
=> |e| {
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
return defaultAbiAndDynamicLinker(cpu, os, cross_target);
},
};
}
fn glibcVerFromRPath(rpath: []const u8) !std.builtin.Version {
var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.InvalidUtf8 => unreachable,
error.BadPathName => unreachable,
error.DeviceBusy => unreachable,
error.FileNotFound,
error.NotDir,
error.InvalidHandle,
error.AccessDenied,
error.NoDevice,
=> return error.GLibCNotFound,
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
error.SystemResources,
error.SymLinkLoop,
error.Unexpected,
=> |e| return e,
};
defer dir.close();
const glibc_so_basename = "libc.so.6";
var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.InvalidUtf8 => unreachable,
error.BadPathName => unreachable,
error.PipeBusy => unreachable,
error.SharingViolation => unreachable,
error.FileLocksNotSupported => unreachable,
error.NoSpaceLeft => unreachable,
error.PathAlreadyExists => unreachable,
error.DeviceBusy => unreachable,
error.FileBusy => unreachable,
error.InvalidHandle => unreachable,
error.WouldBlock => unreachable,
error.NoDevice => unreachable,
error.AccessDenied,
error.FileNotFound,
error.NotDir,
error.IsDir,
=> return error.GLibCNotFound,
error.FileTooBig => return error.Unexpected,
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
error.SystemResources,
error.SymLinkLoop,
error.Unexpected,
=> |e| return e,
};
defer f.close();
return glibcVerFromSoFile(f) catch |err| switch (err) {
error.InvalidElfMagic,
error.InvalidElfEndian,
error.InvalidElfClass,
error.InvalidElfFile,
error.InvalidElfVersion,
error.InvalidGnuLibCVersion,
error.UnexpectedEndOfFile,
=> return error.GLibCNotFound,
error.SystemResources,
error.UnableToReadElfFile,
error.Unexpected,
error.FileSystem,
=> |e| return e,
};
}
fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
_ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
elf.ELFDATA2LSB => .Little,
elf.ELFDATA2MSB => .Big,
else => return error.InvalidElfEndian,
};
const need_bswap = elf_endian != native_endian;
if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
elf.ELFCLASS32 => false,
elf.ELFCLASS64 => true,
else => return error.InvalidElfClass,
};
const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
if (sh_buf.len < shentsize) return error.InvalidElfFile;
_ = try preadMin(file, &sh_buf, str_section_off, shentsize);
const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
var strtab_buf: [4096:0]u8 = undefined;
const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
const shstrtab = strtab_buf[0..shstrtab_read_len];
const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
var sh_i: u16 = 0;
const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
const sh_read_byte_len = try preadMin(
file,
sh_buf[0 .. sh_buf.len - sh_reserve],
shoff,
shentsize,
);
var sh_buf_i: usize = 0;
while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
sh_i += 1;
shoff += shentsize;
sh_buf_i += shentsize;
}) {
const sh32 = @ptrCast(
*elf.Elf32_Shdr,
@alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
);
const sh64 = @ptrCast(
*elf.Elf64_Shdr,
@alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
);
const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
if (mem.eql(u8, sh_name, ".dynstr")) {
break :find_dyn_str .{
.offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
.size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
};
}
}
} else return error.InvalidGnuLibCVersion;
var buf: [80000]u8 = undefined;
if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
const dynstr_size = @intCast(usize, dynstr.size);
const dynstr_bytes = buf[0..dynstr_size];
_ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len);
var it = mem.split(u8, dynstr_bytes, &.{0});
var max_ver: std.builtin.Version = .{ .major = 2, .minor = 2, .patch = 5 };
while (it.next()) |s| {
if (mem.startsWith(u8, s, "GLIBC_2.")) {
const chopped = s["GLIBC_".len..];
const ver = std.builtin.Version.parse(chopped) catch |err| switch (err) {
error.Overflow => return error.InvalidGnuLibCVersion,
error.InvalidCharacter => return error.InvalidGnuLibCVersion,
error.InvalidVersion => return error.InvalidGnuLibCVersion,
};
switch (ver.order(max_ver)) {
.gt => max_ver = ver,
.lt, .eq => continue,
}
}
}
return max_ver;
}
fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {
const suffix = ".so";
if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
return error.UnrecognizedGnuLibCFileName;
}
const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
error.Overflow => return error.InvalidGnuLibCVersion,
error.InvalidCharacter => return error.InvalidGnuLibCVersion,
error.InvalidVersion => return error.InvalidGnuLibCVersion,
};
}
pub const AbiAndDynamicLinkerFromFileError = error{
FileSystem,
SystemResources,
SymLinkLoop,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
UnableToReadElfFile,
InvalidElfClass,
InvalidElfVersion,
InvalidElfEndian,
InvalidElfFile,
InvalidElfMagic,
Unexpected,
UnexpectedEndOfFile,
NameTooLong,
};
pub fn abiAndDynamicLinkerFromFile(
file: fs.File,
cpu: Target.Cpu,
os: Target.Os,
ld_info_list: []const LdInfo,
cross_target: CrossTarget,
) AbiAndDynamicLinkerFromFileError!NativeTargetInfo {
var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
_ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
elf.ELFDATA2LSB => .Little,
elf.ELFDATA2MSB => .Big,
else => return error.InvalidElfEndian,
};
const need_bswap = elf_endian != native_endian;
if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
elf.ELFCLASS32 => false,
elf.ELFCLASS64 => true,
else => return error.InvalidElfClass,
};
var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
var result: NativeTargetInfo = .{
.target = .{
.cpu = cpu,
.os = os,
.abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
.ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
},
.dynamic_linker = cross_target.dynamic_linker,
};
var rpath_offset: ?u64 = null;
const look_for_ld = cross_target.dynamic_linker.get() == null;
var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
var ph_i: u16 = 0;
while (ph_i < phnum) {
const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
var ph_buf_i: usize = 0;
while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
ph_i += 1;
phoff += phentsize;
ph_buf_i += phentsize;
}) {
const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i]));
const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i]));
const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
switch (p_type) {
elf.PT_INTERP => if (look_for_ld) {
const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
const filesz = @intCast(usize, p_filesz);
_ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
const len = filesz - 1;
result.dynamic_linker.max_byte = @intCast(u8, len - 1);
const full_ld_path = result.dynamic_linker.buffer[0..len];
for (ld_info_list) |ld_info| {
const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
result.target.abi = ld_info.abi;
break;
}
}
},
elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
cross_target.glibc_version == null)
{
var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
const dyn_num = p_filesz / dyn_size;
var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
var dyn_i: usize = 0;
dyn: while (dyn_i < dyn_num) {
const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
const dyn_read_byte_len = try preadMin(
file,
dyn_buf[0 .. dyn_buf.len - dyn_reserve],
dyn_off,
dyn_size,
);
var dyn_buf_i: usize = 0;
while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
dyn_i += 1;
dyn_off += dyn_size;
dyn_buf_i += dyn_size;
}) {
const dyn32 = @ptrCast(
*elf.Elf32_Dyn,
@alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]),
);
const dyn64 = @ptrCast(
*elf.Elf64_Dyn,
@alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]),
);
const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
if (tag == elf.DT_RUNPATH) {
rpath_offset = val;
break :dyn;
}
}
}
},
else => continue,
}
}
}
if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
cross_target.glibc_version == null)
{
const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
if (sh_buf.len < shentsize) return error.InvalidElfFile;
_ = try preadMin(file, &sh_buf, str_section_off, shentsize);
const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
var strtab_buf: [4096:0]u8 = undefined;
const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
const shstrtab = strtab_buf[0..shstrtab_read_len];
const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
var sh_i: u16 = 0;
const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
const sh_read_byte_len = try preadMin(
file,
sh_buf[0 .. sh_buf.len - sh_reserve],
shoff,
shentsize,
);
var sh_buf_i: usize = 0;
while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
sh_i += 1;
shoff += shentsize;
sh_buf_i += shentsize;
}) {
const sh32 = @ptrCast(
*elf.Elf32_Shdr,
@alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
);
const sh64 = @ptrCast(
*elf.Elf64_Shdr,
@alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
);
const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
if (mem.eql(u8, sh_name, ".dynstr")) {
break :find_dyn_str .{
.offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
.size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
};
}
}
} else null;
if (dynstr) |ds| {
if (rpath_offset) |rpoff| {
const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile;
if (rpoff_usize > ds.size) return error.InvalidElfFile;
const rpoff_file = ds.offset + rpoff_usize;
const rp_max_size = ds.size - rpoff_usize;
const strtab_len = std.math.min(rp_max_size, strtab_buf.len);
const strtab_read_len = try preadMin(file, &strtab_buf, rpoff_file, strtab_len);
const strtab = strtab_buf[0..strtab_read_len];
const rpath_list = mem.sliceTo(strtab, 0);
var it = mem.tokenize(u8, rpath_list, ":");
while (it.next()) |rpath| {
if (glibcVerFromRPath(rpath)) |ver| {
result.target.os.version_range.linux.glibc = ver;
return result;
} else |err| switch (err) {
error.GLibCNotFound => continue,
else => |e| return e,
}
}
}
}
if (result.dynamic_linker.get()) |dl_path| glibc_ver: {
if (fs.path.dirname(dl_path)) |rpath| {
if (glibcVerFromRPath(rpath)) |ver| {
result.target.os.version_range.linux.glibc = ver;
return result;
} else |err| switch (err) {
error.GLibCNotFound => {},
else => |e| return e,
}
}
var link_buf: [std.os.PATH_MAX]u8 = undefined;
const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.InvalidUtf8 => unreachable,
error.BadPathName => unreachable,
error.UnsupportedReparsePointType => unreachable,
error.AccessDenied,
error.FileNotFound,
error.NotLink,
error.NotDir,
=> break :glibc_ver,
error.SystemResources,
error.FileSystem,
error.SymLinkLoop,
error.Unexpected,
=> |e| return e,
};
result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
fs.path.basename(link_name),
"ld-",
) catch |err| switch (err) {
error.UnrecognizedGnuLibCFileName,
error.InvalidGnuLibCVersion,
=> break :glibc_ver,
};
return result;
}
var path_buf: [std.os.PATH_MAX]u8 = undefined;
var index: usize = 0;
const prefix = "/lib/";
const cpu_arch = @tagName(result.target.cpu.arch);
const os_tag = @tagName(result.target.os.tag);
const abi = @tagName(result.target.abi);
mem.copy(u8, path_buf[index..], prefix);
index += prefix.len;
mem.copy(u8, path_buf[index..], cpu_arch);
index += cpu_arch.len;
path_buf[index] = '-';
index += 1;
mem.copy(u8, path_buf[index..], os_tag);
index += os_tag.len;
path_buf[index] = '-';
index += 1;
mem.copy(u8, path_buf[index..], abi);
index += abi.len;
const rpath = path_buf[0..index];
if (glibcVerFromRPath(rpath)) |ver| {
result.target.os.version_range.linux.glibc = ver;
return result;
} else |err| switch (err) {
error.GLibCNotFound => {},
else => |e| return e,
}
}
return result;
}
fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
var i: usize = 0;
while (i < min_read_len) {
const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
error.OperationAborted => unreachable,
error.WouldBlock => unreachable,
error.NotOpenForReading => unreachable,
error.SystemResources => return error.SystemResources,
error.IsDir => return error.UnableToReadElfFile,
error.BrokenPipe => return error.UnableToReadElfFile,
error.Unseekable => return error.UnableToReadElfFile,
error.ConnectionResetByPeer => return error.UnableToReadElfFile,
error.ConnectionTimedOut => return error.UnableToReadElfFile,
error.NetNameDeleted => return error.UnableToReadElfFile,
error.Unexpected => return error.Unexpected,
error.InputOutput => return error.FileSystem,
error.AccessDenied => return error.Unexpected,
};
if (len == 0) return error.UnexpectedEndOfFile;
i += len;
}
return i;
}
fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
const target: Target = .{
.cpu = cpu,
.os = os,
.abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
.ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
};
return NativeTargetInfo{
.target = target,
.dynamic_linker = if (cross_target.dynamic_linker.get() == null)
target.standardDynamicLinkerPath()
else
cross_target.dynamic_linker,
};
}
pub const LdInfo = struct {
ld: DynamicLinker,
abi: Target.Abi,
};
pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
if (is_64) {
if (need_bswap) {
return @byteSwap(int_64);
} else {
return int_64;
}
} else {
if (need_bswap) {
return @byteSwap(int_32);
} else {
return int_32;
}
}
}
fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu {
switch (builtin.cpu.arch) {
.x86_64, .x86 => {
return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target);
},
else => {},
}
switch (builtin.os.tag) {
.linux => return linux.detectNativeCpuAndFeatures(),
.macos => return darwin.macos.detectNativeCpuAndFeatures(),
.windows => return windows.detectNativeCpuAndFeatures(),
else => {},
}
return null;
}
pub const Executor = union(enum) {
native,
rosetta,
qemu: []const u8,
wine: []const u8,
wasmtime: []const u8,
darling: []const u8,
bad_dl: []const u8,
bad_os_or_cpu,
};
pub const GetExternalExecutorOptions = struct {
allow_darling: bool = true,
allow_qemu: bool = true,
allow_rosetta: bool = true,
allow_wasmtime: bool = true,
allow_wine: bool = true,
qemu_fixes_dl: bool = false,
link_libc: bool = false,
};
pub fn getExternalExecutor(
host: NativeTargetInfo,
candidate: NativeTargetInfo,
options: GetExternalExecutorOptions,
) Executor {
const os_match = host.target.os.tag == candidate.target.os.tag;
const cpu_ok = cpu_ok: {
if (host.target.cpu.arch == candidate.target.cpu.arch)
break :cpu_ok true;
if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .x86)
break :cpu_ok true;
if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm)
break :cpu_ok true;
if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb)
break :cpu_ok true;
break :cpu_ok false;
};
var bad_result: Executor = .bad_os_or_cpu;
if (os_match and cpu_ok) native: {
if (options.link_libc) {
if (candidate.dynamic_linker.get()) |candidate_dl| {
fs.cwd().access(candidate_dl, .{}) catch {
bad_result = .{ .bad_dl = candidate_dl };
break :native;
};
}
}
return .native;
}
if (options.allow_rosetta and os_match and
host.target.os.tag == .macos and host.target.cpu.arch == .aarch64)
{
switch (candidate.target.cpu.arch) {
.x86_64 => return .rosetta,
else => return bad_result,
}
}
if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
return switch (candidate.target.cpu.arch) {
.aarch64 => Executor{ .qemu = "qemu-aarch64" },
.aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
.arm => Executor{ .qemu = "qemu-arm" },
.armeb => Executor{ .qemu = "qemu-armeb" },
.hexagon => Executor{ .qemu = "qemu-hexagon" },
.x86 => Executor{ .qemu = "qemu-i386" },
.m68k => Executor{ .qemu = "qemu-m68k" },
.mips => Executor{ .qemu = "qemu-mips" },
.mipsel => Executor{ .qemu = "qemu-mipsel" },
.mips64 => Executor{ .qemu = "qemu-mips64" },
.mips64el => Executor{ .qemu = "qemu-mips64el" },
.powerpc => Executor{ .qemu = "qemu-ppc" },
.powerpc64 => Executor{ .qemu = "qemu-ppc64" },
.powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
.riscv32 => Executor{ .qemu = "qemu-riscv32" },
.riscv64 => Executor{ .qemu = "qemu-riscv64" },
.s390x => Executor{ .qemu = "qemu-s390x" },
.sparc => Executor{ .qemu = "qemu-sparc" },
.sparc64 => Executor{ .qemu = "qemu-sparc64" },
.x86_64 => Executor{ .qemu = "qemu-x86_64" },
else => return bad_result,
};
}
switch (candidate.target.os.tag) {
.windows => {
if (options.allow_wine) {
if (candidate.target.cpu.arch != builtin.cpu.arch) {
return bad_result;
}
switch (candidate.target.cpu.arch.ptrBitWidth()) {
32 => return Executor{ .wine = "wine" },
64 => return Executor{ .wine = "wine64" },
else => return bad_result,
}
}
return bad_result;
},
.wasi => {
if (options.allow_wasmtime) {
switch (candidate.target.cpu.arch.ptrBitWidth()) {
32 => return Executor{ .wasmtime = "wasmtime" },
else => return bad_result,
}
}
return bad_result;
},
.macos => {
if (options.allow_darling) {
if (candidate.target.cpu.arch != builtin.cpu.arch) {
return bad_result;
}
return Executor{ .darling = "darling" };
}
return bad_result;
},
else => return bad_result,
}
}