diff options
Diffstat (limited to 'src/glibc.cpp')
| -rw-r--r-- | src/glibc.cpp | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/src/glibc.cpp b/src/glibc.cpp new file mode 100644 index 0000000000..d7ae47ed71 --- /dev/null +++ b/src/glibc.cpp @@ -0,0 +1,406 @@ +/* + * 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<ZigGLibCAbi>(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<Slice<uint8_t>> 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<Slice<uint8_t>> opt_component = SplitIterator_next(&it); + if (!opt_component.is_some) break; + SplitIterator line_it = memSplit(opt_component.value, str(" ")); + Optional<Slice<uint8_t>> 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<Slice<uint8_t>> 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<Slice<uint8_t>> opt_line = SplitIterator_next_separate(&it); + if (!opt_line.is_some) break; + + ver_list_base = allocate<ZigGLibCVerList>(glibc_abi->all_functions.length); + ZigTarget *target = allocate<ZigTarget>(1); + SplitIterator line_it = memSplit(opt_line.value, str(" ")); + for (;;) { + Optional<Slice<uint8_t>> opt_target = SplitIterator_next(&line_it); + if (!opt_target.is_some) break; + + SplitIterator component_it = memSplit(opt_target.value, str("-")); + Optional<Slice<uint8_t>> opt_arch = SplitIterator_next(&component_it); + assert(opt_arch.is_some); + Optional<Slice<uint8_t>> opt_os = SplitIterator_next(&component_it); + assert(opt_os.is_some); // it's always "linux" so we ignore it + Optional<Slice<uint8_t>> 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<Slice<uint8_t>> opt_line = SplitIterator_next_separate(&it); + assert(opt_line.is_some); + + SplitIterator line_it = memSplit(opt_line.value, str(" ")); + for (;;) { + Optional<Slice<uint8_t>> 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<CacheHash>(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; + + 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 <unistd.h> +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, 4096); + 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; +} |
