mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 19:09:47 +03:00
std.Target: add DynamicLinker
This commit is contained in:
@@ -516,7 +516,6 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativeTargetInfo.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/Air.zig"
|
||||
|
||||
@@ -46,11 +46,9 @@ pub fn main() !void {
|
||||
return error.InvalidArgs;
|
||||
};
|
||||
|
||||
const detected = try std.zig.system.NativeTargetInfo.detect(.{});
|
||||
const host: std.Build.ResolvedTarget = .{
|
||||
.query = .{},
|
||||
.target = detected.target,
|
||||
.dynamic_linker = detected.dynamic_linker,
|
||||
.target = try std.zig.system.resolveTargetQuery(.{}),
|
||||
};
|
||||
|
||||
const build_root_directory: std.Build.Cache.Directory = .{
|
||||
|
||||
+2
-13
@@ -2129,14 +2129,6 @@ pub fn hex64(x: u64) [16]u8 {
|
||||
pub const ResolvedTarget = struct {
|
||||
query: Target.Query,
|
||||
target: Target,
|
||||
dynamic_linker: Target.DynamicLinker,
|
||||
|
||||
pub fn toNativeTargetInfo(self: ResolvedTarget) std.zig.system.NativeTargetInfo {
|
||||
return .{
|
||||
.target = self.target,
|
||||
.dynamic_linker = self.dynamic_linker,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Converts a target query into a fully resolved target that can be passed to
|
||||
@@ -2146,13 +2138,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {
|
||||
// resolved via a WASI API or via the build protocol.
|
||||
_ = b;
|
||||
|
||||
const result = std.zig.system.NativeTargetInfo.detect(query) catch
|
||||
@panic("unable to resolve target query");
|
||||
|
||||
return .{
|
||||
.query = query,
|
||||
.target = result.target,
|
||||
.dynamic_linker = result.dynamic_linker,
|
||||
.target = std.zig.system.resolveTargetQuery(query) catch
|
||||
@panic("unable to resolve target query"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -746,5 +746,4 @@ const Module = @This();
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const LazyPath = std.Build.LazyPath;
|
||||
const NativeTargetInfo = std.zig.system.NativeTargetInfo;
|
||||
const Step = std.Build.Step;
|
||||
|
||||
@@ -9,7 +9,6 @@ const StringHashMap = std.StringHashMap;
|
||||
const Sha256 = std.crypto.hash.sha2.Sha256;
|
||||
const Allocator = mem.Allocator;
|
||||
const Step = std.Build.Step;
|
||||
const NativeTargetInfo = std.zig.system.NativeTargetInfo;
|
||||
const LazyPath = std.Build.LazyPath;
|
||||
const PkgConfigPkg = std.Build.PkgConfigPkg;
|
||||
const PkgConfigError = std.Build.PkgConfigError;
|
||||
|
||||
@@ -294,11 +294,9 @@ test Options {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const detected = try std.zig.system.NativeTargetInfo.detect(.{});
|
||||
const host: std.Build.ResolvedTarget = .{
|
||||
.query = .{},
|
||||
.target = detected.target,
|
||||
.dynamic_linker = detected.dynamic_linker,
|
||||
.target = try std.zig.system.resolveTargetQuery(.{}),
|
||||
};
|
||||
|
||||
var cache: std.Build.Cache = .{
|
||||
|
||||
@@ -678,8 +678,8 @@ fn runCommand(
|
||||
|
||||
const need_cross_glibc = exe.rootModuleTarget().isGnuLibC() and
|
||||
exe.is_linking_libc;
|
||||
const other_target_info = exe.root_module.target.?.toNativeTargetInfo();
|
||||
switch (b.host.toNativeTargetInfo().getExternalExecutor(&other_target_info, .{
|
||||
const other_target = exe.root_module.target.?.target;
|
||||
switch (std.zig.system.getExternalExecutor(b.host.target, &other_target, .{
|
||||
.qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null,
|
||||
.link_libc = exe.is_linking_libc,
|
||||
})) {
|
||||
@@ -752,7 +752,7 @@ fn runCommand(
|
||||
.bad_dl => |foreign_dl| {
|
||||
if (allow_skip) return error.MakeSkipped;
|
||||
|
||||
const host_dl = b.host.dynamic_linker.get() orelse "(none)";
|
||||
const host_dl = b.host.target.dynamic_linker.get() orelse "(none)";
|
||||
|
||||
return step.fail(
|
||||
\\the host system is unable to execute binaries from the target
|
||||
|
||||
+46
-26
@@ -1,7 +1,13 @@
|
||||
//! All the details about the machine that will be executing code.
|
||||
//! Unlike `Query` which might leave some things as "default" or "host", this
|
||||
//! data is fully resolved into a concrete set of OS versions, CPU features,
|
||||
//! etc.
|
||||
|
||||
cpu: Cpu,
|
||||
os: Os,
|
||||
abi: Abi,
|
||||
ofmt: ObjectFormat,
|
||||
dynamic_linker: DynamicLinker = DynamicLinker.none,
|
||||
|
||||
pub const Query = @import("Target/Query.zig");
|
||||
|
||||
@@ -1529,13 +1535,19 @@ pub inline fn hasDynamicLinker(self: Target) bool {
|
||||
}
|
||||
|
||||
pub const DynamicLinker = struct {
|
||||
/// Contains the memory used to store the dynamic linker path. This field should
|
||||
/// not be used directly. See `get` and `set`. This field exists so that this API requires no allocator.
|
||||
buffer: [255]u8 = undefined,
|
||||
/// Contains the memory used to store the dynamic linker path. This field
|
||||
/// should not be used directly. See `get` and `set`. This field exists so
|
||||
/// that this API requires no allocator.
|
||||
buffer: [255]u8,
|
||||
|
||||
/// Used to construct the dynamic linker path. This field should not be used
|
||||
/// directly. See `get` and `set`.
|
||||
max_byte: ?u8 = null,
|
||||
max_byte: ?u8,
|
||||
|
||||
pub const none: DynamicLinker = .{
|
||||
.buffer = undefined,
|
||||
.max_byte = null,
|
||||
};
|
||||
|
||||
/// Asserts that the length is less than or equal to 255 bytes.
|
||||
pub fn init(dl_or_null: ?[]const u8) DynamicLinker {
|
||||
@@ -1561,8 +1573,12 @@ pub const DynamicLinker = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
|
||||
var result: DynamicLinker = .{};
|
||||
pub fn standardDynamicLinkerPath(target: Target) DynamicLinker {
|
||||
return standardDynamicLinkerPath_cpu_os_abi(target.cpu, target.os.tag, target.abi);
|
||||
}
|
||||
|
||||
pub fn standardDynamicLinkerPath_cpu_os_abi(cpu: Cpu, os_tag: Os.Tag, abi: Abi) DynamicLinker {
|
||||
var result = DynamicLinker.none;
|
||||
const S = struct {
|
||||
fn print(r: *DynamicLinker, comptime fmt: []const u8, args: anytype) DynamicLinker {
|
||||
r.max_byte = @as(u8, @intCast((std.fmt.bufPrint(&r.buffer, fmt, args) catch unreachable).len - 1));
|
||||
@@ -1577,32 +1593,32 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
|
||||
const print = S.print;
|
||||
const copy = S.copy;
|
||||
|
||||
if (self.abi == .android) {
|
||||
const suffix = if (self.ptrBitWidth() == 64) "64" else "";
|
||||
if (abi == .android) {
|
||||
const suffix = if (ptrBitWidth_cpu_abi(cpu, abi) == 64) "64" else "";
|
||||
return print(&result, "/system/bin/linker{s}", .{suffix});
|
||||
}
|
||||
|
||||
if (self.abi.isMusl()) {
|
||||
const is_arm = switch (self.cpu.arch) {
|
||||
if (abi.isMusl()) {
|
||||
const is_arm = switch (cpu.arch) {
|
||||
.arm, .armeb, .thumb, .thumbeb => true,
|
||||
else => false,
|
||||
};
|
||||
const arch_part = switch (self.cpu.arch) {
|
||||
const arch_part = switch (cpu.arch) {
|
||||
.arm, .thumb => "arm",
|
||||
.armeb, .thumbeb => "armeb",
|
||||
else => |arch| @tagName(arch),
|
||||
};
|
||||
const arch_suffix = if (is_arm and self.abi.floatAbi() == .hard) "hf" else "";
|
||||
const arch_suffix = if (is_arm and abi.floatAbi() == .hard) "hf" else "";
|
||||
return print(&result, "/lib/ld-musl-{s}{s}.so.1", .{ arch_part, arch_suffix });
|
||||
}
|
||||
|
||||
switch (self.os.tag) {
|
||||
switch (os_tag) {
|
||||
.freebsd => return copy(&result, "/libexec/ld-elf.so.1"),
|
||||
.netbsd => return copy(&result, "/libexec/ld.elf_so"),
|
||||
.openbsd => return copy(&result, "/usr/libexec/ld.so"),
|
||||
.dragonfly => return copy(&result, "/libexec/ld-elf.so.2"),
|
||||
.solaris, .illumos => return copy(&result, "/lib/64/ld.so.1"),
|
||||
.linux => switch (self.cpu.arch) {
|
||||
.linux => switch (cpu.arch) {
|
||||
.x86,
|
||||
.sparc,
|
||||
.sparcel,
|
||||
@@ -1616,7 +1632,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
|
||||
.armeb,
|
||||
.thumb,
|
||||
.thumbeb,
|
||||
=> return copy(&result, switch (self.abi.floatAbi()) {
|
||||
=> return copy(&result, switch (abi.floatAbi()) {
|
||||
.hard => "/lib/ld-linux-armhf.so.3",
|
||||
else => "/lib/ld-linux.so.3",
|
||||
}),
|
||||
@@ -1626,12 +1642,12 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
|
||||
.mips64,
|
||||
.mips64el,
|
||||
=> {
|
||||
const lib_suffix = switch (self.abi) {
|
||||
const lib_suffix = switch (abi) {
|
||||
.gnuabin32, .gnux32 => "32",
|
||||
.gnuabi64 => "64",
|
||||
else => "",
|
||||
};
|
||||
const is_nan_2008 = mips.featureSetHas(self.cpu.features, .nan2008);
|
||||
const is_nan_2008 = mips.featureSetHas(cpu.features, .nan2008);
|
||||
const loader = if (is_nan_2008) "ld-linux-mipsn8.so.1" else "ld.so.1";
|
||||
return print(&result, "/lib{s}/{s}", .{ lib_suffix, loader });
|
||||
},
|
||||
@@ -1640,7 +1656,7 @@ pub fn standardDynamicLinkerPath(self: Target) DynamicLinker {
|
||||
.powerpc64, .powerpc64le => return copy(&result, "/lib64/ld64.so.2"),
|
||||
.s390x => return copy(&result, "/lib64/ld64.so.1"),
|
||||
.sparc64 => return copy(&result, "/lib64/ld-linux.so.2"),
|
||||
.x86_64 => return copy(&result, switch (self.abi) {
|
||||
.x86_64 => return copy(&result, switch (abi) {
|
||||
.gnux32 => "/libx32/ld-linux-x32.so.2",
|
||||
else => "/lib64/ld-linux-x86-64.so.2",
|
||||
}),
|
||||
@@ -1862,17 +1878,17 @@ pub fn maxIntAlignment(target: Target) u16 {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ptrBitWidth(target: Target) u16 {
|
||||
switch (target.abi) {
|
||||
pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 {
|
||||
switch (abi) {
|
||||
.gnux32, .muslx32, .gnuabin32, .gnuilp32 => return 32,
|
||||
.gnuabi64 => return 64,
|
||||
else => {},
|
||||
}
|
||||
switch (target.cpu.arch) {
|
||||
return switch (cpu.arch) {
|
||||
.avr,
|
||||
.msp430,
|
||||
.spu_2,
|
||||
=> return 16,
|
||||
=> 16,
|
||||
|
||||
.arc,
|
||||
.arm,
|
||||
@@ -1908,7 +1924,7 @@ pub fn ptrBitWidth(target: Target) u16 {
|
||||
.loongarch32,
|
||||
.dxil,
|
||||
.xtensa,
|
||||
=> return 32,
|
||||
=> 32,
|
||||
|
||||
.aarch64,
|
||||
.aarch64_be,
|
||||
@@ -1933,10 +1949,14 @@ pub fn ptrBitWidth(target: Target) u16 {
|
||||
.ve,
|
||||
.spirv64,
|
||||
.loongarch64,
|
||||
=> return 64,
|
||||
=> 64,
|
||||
|
||||
.sparc => return if (std.Target.sparc.featureSetHas(target.cpu.features, .v9)) 64 else 32,
|
||||
}
|
||||
.sparc => if (std.Target.sparc.featureSetHas(cpu.features, .v9)) 64 else 32,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ptrBitWidth(target: Target) u16 {
|
||||
return ptrBitWidth_cpu_abi(target.cpu, target.abi);
|
||||
}
|
||||
|
||||
pub fn stackAlignment(target: Target) u16 {
|
||||
|
||||
+12
-14
@@ -34,7 +34,7 @@ abi: ?Target.Abi = null,
|
||||
|
||||
/// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path
|
||||
/// based on the `os_tag`.
|
||||
dynamic_linker: DynamicLinker = DynamicLinker{},
|
||||
dynamic_linker: Target.DynamicLinker = Target.DynamicLinker.none,
|
||||
|
||||
/// `null` means default for the cpu/arch/os combo.
|
||||
ofmt: ?Target.ObjectFormat = null,
|
||||
@@ -61,8 +61,6 @@ pub const OsVersion = union(enum) {
|
||||
|
||||
pub const SemanticVersion = std.SemanticVersion;
|
||||
|
||||
pub const DynamicLinker = Target.DynamicLinker;
|
||||
|
||||
pub fn fromTarget(target: Target) Query {
|
||||
var result: Query = .{
|
||||
.cpu_arch = target.cpu.arch,
|
||||
@@ -164,7 +162,7 @@ fn updateOsVersionRange(self: *Query, os: Target.Os) void {
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
|
||||
pub fn toTarget(self: Query) Target {
|
||||
return .{
|
||||
.cpu = self.getCpu(),
|
||||
@@ -232,7 +230,7 @@ pub fn parse(args: ParseOptions) !Query {
|
||||
const diags = args.diagnostics orelse &dummy_diags;
|
||||
|
||||
var result: Query = .{
|
||||
.dynamic_linker = DynamicLinker.init(args.dynamic_linker),
|
||||
.dynamic_linker = Target.DynamicLinker.init(args.dynamic_linker),
|
||||
};
|
||||
|
||||
var it = mem.splitScalar(u8, args.arch_os_abi, '-');
|
||||
@@ -379,13 +377,13 @@ test parseVersion {
|
||||
try std.testing.expectError(error.InvalidVersion, parseVersion("1.2.3.4"));
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
|
||||
pub fn getCpu(self: Query) Target.Cpu {
|
||||
switch (self.cpu_model) {
|
||||
.native => {
|
||||
// This works when doing `zig build` because Zig generates a build executable using
|
||||
// native CPU model & features. However this will not be accurate otherwise, and
|
||||
// will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
|
||||
// will need to be integrated with `std.zig.system.resolveTargetQuery`.
|
||||
return builtin.cpu;
|
||||
},
|
||||
.baseline => {
|
||||
@@ -396,7 +394,7 @@ pub fn getCpu(self: Query) Target.Cpu {
|
||||
.determined_by_cpu_arch => if (self.cpu_arch == null) {
|
||||
// This works when doing `zig build` because Zig generates a build executable using
|
||||
// native CPU model & features. However this will not be accurate otherwise, and
|
||||
// will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
|
||||
// will need to be integrated with `std.zig.system.resolveTargetQuery`.
|
||||
return builtin.cpu;
|
||||
} else {
|
||||
var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch());
|
||||
@@ -426,11 +424,11 @@ pub fn getCpuFeatures(self: Query) Target.Cpu.Feature.Set {
|
||||
return self.getCpu().features;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
|
||||
pub fn getOs(self: Query) Target.Os {
|
||||
// `builtin.os` works when doing `zig build` because Zig generates a build executable using
|
||||
// native OS version range. However this will not be accurate otherwise, and
|
||||
// will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
|
||||
// will need to be integrated with `std.zig.system.resolveTargetQuery`.
|
||||
var adjusted_os = if (self.os_tag) |os_tag| os_tag.defaultVersionRange(self.getCpuArch()) else builtin.os;
|
||||
|
||||
if (self.os_version_min) |min| switch (min) {
|
||||
@@ -463,7 +461,7 @@ pub fn getOsTag(self: Query) Target.Os.Tag {
|
||||
return self.os_tag orelse builtin.os.tag;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
|
||||
pub fn getOsVersionMin(self: Query) OsVersion {
|
||||
if (self.os_version_min) |version_min| return version_min;
|
||||
var tmp: Query = undefined;
|
||||
@@ -471,7 +469,7 @@ pub fn getOsVersionMin(self: Query) OsVersion {
|
||||
return tmp.os_version_min.?;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
|
||||
pub fn getOsVersionMax(self: Query) OsVersion {
|
||||
if (self.os_version_max) |version_max| return version_max;
|
||||
var tmp: Query = undefined;
|
||||
@@ -479,14 +477,14 @@ pub fn getOsVersionMax(self: Query) OsVersion {
|
||||
return tmp.os_version_max.?;
|
||||
}
|
||||
|
||||
/// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`.
|
||||
/// TODO deprecated, use `std.zig.system.resolveTargetQuery`.
|
||||
pub fn getAbi(self: Query) Target.Abi {
|
||||
if (self.abi) |abi| return abi;
|
||||
|
||||
if (self.os_tag == null) {
|
||||
// This works when doing `zig build` because Zig generates a build executable using
|
||||
// native CPU model & features. However this will not be accurate otherwise, and
|
||||
// will need to be integrated with `std.zig.system.NativeTargetInfo.detect`.
|
||||
// will need to be integrated with `std.zig.system.resolveTargetQuery`.
|
||||
return builtin.abi;
|
||||
}
|
||||
|
||||
|
||||
+1116
-2
@@ -1,13 +1,1127 @@
|
||||
pub const NativePaths = @import("system/NativePaths.zig");
|
||||
pub const NativeTargetInfo = @import("system/NativeTargetInfo.zig");
|
||||
|
||||
pub const windows = @import("system/windows.zig");
|
||||
pub const darwin = @import("system/darwin.zig");
|
||||
pub const linux = @import("system/linux.zig");
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
/// Return whether or not the given host is capable of running executables of
|
||||
/// the other target.
|
||||
pub fn getExternalExecutor(
|
||||
host: std.Target,
|
||||
candidate: *const std.Target,
|
||||
options: GetExternalExecutorOptions,
|
||||
) Executor {
|
||||
const os_match = host.os.tag == candidate.os.tag;
|
||||
const cpu_ok = cpu_ok: {
|
||||
if (host.cpu.arch == candidate.cpu.arch)
|
||||
break :cpu_ok true;
|
||||
|
||||
if (host.cpu.arch == .x86_64 and candidate.cpu.arch == .x86)
|
||||
break :cpu_ok true;
|
||||
|
||||
if (host.cpu.arch == .aarch64 and candidate.cpu.arch == .arm)
|
||||
break :cpu_ok true;
|
||||
|
||||
if (host.cpu.arch == .aarch64_be and candidate.cpu.arch == .armeb)
|
||||
break :cpu_ok true;
|
||||
|
||||
// TODO additionally detect incompatible CPU features.
|
||||
// Note that in some cases the OS kernel will emulate missing CPU features
|
||||
// when an illegal instruction is encountered.
|
||||
|
||||
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 the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
|
||||
// to emulate the foreign architecture.
|
||||
if (options.allow_rosetta and os_match and
|
||||
host.os.tag == .macos and host.cpu.arch == .aarch64)
|
||||
{
|
||||
switch (candidate.cpu.arch) {
|
||||
.x86_64 => return .rosetta,
|
||||
else => return bad_result,
|
||||
}
|
||||
}
|
||||
|
||||
// If the OS matches, we can use QEMU to emulate a foreign architecture.
|
||||
if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
|
||||
return switch (candidate.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.os.tag) {
|
||||
.windows => {
|
||||
if (options.allow_wine) {
|
||||
// x86_64 wine does not support emulating aarch64-windows and
|
||||
// vice versa.
|
||||
if (candidate.cpu.arch != builtin.cpu.arch) {
|
||||
return bad_result;
|
||||
}
|
||||
switch (candidate.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.ptrBitWidth()) {
|
||||
32 => return Executor{ .wasmtime = "wasmtime" },
|
||||
else => return bad_result,
|
||||
}
|
||||
}
|
||||
return bad_result;
|
||||
},
|
||||
.macos => {
|
||||
if (options.allow_darling) {
|
||||
// This check can be loosened once darling adds a QEMU-based emulation
|
||||
// layer for non-host architectures:
|
||||
// https://github.com/darlinghq/darling/issues/863
|
||||
if (candidate.cpu.arch != builtin.cpu.arch) {
|
||||
return bad_result;
|
||||
}
|
||||
return Executor{ .darling = "darling" };
|
||||
}
|
||||
return bad_result;
|
||||
},
|
||||
else => return bad_result,
|
||||
}
|
||||
}
|
||||
|
||||
pub const DetectError = error{
|
||||
FileSystem,
|
||||
SystemResources,
|
||||
SymLinkLoop,
|
||||
ProcessFdQuotaExceeded,
|
||||
SystemFdQuotaExceeded,
|
||||
DeviceBusy,
|
||||
OSVersionDetectionFail,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
/// Given a `Target.Query`, 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.
|
||||
pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
|
||||
var os = query.getOsTag().defaultVersionRange(query.getCpuArch());
|
||||
if (query.os_tag == null) {
|
||||
switch (builtin.target.os.tag) {
|
||||
.linux => {
|
||||
const uts = std.os.uname();
|
||||
const release = mem.sliceTo(&uts.release, 0);
|
||||
// The release field sometimes has a weird format,
|
||||
// `Version.parse` will attempt to find some meaningful interpretation.
|
||||
if (std.SemanticVersion.parse(release)) |ver| {
|
||||
os.version_range.linux.range.min = ver;
|
||||
os.version_range.linux.range.max = ver;
|
||||
} else |err| switch (err) {
|
||||
error.Overflow => {},
|
||||
error.InvalidVersion => {},
|
||||
}
|
||||
},
|
||||
.solaris, .illumos => {
|
||||
const uts = std.os.uname();
|
||||
const release = mem.sliceTo(&uts.release, 0);
|
||||
if (std.SemanticVersion.parse(release)) |ver| {
|
||||
os.version_range.semver.min = ver;
|
||||
os.version_range.semver.max = ver;
|
||||
} else |err| switch (err) {
|
||||
error.Overflow => {},
|
||||
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, // 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 => return error.OSVersionDetectionFail,
|
||||
};
|
||||
|
||||
switch (builtin.target.os.tag) {
|
||||
.freebsd => {
|
||||
// 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 = value / 100_000;
|
||||
const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
|
||||
const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
|
||||
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 => {
|
||||
// #define __NetBSD_Version__ MMmmrrpp00
|
||||
//
|
||||
// M = major version
|
||||
// m = minor version; a minor number of 99 indicates current.
|
||||
// r = 0 (*)
|
||||
// p = patchlevel
|
||||
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 => {
|
||||
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
|
||||
// flat base10 format: Mmmmpp
|
||||
// M = major
|
||||
// m = minor; odd-numbers indicate current dev branch
|
||||
// p = patch
|
||||
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;
|
||||
// consider that sysctl result includes null-termination
|
||||
// reserve 1 byte to ensure we never overflow when appending ".0"
|
||||
var len: usize = buf.len - 1;
|
||||
|
||||
std.os.sysctl(&mib, &buf, &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 => return error.OSVersionDetectionFail,
|
||||
};
|
||||
|
||||
// append ".0" to satisfy semver
|
||||
buf[len - 1] = '.';
|
||||
buf[len] = '0';
|
||||
len += 1;
|
||||
|
||||
if (std.SemanticVersion.parse(buf[0..len])) |ver| {
|
||||
os.version_range.semver.min = ver;
|
||||
os.version_range.semver.max = ver;
|
||||
} else |_| {
|
||||
return error.OSVersionDetectionFail;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Unimplemented, fall back to default version range.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (query.os_version_min) |min| switch (min) {
|
||||
.none => {},
|
||||
.semver => |semver| switch (query.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 (query.os_version_max) |max| switch (max) {
|
||||
.none => {},
|
||||
.semver => |semver| switch (query.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 (query.glibc_version) |glibc| {
|
||||
assert(query.isGnuLibC());
|
||||
os.version_range.linux.glibc = glibc;
|
||||
}
|
||||
|
||||
// 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 = query.getCpuArch();
|
||||
|
||||
const cpu = switch (query.cpu_model) {
|
||||
.native => detectNativeCpuAndFeatures(cpu_arch, os, query),
|
||||
.baseline => Target.Cpu.baseline(cpu_arch),
|
||||
.determined_by_cpu_arch => if (query.cpu_arch == null)
|
||||
detectNativeCpuAndFeatures(cpu_arch, os, query)
|
||||
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, query);
|
||||
// For x86, we need to populate some CPU feature flags depending on architecture
|
||||
// and mode:
|
||||
// * 16bit_mode => if the abi is code16
|
||||
// * 32bit_mode => if the arch is x86
|
||||
// However, the "mode" flags can be used as overrides, so if the user explicitly
|
||||
// sets one of them, that takes precedence.
|
||||
switch (cpu_arch) {
|
||||
.x86 => {
|
||||
if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{
|
||||
.@"16bit_mode", .@"32bit_mode",
|
||||
})) {
|
||||
switch (result.abi) {
|
||||
.code16 => result.cpu.features.addFeature(
|
||||
@intFromEnum(Target.x86.Feature.@"16bit_mode"),
|
||||
),
|
||||
else => result.cpu.features.addFeature(
|
||||
@intFromEnum(Target.x86.Feature.@"32bit_mode"),
|
||||
),
|
||||
}
|
||||
}
|
||||
},
|
||||
.arm, .armeb => {
|
||||
// XXX What do we do if the target has the noarm feature?
|
||||
// What do we do if the user specifies +thumb_mode?
|
||||
},
|
||||
.thumb, .thumbeb => {
|
||||
result.cpu.features.addFeature(
|
||||
@intFromEnum(Target.arm.Feature.thumb_mode),
|
||||
);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
query.updateCpuFeatures(&result.cpu.features);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?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 (builtin.cpu.arch) {
|
||||
.x86_64, .x86 => {
|
||||
return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (builtin.os.tag) {
|
||||
.linux => return linux.detectNativeCpuAndFeatures(),
|
||||
.macos => return darwin.macos.detectNativeCpuAndFeatures(),
|
||||
.windows => return windows.detectNativeCpuAndFeatures(),
|
||||
else => {},
|
||||
}
|
||||
|
||||
// This architecture does not have CPU model & feature detection yet.
|
||||
// See https://github.com/ziglang/zig/issues/4591
|
||||
return null;
|
||||
}
|
||||
|
||||
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,
|
||||
query: Target.Query,
|
||||
) AbiAndDynamicLinkerFromFileError!Target {
|
||||
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 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
|
||||
const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&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: Target = .{
|
||||
.cpu = cpu,
|
||||
.os = os,
|
||||
.abi = query.abi orelse Target.Abi.default(cpu.arch, os),
|
||||
.ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
|
||||
.dynamic_linker = query.dynamic_linker,
|
||||
};
|
||||
var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
|
||||
const look_for_ld = query.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: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
|
||||
const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&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 = @as(usize, @intCast(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 = @as(u8, @intCast(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.abi = ld_info.abi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
// We only need this for detecting glibc version.
|
||||
elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.isGnuLibC() and
|
||||
query.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: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
|
||||
const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&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.isGnuLibC() and
|
||||
query.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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
|
||||
const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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 = @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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
|
||||
const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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| {
|
||||
if (rpoff > ds.size) return error.InvalidElfFile;
|
||||
const rpoff_file = ds.offset + rpoff;
|
||||
const rp_max_size = ds.size - rpoff;
|
||||
|
||||
const strtab_len = @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.tokenizeScalar(u8, rpath_list, ':');
|
||||
while (it.next()) |rpath| {
|
||||
if (glibcVerFromRPath(rpath)) |ver| {
|
||||
result.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: {
|
||||
// There is no DT_RUNPATH so we try to find libc.so.6 inside the same
|
||||
// directory as the dynamic linker.
|
||||
if (fs.path.dirname(dl_path)) |rpath| {
|
||||
if (glibcVerFromRPath(rpath)) |ver| {
|
||||
result.os.version_range.linux.glibc = ver;
|
||||
return result;
|
||||
} else |err| switch (err) {
|
||||
error.GLibCNotFound => {},
|
||||
else => |e| return e,
|
||||
}
|
||||
}
|
||||
|
||||
// So far, no luck. Next we try to see if the information is
|
||||
// present in the symlink data for the dynamic linker path.
|
||||
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, // Windows only
|
||||
error.BadPathName => unreachable, // Windows only
|
||||
error.UnsupportedReparsePointType => unreachable, // Windows only
|
||||
error.NetworkNotFound => unreachable, // Windows only
|
||||
|
||||
error.AccessDenied,
|
||||
error.FileNotFound,
|
||||
error.NotLink,
|
||||
error.NotDir,
|
||||
=> break :glibc_ver,
|
||||
|
||||
error.SystemResources,
|
||||
error.FileSystem,
|
||||
error.SymLinkLoop,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
};
|
||||
result.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;
|
||||
}
|
||||
|
||||
// Nothing worked so far. Finally we fall back to hard-coded search paths.
|
||||
// Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`.
|
||||
var path_buf: [std.os.PATH_MAX]u8 = undefined;
|
||||
var index: usize = 0;
|
||||
const prefix = "/lib/";
|
||||
const cpu_arch = @tagName(result.cpu.arch);
|
||||
const os_tag = @tagName(result.os.tag);
|
||||
const abi = @tagName(result.abi);
|
||||
@memcpy(path_buf[index..][0..prefix.len], prefix);
|
||||
index += prefix.len;
|
||||
@memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch);
|
||||
index += cpu_arch.len;
|
||||
path_buf[index] = '-';
|
||||
index += 1;
|
||||
@memcpy(path_buf[index..][0..os_tag.len], os_tag);
|
||||
index += os_tag.len;
|
||||
path_buf[index] = '-';
|
||||
index += 1;
|
||||
@memcpy(path_buf[index..][0..abi.len], abi);
|
||||
index += abi.len;
|
||||
const rpath = path_buf[0..index];
|
||||
if (glibcVerFromRPath(rpath)) |ver| {
|
||||
result.os.version_range.linux.glibc = ver;
|
||||
return result;
|
||||
} else |err| switch (err) {
|
||||
error.GLibCNotFound => {},
|
||||
else => |e| return e,
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion {
|
||||
// example: "libc-2.3.4.so"
|
||||
// example: "libc-2.27.so"
|
||||
// example: "ld-2.33.so"
|
||||
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 Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) {
|
||||
error.Overflow => return error.InvalidGnuLibCVersion,
|
||||
error.InvalidVersion => return error.InvalidGnuLibCVersion,
|
||||
};
|
||||
}
|
||||
|
||||
test glibcVerFromLinkName {
|
||||
try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist"));
|
||||
try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-"));
|
||||
|
||||
try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-"));
|
||||
try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-"));
|
||||
try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-"));
|
||||
try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-"));
|
||||
try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));
|
||||
}
|
||||
|
||||
fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
|
||||
var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.InvalidUtf8 => unreachable,
|
||||
error.BadPathName => unreachable,
|
||||
error.DeviceBusy => unreachable,
|
||||
error.NetworkNotFound => unreachable, // Windows-only
|
||||
|
||||
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();
|
||||
|
||||
// Now we have a candidate for the path to libc shared object. In
|
||||
// the past, we used readlink() here because the link name would
|
||||
// reveal the glibc version. However, in more recent GNU/Linux
|
||||
// installations, there is no symlink. Thus we instead use a more
|
||||
// robust check of opening the libc shared object and looking at the
|
||||
// .dynstr section, and finding the max version number of symbols
|
||||
// that start with "GLIBC_2.".
|
||||
const glibc_so_basename = "libc.so.6";
|
||||
var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.InvalidUtf8 => unreachable, // Windows only
|
||||
error.BadPathName => unreachable, // Windows only
|
||||
error.PipeBusy => unreachable, // Windows-only
|
||||
error.SharingViolation => unreachable, // Windows-only
|
||||
error.NetworkNotFound => unreachable, // Windows-only
|
||||
error.FileLocksNotSupported => unreachable, // No lock requested.
|
||||
error.NoSpaceLeft => unreachable, // read-only
|
||||
error.PathAlreadyExists => unreachable, // read-only
|
||||
error.DeviceBusy => unreachable, // read-only
|
||||
error.FileBusy => unreachable, // read-only
|
||||
error.InvalidHandle => unreachable, // should not be in the error set
|
||||
error.WouldBlock => unreachable, // not using O_NONBLOCK
|
||||
error.NoDevice => unreachable, // not asking for a special device
|
||||
|
||||
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.SemanticVersion {
|
||||
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 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
|
||||
const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
|
||||
const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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 = @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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
|
||||
const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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;
|
||||
|
||||
// Here we loop over all the strings in the dynstr string table, assuming that any
|
||||
// strings that start with "GLIBC_2." indicate the existence of such a glibc version,
|
||||
// and furthermore, that the system-installed glibc is at minimum that version.
|
||||
|
||||
// Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
|
||||
// Here I use double this value plus some headroom. This makes it only need
|
||||
// a single read syscall here.
|
||||
var buf: [80000]u8 = undefined;
|
||||
if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
|
||||
|
||||
const dynstr_size: usize = @intCast(dynstr.size);
|
||||
const dynstr_bytes = buf[0..dynstr_size];
|
||||
_ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len);
|
||||
var it = mem.splitScalar(u8, dynstr_bytes, 0);
|
||||
var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
|
||||
while (it.next()) |s| {
|
||||
if (mem.startsWith(u8, s, "GLIBC_2.")) {
|
||||
const chopped = s["GLIBC_".len..];
|
||||
const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
|
||||
error.Overflow => return error.InvalidGnuLibCVersion,
|
||||
error.InvalidVersion => return error.InvalidGnuLibCVersion,
|
||||
};
|
||||
switch (ver.order(max_ver)) {
|
||||
.gt => max_ver = ver,
|
||||
.lt, .eq => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
return max_ver;
|
||||
}
|
||||
|
||||
/// In the past, this function attempted to use the executable's own binary if it was dynamically
|
||||
/// linked to answer both the C ABI question and the dynamic linker question. However, this
|
||||
/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
|
||||
/// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc
|
||||
/// version. The problem is that libc.so.6 glibc version will match that of the system while
|
||||
/// the dynamic linker will match that of the compiler binary. Executables with these versions
|
||||
/// mismatching will fail to run.
|
||||
///
|
||||
/// Therefore, this function works the same regardless of whether the compiler binary is
|
||||
/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
|
||||
/// answer to these questions, or if there is a shebang line, then it chases the referenced
|
||||
/// file recursively. If that does not provide the answer, then the function falls back to
|
||||
/// defaults.
|
||||
fn detectAbiAndDynamicLinker(
|
||||
cpu: Target.Cpu,
|
||||
os: Target.Os,
|
||||
query: Target.Query,
|
||||
) DetectError!Target {
|
||||
const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
|
||||
const is_linux = builtin.target.os.tag == .linux;
|
||||
const is_solarish = builtin.target.os.tag.isSolarish();
|
||||
const have_all_info = query.dynamic_linker.get() != null and
|
||||
query.abi != null and (!is_linux or query.abi.?.isGnu());
|
||||
const os_is_non_native = query.os_tag != null;
|
||||
// The Solaris/illumos environment is always the same.
|
||||
if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) {
|
||||
return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
}
|
||||
if (query.abi) |abi| {
|
||||
if (abi.isMusl()) {
|
||||
// musl implies static linking.
|
||||
return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
}
|
||||
}
|
||||
// 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 `builtin.target`.
|
||||
const all_abis = comptime blk: {
|
||||
assert(@intFromEnum(Target.Abi.none) == 0);
|
||||
const fields = std.meta.fields(Target.Abi)[1..];
|
||||
var array: [fields.len]Target.Abi = undefined;
|
||||
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 = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch);
|
||||
|
||||
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,
|
||||
.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];
|
||||
|
||||
// Best case scenario: the executable is dynamically linked, and we can iterate
|
||||
// over our own shared objects and find a dynamic linker.
|
||||
const elf_file = blk: {
|
||||
// This block looks for a shebang line in /usr/bin/env,
|
||||
// if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
|
||||
// doing the same logic recursively in case it finds another shebang line.
|
||||
|
||||
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
|
||||
// reasonably reliable path to start with.
|
||||
var file_name: []const u8 = "/usr/bin/env";
|
||||
// #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
|
||||
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, // opened without write permissions
|
||||
|
||||
error.IsDir,
|
||||
error.NotDir,
|
||||
error.InvalidHandle,
|
||||
error.AccessDenied,
|
||||
error.NoDevice,
|
||||
error.FileNotFound,
|
||||
error.NetworkNotFound,
|
||||
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, query);
|
||||
},
|
||||
|
||||
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.tokenizeScalar(u8, line[2..], ' ');
|
||||
file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
file.close();
|
||||
}
|
||||
};
|
||||
defer elf_file.close();
|
||||
|
||||
// If Zig is statically linked, such as via distributed binary static builds, the above
|
||||
// trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file.
|
||||
// TODO: inline this function and combine the buffer we already read above to find
|
||||
// the possible shebang line with the buffer we use for the ELF header.
|
||||
return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) 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.
|
||||
=> |e| {
|
||||
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
|
||||
return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target {
|
||||
const abi = query.abi orelse Target.Abi.default(cpu.arch, os);
|
||||
return .{
|
||||
.cpu = cpu,
|
||||
.os = os,
|
||||
.abi = abi,
|
||||
.ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
|
||||
.dynamic_linker = if (query.dynamic_linker.get() == null)
|
||||
Target.standardDynamicLinkerPath_cpu_os_abi(cpu, os.tag, abi)
|
||||
else
|
||||
query.dynamic_linker,
|
||||
};
|
||||
}
|
||||
|
||||
const LdInfo = struct {
|
||||
ld: Target.DynamicLinker,
|
||||
abi: Target.Abi,
|
||||
};
|
||||
|
||||
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, // 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.SocketNotConnected => 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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("../std.zig");
|
||||
const mem = std.mem;
|
||||
const elf = std.elf;
|
||||
const fs = std.fs;
|
||||
const assert = std.debug.assert;
|
||||
const Target = std.Target;
|
||||
const native_endian = builtin.cpu.arch.endian();
|
||||
|
||||
test {
|
||||
_ = NativePaths;
|
||||
_ = NativeTargetInfo;
|
||||
|
||||
_ = darwin;
|
||||
_ = linux;
|
||||
|
||||
@@ -5,7 +5,6 @@ const process = std.process;
|
||||
const mem = std.mem;
|
||||
|
||||
const NativePaths = @This();
|
||||
const NativeTargetInfo = std.zig.system.NativeTargetInfo;
|
||||
|
||||
arena: Allocator,
|
||||
include_dirs: std.ArrayListUnmanaged([]const u8) = .{},
|
||||
@@ -14,8 +13,7 @@ framework_dirs: std.ArrayListUnmanaged([]const u8) = .{},
|
||||
rpaths: std.ArrayListUnmanaged([]const u8) = .{},
|
||||
warnings: std.ArrayListUnmanaged([]const u8) = .{},
|
||||
|
||||
pub fn detect(arena: Allocator, native_info: NativeTargetInfo) !NativePaths {
|
||||
const native_target = native_info.target;
|
||||
pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
|
||||
var self: NativePaths = .{ .arena = arena };
|
||||
var is_nix = false;
|
||||
if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
|
||||
|
||||
@@ -1,1130 +0,0 @@
|
||||
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 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,
|
||||
};
|
||||
|
||||
/// Given a `Target.Query`, 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.
|
||||
pub fn detect(query: Target.Query) DetectError!NativeTargetInfo {
|
||||
var os = query.getOsTag().defaultVersionRange(query.getCpuArch());
|
||||
if (query.os_tag == null) {
|
||||
switch (builtin.target.os.tag) {
|
||||
.linux => {
|
||||
const uts = std.os.uname();
|
||||
const release = mem.sliceTo(&uts.release, 0);
|
||||
// The release field sometimes has a weird format,
|
||||
// `Version.parse` will attempt to find some meaningful interpretation.
|
||||
if (std.SemanticVersion.parse(release)) |ver| {
|
||||
os.version_range.linux.range.min = ver;
|
||||
os.version_range.linux.range.max = ver;
|
||||
} else |err| switch (err) {
|
||||
error.Overflow => {},
|
||||
error.InvalidVersion => {},
|
||||
}
|
||||
},
|
||||
.solaris, .illumos => {
|
||||
const uts = std.os.uname();
|
||||
const release = mem.sliceTo(&uts.release, 0);
|
||||
if (std.SemanticVersion.parse(release)) |ver| {
|
||||
os.version_range.semver.min = ver;
|
||||
os.version_range.semver.max = ver;
|
||||
} else |err| switch (err) {
|
||||
error.Overflow => {},
|
||||
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, // 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 => return error.OSVersionDetectionFail,
|
||||
};
|
||||
|
||||
switch (builtin.target.os.tag) {
|
||||
.freebsd => {
|
||||
// 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 = value / 100_000;
|
||||
const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
|
||||
const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
|
||||
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 => {
|
||||
// #define __NetBSD_Version__ MMmmrrpp00
|
||||
//
|
||||
// M = major version
|
||||
// m = minor version; a minor number of 99 indicates current.
|
||||
// r = 0 (*)
|
||||
// p = patchlevel
|
||||
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 => {
|
||||
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
|
||||
// flat base10 format: Mmmmpp
|
||||
// M = major
|
||||
// m = minor; odd-numbers indicate current dev branch
|
||||
// p = patch
|
||||
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;
|
||||
// consider that sysctl result includes null-termination
|
||||
// reserve 1 byte to ensure we never overflow when appending ".0"
|
||||
var len: usize = buf.len - 1;
|
||||
|
||||
std.os.sysctl(&mib, &buf, &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 => return error.OSVersionDetectionFail,
|
||||
};
|
||||
|
||||
// append ".0" to satisfy semver
|
||||
buf[len - 1] = '.';
|
||||
buf[len] = '0';
|
||||
len += 1;
|
||||
|
||||
if (std.SemanticVersion.parse(buf[0..len])) |ver| {
|
||||
os.version_range.semver.min = ver;
|
||||
os.version_range.semver.max = ver;
|
||||
} else |_| {
|
||||
return error.OSVersionDetectionFail;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Unimplemented, fall back to default version range.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (query.os_version_min) |min| switch (min) {
|
||||
.none => {},
|
||||
.semver => |semver| switch (query.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 (query.os_version_max) |max| switch (max) {
|
||||
.none => {},
|
||||
.semver => |semver| switch (query.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 (query.glibc_version) |glibc| {
|
||||
assert(query.isGnuLibC());
|
||||
os.version_range.linux.glibc = glibc;
|
||||
}
|
||||
|
||||
// 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 = query.getCpuArch();
|
||||
|
||||
const cpu = switch (query.cpu_model) {
|
||||
.native => detectNativeCpuAndFeatures(cpu_arch, os, query),
|
||||
.baseline => Target.Cpu.baseline(cpu_arch),
|
||||
.determined_by_cpu_arch => if (query.cpu_arch == null)
|
||||
detectNativeCpuAndFeatures(cpu_arch, os, query)
|
||||
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, query);
|
||||
// For x86, we need to populate some CPU feature flags depending on architecture
|
||||
// and mode:
|
||||
// * 16bit_mode => if the abi is code16
|
||||
// * 32bit_mode => if the arch is x86
|
||||
// However, the "mode" flags can be used as overrides, so if the user explicitly
|
||||
// sets one of them, that takes precedence.
|
||||
switch (cpu_arch) {
|
||||
.x86 => {
|
||||
if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{
|
||||
.@"16bit_mode", .@"32bit_mode",
|
||||
})) {
|
||||
switch (result.target.abi) {
|
||||
.code16 => result.target.cpu.features.addFeature(
|
||||
@intFromEnum(Target.x86.Feature.@"16bit_mode"),
|
||||
),
|
||||
else => result.target.cpu.features.addFeature(
|
||||
@intFromEnum(Target.x86.Feature.@"32bit_mode"),
|
||||
),
|
||||
}
|
||||
}
|
||||
},
|
||||
.arm, .armeb => {
|
||||
// XXX What do we do if the target has the noarm feature?
|
||||
// What do we do if the user specifies +thumb_mode?
|
||||
},
|
||||
.thumb, .thumbeb => {
|
||||
result.target.cpu.features.addFeature(
|
||||
@intFromEnum(Target.arm.Feature.thumb_mode),
|
||||
);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
query.updateCpuFeatures(&result.target.cpu.features);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// In the past, this function attempted to use the executable's own binary if it was dynamically
|
||||
/// linked to answer both the C ABI question and the dynamic linker question. However, this
|
||||
/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
|
||||
/// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc
|
||||
/// version. The problem is that libc.so.6 glibc version will match that of the system while
|
||||
/// the dynamic linker will match that of the compiler binary. Executables with these versions
|
||||
/// mismatching will fail to run.
|
||||
///
|
||||
/// Therefore, this function works the same regardless of whether the compiler binary is
|
||||
/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
|
||||
/// answer to these questions, or if there is a shebang line, then it chases the referenced
|
||||
/// file recursively. If that does not provide the answer, then the function falls back to
|
||||
/// defaults.
|
||||
fn detectAbiAndDynamicLinker(
|
||||
cpu: Target.Cpu,
|
||||
os: Target.Os,
|
||||
query: Target.Query,
|
||||
) DetectError!NativeTargetInfo {
|
||||
const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
|
||||
const is_linux = builtin.target.os.tag == .linux;
|
||||
const is_solarish = builtin.target.os.tag.isSolarish();
|
||||
const have_all_info = query.dynamic_linker.get() != null and
|
||||
query.abi != null and (!is_linux or query.abi.?.isGnu());
|
||||
const os_is_non_native = query.os_tag != null;
|
||||
// The Solaris/illumos environment is always the same.
|
||||
if (!native_target_has_ld or have_all_info or os_is_non_native or is_solarish) {
|
||||
return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
}
|
||||
if (query.abi) |abi| {
|
||||
if (abi.isMusl()) {
|
||||
// musl implies static linking.
|
||||
return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
}
|
||||
}
|
||||
// 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 `builtin.target`.
|
||||
const all_abis = comptime blk: {
|
||||
assert(@intFromEnum(Target.Abi.none) == 0);
|
||||
const fields = std.meta.fields(Target.Abi)[1..];
|
||||
var array: [fields.len]Target.Abi = undefined;
|
||||
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 = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch);
|
||||
|
||||
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,
|
||||
.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];
|
||||
|
||||
// Best case scenario: the executable is dynamically linked, and we can iterate
|
||||
// over our own shared objects and find a dynamic linker.
|
||||
const elf_file = blk: {
|
||||
// This block looks for a shebang line in /usr/bin/env,
|
||||
// if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
|
||||
// doing the same logic recursively in case it finds another shebang line.
|
||||
|
||||
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
|
||||
// reasonably reliable path to start with.
|
||||
var file_name: []const u8 = "/usr/bin/env";
|
||||
// #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
|
||||
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, // opened without write permissions
|
||||
|
||||
error.IsDir,
|
||||
error.NotDir,
|
||||
error.InvalidHandle,
|
||||
error.AccessDenied,
|
||||
error.NoDevice,
|
||||
error.FileNotFound,
|
||||
error.NetworkNotFound,
|
||||
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, query);
|
||||
},
|
||||
|
||||
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.tokenizeScalar(u8, line[2..], ' ');
|
||||
file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
file.close();
|
||||
}
|
||||
};
|
||||
defer elf_file.close();
|
||||
|
||||
// If Zig is statically linked, such as via distributed binary static builds, the above
|
||||
// trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file.
|
||||
// TODO: inline this function and combine the buffer we already read above to find
|
||||
// the possible shebang line with the buffer we use for the ELF header.
|
||||
return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) 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.
|
||||
=> |e| {
|
||||
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
|
||||
return defaultAbiAndDynamicLinker(cpu, os, query);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
|
||||
var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.InvalidUtf8 => unreachable,
|
||||
error.BadPathName => unreachable,
|
||||
error.DeviceBusy => unreachable,
|
||||
error.NetworkNotFound => unreachable, // Windows-only
|
||||
|
||||
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();
|
||||
|
||||
// Now we have a candidate for the path to libc shared object. In
|
||||
// the past, we used readlink() here because the link name would
|
||||
// reveal the glibc version. However, in more recent GNU/Linux
|
||||
// installations, there is no symlink. Thus we instead use a more
|
||||
// robust check of opening the libc shared object and looking at the
|
||||
// .dynstr section, and finding the max version number of symbols
|
||||
// that start with "GLIBC_2.".
|
||||
const glibc_so_basename = "libc.so.6";
|
||||
var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.InvalidUtf8 => unreachable, // Windows only
|
||||
error.BadPathName => unreachable, // Windows only
|
||||
error.PipeBusy => unreachable, // Windows-only
|
||||
error.SharingViolation => unreachable, // Windows-only
|
||||
error.NetworkNotFound => unreachable, // Windows-only
|
||||
error.FileLocksNotSupported => unreachable, // No lock requested.
|
||||
error.NoSpaceLeft => unreachable, // read-only
|
||||
error.PathAlreadyExists => unreachable, // read-only
|
||||
error.DeviceBusy => unreachable, // read-only
|
||||
error.FileBusy => unreachable, // read-only
|
||||
error.InvalidHandle => unreachable, // should not be in the error set
|
||||
error.WouldBlock => unreachable, // not using O_NONBLOCK
|
||||
error.NoDevice => unreachable, // not asking for a special device
|
||||
|
||||
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.SemanticVersion {
|
||||
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 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
|
||||
const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
|
||||
const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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 = @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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
|
||||
const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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;
|
||||
|
||||
// Here we loop over all the strings in the dynstr string table, assuming that any
|
||||
// strings that start with "GLIBC_2." indicate the existence of such a glibc version,
|
||||
// and furthermore, that the system-installed glibc is at minimum that version.
|
||||
|
||||
// Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
|
||||
// Here I use double this value plus some headroom. This makes it only need
|
||||
// a single read syscall here.
|
||||
var buf: [80000]u8 = undefined;
|
||||
if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
|
||||
|
||||
const dynstr_size: usize = @intCast(dynstr.size);
|
||||
const dynstr_bytes = buf[0..dynstr_size];
|
||||
_ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len);
|
||||
var it = mem.splitScalar(u8, dynstr_bytes, 0);
|
||||
var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
|
||||
while (it.next()) |s| {
|
||||
if (mem.startsWith(u8, s, "GLIBC_2.")) {
|
||||
const chopped = s["GLIBC_".len..];
|
||||
const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
|
||||
error.Overflow => 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) error{ UnrecognizedGnuLibCFileName, InvalidGnuLibCVersion }!std.SemanticVersion {
|
||||
// example: "libc-2.3.4.so"
|
||||
// example: "libc-2.27.so"
|
||||
// example: "ld-2.33.so"
|
||||
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 Target.Query.parseVersion(link_name_chopped) catch |err| switch (err) {
|
||||
error.Overflow => return error.InvalidGnuLibCVersion,
|
||||
error.InvalidVersion => return error.InvalidGnuLibCVersion,
|
||||
};
|
||||
}
|
||||
|
||||
test glibcVerFromLinkName {
|
||||
try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("ld-2.37.so", "this-prefix-does-not-exist"));
|
||||
try std.testing.expectError(error.UnrecognizedGnuLibCFileName, glibcVerFromLinkName("libc-2.37.so-is-not-end", "libc-"));
|
||||
|
||||
try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.so", "ld-"));
|
||||
try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.so", "ld-"));
|
||||
try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 0 }, try glibcVerFromLinkName("ld-2.37.0.so", "ld-"));
|
||||
try std.testing.expectEqual(std.SemanticVersion{ .major = 2, .minor = 37, .patch = 1 }, try glibcVerFromLinkName("ld-2.37.1.so", "ld-"));
|
||||
try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));
|
||||
}
|
||||
|
||||
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,
|
||||
query: Target.Query,
|
||||
) 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 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
|
||||
const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&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 = query.abi orelse Target.Abi.default(cpu.arch, os),
|
||||
.ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
|
||||
},
|
||||
.dynamic_linker = query.dynamic_linker,
|
||||
};
|
||||
var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
|
||||
const look_for_ld = query.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: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
|
||||
const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&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 = @as(usize, @intCast(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 = @as(u8, @intCast(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 (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
|
||||
query.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: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
|
||||
const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&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
|
||||
query.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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
|
||||
const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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 = @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: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
|
||||
const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&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| {
|
||||
if (rpoff > ds.size) return error.InvalidElfFile;
|
||||
const rpoff_file = ds.offset + rpoff;
|
||||
const rp_max_size = ds.size - rpoff;
|
||||
|
||||
const strtab_len = @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.tokenizeScalar(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: {
|
||||
// There is no DT_RUNPATH so we try to find libc.so.6 inside the same
|
||||
// directory as the dynamic linker.
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// So far, no luck. Next we try to see if the information is
|
||||
// present in the symlink data for the dynamic linker path.
|
||||
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, // Windows only
|
||||
error.BadPathName => unreachable, // Windows only
|
||||
error.UnsupportedReparsePointType => unreachable, // Windows only
|
||||
error.NetworkNotFound => unreachable, // Windows only
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Nothing worked so far. Finally we fall back to hard-coded search paths.
|
||||
// Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`.
|
||||
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);
|
||||
@memcpy(path_buf[index..][0..prefix.len], prefix);
|
||||
index += prefix.len;
|
||||
@memcpy(path_buf[index..][0..cpu_arch.len], cpu_arch);
|
||||
index += cpu_arch.len;
|
||||
path_buf[index] = '-';
|
||||
index += 1;
|
||||
@memcpy(path_buf[index..][0..os_tag.len], os_tag);
|
||||
index += os_tag.len;
|
||||
path_buf[index] = '-';
|
||||
index += 1;
|
||||
@memcpy(path_buf[index..][0..abi.len], 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, // 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.SocketNotConnected => 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, query: Target.Query) !NativeTargetInfo {
|
||||
const target: Target = .{
|
||||
.cpu = cpu,
|
||||
.os = os,
|
||||
.abi = query.abi orelse Target.Abi.default(cpu.arch, os),
|
||||
.ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
|
||||
};
|
||||
return NativeTargetInfo{
|
||||
.target = target,
|
||||
.dynamic_linker = if (query.dynamic_linker.get() == null)
|
||||
target.standardDynamicLinkerPath()
|
||||
else
|
||||
query.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, query: Target.Query) ?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 (builtin.cpu.arch) {
|
||||
.x86_64, .x86 => {
|
||||
return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, query);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (builtin.os.tag) {
|
||||
.linux => return linux.detectNativeCpuAndFeatures(),
|
||||
.macos => return darwin.macos.detectNativeCpuAndFeatures(),
|
||||
.windows => return windows.detectNativeCpuAndFeatures(),
|
||||
else => {},
|
||||
}
|
||||
|
||||
// This architecture does not have CPU model & feature detection yet.
|
||||
// See https://github.com/ziglang/zig/issues/4591
|
||||
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,
|
||||
};
|
||||
|
||||
/// Return whether or not the given host is capable of running executables of
|
||||
/// the other target.
|
||||
pub fn getExternalExecutor(
|
||||
host: NativeTargetInfo,
|
||||
candidate: *const 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;
|
||||
|
||||
// TODO additionally detect incompatible CPU features.
|
||||
// Note that in some cases the OS kernel will emulate missing CPU features
|
||||
// when an illegal instruction is encountered.
|
||||
|
||||
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 the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
|
||||
// to emulate the foreign architecture.
|
||||
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 the OS matches, we can use QEMU to emulate a foreign architecture.
|
||||
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) {
|
||||
// x86_64 wine does not support emulating aarch64-windows and
|
||||
// vice versa.
|
||||
if (candidate.target.cpu.arch != builtin.cpu.arch) {
|
||||
return bad_result;
|
||||
}
|
||||
switch (candidate.target.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.ptrBitWidth()) {
|
||||
32 => return Executor{ .wasmtime = "wasmtime" },
|
||||
else => return bad_result,
|
||||
}
|
||||
}
|
||||
return bad_result;
|
||||
},
|
||||
.macos => {
|
||||
if (options.allow_darling) {
|
||||
// This check can be loosened once darling adds a QEMU-based emulation
|
||||
// layer for non-host architectures:
|
||||
// https://github.com/darlinghq/darling/issues/863
|
||||
if (candidate.target.cpu.arch != builtin.cpu.arch) {
|
||||
return bad_result;
|
||||
}
|
||||
return Executor{ .darling = "darling" };
|
||||
}
|
||||
return bad_result;
|
||||
},
|
||||
else => return bad_result,
|
||||
}
|
||||
}
|
||||
+21
-6
@@ -6527,7 +6527,6 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
|
||||
try buffer.writer().print(" .{},\n", .{std.zig.fmtId(feature.name)});
|
||||
}
|
||||
}
|
||||
|
||||
try buffer.writer().print(
|
||||
\\ }}),
|
||||
\\}};
|
||||
@@ -6607,15 +6606,31 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
|
||||
.{ windows.min, windows.max },
|
||||
),
|
||||
}
|
||||
try buffer.appendSlice("};\n");
|
||||
|
||||
try buffer.writer().print(
|
||||
\\pub const target = std.Target{{
|
||||
try buffer.appendSlice(
|
||||
\\};
|
||||
\\pub const target: std.Target = .{
|
||||
\\ .cpu = cpu,
|
||||
\\ .os = os,
|
||||
\\ .abi = abi,
|
||||
\\ .ofmt = object_format,
|
||||
\\}};
|
||||
\\
|
||||
);
|
||||
|
||||
if (target.dynamic_linker.get()) |dl| {
|
||||
try buffer.writer().print(
|
||||
\\ .dynamic_linker = std.Target.DynamicLinker.init("{s}"),
|
||||
\\}};
|
||||
\\
|
||||
, .{dl});
|
||||
} else {
|
||||
try buffer.appendSlice(
|
||||
\\ .dynamic_linker = std.Target.DynamicLinker.none,
|
||||
\\};
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
try buffer.writer().print(
|
||||
\\pub const object_format = std.Target.ObjectFormat.{};
|
||||
\\pub const mode = std.builtin.OptimizeMode.{};
|
||||
\\pub const link_libc = {};
|
||||
|
||||
+62
-70
@@ -321,13 +321,14 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
|
||||
} else if (mem.eql(u8, cmd, "init")) {
|
||||
return cmdInit(gpa, arena, cmd_args);
|
||||
} else if (mem.eql(u8, cmd, "targets")) {
|
||||
const info = try detectNativeTargetInfo(.{});
|
||||
const host = try std.zig.system.resolveTargetQuery(.{});
|
||||
const stdout = io.getStdOut().writer();
|
||||
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
|
||||
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, host);
|
||||
} else if (mem.eql(u8, cmd, "version")) {
|
||||
try std.io.getStdOut().writeAll(build_options.version ++ "\n");
|
||||
// Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version"
|
||||
// to avoid affecting the startup time for build-critical commands (check takes about ~10 μs)
|
||||
// Check libc++ linkage to make sure Zig was built correctly, but only
|
||||
// for "env" and "version" to avoid affecting the startup time for
|
||||
// build-critical commands (check takes about ~10 μs)
|
||||
return verifyLibcxxCorrectlyLinked();
|
||||
} else if (mem.eql(u8, cmd, "env")) {
|
||||
verifyLibcxxCorrectlyLinked();
|
||||
@@ -2608,9 +2609,9 @@ fn buildOutputType(
|
||||
}
|
||||
|
||||
const target_query = try parseTargetQueryOrReportFatalError(arena, target_parse_options);
|
||||
const target_info = try detectNativeTargetInfo(target_query);
|
||||
const target = try std.zig.system.resolveTargetQuery(target_query);
|
||||
|
||||
if (target_info.target.os.tag != .freestanding) {
|
||||
if (target.os.tag != .freestanding) {
|
||||
if (ensure_libc_on_non_freestanding)
|
||||
link_libc = true;
|
||||
if (ensure_libcpp_on_non_freestanding)
|
||||
@@ -2621,7 +2622,7 @@ fn buildOutputType(
|
||||
if (!force) {
|
||||
entry = null;
|
||||
} else if (entry == null and output_mode == .Exe) {
|
||||
entry = switch (target_info.target.ofmt) {
|
||||
entry = switch (target.ofmt) {
|
||||
.coff => "wWinMainCRTStartup",
|
||||
.macho => "_main",
|
||||
.elf, .plan9 => "_start",
|
||||
@@ -2629,12 +2630,12 @@ fn buildOutputType(
|
||||
else => |tag| fatal("No default entry point available for output format {s}", .{@tagName(tag)}),
|
||||
};
|
||||
}
|
||||
} else if (entry == null and target_info.target.isWasm() and output_mode == .Exe) {
|
||||
} else if (entry == null and target.isWasm() and output_mode == .Exe) {
|
||||
// For WebAssembly the compiler defaults to setting the entry name when no flags are set.
|
||||
entry = defaultWasmEntryName(wasi_exec_model);
|
||||
}
|
||||
|
||||
if (target_info.target.ofmt == .coff) {
|
||||
if (target.ofmt == .coff) {
|
||||
// Now that we know the target supports resources,
|
||||
// we can add the res files as link objects.
|
||||
for (res_files.items) |res_file| {
|
||||
@@ -2652,7 +2653,7 @@ fn buildOutputType(
|
||||
}
|
||||
}
|
||||
|
||||
if (target_info.target.cpu.arch.isWasm()) blk: {
|
||||
if (target.cpu.arch.isWasm()) blk: {
|
||||
if (single_threaded == null) {
|
||||
single_threaded = true;
|
||||
}
|
||||
@@ -2678,8 +2679,8 @@ fn buildOutputType(
|
||||
fatal("shared memory is not allowed in object files", .{});
|
||||
}
|
||||
|
||||
if (!target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or
|
||||
!target_info.target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory)))
|
||||
if (!target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or
|
||||
!target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory)))
|
||||
{
|
||||
fatal("'atomics' and 'bulk-memory' features must be enabled to use shared memory", .{});
|
||||
}
|
||||
@@ -2777,15 +2778,15 @@ fn buildOutputType(
|
||||
}
|
||||
|
||||
for (system_libs.keys(), system_libs.values()) |lib_name, info| {
|
||||
if (target_info.target.is_libc_lib_name(lib_name)) {
|
||||
if (target.is_libc_lib_name(lib_name)) {
|
||||
link_libc = true;
|
||||
continue;
|
||||
}
|
||||
if (target_info.target.is_libcpp_lib_name(lib_name)) {
|
||||
if (target.is_libcpp_lib_name(lib_name)) {
|
||||
link_libcpp = true;
|
||||
continue;
|
||||
}
|
||||
switch (target_util.classifyCompilerRtLibName(target_info.target, lib_name)) {
|
||||
switch (target_util.classifyCompilerRtLibName(target, lib_name)) {
|
||||
.none => {},
|
||||
.only_libunwind, .both => {
|
||||
link_libunwind = true;
|
||||
@@ -2797,8 +2798,8 @@ fn buildOutputType(
|
||||
},
|
||||
}
|
||||
|
||||
if (target_info.target.isMinGW()) {
|
||||
const exists = mingw.libExists(arena, target_info.target, zig_lib_directory, lib_name) catch |err| {
|
||||
if (target.isMinGW()) {
|
||||
const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| {
|
||||
fatal("failed to check zig installation for DLL import libs: {s}", .{
|
||||
@errorName(err),
|
||||
});
|
||||
@@ -2820,7 +2821,7 @@ fn buildOutputType(
|
||||
fatal("cannot use absolute path as a system library: {s}", .{lib_name});
|
||||
}
|
||||
|
||||
if (target_info.target.os.tag == .wasi) {
|
||||
if (target.os.tag == .wasi) {
|
||||
if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| {
|
||||
try wasi_emulated_libs.append(crt_file);
|
||||
continue;
|
||||
@@ -2838,7 +2839,7 @@ fn buildOutputType(
|
||||
if (sysroot == null and target_query.isNativeOs() and target_query.isNativeAbi() and
|
||||
(external_system_libs.len != 0 or want_native_include_dirs))
|
||||
{
|
||||
const paths = std.zig.system.NativePaths.detect(arena, target_info) catch |err| {
|
||||
const paths = std.zig.system.NativePaths.detect(arena, target) catch |err| {
|
||||
fatal("unable to detect native system paths: {s}", .{@errorName(err)});
|
||||
};
|
||||
for (paths.warnings.items) |warning| {
|
||||
@@ -2857,7 +2858,7 @@ fn buildOutputType(
|
||||
}
|
||||
|
||||
if (builtin.target.os.tag == .windows and
|
||||
target_info.target.abi == .msvc and
|
||||
target.abi == .msvc and
|
||||
external_system_libs.len != 0)
|
||||
{
|
||||
if (libc_installation == null) {
|
||||
@@ -2902,7 +2903,7 @@ fn buildOutputType(
|
||||
&checked_paths,
|
||||
lib_dir_path,
|
||||
lib_name,
|
||||
target_info.target,
|
||||
target,
|
||||
info.preferred_mode,
|
||||
)) {
|
||||
const path = try arena.dupe(u8, test_path.items);
|
||||
@@ -2936,7 +2937,7 @@ fn buildOutputType(
|
||||
&checked_paths,
|
||||
lib_dir_path,
|
||||
lib_name,
|
||||
target_info.target,
|
||||
target,
|
||||
info.fallbackMode(),
|
||||
)) {
|
||||
const path = try arena.dupe(u8, test_path.items);
|
||||
@@ -2970,7 +2971,7 @@ fn buildOutputType(
|
||||
&checked_paths,
|
||||
lib_dir_path,
|
||||
lib_name,
|
||||
target_info.target,
|
||||
target,
|
||||
info.preferred_mode,
|
||||
)) {
|
||||
const path = try arena.dupe(u8, test_path.items);
|
||||
@@ -2994,7 +2995,7 @@ fn buildOutputType(
|
||||
&checked_paths,
|
||||
lib_dir_path,
|
||||
lib_name,
|
||||
target_info.target,
|
||||
target,
|
||||
info.fallbackMode(),
|
||||
)) {
|
||||
const path = try arena.dupe(u8, test_path.items);
|
||||
@@ -3089,15 +3090,13 @@ fn buildOutputType(
|
||||
}
|
||||
// After this point, resolved_frameworks is used instead of frameworks.
|
||||
|
||||
const object_format = target_info.target.ofmt;
|
||||
|
||||
if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) {
|
||||
if (output_mode == .Obj and (target.ofmt == .coff or target.ofmt == .macho)) {
|
||||
const total_obj_count = c_source_files.items.len +
|
||||
@intFromBool(root_src_file != null) +
|
||||
rc_source_files.items.len +
|
||||
link_objects.items.len;
|
||||
if (total_obj_count > 1) {
|
||||
fatal("{s} does not support linking multiple objects into one", .{@tagName(object_format)});
|
||||
fatal("{s} does not support linking multiple objects into one", .{@tagName(target.ofmt)});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3110,7 +3109,7 @@ fn buildOutputType(
|
||||
const resolved_soname: ?[]const u8 = switch (soname) {
|
||||
.yes => |explicit| explicit,
|
||||
.no => null,
|
||||
.yes_default_value => switch (object_format) {
|
||||
.yes_default_value => switch (target.ofmt) {
|
||||
.elf => if (have_version)
|
||||
try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ root_name, version.major })
|
||||
else
|
||||
@@ -3119,7 +3118,7 @@ fn buildOutputType(
|
||||
},
|
||||
};
|
||||
|
||||
const a_out_basename = switch (object_format) {
|
||||
const a_out_basename = switch (target.ofmt) {
|
||||
.coff => "a.exe",
|
||||
else => "a.out",
|
||||
};
|
||||
@@ -3141,7 +3140,7 @@ fn buildOutputType(
|
||||
},
|
||||
.basename = try std.zig.binNameAlloc(arena, .{
|
||||
.root_name = root_name,
|
||||
.target = target_info.target,
|
||||
.target = target,
|
||||
.output_mode = output_mode,
|
||||
.link_mode = link_mode,
|
||||
.version = optional_version,
|
||||
@@ -3269,7 +3268,7 @@ fn buildOutputType(
|
||||
// Note that cmake when targeting Windows will try to execute
|
||||
// zig cc to make an executable and output an implib too.
|
||||
const implib_eligible = is_exe_or_dyn_lib and
|
||||
emit_bin_loc != null and target_info.target.os.tag == .windows;
|
||||
emit_bin_loc != null and target.os.tag == .windows;
|
||||
if (!implib_eligible) {
|
||||
if (!emit_implib_arg_provided) {
|
||||
emit_implib = .no;
|
||||
@@ -3419,7 +3418,7 @@ fn buildOutputType(
|
||||
// "-" is stdin. Dump it to a real file.
|
||||
const sep = fs.path.sep_str;
|
||||
const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{
|
||||
std.crypto.random.int(u64), ext.canonicalName(target_info.target),
|
||||
std.crypto.random.int(u64), ext.canonicalName(target),
|
||||
});
|
||||
try local_cache_directory.handle.makePath("tmp");
|
||||
// Note that in one of the happy paths, execve() is used to switch
|
||||
@@ -3454,10 +3453,10 @@ fn buildOutputType(
|
||||
.local_cache_directory = local_cache_directory,
|
||||
.global_cache_directory = global_cache_directory,
|
||||
.root_name = root_name,
|
||||
.target = target_info.target,
|
||||
.target = target,
|
||||
.is_native_os = target_query.isNativeOs(),
|
||||
.is_native_abi = target_query.isNativeAbi(),
|
||||
.dynamic_linker = target_info.dynamic_linker.get(),
|
||||
.dynamic_linker = target.dynamic_linker.get(),
|
||||
.sysroot = sysroot,
|
||||
.output_mode = output_mode,
|
||||
.main_mod = main_mod,
|
||||
@@ -3603,7 +3602,6 @@ fn buildOutputType(
|
||||
.want_structured_cfg = want_structured_cfg,
|
||||
}) catch |err| switch (err) {
|
||||
error.LibCUnavailable => {
|
||||
const target = target_info.target;
|
||||
const triple_name = try target.zigTriple(arena);
|
||||
std.log.err("unable to find or provide libc for target '{s}'", .{triple_name});
|
||||
|
||||
@@ -3692,7 +3690,7 @@ fn buildOutputType(
|
||||
try comp.makeBinFileExecutable();
|
||||
saveState(comp, debug_incremental);
|
||||
|
||||
if (test_exec_args.items.len == 0 and object_format == .c) default_exec_args: {
|
||||
if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: {
|
||||
// Default to using `zig run` to execute the produced .c code from `zig test`.
|
||||
const c_code_loc = emit_bin_loc orelse break :default_exec_args;
|
||||
const c_code_directory = c_code_loc.directory orelse comp.bin_file.options.emit.?.directory;
|
||||
@@ -3707,7 +3705,7 @@ fn buildOutputType(
|
||||
|
||||
if (link_libc) {
|
||||
try test_exec_args.append("-lc");
|
||||
} else if (target_info.target.os.tag == .windows) {
|
||||
} else if (target.os.tag == .windows) {
|
||||
try test_exec_args.appendSlice(&.{
|
||||
"--subsystem", "console",
|
||||
"-lkernel32", "-lntdll",
|
||||
@@ -3741,7 +3739,7 @@ fn buildOutputType(
|
||||
test_exec_args.items,
|
||||
self_exe_path.?,
|
||||
arg_mode,
|
||||
&target_info,
|
||||
&target,
|
||||
&comp_destroyed,
|
||||
all_args,
|
||||
runtime_args_start,
|
||||
@@ -3861,7 +3859,7 @@ fn serve(
|
||||
// test_exec_args,
|
||||
// self_exe_path.?,
|
||||
// arg_mode,
|
||||
// target_info,
|
||||
// target,
|
||||
// true,
|
||||
// &comp_destroyed,
|
||||
// all_args,
|
||||
@@ -4071,7 +4069,7 @@ fn runOrTest(
|
||||
test_exec_args: []const ?[]const u8,
|
||||
self_exe_path: []const u8,
|
||||
arg_mode: ArgMode,
|
||||
target_info: *const std.zig.system.NativeTargetInfo,
|
||||
target: *const std.Target,
|
||||
comp_destroyed: *bool,
|
||||
all_args: []const []const u8,
|
||||
runtime_args_start: ?usize,
|
||||
@@ -4105,7 +4103,7 @@ fn runOrTest(
|
||||
if (process.can_execv and arg_mode == .run) {
|
||||
// execv releases the locks; no need to destroy the Compilation here.
|
||||
const err = process.execve(gpa, argv.items, &env_map);
|
||||
try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
|
||||
try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
|
||||
const cmd = try std.mem.join(arena, " ", argv.items);
|
||||
fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
|
||||
} else if (process.can_spawn) {
|
||||
@@ -4121,7 +4119,7 @@ fn runOrTest(
|
||||
comp_destroyed.* = true;
|
||||
|
||||
const term = child.spawnAndWait() catch |err| {
|
||||
try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
|
||||
try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
|
||||
const cmd = try std.mem.join(arena, " ", argv.items);
|
||||
fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
|
||||
};
|
||||
@@ -4820,12 +4818,10 @@ pub fn cmdLibC(gpa: Allocator, args: []const []const u8) !void {
|
||||
if (!target_query.isNative()) {
|
||||
fatal("unable to detect libc for non-native target", .{});
|
||||
}
|
||||
const target_info = try detectNativeTargetInfo(target_query);
|
||||
|
||||
var libc = LibCInstallation.findNative(.{
|
||||
.allocator = gpa,
|
||||
.verbose = true,
|
||||
.target = target_info.target,
|
||||
.target = try std.zig.system.resolveTargetQuery(target_query),
|
||||
}) catch |err| {
|
||||
fatal("unable to detect native libc: {s}", .{@errorName(err)});
|
||||
};
|
||||
@@ -5114,11 +5110,11 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
|
||||
gimmeMoreOfThoseSweetSweetFileDescriptors();
|
||||
|
||||
const target_query: std.Target.Query = .{};
|
||||
const target_info = try detectNativeTargetInfo(target_query);
|
||||
const target = try std.zig.system.resolveTargetQuery(target_query);
|
||||
|
||||
const exe_basename = try std.zig.binNameAlloc(arena, .{
|
||||
.root_name = "build",
|
||||
.target = target_info.target,
|
||||
.target = target,
|
||||
.output_mode = .Exe,
|
||||
});
|
||||
const emit_bin: Compilation.EmitLoc = .{
|
||||
@@ -5282,10 +5278,10 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
|
||||
.local_cache_directory = local_cache_directory,
|
||||
.global_cache_directory = global_cache_directory,
|
||||
.root_name = "build",
|
||||
.target = target_info.target,
|
||||
.target = target,
|
||||
.is_native_os = target_query.isNativeOs(),
|
||||
.is_native_abi = target_query.isNativeAbi(),
|
||||
.dynamic_linker = target_info.dynamic_linker.get(),
|
||||
.dynamic_linker = target.dynamic_linker.get(),
|
||||
.output_mode = .Exe,
|
||||
.main_mod = &main_mod,
|
||||
.emit_bin = emit_bin,
|
||||
@@ -6269,10 +6265,6 @@ test "fds" {
|
||||
gimmeMoreOfThoseSweetSweetFileDescriptors();
|
||||
}
|
||||
|
||||
fn detectNativeTargetInfo(target_query: std.Target.Query) !std.zig.system.NativeTargetInfo {
|
||||
return std.zig.system.NativeTargetInfo.detect(target_query);
|
||||
}
|
||||
|
||||
const usage_ast_check =
|
||||
\\Usage: zig ast-check [file]
|
||||
\\
|
||||
@@ -6669,24 +6661,24 @@ fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 {
|
||||
fn warnAboutForeignBinaries(
|
||||
arena: Allocator,
|
||||
arg_mode: ArgMode,
|
||||
target_info: *const std.zig.system.NativeTargetInfo,
|
||||
target: *const std.Target,
|
||||
link_libc: bool,
|
||||
) !void {
|
||||
const host_query: std.Target.Query = .{};
|
||||
const host_target_info = try detectNativeTargetInfo(host_query);
|
||||
const host_target = try std.zig.system.resolveTargetQuery(host_query);
|
||||
|
||||
switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
|
||||
switch (std.zig.system.getExternalExecutor(host_target, target, .{ .link_libc = link_libc })) {
|
||||
.native => return,
|
||||
.rosetta => {
|
||||
const host_name = try host_target_info.target.zigTriple(arena);
|
||||
const foreign_name = try target_info.target.zigTriple(arena);
|
||||
const host_name = try host_target.zigTriple(arena);
|
||||
const foreign_name = try target.zigTriple(arena);
|
||||
warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{
|
||||
host_name, foreign_name,
|
||||
});
|
||||
},
|
||||
.qemu => |qemu| {
|
||||
const host_name = try host_target_info.target.zigTriple(arena);
|
||||
const foreign_name = try target_info.target.zigTriple(arena);
|
||||
const host_name = try host_target.zigTriple(arena);
|
||||
const foreign_name = try target.zigTriple(arena);
|
||||
switch (arg_mode) {
|
||||
.zig_test => warn(
|
||||
"the host system ({s}) does not appear to be capable of executing binaries " ++
|
||||
@@ -6702,8 +6694,8 @@ fn warnAboutForeignBinaries(
|
||||
}
|
||||
},
|
||||
.wine => |wine| {
|
||||
const host_name = try host_target_info.target.zigTriple(arena);
|
||||
const foreign_name = try target_info.target.zigTriple(arena);
|
||||
const host_name = try host_target.zigTriple(arena);
|
||||
const foreign_name = try target.zigTriple(arena);
|
||||
switch (arg_mode) {
|
||||
.zig_test => warn(
|
||||
"the host system ({s}) does not appear to be capable of executing binaries " ++
|
||||
@@ -6719,8 +6711,8 @@ fn warnAboutForeignBinaries(
|
||||
}
|
||||
},
|
||||
.wasmtime => |wasmtime| {
|
||||
const host_name = try host_target_info.target.zigTriple(arena);
|
||||
const foreign_name = try target_info.target.zigTriple(arena);
|
||||
const host_name = try host_target.zigTriple(arena);
|
||||
const foreign_name = try target.zigTriple(arena);
|
||||
switch (arg_mode) {
|
||||
.zig_test => warn(
|
||||
"the host system ({s}) does not appear to be capable of executing binaries " ++
|
||||
@@ -6736,8 +6728,8 @@ fn warnAboutForeignBinaries(
|
||||
}
|
||||
},
|
||||
.darling => |darling| {
|
||||
const host_name = try host_target_info.target.zigTriple(arena);
|
||||
const foreign_name = try target_info.target.zigTriple(arena);
|
||||
const host_name = try host_target.zigTriple(arena);
|
||||
const foreign_name = try target.zigTriple(arena);
|
||||
switch (arg_mode) {
|
||||
.zig_test => warn(
|
||||
"the host system ({s}) does not appear to be capable of executing binaries " ++
|
||||
@@ -6753,7 +6745,7 @@ fn warnAboutForeignBinaries(
|
||||
}
|
||||
},
|
||||
.bad_dl => |foreign_dl| {
|
||||
const host_dl = host_target_info.dynamic_linker.get() orelse "(none)";
|
||||
const host_dl = host_target.dynamic_linker.get() orelse "(none)";
|
||||
const tip_suffix = switch (arg_mode) {
|
||||
.zig_test => ", '--test-no-exec', or '--test-cmd'",
|
||||
else => "",
|
||||
@@ -6763,8 +6755,8 @@ fn warnAboutForeignBinaries(
|
||||
});
|
||||
},
|
||||
.bad_os_or_cpu => {
|
||||
const host_name = try host_target_info.target.zigTriple(arena);
|
||||
const foreign_name = try target_info.target.zigTriple(arena);
|
||||
const host_name = try host_target.zigTriple(arena);
|
||||
const foreign_name = try target.zigTriple(arena);
|
||||
const tip_suffix = switch (arg_mode) {
|
||||
.zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",
|
||||
else => "",
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8, stdout: std.fs.File.Wr
|
||||
|
||||
const global_cache_dir = try introspect.resolveGlobalCacheDir(arena);
|
||||
|
||||
const info = try std.zig.system.NativeTargetInfo.detect(.{});
|
||||
const triple = try info.target.zigTriple(arena);
|
||||
const host = try std.zig.system.resolveTargetQuery(.{});
|
||||
const triple = try host.zigTriple(arena);
|
||||
|
||||
var bw = std.io.bufferedWriter(stdout);
|
||||
const w = bw.writer();
|
||||
|
||||
+13
-17
@@ -541,7 +541,7 @@ pub fn lowerToBuildSteps(
|
||||
cases_dir_path: []const u8,
|
||||
incremental_exe: *std.Build.Step.Compile,
|
||||
) void {
|
||||
const host = std.zig.system.NativeTargetInfo.detect(.{}) catch |err|
|
||||
const host = std.zig.system.resolveTargetQuery(.{}) catch |err|
|
||||
std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)});
|
||||
|
||||
for (self.incremental_cases.items) |incr_case| {
|
||||
@@ -648,8 +648,7 @@ pub fn lowerToBuildSteps(
|
||||
},
|
||||
.Execution => |expected_stdout| no_exec: {
|
||||
const run = if (case.target.target.ofmt == .c) run_step: {
|
||||
const target_info = case.target.toNativeTargetInfo();
|
||||
if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) {
|
||||
if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) {
|
||||
// We wouldn't be able to run the compiled C code.
|
||||
break :no_exec;
|
||||
}
|
||||
@@ -694,8 +693,7 @@ pub fn lowerToBuildSteps(
|
||||
continue; // Pass test.
|
||||
}
|
||||
|
||||
const target_info = case.target.toNativeTargetInfo();
|
||||
if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) {
|
||||
if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) {
|
||||
// We wouldn't be able to run the compiled C code.
|
||||
continue; // Pass test.
|
||||
}
|
||||
@@ -1199,6 +1197,8 @@ const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const getExternalExecutor = std.zig.system.getExternalExecutor;
|
||||
|
||||
const Compilation = @import("../../src/Compilation.zig");
|
||||
const zig_h = @import("../../src/link.zig").File.C.zig_h;
|
||||
const introspect = @import("../../src/introspect.zig");
|
||||
@@ -1386,18 +1386,15 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
fn resolveTargetQuery(query: std.Target.Query) std.Build.ResolvedTarget {
|
||||
const result = std.zig.system.NativeTargetInfo.detect(query) catch
|
||||
@panic("unable to resolve target query");
|
||||
|
||||
return .{
|
||||
.query = query,
|
||||
.target = result.target,
|
||||
.dynamic_linker = result.dynamic_linker,
|
||||
.target = std.zig.system.resolveTargetQuery(query) catch
|
||||
@panic("unable to resolve target query"),
|
||||
};
|
||||
}
|
||||
|
||||
fn runCases(self: *Cases, zig_exe_path: []const u8) !void {
|
||||
const host = try std.zig.system.NativeTargetInfo.detect(.{});
|
||||
const host = try std.zig.system.resolveTargetQuery(.{});
|
||||
|
||||
var progress = std.Progress{};
|
||||
const root_node = progress.start("compiler", self.cases.items.len);
|
||||
@@ -1478,7 +1475,7 @@ fn runOneCase(
|
||||
zig_exe_path: []const u8,
|
||||
thread_pool: *ThreadPool,
|
||||
global_cache_directory: Compilation.Directory,
|
||||
host: std.zig.system.NativeTargetInfo,
|
||||
host: std.Target,
|
||||
) !void {
|
||||
const tmp_src_path = "tmp.zig";
|
||||
const enable_rosetta = build_options.enable_rosetta;
|
||||
@@ -1488,8 +1485,7 @@ fn runOneCase(
|
||||
const enable_darling = build_options.enable_darling;
|
||||
const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir;
|
||||
|
||||
const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
|
||||
const target = target_info.target;
|
||||
const target = try std.zig.system.resolveTargetQuery(case.target);
|
||||
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena_allocator.deinit();
|
||||
@@ -1579,7 +1575,7 @@ fn runOneCase(
|
||||
.keep_source_files_loaded = true,
|
||||
.is_native_os = case.target.isNativeOs(),
|
||||
.is_native_abi = case.target.isNativeAbi(),
|
||||
.dynamic_linker = target_info.dynamic_linker.get(),
|
||||
.dynamic_linker = target.dynamic_linker.get(),
|
||||
.link_libc = case.link_libc,
|
||||
.use_llvm = use_llvm,
|
||||
.self_exe_path = zig_exe_path,
|
||||
@@ -1715,7 +1711,7 @@ fn runOneCase(
|
||||
.{ &tmp.sub_path, bin_name },
|
||||
);
|
||||
if (case.target.ofmt != null and case.target.ofmt.? == .c) {
|
||||
if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
|
||||
if (getExternalExecutor(host, &target, .{ .link_libc = true }) != .native) {
|
||||
// We wouldn't be able to run the compiled C code.
|
||||
continue :update; // Pass test.
|
||||
}
|
||||
@@ -1734,7 +1730,7 @@ fn runOneCase(
|
||||
if (zig_lib_directory.path) |p| {
|
||||
try argv.appendSlice(&.{ "-I", p });
|
||||
}
|
||||
} else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) {
|
||||
} else switch (getExternalExecutor(host, &target, .{ .link_libc = case.link_libc })) {
|
||||
.native => {
|
||||
if (case.backend == .stage2 and case.target.getCpuArch().isArmOrThumb()) {
|
||||
// https://github.com/ziglang/zig/issues/13623
|
||||
|
||||
Reference in New Issue
Block a user