Zig 0.17.0-dev (Split by item)

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 library itself.

abiAndDynamicLinkerFromFile

system.abiAndDynamicLinkerFromFile
fn abiAndDynamicLinkerFromFile(
    file_reader: *Io.File.Reader,
    header: *const elf.Header,
    cpu: Target.Cpu,
    os: Target.Os,
    ld_info_list: []const LdInfo,
    query: Target.Query,
) AbiAndDynamicLinkerFromFileError!Target

File

lib/std/zig/system.zig:584

Code

fn abiAndDynamicLinkerFromFile(
    file_reader: *Io.File.Reader,
    header: *const elf.Header,
    cpu: Target.Cpu,
    os: Target.Os,
    ld_info_list: []const LdInfo,
    query: Target.Query,
) AbiAndDynamicLinkerFromFileError!Target {
    const io = file_reader.io;
    var result: Target = .{
        .cpu = cpu,
        .os = os,
        .abi = query.abi orelse Target.Abi.default(cpu.arch, os.tag),
        .ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
        .dynamic_linker = query.dynamic_linker orelse .none,
    };
    var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
    const look_for_ld = query.dynamic_linker == null;

    var got_dyn_section: bool = false;
    {
        var it = header.iterateProgramHeaders(file_reader);
        while (try it.next()) |phdr| switch (phdr.p_type) {
            elf.PT_INTERP => {
                got_dyn_section = true;

                if (look_for_ld) {
                    const p_filesz = phdr.p_filesz;
                    if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
                    const filesz: usize = @intCast(p_filesz);
                    try file_reader.seekTo(phdr.p_offset);
                    try file_reader.interface.readSliceAll(result.dynamic_linker.buffer[0..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.len = @intCast(len);

                    // 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 => {
                got_dyn_section = true;

                if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
                    var dyn_it = header.iterateDynamicSection(file_reader, phdr.p_offset, phdr.p_filesz);
                    while (try dyn_it.next()) |dyn| {
                        if (dyn.d_tag == elf.DT_RUNPATH) {
                            rpath_offset = dyn.d_val;
                            break;
                        }
                    }
                }
            },
            else => continue,
        };
    }

    if (!got_dyn_section) {
        return error.StaticElfFile;
    }

    if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
        const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx);
        try file_reader.seekTo(str_section_off);
        const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian);
        var strtab_buf: [4096]u8 = undefined;
        const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)];
        try file_reader.seekTo(shstr.sh_offset);
        try file_reader.interface.readSliceAll(shstrtab);
        const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: {
            var it = header.iterateSectionHeaders(file_reader);
            while (try it.next()) |shdr| {
                const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
                const sh_name = shstrtab[shdr.sh_name..end :0];
                if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
                    .offset = shdr.sh_offset,
                    .size = shdr.sh_size,
                };
            } else break :find_dyn_str 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;

                try file_reader.seekTo(rpoff_file);
                const rpath_list = try file_reader.interface.takeSentinel(0);
                if (rpath_list.len > rp_max_size) return error.StreamTooLong;

                var it = mem.tokenizeScalar(u8, rpath_list, ':');
                while (it.next()) |rpath| {
                    if (glibcVerFromRPath(io, 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(io, 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_buffer: [posix.PATH_MAX]u8 = undefined;
            const link_name = if (Io.Dir.readLinkAbsolute(io, dl_path, &link_buffer)) |n|
                link_buffer[0..n]
            else |err| switch (err) {
                error.NameTooLong => unreachable,
                error.BadPathName => unreachable, // Windows only
                error.UnsupportedReparsePointType => unreachable, // Windows only
                error.NetworkNotFound => unreachable, // Windows only
                error.AntivirusInterference => unreachable, // Windows only
                error.FileBusy => unreachable, // Windows only

                error.AccessDenied,
                error.PermissionDenied,
                error.FileNotFound,
                error.NotLink,
                error.NotDir,
                => break :glibc_ver,

                error.SystemResources,
                error.FileSystem,
                error.SymLinkLoop,
                error.Canceled,
                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: [posix.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(io, rpath)) |ver| {
            result.os.version_range.linux.glibc = ver;
            return result;
        } else |err| switch (err) {
            error.GLibCNotFound => {},
            else => |e| return e,
        }
    }

    return result;
}