From 7ccf7807b3f12428adc5d9ca0ede4e3f4ec6dbbc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 7 Jul 2019 17:06:09 -0400 Subject: [PATCH] ability to target any glibc version --- CMakeLists.txt | 4 + src/all_types.hpp | 4 + src/codegen.cpp | 36 +++- src/codegen.hpp | 3 + src/compiler.cpp | 26 +++ src/compiler.hpp | 1 + src/glibc.cpp | 410 ++++++++++++++++++++++++++++++++++++++++++++++ src/glibc.hpp | 53 ++++++ src/link.cpp | 79 ++++----- src/main.cpp | 56 ++++++- src/target.cpp | 36 ++++ src/target.hpp | 9 + src/util.cpp | 26 +++ src/util.hpp | 1 + 14 files changed, 691 insertions(+), 53 deletions(-) create mode 100644 src/glibc.cpp create mode 100644 src/glibc.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a174e2172..907bcf876 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -421,6 +421,7 @@ set(ZIG_MAIN_SRC "${CMAKE_SOURCE_DIR}/src/main.cpp") set(ZIG0_SHIM_SRC "${CMAKE_SOURCE_DIR}/src/userland.cpp") set(ZIG_SOURCES + "${CMAKE_SOURCE_DIR}/src/glibc.cpp" "${CMAKE_SOURCE_DIR}/src/analyze.cpp" "${CMAKE_SOURCE_DIR}/src/ast_render.cpp" "${CMAKE_SOURCE_DIR}/src/bigfloat.cpp" @@ -2667,6 +2668,9 @@ set(ZIG_MUSL_SRC_FILES ) set(ZIG_LIBC_FILES + "glibc/abi.txt" + "glibc/fns.txt" + "glibc/vers.txt" "glibc/bits/byteswap.h" "glibc/bits/endian.h" "glibc/bits/floatn-common.h" diff --git a/src/all_types.hpp b/src/all_types.hpp index 8e236c459..b49ed9820 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1921,6 +1921,7 @@ struct CodeGen { Buf *zig_lib_dir; Buf *zig_std_dir; Buf *dynamic_linker_path; + Buf *version_script_path; const char **llvm_argv; size_t llvm_argv_len; @@ -3788,6 +3789,9 @@ static const size_t stack_trace_ptr_count = 32; #define NAMESPACE_SEP_CHAR '.' #define NAMESPACE_SEP_STR "." +#define CACHE_OUT_SUBDIR "o" +#define CACHE_HASH_SUBDIR "h" + enum FloatMode { FloatModeStrict, FloatModeOptimized, diff --git a/src/codegen.cpp b/src/codegen.cpp index 47e5f542e..57be8beed 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -24,9 +24,6 @@ #include #include -#define CACHE_OUT_SUBDIR "o" -#define CACHE_HASH_SUBDIR "h" - static void init_darwin_native(CodeGen *g) { char *osx_target = getenv("MACOSX_DEPLOYMENT_TARGET"); char *ios_target = getenv("IPHONEOS_DEPLOYMENT_TARGET"); @@ -9502,6 +9499,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { cache_buf(ch, &g->libc->kernel32_lib_dir); } cache_buf_opt(ch, g->dynamic_linker_path); + cache_buf_opt(ch, g->version_script_path); // gen_c_objects appends objects to g->link_objects which we want to include in the hash gen_c_objects(g); @@ -9695,3 +9693,35 @@ ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const c } return pkg; } + +CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type, + ZigLibCInstallation *libc) +{ + CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type, + parent_gen->build_mode, parent_gen->zig_lib_dir, parent_gen->zig_std_dir, libc, get_stage1_cache_path()); + child_gen->disable_gen_h = true; + child_gen->want_stack_check = WantStackCheckDisabled; + child_gen->verbose_tokenize = parent_gen->verbose_tokenize; + child_gen->verbose_ast = parent_gen->verbose_ast; + child_gen->verbose_link = parent_gen->verbose_link; + child_gen->verbose_ir = parent_gen->verbose_ir; + child_gen->verbose_llvm_ir = parent_gen->verbose_llvm_ir; + child_gen->verbose_cimport = parent_gen->verbose_cimport; + child_gen->verbose_cc = parent_gen->verbose_cc; + child_gen->llvm_argv = parent_gen->llvm_argv; + child_gen->dynamic_linker_path = parent_gen->dynamic_linker_path; + + codegen_set_strip(child_gen, parent_gen->strip_debug_symbols); + child_gen->want_pic = parent_gen->have_pic ? WantPICEnabled : WantPICDisabled; + child_gen->valgrind_support = ValgrindSupportDisabled; + + codegen_set_errmsg_color(child_gen, parent_gen->err_color); + + codegen_set_mmacosx_version_min(child_gen, parent_gen->mmacosx_version_min); + codegen_set_mios_version_min(child_gen, parent_gen->mios_version_min); + + child_gen->enable_cache = true; + + return child_gen; +} + diff --git a/src/codegen.hpp b/src/codegen.hpp index 9a340d720..d7cabe879 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -20,6 +20,9 @@ CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget OutType out_type, BuildMode build_mode, Buf *zig_lib_dir, Buf *override_std_dir, ZigLibCInstallation *libc, Buf *cache_dir); +CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type, + ZigLibCInstallation *libc); + void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len); void codegen_set_llvm_argv(CodeGen *codegen, const char **args, size_t len); void codegen_set_is_test(CodeGen *codegen, bool is_test); diff --git a/src/compiler.cpp b/src/compiler.cpp index 8bfe87bfc..5d401f185 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -9,9 +9,13 @@ static Buf saved_stage1_path = BUF_INIT; static Buf saved_lib_dir = BUF_INIT; static Buf saved_special_dir = BUF_INIT; static Buf saved_std_dir = BUF_INIT; + static Buf saved_dynamic_linker_path = BUF_INIT; static bool searched_for_dyn_linker = false; +static Buf saved_libc_path = BUF_INIT; +static bool searched_for_libc = false; + Buf *get_stage1_cache_path(void) { if (saved_stage1_path.list.length != 0) { return &saved_stage1_path; @@ -36,6 +40,28 @@ static void detect_dynamic_linker(Buf *lib_path) { #endif } +const Buf *get_self_libc_path(void) { + for (;;) { + if (saved_libc_path.list.length != 0) { + return &saved_libc_path; + } + if (searched_for_libc) + return nullptr; + ZigList lib_paths = {}; + Error err; + if ((err = os_self_exe_shared_libs(lib_paths))) + return nullptr; + for (size_t i = 0; i < lib_paths.length; i += 1) { + Buf *lib_path = lib_paths.at(i); + if (buf_ends_with_str(lib_path, "libc.so.6")) { + buf_init_from_buf(&saved_libc_path, lib_path); + return &saved_libc_path; + } + } + searched_for_libc = true; + } +} + Buf *get_self_dynamic_linker_path(void) { for (;;) { if (saved_dynamic_linker_path.list.length != 0) { diff --git a/src/compiler.hpp b/src/compiler.hpp index 4d682ba2f..62991570f 100644 --- a/src/compiler.hpp +++ b/src/compiler.hpp @@ -14,6 +14,7 @@ Buf *get_stage1_cache_path(void); Error get_compiler_id(Buf **result); Buf *get_self_dynamic_linker_path(void); +Buf *get_self_libc_path(void); Buf *get_zig_lib_dir(void); Buf *get_zig_special_dir(Buf *zig_lib_dir); diff --git a/src/glibc.cpp b/src/glibc.cpp new file mode 100644 index 000000000..a226b23a9 --- /dev/null +++ b/src/glibc.cpp @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2019 Andrew Kelley + * + * This file is part of zig, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "glibc.hpp" +#include "compiler.hpp" +#include "cache_hash.hpp" +#include "codegen.hpp" + +static const ZigGLibCLib glibc_libs[] = { + {"c", 6}, + {"m", 6}, + {"pthread", 0}, + {"dl", 2}, + {"rt", 1}, +}; + +Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbose) { + Error err; + + ZigGLibCAbi *glibc_abi = allocate(1); + glibc_abi->vers_txt_path = buf_sprintf("%s" OS_SEP "libc" OS_SEP "glibc" OS_SEP "vers.txt", buf_ptr(zig_lib_dir)); + glibc_abi->fns_txt_path = buf_sprintf("%s" OS_SEP "libc" OS_SEP "glibc" OS_SEP "fns.txt", buf_ptr(zig_lib_dir)); + glibc_abi->abi_txt_path = buf_sprintf("%s" OS_SEP "libc" OS_SEP "glibc" OS_SEP "abi.txt", buf_ptr(zig_lib_dir)); + glibc_abi->version_table.init(16); + + Buf *vers_txt_contents = buf_alloc(); + if ((err = os_fetch_file_path(glibc_abi->vers_txt_path, vers_txt_contents))) { + if (verbose) { + fprintf(stderr, "Unable to read %s: %s\n", buf_ptr(glibc_abi->vers_txt_path), err_str(err)); + } + return err; + } + Buf *fns_txt_contents = buf_alloc(); + if ((err = os_fetch_file_path(glibc_abi->fns_txt_path, fns_txt_contents))) { + if (verbose) { + fprintf(stderr, "Unable to read %s: %s\n", buf_ptr(glibc_abi->fns_txt_path), err_str(err)); + } + return err; + } + Buf *abi_txt_contents = buf_alloc(); + if ((err = os_fetch_file_path(glibc_abi->abi_txt_path, abi_txt_contents))) { + if (verbose) { + fprintf(stderr, "Unable to read %s: %s\n", buf_ptr(glibc_abi->abi_txt_path), err_str(err)); + } + return err; + } + + { + SplitIterator it = memSplit(buf_to_slice(vers_txt_contents), str("\n")); + for (;;) { + Optional> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) break; + Buf *ver_buf = buf_create_from_slice(opt_component.value); + ZigGLibCVersion *this_ver = glibc_abi->all_versions.add_one(); + if ((err = target_parse_glibc_version(this_ver, buf_ptr(ver_buf)))) { + if (verbose) { + fprintf(stderr, "Unable to parse glibc version '%s': %s\n", buf_ptr(ver_buf), err_str(err)); + } + return err; + } + } + } + { + SplitIterator it = memSplit(buf_to_slice(fns_txt_contents), str("\n")); + for (;;) { + Optional> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) break; + SplitIterator line_it = memSplit(opt_component.value, str(" ")); + Optional> opt_fn_name = SplitIterator_next(&line_it); + if (!opt_fn_name.is_some) { + if (verbose) { + fprintf(stderr, "%s: Expected function name\n", buf_ptr(glibc_abi->fns_txt_path)); + } + return ErrorInvalidFormat; + } + Optional> opt_lib_name = SplitIterator_next(&line_it); + if (!opt_lib_name.is_some) { + if (verbose) { + fprintf(stderr, "%s: Expected lib name\n", buf_ptr(glibc_abi->fns_txt_path)); + } + return ErrorInvalidFormat; + } + + Buf *this_fn_name = buf_create_from_slice(opt_fn_name.value); + Buf *this_lib_name = buf_create_from_slice(opt_lib_name.value); + glibc_abi->all_functions.append({ this_fn_name, glibc_lib_find(buf_ptr(this_lib_name)) }); + } + } + { + SplitIterator it = memSplit(buf_to_slice(abi_txt_contents), str("\n")); + ZigGLibCVerList *ver_list_base = nullptr; + for (;;) { + if (ver_list_base == nullptr) { + Optional> opt_line = SplitIterator_next_separate(&it); + if (!opt_line.is_some) break; + + ver_list_base = allocate(glibc_abi->all_functions.length); + ZigTarget *target = allocate(1); + SplitIterator line_it = memSplit(opt_line.value, str(" ")); + for (;;) { + Optional> opt_target = SplitIterator_next(&line_it); + if (!opt_target.is_some) break; + + SplitIterator component_it = memSplit(opt_target.value, str("-")); + Optional> opt_arch = SplitIterator_next(&component_it); + assert(opt_arch.is_some); + Optional> opt_os = SplitIterator_next(&component_it); + assert(opt_os.is_some); // it's always "linux" so we ignore it + Optional> opt_abi = SplitIterator_next(&component_it); + assert(opt_abi.is_some); + + + err = target_parse_archsub(&target->arch, &target->sub_arch, + (char*)opt_arch.value.ptr, opt_arch.value.len); + // there's no sub arch so we might get an error, but the arch is still populated + assert(err == ErrorNone || err == ErrorUnknownArchitecture); + + target->os = OsLinux; + + err = target_parse_abi(&target->abi, (char*)opt_abi.value.ptr, opt_abi.value.len); + assert(err == ErrorNone); + + glibc_abi->version_table.put(target, ver_list_base); + } + continue; + } + for (size_t fn_i = 0; fn_i < glibc_abi->all_functions.length; fn_i += 1) { + ZigGLibCVerList *ver_list = &ver_list_base[fn_i]; + Optional> opt_line = SplitIterator_next_separate(&it); + assert(opt_line.is_some); + + SplitIterator line_it = memSplit(opt_line.value, str(" ")); + for (;;) { + Optional> opt_ver = SplitIterator_next(&line_it); + if (!opt_ver.is_some) break; + assert(ver_list->len < 8); // increase the array len in the type + + unsigned long ver_index = strtoul(buf_ptr(buf_create_from_slice(opt_ver.value)), nullptr, 10); + assert(ver_index < 255); // use a bigger integer in the type + ver_list->versions[ver_list->len] = ver_index; + ver_list->len += 1; + } + } + ver_list_base = nullptr; + } + } + + *out_result = glibc_abi; + return ErrorNone; +} + +Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, const ZigTarget *target, + Buf **out_dir, bool verbose) +{ + Error err; + + Buf *cache_dir = get_stage1_cache_path(); + CacheHash *cache_hash = allocate(1); + Buf *manifest_dir = buf_sprintf("%s" OS_SEP CACHE_HASH_SUBDIR, buf_ptr(cache_dir)); + cache_init(cache_hash, manifest_dir); + + Buf *compiler_id; + if ((err = get_compiler_id(&compiler_id))) { + if (verbose) { + fprintf(stderr, "unable to get compiler id: %s\n", err_str(err)); + } + return err; + } + cache_buf(cache_hash, compiler_id); + cache_int(cache_hash, target->arch); + cache_int(cache_hash, target->abi); + cache_int(cache_hash, target->glibc_version->major); + cache_int(cache_hash, target->glibc_version->minor); + cache_int(cache_hash, target->glibc_version->patch); + + Buf digest = BUF_INIT; + buf_resize(&digest, 0); + if ((err = cache_hit(cache_hash, &digest))) { + // Treat an invalid format error as a cache miss. + if (err != ErrorInvalidFormat) + return err; + } + // We should always get a cache hit because there are no + // files in the input hash. + assert(buf_len(&digest) != 0); + + Buf *dummy_dir = buf_alloc(); + os_path_join(manifest_dir, &digest, dummy_dir); + + if ((err = os_make_path(dummy_dir))) + return err; + + Buf *test_if_exists_path = buf_alloc(); + os_path_join(dummy_dir, buf_create_from_str("ok"), test_if_exists_path); + + bool hit; + if ((err = os_file_exists(test_if_exists_path, &hit))) + return err; + + // TODO this is for debugging + fprintf(stderr, "dummy so dir: %s\n", buf_ptr(dummy_dir)); + + if (hit) { + *out_dir = dummy_dir; + return ErrorNone; + } + + + ZigGLibCVerList *ver_list_base = glibc_abi->version_table.get(target); + + uint8_t target_ver_index = 0; + for (;target_ver_index < glibc_abi->all_versions.length; target_ver_index += 1) { + const ZigGLibCVersion *this_ver = &glibc_abi->all_versions.at(target_ver_index); + if (this_ver->major == target->glibc_version->major && + this_ver->minor == target->glibc_version->minor && + this_ver->patch == target->glibc_version->patch) + { + break; + } + } + if (target_ver_index == glibc_abi->all_versions.length) { + if (verbose) { + fprintf(stderr, "Unrecognized glibc version: %d.%d.%d\n", + target->glibc_version->major, + target->glibc_version->minor, + target->glibc_version->patch); + } + return ErrorUnknownABI; + } + + Buf *map_file_path = buf_sprintf("%s" OS_SEP "all.map", buf_ptr(dummy_dir)); + Buf *map_contents = buf_alloc(); + + for (uint8_t ver_i = 0; ver_i < glibc_abi->all_versions.length; ver_i += 1) { + const ZigGLibCVersion *ver = &glibc_abi->all_versions.at(ver_i); + if (ver->patch == 0) { + buf_appendf(map_contents, "GLIBC_%d.%d { };\n", ver->major, ver->minor); + } else { + buf_appendf(map_contents, "GLIBC_%d.%d.%d { };\n", ver->major, ver->minor, ver->patch); + } + } + + if ((err = os_write_file(map_file_path, map_contents))) { + if (verbose) { + fprintf(stderr, "unable to write %s: %s", buf_ptr(map_file_path), err_str(err)); + } + return err; + } + + + for (size_t lib_i = 0; lib_i < array_length(glibc_libs); lib_i += 1) { + const ZigGLibCLib *lib = &glibc_libs[lib_i]; + Buf *zig_file_path = buf_sprintf("%s" OS_SEP "%s.zig", buf_ptr(dummy_dir), lib->name); + Buf *zig_body = buf_alloc(); + Buf *zig_footer = buf_alloc(); + + buf_appendf(zig_body, "comptime {\n"); + buf_appendf(zig_body, " asm (\n"); + + for (size_t fn_i = 0; fn_i < glibc_abi->all_functions.length; fn_i += 1) { + const ZigGLibCFn *libc_fn = &glibc_abi->all_functions.at(fn_i); + if (libc_fn->lib != lib) continue; + ZigGLibCVerList *ver_list = &ver_list_base[fn_i]; + // Pick the default symbol version: + // - If there are no versions, don't emit it + // - Take the greatest one <= than the target one + // - If none of them is <= than the + // specified one don't pick any default version + if (ver_list->len == 0) continue; + uint8_t chosen_def_ver_index = 255; + for (uint8_t ver_i = 0; ver_i < ver_list->len; ver_i += 1) { + uint8_t ver_index = ver_list->versions[ver_i]; + if ((chosen_def_ver_index == 255 || ver_index > chosen_def_ver_index) && + target_ver_index >= ver_index) + { + chosen_def_ver_index = ver_index; + } + } + for (uint8_t ver_i = 0; ver_i < ver_list->len; ver_i += 1) { + uint8_t ver_index = ver_list->versions[ver_i]; + + Buf *stub_name; + const ZigGLibCVersion *ver = &glibc_abi->all_versions.at(ver_index); + const char *sym_name = buf_ptr(libc_fn->name); + if (ver->patch == 0) { + stub_name = buf_sprintf("%s_%d_%d", sym_name, ver->major, ver->minor); + } else { + stub_name = buf_sprintf("%s_%d_%d_%d", sym_name, ver->major, ver->minor, ver->patch); + } + + buf_appendf(zig_footer, "export fn %s() void {}\n", buf_ptr(stub_name)); + + // Default symbol version definition vs normal symbol version definition + const char *at_sign_str = (chosen_def_ver_index != 255 && + ver_index == chosen_def_ver_index) ? "@@" : "@"; + if (ver->patch == 0) { + buf_appendf(zig_body, " \\\\ .symver %s, %s%sGLIBC_%d.%d\n", + buf_ptr(stub_name), sym_name, at_sign_str, ver->major, ver->minor); + } else { + buf_appendf(zig_body, " \\\\ .symver %s, %s%sGLIBC_%d.%d.%d\n", + buf_ptr(stub_name), sym_name, at_sign_str, ver->major, ver->minor, ver->patch); + } + // Hide the stub to keep the symbol table clean + buf_appendf(zig_body, " \\\\ .hidden %s\n", buf_ptr(stub_name)); + } + } + + buf_appendf(zig_body, " );\n"); + buf_appendf(zig_body, "}\n"); + buf_append_buf(zig_body, zig_footer); + + if ((err = os_write_file(zig_file_path, zig_body))) { + if (verbose) { + fprintf(stderr, "unable to write %s: %s", buf_ptr(zig_file_path), err_str(err)); + } + return err; + } + + CodeGen *child_gen = create_child_codegen(g, zig_file_path, OutTypeLib, nullptr); + codegen_set_out_name(child_gen, buf_create_from_str(lib->name)); + codegen_set_lib_version(child_gen, lib->sover, 0, 0); + child_gen->is_dynamic = true; + child_gen->is_dummy_so = true; + child_gen->version_script_path = map_file_path; + child_gen->enable_cache = false; + child_gen->output_dir = dummy_dir; + codegen_build_and_link(child_gen); + } + + if ((err = os_write_file(test_if_exists_path, buf_alloc()))) { + if (verbose) { + fprintf(stderr, "unable to write %s: %s", buf_ptr(test_if_exists_path), err_str(err)); + } + return err; + } + *out_dir = dummy_dir; + return ErrorNone; +} + +uint32_t hash_glibc_target(const ZigTarget *x) { + return x->arch * 3250106448 + + x->os * 542534372 + + x->abi * 59162639; +} + +bool eql_glibc_target(const ZigTarget *a, const ZigTarget *b) { + return a->arch == b->arch && + a->os == b->os && + a->abi == b->abi; +} + +#ifdef ZIG_OS_LINUX +#include +#include +Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver) { + Buf *self_libc_path = get_self_libc_path(); + if (self_libc_path == nullptr) { + // TODO There is still more we could do to detect the native glibc version. For example, + // we could look at the ELF file of `/usr/bin/env`, find `libc.so.6`, and then `readlink` + // to find out the glibc version. This is relevant for the static zig builds distributed + // on the download page, since the above detection based on zig's own dynamic linking + // will not work. + + return ErrorUnknownABI; + } + Buf *link_name = buf_alloc(); + buf_resize(link_name, PATH_MAX); + ssize_t amt = readlink(buf_ptr(self_libc_path), buf_ptr(link_name), buf_len(link_name)); + if (amt == -1) { + return ErrorUnknownABI; + } + buf_resize(link_name, amt); + if (!buf_starts_with_str(link_name, "libc-") || !buf_ends_with_str(link_name, ".so")) { + return ErrorUnknownABI; + } + // example: "libc-2.3.4.so" + // example: "libc-2.27.so" + buf_resize(link_name, buf_len(link_name) - 3); // chop off ".so" + glibc_ver->major = 2; + glibc_ver->minor = 0; + glibc_ver->patch = 0; + return target_parse_glibc_version(glibc_ver, buf_ptr(link_name) + 5); +} +#else +Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver) { + return ErrorUnknownABI; +} +#endif + +size_t glibc_lib_count(void) { + return array_length(glibc_libs); +} + +const ZigGLibCLib *glibc_lib_enum(size_t index) { + assert(index < array_length(glibc_libs)); + return &glibc_libs[index]; +} + +const ZigGLibCLib *glibc_lib_find(const char *name) { + for (size_t i = 0; i < array_length(glibc_libs); i += 1) { + if (strcmp(glibc_libs[i].name, name) == 0) { + return &glibc_libs[i]; + } + } + return nullptr; +} diff --git a/src/glibc.hpp b/src/glibc.hpp new file mode 100644 index 000000000..50796197d --- /dev/null +++ b/src/glibc.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Andrew Kelley + * + * This file is part of zig, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef ZIG_GLIBC_HPP +#define ZIG_GLIBC_HPP + +#include "all_types.hpp" + +struct ZigGLibCLib { + const char *name; + uint8_t sover; +}; + +struct ZigGLibCFn { + Buf *name; + const ZigGLibCLib *lib; +}; + +struct ZigGLibCVerList { + uint8_t versions[8]; // 8 is just the max number, we know statically it's big enough + uint8_t len; +}; + +uint32_t hash_glibc_target(const ZigTarget *x); +bool eql_glibc_target(const ZigTarget *a, const ZigTarget *b); + +struct ZigGLibCAbi { + Buf *abi_txt_path; + Buf *vers_txt_path; + Buf *fns_txt_path; + ZigList all_versions; + ZigList all_functions; + // The value is a pointer to all_functions.length items and each item is an index + // into all_functions. + HashMap version_table; +}; + +Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbose); +Error glibc_build_dummies_and_maps(CodeGen *codegen, const ZigGLibCAbi *glibc_abi, const ZigTarget *target, + Buf **out_dir, bool verbose); + +// returns ErrorUnknownABI when glibc is not the native libc +Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver); + +size_t glibc_lib_count(void); +const ZigGLibCLib *glibc_lib_enum(size_t index); +const ZigGLibCLib *glibc_lib_find(const char *name); + +#endif diff --git a/src/link.cpp b/src/link.cpp index 401f47776..7b41f3b55 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -11,6 +11,7 @@ #include "analyze.hpp" #include "compiler.hpp" #include "install_files.h" +#include "glibc.hpp" struct LinkJob { CodeGen *codegen; @@ -19,37 +20,6 @@ struct LinkJob { HashMap rpath_table; }; -static CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type, - ZigLibCInstallation *libc) -{ - CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type, - parent_gen->build_mode, parent_gen->zig_lib_dir, parent_gen->zig_std_dir, libc, get_stage1_cache_path()); - child_gen->disable_gen_h = true; - child_gen->want_stack_check = WantStackCheckDisabled; - child_gen->verbose_tokenize = parent_gen->verbose_tokenize; - child_gen->verbose_ast = parent_gen->verbose_ast; - child_gen->verbose_link = parent_gen->verbose_link; - child_gen->verbose_ir = parent_gen->verbose_ir; - child_gen->verbose_llvm_ir = parent_gen->verbose_llvm_ir; - child_gen->verbose_cimport = parent_gen->verbose_cimport; - child_gen->verbose_cc = parent_gen->verbose_cc; - child_gen->llvm_argv = parent_gen->llvm_argv; - child_gen->dynamic_linker_path = parent_gen->dynamic_linker_path; - - codegen_set_strip(child_gen, parent_gen->strip_debug_symbols); - child_gen->want_pic = parent_gen->have_pic ? WantPICEnabled : WantPICDisabled; - child_gen->valgrind_support = ValgrindSupportDisabled; - - codegen_set_errmsg_color(child_gen, parent_gen->err_color); - - codegen_set_mmacosx_version_min(child_gen, parent_gen->mmacosx_version_min); - codegen_set_mios_version_min(child_gen, parent_gen->mios_version_min); - - child_gen->enable_cache = true; - - return child_gen; -} - static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFile *c_file) { CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr); codegen_set_out_name(child_gen, buf_create_from_str(name)); @@ -76,18 +46,6 @@ static const char *path_from_libunwind(CodeGen *g, const char *subpath) { return path_from_zig_lib(g, "libunwind", subpath); } -static const char *build_dummy_so(CodeGen *parent, const char *name, size_t major_version) { - Buf *glibc_dummy_root_src = buf_sprintf("%s" OS_SEP "libc" OS_SEP "dummy" OS_SEP "%s.zig", - buf_ptr(parent->zig_lib_dir), name); - CodeGen *child_gen = create_child_codegen(parent, glibc_dummy_root_src, OutTypeLib, nullptr); - codegen_set_out_name(child_gen, buf_create_from_str(name)); - codegen_set_lib_version(child_gen, major_version, 0, 0); - child_gen->is_dynamic = true; - child_gen->is_dummy_so = true; - codegen_build_and_link(child_gen); - return buf_ptr(&child_gen->output_file_path); -} - static const char *build_libunwind(CodeGen *parent) { CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr); codegen_set_out_name(child_gen, buf_create_from_str("unwind")); @@ -892,6 +850,30 @@ static void add_rpath(LinkJob *lj, Buf *rpath) { lj->rpath_table.put(rpath, true); } +static void add_glibc_libs(LinkJob *lj) { + Error err; + ZigGLibCAbi *glibc_abi; + if ((err = glibc_load_metadata(&glibc_abi, lj->codegen->zig_lib_dir, true))) { + fprintf(stderr, "%s\n", err_str(err)); + exit(1); + } + + Buf *artifact_dir; + if ((err = glibc_build_dummies_and_maps(lj->codegen, glibc_abi, lj->codegen->zig_target, + &artifact_dir, true))) + { + fprintf(stderr, "%s\n", err_str(err)); + exit(1); + } + + size_t lib_count = glibc_lib_count(); + for (size_t i = 0; i < lib_count; i += 1) { + const ZigGLibCLib *lib = glibc_lib_enum(i); + Buf *so_path = buf_sprintf("%s" OS_SEP "lib%s.so.%d.0.0", buf_ptr(artifact_dir), lib->name, lib->sover); + lj->args.append(buf_ptr(so_path)); + } +} + static void construct_linker_job_elf(LinkJob *lj) { CodeGen *g = lj->codegen; @@ -990,6 +972,11 @@ static void construct_linker_job_elf(LinkJob *lj) { if (is_dyn_lib) { lj->args.append("-soname"); lj->args.append(buf_ptr(soname)); + + if (g->version_script_path != nullptr) { + lj->args.append("-version-script"); + lj->args.append(buf_ptr(g->version_script_path)); + } } // .o files @@ -1053,11 +1040,7 @@ static void construct_linker_job_elf(LinkJob *lj) { } } else if (target_is_glibc(g->zig_target)) { lj->args.append(build_libunwind(g)); - lj->args.append(build_dummy_so(g, "c", 6)); - lj->args.append(build_dummy_so(g, "m", 6)); - lj->args.append(build_dummy_so(g, "pthread", 0)); - lj->args.append(build_dummy_so(g, "dl", 2)); - lj->args.append(build_dummy_so(g, "rt", 1)); + add_glibc_libs(lj); lj->args.append(get_libc_crt_file(g, "libc_nonshared.a")); } else if (target_is_musl(g->zig_target)) { lj->args.append(build_libunwind(g)); diff --git a/src/main.cpp b/src/main.cpp index 179b4fddb..17d3d627e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,7 @@ #include "target.hpp" #include "libc_installation.hpp" #include "userland.h" +#include "glibc.hpp" #include @@ -96,6 +97,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) { " --forbid-library [lib] make it an error to link against lib\n" " --library-path [dir] add a directory to the library search path\n" " --linker-script [path] use a custom linker script\n" + " --version-script [path] provide a version .map file\n" " --object [obj] add object file to build\n" " -L[dir] alias for --library-path\n" " -rdynamic add all symbols to the dynamic symbol table\n" @@ -189,10 +191,33 @@ static int print_target_list(FILE *f) { for (size_t i = 0; i < libc_count; i += 1) { ZigTarget libc_target; target_libc_enum(i, &libc_target); - fprintf(f, " %s-%s-%s\n", target_arch_name(libc_target.arch), - target_os_name(libc_target.os), target_abi_name(libc_target.abi)); + bool is_native = native.arch == libc_target.arch && + native.os == libc_target.os && + native.abi == libc_target.abi; + const char *native_str = is_native ? " (native)" : ""; + fprintf(f, " %s-%s-%s%s\n", target_arch_name(libc_target.arch), + target_os_name(libc_target.os), target_abi_name(libc_target.abi), native_str); } + fprintf(f, "\nAvailable glibc versions:\n"); + ZigGLibCAbi *glibc_abi; + Error err; + if ((err = glibc_load_metadata(&glibc_abi, get_zig_lib_dir(), true))) { + return EXIT_FAILURE; + } + for (size_t i = 0; i < glibc_abi->all_versions.length; i += 1) { + ZigGLibCVersion *this_ver = &glibc_abi->all_versions.at(i); + bool is_native = native.glibc_version != nullptr && + native.glibc_version->major == this_ver->major && + native.glibc_version->minor == this_ver->minor && + native.glibc_version->patch == this_ver->patch; + const char *native_str = is_native ? " (native)" : ""; + if (this_ver->patch == 0) { + fprintf(f, " %d.%d%s\n", this_ver->major, this_ver->minor, native_str); + } else { + fprintf(f, " %d.%d.%d%s\n", this_ver->major, this_ver->minor, this_ver->patch, native_str); + } + } return EXIT_SUCCESS; } @@ -437,6 +462,8 @@ int main(int argc, char **argv) { const char *mmacosx_version_min = nullptr; const char *mios_version_min = nullptr; const char *linker_script = nullptr; + Buf *version_script = nullptr; + const char *target_glibc = nullptr; ZigList rpath_list = {0}; bool each_lib_rpath = false; ZigList objects = {0}; @@ -783,6 +810,10 @@ int main(int argc, char **argv) { frameworks.append(argv[i]); } else if (strcmp(arg, "--linker-script") == 0) { linker_script = argv[i]; + } else if (strcmp(arg, "--version-script") == 0) { + version_script = buf_create_from_str(argv[i]); + } else if (strcmp(arg, "-target-glibc") == 0) { + target_glibc = argv[i]; } else if (strcmp(arg, "-rpath") == 0) { rpath_list.append(argv[i]); } else if (strcmp(arg, "--test-filter") == 0) { @@ -904,6 +935,10 @@ int main(int argc, char **argv) { ZigTarget target; if (target_string == nullptr) { get_native_target(&target); + if (target_glibc != nullptr) { + fprintf(stderr, "-target-glibc provided but no -target parameter\n"); + return print_error_usage(arg0); + } } else { if ((err = target_parse_triple(&target, target_string))) { if (err == ErrorUnknownArchitecture && target.arch != ZigLLVM_UnknownArch) { @@ -921,6 +956,22 @@ int main(int argc, char **argv) { return print_error_usage(arg0); } } + if (target_is_glibc(&target)) { + target.glibc_version = allocate(1); + + if (target_glibc != nullptr) { + if ((err = target_parse_glibc_version(target.glibc_version, target_glibc))) { + fprintf(stderr, "invalid glibc version '%s': %s\n", target_glibc, err_str(err)); + return print_error_usage(arg0); + } + } else { + // Default cross-compiling glibc version + *target.glibc_version = {2, 17, 0}; + } + } else if (target_glibc != nullptr) { + fprintf(stderr, "'%s' is not a glibc-compatible target", target_string); + return print_error_usage(arg0); + } } if (output_dir != nullptr && enable_cache == CacheOptOn) { @@ -1074,6 +1125,7 @@ int main(int argc, char **argv) { codegen_set_is_test(g, cmd == CmdTest); g->want_single_threaded = want_single_threaded; codegen_set_linker_script(g, linker_script); + g->version_script_path = version_script; if (each_lib_rpath) codegen_set_each_lib_rpath(g, each_lib_rpath); diff --git a/src/target.cpp b/src/target.cpp index f646b33e2..eace93094 100644 --- a/src/target.cpp +++ b/src/target.cpp @@ -10,6 +10,8 @@ #include "target.hpp" #include "util.hpp" #include "os.hpp" +#include "compiler.hpp" +#include "glibc.hpp" #include @@ -466,6 +468,29 @@ const char *target_abi_name(ZigLLVM_EnvironmentType abi) { return ZigLLVMGetEnvironmentTypeName(abi); } +Error target_parse_glibc_version(ZigGLibCVersion *glibc_ver, const char *text) { + glibc_ver->major = 2; + glibc_ver->minor = 0; + glibc_ver->patch = 0; + SplitIterator it = memSplit(str(text), str("GLIBC_.")); + { + Optional> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) return ErrorUnknownABI; + glibc_ver->major = strtoul(buf_ptr(buf_create_from_slice(opt_component.value)), nullptr, 10); + } + { + Optional> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) return ErrorNone; + glibc_ver->minor = strtoul(buf_ptr(buf_create_from_slice(opt_component.value)), nullptr, 10); + } + { + Optional> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) return ErrorNone; + glibc_ver->patch = strtoul(buf_ptr(buf_create_from_slice(opt_component.value)), nullptr, 10); + } + return ErrorNone; +} + void get_native_target(ZigTarget *target) { ZigLLVM_OSType os_type; ZigLLVM_ObjectFormatType oformat; // ignored; based on arch/os @@ -481,6 +506,17 @@ void get_native_target(ZigTarget *target) { if (target->abi == ZigLLVM_UnknownEnvironment) { target->abi = target_default_abi(target->arch, target->os); } + target->glibc_version = nullptr; +#ifdef ZIG_OS_LINUX + if (target_is_glibc(target)) { + target->glibc_version = allocate(1); + Error err; + if ((err = glibc_detect_native_version(target->glibc_version))) { + // Use a default version. + *target->glibc_version = {2, 17, 0}; + } + } +#endif } Error target_parse_archsub(ZigLLVM_ArchType *out_arch, ZigLLVM_SubArchType *out_sub, diff --git a/src/target.hpp b/src/target.hpp index 7fca430df..99e38f1c6 100644 --- a/src/target.hpp +++ b/src/target.hpp @@ -77,12 +77,19 @@ enum TargetSubsystem { TargetSubsystemAuto }; +struct ZigGLibCVersion { + uint32_t major; // always 2 + uint32_t minor; + uint32_t patch; +}; + struct ZigTarget { ZigLLVM_ArchType arch; ZigLLVM_SubArchType sub_arch; ZigLLVM_VendorType vendor; Os os; ZigLLVM_EnvironmentType abi; + ZigGLibCVersion *glibc_version; // null means default bool is_native; }; @@ -105,6 +112,8 @@ Error target_parse_archsub(ZigLLVM_ArchType *arch, ZigLLVM_SubArchType *sub, Error target_parse_os(Os *os, const char *os_ptr, size_t os_len); Error target_parse_abi(ZigLLVM_EnvironmentType *abi, const char *abi_ptr, size_t abi_len); +Error target_parse_glibc_version(ZigGLibCVersion *out, const char *text); + size_t target_arch_count(void); ZigLLVM_ArchType target_arch_enum(size_t index); const char *target_arch_name(ZigLLVM_ArchType arch); diff --git a/src/util.cpp b/src/util.cpp index 9a6a38299..65b1fe308 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -86,6 +86,32 @@ Optional> SplitIterator_next(SplitIterator *self) { return Optional>::some(self->buffer.slice(start, end)); } +// Ported from std/mem.zig. +// This one won't collapse multiple separators into one, so you could use it, for example, +// to parse Comma Separated Value format. +Optional> SplitIterator_next_separate(SplitIterator *self) { + // move to beginning of token + if (self->index < self->buffer.len && + SplitIterator_isSplitByte(self, self->buffer.ptr[self->index])) + { + self->index += 1; + } + size_t start = self->index; + if (start == self->buffer.len) { + return {}; + } + + // move to end of token + while (self->index < self->buffer.len && + !SplitIterator_isSplitByte(self, self->buffer.ptr[self->index])) + { + self->index += 1; + } + size_t end = self->index; + + return Optional>::some(self->buffer.slice(start, end)); +} + // Ported from std/mem.zig Slice SplitIterator_rest(SplitIterator *self) { // move to beginning of token diff --git a/src/util.hpp b/src/util.hpp index f1942dd48..6f2672513 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -314,6 +314,7 @@ struct SplitIterator { bool SplitIterator_isSplitByte(SplitIterator *self, uint8_t byte); Optional< Slice > SplitIterator_next(SplitIterator *self); +Optional< Slice > SplitIterator_next_separate(SplitIterator *self); Slice SplitIterator_rest(SplitIterator *self); SplitIterator memSplit(Slice buffer, Slice split_bytes);