zig/src/glibc.cpp
Andrew Kelley ffef5d26b6
significantly increase test coverage
* add zig build option `-Dskip-libc` to skip tests that build libc
   (e.g. if you don't want to wait for musl to build)
 * add `-Denable-wine` option which uses wine to run cross compiled
   windows tests on non-windows hosts
 * add `-Denable-qemu` option which uses qemu to run cross compiled
   foreign architecture tests
 * add `-Denable-foreign-glibc=path` option which combined with
   `-Denable-qemu` enables running cross compiled tests that link
   against glibc. See
   https://github.com/ziglang/zig/wiki/Updating-libc#glibc for how to
   produce this directory.
 * the test matrix is done manually. release test builds are only
   enabled by default for the native target. this should save us some CI
   time, while still providing decent coverage of release builds.
   - add test coverage for `x86_64-linux-musl -lc` (building musl libc)
   - add test coverage for `x86_64-linux-gnu -lc` (building glibc)
   - add test coverage for `aarch64v8_5a-linux-none`
   - add test coverage for `aarch64v8_5a-linux-musl -lc` (building musl libc)
   - add test coverage for `aarch64v8_5a-linux-gnu -lc` (building glibc)
   - add test coverage for `arm-linux-none`
   - test coverage for `arm-linux-musleabihf -lc` (building musl libc) is
     disabled due to #3286
   - test coverage for `arm-linux-gnueabihf -lc` (building glibc) is disabled
     due to #3287
   - test coverage for `x86_64-windows-gnu -lc` (building mingw-w64) is
     disabled due to #3285
 * enable qemu testing on the Linux CI job. There's not really a good
   reason to enable wine, since we have a Windows CI job as well.
 * remove the no longer needed `--build-file ../build.zig` from CI
   scripts
 * fix bug in glibc compilation where it wasn't properly reading the abi
   list txt files, resulting in "key not found" error.
 * std.build.Target gains:
   - isNetBSD
   - isLinux
   - osRequiresLibC
   - getArchPtrBitWidth
   - getExternalExecutor
 * zig build system gains support for enabling wine and enabling qemu.
   `artifact.enable_wine = true;`, `artifact.enable_qemu = true;`. This
   communicates that the system has these tools installed and the build
   system will use them to run tests.
 * zig build system gains support for overriding the dynamic linker of
   an executable artifact.
 * fix std.c.lseek prototype. makes behavior tests for
   arm-linux-musleabihf pass.
 * disable std lib tests that are failing on ARM. See #3288, #3289
 * provide `std.os.off_t`.
 * disable some of the compiler_rt symbols for arm 32 bit. Fixes
   compiler_rt tests for arm 32 bit
 * add __stack_chk_guard when linking against glibc. Fixes std lib tests
   for aarch64-linux-gnu
 * workaround for "unable to inline function" using `@inlineCall`. Fixes
   compiler_rt tests for arm 32 bit.
2019-09-22 11:22:17 -04:00

407 lines
16 KiB
C++

/*
* 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);
SplitIterator line_it = memSplit(opt_line.value, str(" "));
for (;;) {
ZigTarget *target = allocate<ZigTarget>(1);
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;
}