Zig 0.17.0-dev (Split by item)

Shortcut keys
?
Show these tips
S
Focus search field
Esc
Clear focus & close tips
U
Jump to source code
↑
Move up in search results
↓
Move down in search results
↵
Go to active search result
This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig standard library. Private Function

detectAbiAndDynamicLinker

Private Function
std.zig.system.detectAbiAndDynamicLinker
fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target

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.

Params

File

zig/system.zig:951
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target {
    const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none;
    const is_linux = builtin.target.os.tag == .linux;
    const is_illumos = builtin.target.os.tag == .illumos;
    const is_darwin = builtin.target.os.tag.isDarwin();
    const have_all_info = query.dynamic_linker != null and
        query.abi != null and (!is_linux or query.abi.?.isGnu());
    const os_is_non_native = query.os_tag != null;
    // The illumos environment is always the same.
    if (!native_target_has_ld or have_all_info or os_is_non_native or is_illumos or is_darwin) {
        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(@backingInt(Target.Abi.none) == 0);
        const field_names = @typeInfo(Target.Abi).@"enum".field_names[1..];
        var array: [field_names.len]Target.Abi = undefined;
        for (field_names, 0..) |field_name, 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];

    var file_reader: Io.File.Reader = undefined;
    // 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).
    const max_shebang_line_size = 256;
    var file_reader_buffer: [4096]u8 = undefined;
    comptime assert(file_reader_buffer.len >= max_shebang_line_size);

    // Best case scenario: the executable is dynamically linked, and we can iterate
    // over our own shared objects and find a dynamic linker.
    const header = 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",
        };

        while (true) {
            const file = Io.Dir.openFileAbsolute(io, file_name, .{}) catch |err| switch (err) {
                error.NoSpaceLeft => return error.Unexpected,
                error.NameTooLong => return error.Unexpected,
                error.PathAlreadyExists => return error.Unexpected,
                error.BadPathName => return error.Unexpected,
                error.PipeBusy => return error.Unexpected,
                error.FileLocksUnsupported => return error.Unexpected,
                error.FileBusy => return error.Unexpected, // opened without write permissions
                error.AntivirusInterference => return error.Unexpected, // Windows-only error

                error.IsDir,
                error.NotDir,
                error.AccessDenied,
                error.PermissionDenied,
                error.NoDevice,
                error.FileNotFound,
                error.NetworkNotFound,
                error.FileTooBig,
                error.Unexpected,
                => |e| if (e == error.FileNotFound and os.tag == .linux and mem.eql(u8, file_name, "/usr/bin/env")) {
                    // Android does not have a /usr directory, so try again
                    file_name = "/system/bin/env";
                    continue;
                } else return error.UnableToOpenElfFile,
                else => |e| return e,
            };
            var is_elf_file = false;
            defer if (!is_elf_file) file.close(io);

            file_reader = .init(file, io, &file_reader_buffer);
            file_name = undefined; // it aliases file_reader_buffer

            const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) {
                error.EndOfStream,
                error.InvalidElfMagic,
                => {
                    const shebang_line = file_reader.interface.takeSentinel('\n') catch |err| switch (err) {
                        error.ReadFailed => return file_reader.err.?,
                        // It's neither an ELF file nor file with shebang line.
                        error.EndOfStream, error.StreamTooLong => return error.UnhelpfulFile,
                    };
                    if (!mem.startsWith(u8, shebang_line, "#!")) return error.UnhelpfulFile;
                    // We detected shebang, now parse entire line.

                    // Trim leading "#!", spaces and tabs.
                    const trimmed_line = mem.trimStart(u8, shebang_line[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.findAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
                    const unvalidated_path = path_maybe_args[0..path_end];
                    file_name = if (fs.path.isAbsolute(unvalidated_path)) unvalidated_path else return error.RelativeShebang;
                    continue;
                },

                error.InvalidElfVersion,
                error.InvalidElfClass,
                error.InvalidElfEndian,
                => return error.InvalidElfFile,

                error.ReadFailed => return file_reader.err.?,
            };
            is_elf_file = true;
            break :elf_file header;
        }
    };
    defer file_reader.file.close(io);

    return abiAndDynamicLinkerFromFile(&file_reader, &header, cpu, os, ld_info_list, query) catch |err| switch (err) {
        error.FileSystem,
        error.SystemResources,
        error.SymLinkLoop,
        error.ProcessFdQuotaExceeded,
        error.SystemFdQuotaExceeded,
        error.Canceled,
        => |e| return e,

        error.ReadFailed => return file_reader.err.?,

        else => |e| {
            std.log.warn("encountered {t}; falling back to default ABI and dynamic linker", .{e});
            return defaultAbiAndDynamicLinker(cpu, os, query);
        },
    };
}