From 8cf40f3445b6548836be3db2a8ef5317af6545ac Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 10 Sep 2020 22:24:27 -0700 Subject: [PATCH] stage2: loading glibc metadata --- src-self-hosted/Module.zig | 13 ++ src-self-hosted/glibc.zig | 231 ++++++++++++++++++++++++++++++ src-self-hosted/main.zig | 2 +- src-self-hosted/print_targets.zig | 43 ++---- 4 files changed, 258 insertions(+), 31 deletions(-) create mode 100644 src-self-hosted/glibc.zig diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 482a1d9a49..715979bf11 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1256,6 +1256,14 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { mod.c_object_table.putAssumeCapacityNoClobber(c_object, {}); } + // If we need to build glibc for the target, add work items for it. + if (mod.bin_file.options.link_libc and + mod.bin_file.options.libc_installation == null and + mod.bin_file.options.target.isGnuLibC()) + { + try mod.addBuildingGLibCWorkItems(); + } + return mod; } @@ -4495,3 +4503,8 @@ pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8) const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename }); return full_path; } + +fn addBuildingGLibCWorkItems(mod: *Module) !void { + // crti.o, crtn.o, start.os, abi-note.o, Scrt1.o, libc_nonshared.a + try mod.work_queue.ensureUnusedCapacity(6); +} diff --git a/src-self-hosted/glibc.zig b/src-self-hosted/glibc.zig new file mode 100644 index 0000000000..ea429a69a3 --- /dev/null +++ b/src-self-hosted/glibc.zig @@ -0,0 +1,231 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const target_util = @import("target.zig"); +const mem = std.mem; + +pub const Lib = struct { + name: []const u8, + sover: u8, +}; + +pub const Fn = struct { + name: []const u8, + lib: *const Lib, +}; + +pub const VerList = struct { + /// 7 is just the max number, we know statically it's big enough. + versions: [7]u8, + len: u8, +}; + +pub const ABI = struct { + all_versions: []const std.builtin.Version, + all_functions: []const Fn, + /// The value is a pointer to all_functions.len items and each item is an index into all_functions. + version_table: std.AutoHashMapUnmanaged(target_util.ArchOsAbi, [*]VerList), + arena_state: std.heap.ArenaAllocator.State, + + pub fn destroy(abi: *ABI, gpa: *Allocator) void { + abi.version_table.deinit(gpa); + abi.arena_state.promote(gpa).deinit(); // Frees the ABI memory too. + } +}; + +pub const libs = [_]Lib{ + .{ .name = "c", .sover = 6 }, + .{ .name = "m", .sover = 6 }, + .{ .name = "pthread", .sover = 0 }, + .{ .name = "dl", .sover = 2 }, + .{ .name = "rt", .sover = 1 }, + .{ .name = "ld", .sover = 2 }, + .{ .name = "util", .sover = 1 }, +}; + +pub const LoadMetaDataError = error{ + /// The files that ship with the Zig compiler were unable to be read, or otherwise had malformed data. + ZigInstallationCorrupt, + OutOfMemory, +}; + +/// This function will emit a log error when there is a problem with the zig installation and then return +/// `error.ZigInstallationCorrupt`. +pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError!*ABI { + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + errdefer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + var all_versions = std.ArrayListUnmanaged(std.builtin.Version){}; + var all_functions = std.ArrayListUnmanaged(Fn){}; + var version_table = std.AutoHashMapUnmanaged(target_util.ArchOsAbi, [*]VerList){}; + errdefer version_table.deinit(gpa); + + var glibc_dir = zig_lib_dir.openDir("libc" ++ std.fs.path.sep_str ++ "glibc", .{}) catch |err| { + std.log.err("unable to open glibc dir: {}", .{@errorName(err)}); + return error.ZigInstallationCorrupt; + }; + defer glibc_dir.close(); + + const max_txt_size = 500 * 1024; // Bigger than this and something is definitely borked. + const vers_txt_contents = glibc_dir.readFileAlloc(gpa, "vers.txt", max_txt_size) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + std.log.err("unable to read vers.txt: {}", .{@errorName(err)}); + return error.ZigInstallationCorrupt; + }, + }; + defer gpa.free(vers_txt_contents); + + const fns_txt_contents = glibc_dir.readFileAlloc(gpa, "fns.txt", max_txt_size) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + std.log.err("unable to read fns.txt: {}", .{@errorName(err)}); + return error.ZigInstallationCorrupt; + }, + }; + defer gpa.free(fns_txt_contents); + + const abi_txt_contents = glibc_dir.readFileAlloc(gpa, "abi.txt", max_txt_size) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + std.log.err("unable to read abi.txt: {}", .{@errorName(err)}); + return error.ZigInstallationCorrupt; + }, + }; + defer gpa.free(abi_txt_contents); + + { + var it = mem.tokenize(vers_txt_contents, "\r\n"); + var line_i: usize = 1; + while (it.next()) |line| : (line_i += 1) { + const prefix = "GLIBC_"; + if (!mem.startsWith(u8, line, prefix)) { + std.log.err("vers.txt:{}: expected 'GLIBC_' prefix", .{line_i}); + return error.ZigInstallationCorrupt; + } + const adjusted_line = line[prefix.len..]; + const ver = std.builtin.Version.parse(adjusted_line) catch |err| { + std.log.err("vers.txt:{}: unable to parse glibc version '{}': {}", .{ line_i, line, @errorName(err) }); + return error.ZigInstallationCorrupt; + }; + try all_versions.append(arena, ver); + } + } + { + var file_it = mem.tokenize(fns_txt_contents, "\r\n"); + var line_i: usize = 1; + while (file_it.next()) |line| : (line_i += 1) { + var line_it = mem.tokenize(line, " "); + const fn_name = line_it.next() orelse { + std.log.err("fns.txt:{}: expected function name", .{line_i}); + return error.ZigInstallationCorrupt; + }; + const lib_name = line_it.next() orelse { + std.log.err("fns.txt:{}: expected library name", .{line_i}); + return error.ZigInstallationCorrupt; + }; + const lib = findLib(lib_name) orelse { + std.log.err("fns.txt:{}: unknown library name: {}", .{ line_i, lib_name }); + return error.ZigInstallationCorrupt; + }; + try all_functions.append(arena, .{ + .name = fn_name, + .lib = lib, + }); + } + } + { + var file_it = mem.split(abi_txt_contents, "\n"); + var line_i: usize = 0; + while (true) { + const ver_list_base: []VerList = blk: { + const line = file_it.next() orelse break; + if (line.len == 0) break; + line_i += 1; + const ver_list_base = try arena.alloc(VerList, all_functions.items.len); + var line_it = mem.tokenize(line, " "); + while (line_it.next()) |target_string| { + var component_it = mem.tokenize(target_string, "-"); + const arch_name = component_it.next() orelse { + std.log.err("abi.txt:{}: expected arch name", .{line_i}); + return error.ZigInstallationCorrupt; + }; + const os_name = component_it.next() orelse { + std.log.err("abi.txt:{}: expected OS name", .{line_i}); + return error.ZigInstallationCorrupt; + }; + const abi_name = component_it.next() orelse { + std.log.err("abi.txt:{}: expected ABI name", .{line_i}); + return error.ZigInstallationCorrupt; + }; + const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse { + std.log.err("abi.txt:{}: unrecognized arch: '{}'", .{ line_i, arch_name }); + return error.ZigInstallationCorrupt; + }; + if (!mem.eql(u8, os_name, "linux")) { + std.log.err("abi.txt:{}: expected OS 'linux', found '{}'", .{ line_i, os_name }); + return error.ZigInstallationCorrupt; + } + const abi_tag = std.meta.stringToEnum(std.Target.Abi, abi_name) orelse { + std.log.err("abi.txt:{}: unrecognized ABI: '{}'", .{ line_i, abi_name }); + return error.ZigInstallationCorrupt; + }; + + const triple = target_util.ArchOsAbi{ + .arch = arch_tag, + .os = .linux, + .abi = abi_tag, + }; + try version_table.put(arena, triple, ver_list_base.ptr); + } + break :blk ver_list_base; + }; + for (ver_list_base) |*ver_list| { + const line = file_it.next() orelse { + std.log.err("abi.txt:{}: missing version number line", .{line_i}); + return error.ZigInstallationCorrupt; + }; + line_i += 1; + + ver_list.* = .{ + .versions = undefined, + .len = 0, + }; + var line_it = mem.tokenize(line, " "); + while (line_it.next()) |version_index_string| { + if (ver_list.len >= ver_list.versions.len) { + // If this happens with legit data, increase the array len in the type. + std.log.err("abi.txt:{}: too many versions", .{line_i}); + return error.ZigInstallationCorrupt; + } + const version_index = std.fmt.parseInt(u8, version_index_string, 10) catch |err| { + // If this happens with legit data, increase the size of the integer type in the struct. + std.log.err("abi.txt:{}: unable to parse version: {}", .{ line_i, @errorName(err) }); + return error.ZigInstallationCorrupt; + }; + + ver_list.versions[ver_list.len] = version_index; + ver_list.len += 1; + } + } + } + } + + const abi = try arena.create(ABI); + abi.* = .{ + .all_versions = all_versions.items, + .all_functions = all_functions.items, + .version_table = version_table, + .arena_state = arena_allocator.state, + }; + return abi; +} + +fn findLib(name: []const u8) ?*const Lib { + for (libs) |*lib| { + if (mem.eql(u8, lib.name, name)) { + return lib; + } + } + return null; +} diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 1de025769d..bc3e5a8e6c 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -16,7 +16,7 @@ const warn = std.log.warn; const introspect = @import("introspect.zig"); const LibCInstallation = @import("libc_installation.zig").LibCInstallation; -fn fatal(comptime format: []const u8, args: anytype) noreturn { +pub fn fatal(comptime format: []const u8, args: anytype) noreturn { std.log.emerg(format, args); process.exit(1); } diff --git a/src-self-hosted/print_targets.zig b/src-self-hosted/print_targets.zig index 33a513efd7..e6ed33b80c 100644 --- a/src-self-hosted/print_targets.zig +++ b/src-self-hosted/print_targets.zig @@ -6,8 +6,9 @@ const Allocator = mem.Allocator; const Target = std.Target; const target = @import("target.zig"); const assert = std.debug.assert; - +const glibc = @import("glibc.zig"); const introspect = @import("introspect.zig"); +const fatal = @import("main.zig").fatal; pub fn cmdTargets( allocator: *Allocator, @@ -16,33 +17,16 @@ pub fn cmdTargets( stdout: anytype, native_target: Target, ) !void { - const available_glibcs = blk: { - const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch |err| { - std.debug.print("unable to find zig installation directory: {}\n", .{@errorName(err)}); - std.process.exit(1); - }; - defer allocator.free(zig_lib_dir); - - var dir = try std.fs.cwd().openDir(zig_lib_dir, .{}); - defer dir.close(); - - const vers_txt = try dir.readFileAlloc(allocator, "libc" ++ std.fs.path.sep_str ++ "glibc" ++ std.fs.path.sep_str ++ "vers.txt", 10 * 1024); - defer allocator.free(vers_txt); - - var list = std.ArrayList(std.builtin.Version).init(allocator); - defer list.deinit(); - - var it = mem.tokenize(vers_txt, "\r\n"); - while (it.next()) |line| { - const prefix = "GLIBC_"; - assert(mem.startsWith(u8, line, prefix)); - const adjusted_line = line[prefix.len..]; - const ver = try std.builtin.Version.parse(adjusted_line); - try list.append(ver); - } - break :blk list.toOwnedSlice(); + const zig_lib_dir_path = introspect.resolveZigLibDir(allocator) catch |err| { + fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); }; - defer allocator.free(available_glibcs); + defer allocator.free(zig_lib_dir_path); + + var zig_lib_dir = try fs.cwd().openDir(zig_lib_dir_path, .{}); + defer zig_lib_dir.close(); + + const glibc_abi = try glibc.loadMetaData(allocator, zig_lib_dir); + errdefer glibc_abi.destroy(allocator); var bos = io.bufferedOutStream(stdout); const bos_stream = bos.outStream(); @@ -90,10 +74,10 @@ pub fn cmdTargets( try jws.objectField("glibc"); try jws.beginArray(); - for (available_glibcs) |glibc| { + for (glibc_abi.all_versions) |ver| { try jws.arrayElem(); - const tmp = try std.fmt.allocPrint(allocator, "{}", .{glibc}); + const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver}); defer allocator.free(tmp); try jws.emitString(tmp); } @@ -170,7 +154,6 @@ pub fn cmdTargets( try jws.emitString(@tagName(native_target.os.tag)); try jws.objectField("abi"); try jws.emitString(@tagName(native_target.abi)); - // TODO implement native glibc version detection in self-hosted try jws.endObject(); try jws.endObject();