detectAbiAndDynamicLinker
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.
Function parameters
Parameters
- cpu:Target.Cpu
- os:Target.Os
- query:Target.Query
Type definitions in this namespace
Types
Return whether or not the given host is capable of running executables of
Functions
- getExternalExecutor
- Return whether or not the given host is capable of running executables of
- resolveTargetQuery
- Given a `Target.Query`, which specifies in detail which parts of the
Error sets in this namespace
Error Sets
Source
Implementation
fn detectAbiAndDynamicLinker(
cpu: Target.Cpu,
os: Target.Os,
query: Target.Query,
) DetectError!Target {
const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none;
const is_linux = builtin.target.os.tag == .linux;
const is_solarish = builtin.target.os.tag.isSolarish();
const is_darwin = builtin.target.os.tag.isDarwin();
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 or is_darwin) {
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;
switch (Target.DynamicLinker.kind(os.tag)) {
// The OS has no dynamic linker. Leave the list empty and rely on `Abi.default()` to pick
// something sensible in `abiAndDynamicLinkerFromFile()`.
.none => {},
// The OS has a system-wide dynamic linker. Unfortunately, this implies that there's no
// useful ABI information that we can glean from it merely being present. That means the
// best we can do for this case (for now) is also `Abi.default()`.
.arch_os => {},
// The OS can have different dynamic linker paths depending on libc/ABI. In this case, we
// need to gather all the valid arch/OS/ABI combinations. `abiAndDynamicLinkerFromFile()`
// will then look for a dynamic linker with a matching path on the system and pick the ABI
// we associated it with here.
.arch_os_abi => for (all_abis) |abi| {
const ld = Target.DynamicLinker.standard(cpu, os, abi);
// Does the generated target triple actually have a standard dynamic linker path?
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 = elf_file: {
// 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.
var file_name: []const u8 = switch (os.tag) {
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
// reasonably reliable path to start with.
else => "/usr/bin/env",
// Haiku does not have a /usr root directory.
.haiku => "/bin/env",
};
// According to `man 2 execve`:
//
// The kernel imposes a maximum length on the text
// that follows the "#!" characters at the start of a script;
// characters beyond the limit are ignored.
// Before Linux 5.1, the limit is 127 characters.
// Since Linux 5.1, the limit is 255 characters.
//
// Tests show that bash and zsh consider 255 as total limit,
// *including* "#!" characters and ignoring newline.
// For safety, we set max length as 255 + \n (1).
var buffer: [255 + 1]u8 = undefined;
while (true) {
// Interpreter path can be relative on Linux, but
// for simplicity we are asserting it is an absolute path.
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, // WASI only
error.InvalidWtf8 => unreachable, // Windows only
error.BadPathName => unreachable,
error.PipeBusy => unreachable,
error.FileLocksNotSupported => unreachable,
error.WouldBlock => unreachable,
error.FileBusy => unreachable, // opened without write permissions
error.AntivirusInterference => unreachable, // Windows-only error
error.IsDir,
error.NotDir,
error.AccessDenied,
error.PermissionDenied,
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.", .{@errorName(e)});
return defaultAbiAndDynamicLinker(cpu, os, query);
},
else => |e| return e,
};
var is_elf_file = false;
defer if (is_elf_file == false) file.close();
// Shortest working interpreter path is "#!/i" (4)
// (interpreter is "/i", assuming all paths are absolute, like in above comment).
// ELF magic number length is also 4.
//
// If file is shorter than that, it is definitely not ELF file
// nor file with "shebang" line.
const min_len: usize = 4;
const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) {
error.UnexpectedEndOfFile,
error.UnableToReadElfFile,
error.ProcessNotFound,
=> return defaultAbiAndDynamicLinker(cpu, os, query),
else => |e| return e,
};
const content = buffer[0..len];
if (mem.eql(u8, content[0..4], std.elf.MAGIC)) {
// It is very likely ELF file!
is_elf_file = true;
break :elf_file file;
} else if (mem.eql(u8, content[0..2], "#!")) {
// We detected shebang, now parse entire line.
// Trim leading "#!", spaces and tabs.
const trimmed_line = mem.trimStart(u8, content[2..], &.{ ' ', '\t' });
// This line can have:
// * Interpreter path only,
// * Interpreter path and arguments, all separated by space, tab or NUL character.
// And optionally newline at the end.
const path_maybe_args = mem.trimEnd(u8, trimmed_line, "\n");
// Separate path and args.
const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
file_name = path_maybe_args[0..path_end];
continue;
} else {
// Not a ELF file, not a shell script with "shebang line", invalid duck.
return defaultAbiAndDynamicLinker(cpu, os, query);
}
}
};
defer elf_file.close();
// 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,
error.ProcessNotFound,
=> |e| return e,
error.UnableToReadElfFile,
error.InvalidElfClass,
error.InvalidElfVersion,
error.InvalidElfEndian,
error.InvalidElfFile,
error.InvalidElfMagic,
error.Unexpected,
error.UnexpectedEndOfFile,
error.NameTooLong,
error.StaticElfFile,
// Finally, we fall back on the standard path.
=> |e| {
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
return defaultAbiAndDynamicLinker(cpu, os, query);
},
};
}