mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-07 14:24:43 +00:00
This commit fixes linking issue on macOS 11 BigSur by appending a prefix path to all lib and framework search paths known as `-syslibroot`. The reason this is needed is that in macOS 11, the system libraries and frameworks are no longer readily available in the filesystem. Instead, the new macOS ships with a built-in dynamic linker cache of all system-provided libraries, and hence, when linking with either `lld.ld64` or `ld64`, it is required to pass in `-syslibroot [dir]`. The latter can usually be obtained by invoking `xcrun --show-sdk-path`. With this commit, Zig will do this automatically when compiling natively on macOS. However, it also provides a flag `-syslibroot` which can be used to overwrite the automtically populated value. To summarise, with this change, the user of Zig is not required to generate and append their own syslibroot path. Standard invocations such as `zig build-exe hello.zig` or `zig build` for projects will work out of the box. The only missing bit is `zig cc` and `zig c++` since the addition of the `-syslibroot` option would be a mismatch between the values provided by `clang` itself and Zig's wrapper.
948 lines
43 KiB
Zig
948 lines
43 KiB
Zig
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2015-2020 Zig Contributors
|
|
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
|
// The MIT license requires this copyright notice to be included in all copies
|
|
// and substantial portions of the software.
|
|
const std = @import("../std.zig");
|
|
const elf = std.elf;
|
|
const mem = std.mem;
|
|
const fs = std.fs;
|
|
const Allocator = std.mem.Allocator;
|
|
const ArrayList = std.ArrayList;
|
|
const assert = std.debug.assert;
|
|
const process = std.process;
|
|
const Target = std.Target;
|
|
const CrossTarget = std.zig.CrossTarget;
|
|
const macos = @import("system/macos.zig");
|
|
|
|
const is_windows = Target.current.os.tag == .windows;
|
|
|
|
pub const getSDKPath = macos.getSDKPath;
|
|
|
|
pub const NativePaths = struct {
|
|
include_dirs: ArrayList([:0]u8),
|
|
lib_dirs: ArrayList([:0]u8),
|
|
rpaths: ArrayList([:0]u8),
|
|
warnings: ArrayList([:0]u8),
|
|
|
|
pub fn detect(allocator: *Allocator) !NativePaths {
|
|
var self: NativePaths = .{
|
|
.include_dirs = ArrayList([:0]u8).init(allocator),
|
|
.lib_dirs = ArrayList([:0]u8).init(allocator),
|
|
.rpaths = ArrayList([:0]u8).init(allocator),
|
|
.warnings = ArrayList([:0]u8).init(allocator),
|
|
};
|
|
errdefer self.deinit();
|
|
|
|
var is_nix = false;
|
|
if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
|
|
defer allocator.free(nix_cflags_compile);
|
|
|
|
is_nix = true;
|
|
var it = mem.tokenize(nix_cflags_compile, " ");
|
|
while (true) {
|
|
const word = it.next() orelse break;
|
|
if (mem.eql(u8, word, "-isystem")) {
|
|
const include_path = it.next() orelse {
|
|
try self.addWarning("Expected argument after -isystem in NIX_CFLAGS_COMPILE");
|
|
break;
|
|
};
|
|
try self.addIncludeDir(include_path);
|
|
} else {
|
|
try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {}", .{word});
|
|
break;
|
|
}
|
|
}
|
|
} else |err| switch (err) {
|
|
error.InvalidUtf8 => {},
|
|
error.EnvironmentVariableNotFound => {},
|
|
error.OutOfMemory => |e| return e,
|
|
}
|
|
if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
|
|
defer allocator.free(nix_ldflags);
|
|
|
|
is_nix = true;
|
|
var it = mem.tokenize(nix_ldflags, " ");
|
|
while (true) {
|
|
const word = it.next() orelse break;
|
|
if (mem.eql(u8, word, "-rpath")) {
|
|
const rpath = it.next() orelse {
|
|
try self.addWarning("Expected argument after -rpath in NIX_LDFLAGS");
|
|
break;
|
|
};
|
|
try self.addRPath(rpath);
|
|
} else if (word.len > 2 and word[0] == '-' and word[1] == 'L') {
|
|
const lib_path = word[2..];
|
|
try self.addLibDir(lib_path);
|
|
} else {
|
|
try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {}", .{word});
|
|
break;
|
|
}
|
|
}
|
|
} else |err| switch (err) {
|
|
error.InvalidUtf8 => {},
|
|
error.EnvironmentVariableNotFound => {},
|
|
error.OutOfMemory => |e| return e,
|
|
}
|
|
if (is_nix) {
|
|
return self;
|
|
}
|
|
|
|
if (!is_windows) {
|
|
const triple = try Target.current.linuxTriple(allocator);
|
|
const qual = Target.current.cpu.arch.ptrBitWidth();
|
|
|
|
// TODO: $ ld --verbose | grep SEARCH_DIR
|
|
// the output contains some paths that end with lib64, maybe include them too?
|
|
// TODO: what is the best possible order of things?
|
|
// TODO: some of these are suspect and should only be added on some systems. audit needed.
|
|
|
|
try self.addIncludeDir("/usr/local/include");
|
|
try self.addLibDirFmt("/usr/local/lib{}", .{qual});
|
|
try self.addLibDir("/usr/local/lib");
|
|
|
|
try self.addIncludeDirFmt("/usr/include/{}", .{triple});
|
|
try self.addLibDirFmt("/usr/lib/{}", .{triple});
|
|
|
|
try self.addIncludeDir("/usr/include");
|
|
try self.addLibDirFmt("/lib{}", .{qual});
|
|
try self.addLibDir("/lib");
|
|
try self.addLibDirFmt("/usr/lib{}", .{qual});
|
|
try self.addLibDir("/usr/lib");
|
|
|
|
// example: on a 64-bit debian-based linux distro, with zlib installed from apt:
|
|
// zlib.h is in /usr/include (added above)
|
|
// libz.so.1 is in /lib/x86_64-linux-gnu (added here)
|
|
try self.addLibDirFmt("/lib/{}", .{triple});
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: *NativePaths) void {
|
|
deinitArray(&self.include_dirs);
|
|
deinitArray(&self.lib_dirs);
|
|
deinitArray(&self.rpaths);
|
|
deinitArray(&self.warnings);
|
|
self.* = undefined;
|
|
}
|
|
|
|
fn deinitArray(array: *ArrayList([:0]u8)) void {
|
|
for (array.span()) |item| {
|
|
array.allocator.free(item);
|
|
}
|
|
array.deinit();
|
|
}
|
|
|
|
pub fn addIncludeDir(self: *NativePaths, s: []const u8) !void {
|
|
return self.appendArray(&self.include_dirs, s);
|
|
}
|
|
|
|
pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
|
|
const item = try std.fmt.allocPrint0(self.include_dirs.allocator, fmt, args);
|
|
errdefer self.include_dirs.allocator.free(item);
|
|
try self.include_dirs.append(item);
|
|
}
|
|
|
|
pub fn addLibDir(self: *NativePaths, s: []const u8) !void {
|
|
return self.appendArray(&self.lib_dirs, s);
|
|
}
|
|
|
|
pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
|
|
const item = try std.fmt.allocPrint0(self.lib_dirs.allocator, fmt, args);
|
|
errdefer self.lib_dirs.allocator.free(item);
|
|
try self.lib_dirs.append(item);
|
|
}
|
|
|
|
pub fn addWarning(self: *NativePaths, s: []const u8) !void {
|
|
return self.appendArray(&self.warnings, s);
|
|
}
|
|
|
|
pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
|
|
const item = try std.fmt.allocPrint0(self.warnings.allocator, fmt, args);
|
|
errdefer self.warnings.allocator.free(item);
|
|
try self.warnings.append(item);
|
|
}
|
|
|
|
pub fn addRPath(self: *NativePaths, s: []const u8) !void {
|
|
return self.appendArray(&self.rpaths, s);
|
|
}
|
|
|
|
fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void {
|
|
const item = try array.allocator.dupeZ(u8, s);
|
|
errdefer array.allocator.free(item);
|
|
try array.append(item);
|
|
}
|
|
};
|
|
|
|
pub const NativeTargetInfo = struct {
|
|
target: Target,
|
|
|
|
dynamic_linker: DynamicLinker = DynamicLinker{},
|
|
|
|
/// Only some architectures have CPU detection implemented. This field reveals whether
|
|
/// CPU detection actually occurred. When this is `true` it means that the reported
|
|
/// CPU is baseline only because of a missing implementation for that architecture.
|
|
cpu_detection_unimplemented: bool = false,
|
|
|
|
pub const DynamicLinker = Target.DynamicLinker;
|
|
|
|
pub const DetectError = error{
|
|
OutOfMemory,
|
|
FileSystem,
|
|
SystemResources,
|
|
SymLinkLoop,
|
|
ProcessFdQuotaExceeded,
|
|
SystemFdQuotaExceeded,
|
|
DeviceBusy,
|
|
};
|
|
|
|
/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
|
|
/// natively, which should be standard or default, and which are provided explicitly, this function
|
|
/// resolves the native components by detecting the native system, and then resolves standard/default parts
|
|
/// relative to that.
|
|
/// Any resources this function allocates are released before returning, and so there is no
|
|
/// deinitialization method.
|
|
/// TODO Remove the Allocator requirement from this function.
|
|
pub fn detect(allocator: *Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
|
|
var os = cross_target.getOsTag().defaultVersionRange();
|
|
if (cross_target.os_tag == null) {
|
|
switch (Target.current.os.tag) {
|
|
.linux => {
|
|
const uts = std.os.uname();
|
|
const release = mem.spanZ(&uts.release);
|
|
// The release field may have several other fields after the
|
|
// kernel version
|
|
const kernel_version = if (mem.indexOfScalar(u8, release, '-')) |pos|
|
|
release[0..pos]
|
|
else if (mem.indexOfScalar(u8, release, '_')) |pos|
|
|
release[0..pos]
|
|
else
|
|
release;
|
|
|
|
if (std.builtin.Version.parse(kernel_version)) |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 => {},
|
|
}
|
|
},
|
|
.windows => {
|
|
var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined;
|
|
version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info));
|
|
|
|
switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) {
|
|
.SUCCESS => {},
|
|
else => unreachable,
|
|
}
|
|
|
|
// Starting from the system infos build a NTDDI-like version
|
|
// constant whose format is:
|
|
// B0 B1 B2 B3
|
|
// `---` `` ``--> Sub-version (Starting from Windows 10 onwards)
|
|
// \ `--> Service pack (Always zero in the constants defined)
|
|
// `--> OS version (Major & minor)
|
|
const os_ver: u16 = //
|
|
@intCast(u16, version_info.dwMajorVersion & 0xff) << 8 |
|
|
@intCast(u16, version_info.dwMinorVersion & 0xff);
|
|
const sp_ver: u8 = 0;
|
|
const sub_ver: u8 = if (os_ver >= 0x0A00) subver: {
|
|
// There's no other way to obtain this info beside
|
|
// checking the build number against a known set of
|
|
// values
|
|
const known_build_numbers = [_]u32{
|
|
10240, 10586, 14393, 15063, 16299, 17134, 17763,
|
|
18362, 19041,
|
|
};
|
|
var last_idx: usize = 0;
|
|
for (known_build_numbers) |build, i| {
|
|
if (version_info.dwBuildNumber >= build)
|
|
last_idx = i;
|
|
}
|
|
break :subver @truncate(u8, last_idx);
|
|
} else 0;
|
|
|
|
const version: u32 = @as(u32, os_ver) << 16 | @as(u32, sp_ver) << 8 | sub_ver;
|
|
|
|
os.version_range.windows.max = @intToEnum(Target.Os.WindowsVersion, version);
|
|
os.version_range.windows.min = @intToEnum(Target.Os.WindowsVersion, version);
|
|
},
|
|
.macos => {
|
|
var scbuf: [32]u8 = undefined;
|
|
var size: usize = undefined;
|
|
|
|
// The osproductversion sysctl was introduced first with 10.13.4 High Sierra.
|
|
const key_osproductversion = "kern.osproductversion"; // eg. "10.15.4"
|
|
size = scbuf.len;
|
|
if (std.os.sysctlbynameZ(key_osproductversion, &scbuf, &size, null, 0)) |_| {
|
|
const string_version = scbuf[0 .. size - 1];
|
|
if (std.builtin.Version.parse(string_version)) |ver| {
|
|
os.version_range.semver.min = ver;
|
|
os.version_range.semver.max = ver;
|
|
} else |err| switch (err) {
|
|
error.Overflow => {},
|
|
error.InvalidCharacter => {},
|
|
error.InvalidVersion => {},
|
|
}
|
|
} else |err| switch (err) {
|
|
error.UnknownName => {
|
|
const key_osversion = "kern.osversion"; // eg. "19E287"
|
|
size = scbuf.len;
|
|
std.os.sysctlbynameZ(key_osversion, &scbuf, &size, null, 0) catch {
|
|
@panic("unable to detect macOS version: " ++ key_osversion);
|
|
};
|
|
if (macos.version_from_build(scbuf[0 .. size - 1])) |ver| {
|
|
os.version_range.semver.min = ver;
|
|
os.version_range.semver.max = ver;
|
|
} else |_| {}
|
|
},
|
|
else => @panic("unable to detect macOS version: " ++ key_osproductversion),
|
|
}
|
|
},
|
|
.freebsd => {
|
|
var osreldate: u32 = undefined;
|
|
var len: usize = undefined;
|
|
|
|
std.os.sysctlbynameZ("kern.osreldate", &osreldate, &len, null, 0) catch |err| switch (err) {
|
|
error.NameTooLong => unreachable, // constant, known good value
|
|
error.PermissionDenied => unreachable, // only when setting values,
|
|
error.SystemResources => unreachable, // memory already on the stack
|
|
error.UnknownName => unreachable, // constant, known good value
|
|
error.Unexpected => unreachable, // EFAULT: stack should be safe, EISDIR/ENOTDIR: constant, known good value
|
|
};
|
|
|
|
// https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
|
|
// Major * 100,000 has been convention since FreeBSD 2.2 (1997)
|
|
// Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
|
|
// e.g. 492101 = 4.11-STABLE = 4.(9+2)
|
|
const major = osreldate / 100_000;
|
|
const minor1 = osreldate % 100_000 / 10_000; // usually 0 since 5.1
|
|
const minor2 = osreldate % 10_000 / 1_000; // 0 before 5.1, minor version since
|
|
const patch = osreldate % 1_000;
|
|
os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
|
|
os.version_range.semver.max = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
|
|
},
|
|
else => {
|
|
// Unimplemented, fall back to default version range.
|
|
},
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
var cpu_detection_unimplemented = false;
|
|
|
|
// Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
|
|
// native CPU architecture as being different than the current target), we use this:
|
|
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: {
|
|
cpu_detection_unimplemented = true;
|
|
break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
|
|
};
|
|
cross_target.updateCpuFeatures(&cpu.features);
|
|
|
|
var target = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
|
|
target.cpu_detection_unimplemented = cpu_detection_unimplemented;
|
|
return target;
|
|
}
|
|
|
|
/// First we attempt to use the executable's own binary. If it is dynamically
|
|
/// linked, then it should answer both the C ABI question and the dynamic linker question.
|
|
/// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then
|
|
/// we fall back to the defaults.
|
|
/// TODO Remove the Allocator requirement from this function.
|
|
fn detectAbiAndDynamicLinker(
|
|
allocator: *Allocator,
|
|
cpu: Target.Cpu,
|
|
os: Target.Os,
|
|
cross_target: CrossTarget,
|
|
) DetectError!NativeTargetInfo {
|
|
const native_target_has_ld = comptime Target.current.hasDynamicLinker();
|
|
const is_linux = Target.current.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()) {
|
|
// musl implies static linking.
|
|
return defaultAbiAndDynamicLinker(cpu, os, cross_target);
|
|
}
|
|
}
|
|
// The current target's ABI cannot be relied on for this. For example, we may build the zig
|
|
// compiler for target riscv64-linux-musl and provide a tarball for users to download.
|
|
// A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
|
|
// and supported by Zig. But that means that we must detect the system ABI here rather than
|
|
// relying on `Target.current`.
|
|
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) |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;
|
|
|
|
for (all_abis) |abi| {
|
|
// This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
|
|
// skip adding it to `ld_info_list`.
|
|
const target: Target = .{
|
|
.cpu = cpu,
|
|
.os = os,
|
|
.abi = abi,
|
|
};
|
|
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];
|
|
|
|
if (cross_target.dynamic_linker.get()) |explicit_ld| {
|
|
const explicit_ld_basename = fs.path.basename(explicit_ld);
|
|
for (ld_info_list) |ld_info| {
|
|
const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
|
|
}
|
|
}
|
|
|
|
// Best case scenario: the executable is dynamically linked, and we can iterate
|
|
// over our own shared objects and find a dynamic linker.
|
|
self_exe: {
|
|
const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
|
|
defer {
|
|
for (lib_paths) |lib_path| {
|
|
allocator.free(lib_path);
|
|
}
|
|
allocator.free(lib_paths);
|
|
}
|
|
|
|
var found_ld_info: LdInfo = undefined;
|
|
var found_ld_path: [:0]const u8 = undefined;
|
|
|
|
// Look for dynamic linker.
|
|
// This is O(N^M) but typical case here is N=2 and M=10.
|
|
find_ld: for (lib_paths) |lib_path| {
|
|
for (ld_info_list) |ld_info| {
|
|
const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
|
|
if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
|
|
found_ld_info = ld_info;
|
|
found_ld_path = lib_path;
|
|
break :find_ld;
|
|
}
|
|
}
|
|
} else break :self_exe;
|
|
|
|
// Look for glibc version.
|
|
var os_adjusted = os;
|
|
if (Target.current.os.tag == .linux and found_ld_info.abi.isGnu() and
|
|
cross_target.glibc_version == null)
|
|
{
|
|
for (lib_paths) |lib_path| {
|
|
if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
|
|
os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
|
|
error.UnrecognizedGnuLibCFileName => continue,
|
|
error.InvalidGnuLibCVersion => continue,
|
|
error.GnuLibCVersionUnavailable => continue,
|
|
else => |e| return e,
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
var result: NativeTargetInfo = .{
|
|
.target = .{
|
|
.cpu = cpu,
|
|
.os = os_adjusted,
|
|
.abi = cross_target.abi orelse found_ld_info.abi,
|
|
},
|
|
.dynamic_linker = if (cross_target.dynamic_linker.get() == null)
|
|
DynamicLinker.init(found_ld_path)
|
|
else
|
|
cross_target.dynamic_linker,
|
|
};
|
|
return result;
|
|
}
|
|
|
|
const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) 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.IsDir,
|
|
error.NotDir,
|
|
error.AccessDenied,
|
|
error.NoDevice,
|
|
error.FileNotFound,
|
|
error.FileTooBig,
|
|
error.Unexpected,
|
|
=> return defaultAbiAndDynamicLinker(cpu, os, cross_target),
|
|
|
|
else => |e| return e,
|
|
};
|
|
defer env_file.close();
|
|
|
|
// If Zig is statically linked, such as via distributed binary static builds, the above
|
|
// trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
|
|
// Since that path is hard-coded into the shebang line of many portable scripts, it's a
|
|
// reasonably reliable path to check for.
|
|
return abiAndDynamicLinkerFromFile(env_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,
|
|
// Finally, we fall back on the standard path.
|
|
=> defaultAbiAndDynamicLinker(cpu, os, cross_target),
|
|
};
|
|
}
|
|
|
|
const glibc_so_basename = "libc.so.6";
|
|
|
|
fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {
|
|
var link_buf: [std.os.PATH_MAX]u8 = undefined;
|
|
const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) {
|
|
error.AccessDenied => return error.GnuLibCVersionUnavailable,
|
|
error.FileSystem => return error.FileSystem,
|
|
error.SymLinkLoop => return error.SymLinkLoop,
|
|
error.NameTooLong => unreachable,
|
|
error.FileNotFound => return error.GnuLibCVersionUnavailable,
|
|
error.SystemResources => return error.SystemResources,
|
|
error.NotDir => return error.GnuLibCVersionUnavailable,
|
|
error.Unexpected => return error.GnuLibCVersionUnavailable,
|
|
error.InvalidUtf8 => unreachable, // Windows only
|
|
error.BadPathName => unreachable, // Windows only
|
|
error.UnsupportedReparsePointType => unreachable, // Windows only
|
|
};
|
|
return glibcVerFromLinkName(link_name);
|
|
}
|
|
|
|
fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version {
|
|
// example: "libc-2.3.4.so"
|
|
// example: "libc-2.27.so"
|
|
const prefix = "libc-";
|
|
const suffix = ".so";
|
|
if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
|
|
return error.UnrecognizedGnuLibCFileName;
|
|
}
|
|
// chop off "libc-" and ".so"
|
|
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], "\x7fELF")) 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 != std.builtin.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),
|
|
},
|
|
.dynamic_linker = cross_target.dynamic_linker,
|
|
};
|
|
var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
|
|
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) {
|
|
// Reserve some bytes so that we can deref the 64-bit struct fields
|
|
// even when the ELF file is 32-bits.
|
|
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);
|
|
// PT_INTERP includes a null byte in filesz.
|
|
const len = filesz - 1;
|
|
// dynamic_linker.max_byte is "max", not "len".
|
|
// We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
|
|
result.dynamic_linker.max_byte = @intCast(u8, len - 1);
|
|
|
|
// Use it to determine ABI.
|
|
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;
|
|
}
|
|
}
|
|
},
|
|
// We only need this for detecting glibc version.
|
|
elf.PT_DYNAMIC => if (Target.current.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) {
|
|
// Reserve some bytes so that we can deref the 64-bit struct fields
|
|
// even when the ELF file is 32-bits.
|
|
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 (Target.current.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) {
|
|
if (rpath_offset) |rpoff| {
|
|
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) {
|
|
// Reserve some bytes so that we can deref the 64-bit struct fields
|
|
// even when the ELF file is 32-bits.
|
|
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);
|
|
// TODO this pointer cast should not be necessary
|
|
const sh_name = mem.spanZ(@ptrCast([*:0]u8, shstrtab[sh_name_off..].ptr));
|
|
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| {
|
|
const strtab_len = std.math.min(ds.size, strtab_buf.len);
|
|
const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, shstrtab_len);
|
|
const strtab = strtab_buf[0..strtab_read_len];
|
|
// TODO this pointer cast should not be necessary
|
|
const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) {
|
|
error.Overflow => return error.InvalidElfFile,
|
|
};
|
|
const rpath_list = mem.spanZ(@ptrCast([*:0]u8, strtab[rpoff_usize..].ptr));
|
|
var it = mem.tokenize(rpath_list, ":");
|
|
while (it.next()) |rpath| {
|
|
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.AccessDenied,
|
|
error.NoDevice,
|
|
=> continue,
|
|
|
|
error.ProcessFdQuotaExceeded,
|
|
error.SystemFdQuotaExceeded,
|
|
error.SystemResources,
|
|
error.SymLinkLoop,
|
|
error.Unexpected,
|
|
=> |e| return e,
|
|
};
|
|
defer dir.close();
|
|
|
|
var link_buf: [std.os.PATH_MAX]u8 = undefined;
|
|
const link_name = std.os.readlinkatZ(
|
|
dir.fd,
|
|
glibc_so_basename,
|
|
&link_buf,
|
|
) catch |err| switch (err) {
|
|
error.NameTooLong => unreachable,
|
|
error.InvalidUtf8 => unreachable, // Windows only
|
|
error.BadPathName => unreachable, // Windows only
|
|
error.UnsupportedReparsePointType => unreachable, // Windows only
|
|
|
|
error.AccessDenied,
|
|
error.FileNotFound,
|
|
error.NotDir,
|
|
=> continue,
|
|
|
|
error.SystemResources,
|
|
error.FileSystem,
|
|
error.SymLinkLoop,
|
|
error.Unexpected,
|
|
=> |e| return e,
|
|
};
|
|
result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
|
|
link_name,
|
|
) catch |err| switch (err) {
|
|
error.UnrecognizedGnuLibCFileName,
|
|
error.InvalidGnuLibCVersion,
|
|
=> continue,
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 .. buf.len - i], offset + i) catch |err| switch (err) {
|
|
error.OperationAborted => unreachable, // Windows-only
|
|
error.WouldBlock => unreachable, // Did not request blocking mode
|
|
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.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),
|
|
};
|
|
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(@TypeOf(int_64), int_64);
|
|
} else {
|
|
return int_64;
|
|
}
|
|
} else {
|
|
if (need_bswap) {
|
|
return @byteSwap(@TypeOf(int_32), int_32);
|
|
} else {
|
|
return int_32;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu {
|
|
// Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
|
|
// although it is a runtime value, is guaranteed to be one of the architectures in the set
|
|
// of the respective switch prong.
|
|
switch (std.Target.current.cpu.arch) {
|
|
.x86_64, .i386 => {
|
|
return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target);
|
|
},
|
|
else => {
|
|
// This architecture does not have CPU model & feature detection yet.
|
|
// See https://github.com/ziglang/zig/issues/4591
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
test "" {
|
|
_ = @import("system/macos.zig");
|
|
}
|