Function abiAndDynamicLinkerFromFile [src]

Prototype

pub fn abiAndDynamicLinkerFromFile( file: fs.File, cpu: Target.Cpu, os: Target.Os, ld_info_list: []const LdInfo, query: Target.Query, ) AbiAndDynamicLinkerFromFileError!Target

Parameters

file: fs.Filecpu: Target.Cpuos: Target.Osld_info_list: []const LdInfoquery: Target.Query

Possible Errors

FileSystem
InvalidElfClass
InvalidElfEndian
InvalidElfFile
InvalidElfMagic
InvalidElfVersion
NameTooLong
ProcessFdQuotaExceeded
ProcessNotFound
SymLinkLoop
SystemFdQuotaExceeded
SystemResources
UnableToReadElfFile
Unexpected
UnexpectedEndOfFile

Source

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; 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 => 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 => 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 (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; }