DoxigAlpha

abiAndDynamicLinkerFromFile

Function parameters

Parameters

#
file:fs.File
cpu:Target.Cpu
os:Target.Os
ld_info_list:[]const LdInfo
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

#
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 preadAtLeast(file, &hdr_buf, 0, hdr_buf.len);
    const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf);
    const hdr64: *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.tag),
        .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;
    var got_dyn_section: bool = false;

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

                    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: usize = @intCast(p_filesz);
                        _ = try preadAtLeast(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.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_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 preadAtLeast(
                                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 (!got_dyn_section) {
        return error.StaticElfFile;
    }

    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 preadAtLeast(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 preadAtLeast(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 preadAtLeast(
                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 preadAtLeast(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: [posix.PATH_MAX]u8 = undefined;
            const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) {
                error.NameTooLong => unreachable,
                error.InvalidUtf8 => unreachable, // WASI only
                error.InvalidWtf8 => unreachable, // Windows only
                error.BadPathName => unreachable, // Windows only
                error.UnsupportedReparsePointType => unreachable, // Windows only
                error.NetworkNotFound => unreachable, // Windows only

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

    return result;
}