|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 3 | +# or more contributor license agreements. See the NOTICE file |
| 4 | +# distributed with this work for additional information |
| 5 | +# regarding copyright ownership. The ASF licenses this file |
| 6 | +# to you under the Apache License, Version 2.0 (the |
| 7 | +# "License"); you may not use this file except in compliance |
| 8 | +# with the License. You may obtain a copy of the License at |
| 9 | +# |
| 10 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +# |
| 12 | +# Unless required by applicable law or agreed to in writing, |
| 13 | +# software distributed under the License is distributed on an |
| 14 | +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | +# KIND, either express or implied. See the License for the |
| 16 | +# specific language governing permissions and limitations |
| 17 | +# under the License. |
| 18 | + |
| 19 | +# libunwind 1.6.2 hard-coded a 4 KiB page size in its AArch64 memory validator. |
| 20 | +# On a 64 KiB-page kernel (Kunpeng/Kylin/openEuler/CentOS aarch64) mincore() and |
| 21 | +# msync() reject its 4 KiB-aligned probes with EINVAL, every validation fails, |
| 22 | +# unw_step() falls back to a link register that getcontext_trace never saved, |
| 23 | +# and is_plt_entry() dereferences that garbage unvalidated. Because the static |
| 24 | +# libunwind's weak `backtrace` alias replaces glibc's, bRPC's pre-main |
| 25 | +# `backtrace()` warm-up crashed every aarch64 BE on such hosts before main(). |
| 26 | +# |
| 27 | +# CI has no 64 KiB-page host, so this drives the installed libunwind.a through a |
| 28 | +# program shaped like that warm-up while an LD_PRELOAD shim makes the kernel |
| 29 | +# look like a 64 KiB-page one to libunwind: the same EINVAL for unaligned |
| 30 | +# probes and a 64 KiB sysconf(_SC_PAGESIZE). Run it after |
| 31 | +# `build-thirdparty.sh libunwind` on Linux; it only needs a C compiler. |
| 32 | + |
| 33 | +set -eo pipefail |
| 34 | + |
| 35 | +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." &>/dev/null && pwd)" |
| 36 | +TP_INSTALL_DIR="${TP_INSTALL_DIR:-${ROOT}/installed}" |
| 37 | + |
| 38 | +fail() { |
| 39 | + echo "FAIL: $*" >&2 |
| 40 | + exit 1 |
| 41 | +} |
| 42 | + |
| 43 | +if [[ "$(uname -s)" != 'Linux' ]]; then |
| 44 | + echo "SKIP: GNU libunwind is only built on Linux" |
| 45 | + exit 0 |
| 46 | +fi |
| 47 | + |
| 48 | +if [[ -n "${CC:-}" ]]; then |
| 49 | + cc="${CC}" |
| 50 | +else |
| 51 | + for candidate in gcc clang cc; do |
| 52 | + if command -v "${candidate}" >/dev/null 2>&1; then |
| 53 | + cc="${candidate}" |
| 54 | + break |
| 55 | + fi |
| 56 | + done |
| 57 | +fi |
| 58 | +[[ -n "${cc:-}" ]] || fail "no C compiler found; set CC" |
| 59 | +[[ -f "${TP_INSTALL_DIR}/lib/libunwind.a" ]] || fail "${TP_INSTALL_DIR}/lib/libunwind.a not found; build libunwind first" |
| 60 | + |
| 61 | +tmpdir="$(mktemp -d)" |
| 62 | +trap 'rm -rf "${tmpdir}"' EXIT |
| 63 | + |
| 64 | +cat >"${tmpdir}/shim64k.c" <<'EOF' |
| 65 | +#define _GNU_SOURCE |
| 66 | +#include <dlfcn.h> |
| 67 | +#include <errno.h> |
| 68 | +#include <stddef.h> |
| 69 | +#include <stdint.h> |
| 70 | +#include <sys/mman.h> |
| 71 | +#include <unistd.h> |
| 72 | +
|
| 73 | +/* What a 64 KiB-page kernel does with libunwind's validation probes. */ |
| 74 | +#define EMU_PAGE 65536UL |
| 75 | +
|
| 76 | +int mincore(void *addr, size_t len, unsigned char *vec) { |
| 77 | + static int (*real)(void *, size_t, unsigned char *); |
| 78 | + if (!real) real = dlsym(RTLD_NEXT, "mincore"); |
| 79 | + if ((uintptr_t)addr & (EMU_PAGE - 1)) { errno = EINVAL; return -1; } |
| 80 | + size_t n = (len + EMU_PAGE - 1) / EMU_PAGE; |
| 81 | + for (size_t i = 0; i < n; i++) { |
| 82 | + unsigned char tmp[EMU_PAGE / 4096]; |
| 83 | + if (real((char *)addr + i * EMU_PAGE, EMU_PAGE, tmp) != 0) return -1; |
| 84 | + unsigned char any = 0; |
| 85 | + for (size_t j = 0; j < sizeof tmp; j++) any |= tmp[j] & 1; |
| 86 | + vec[i] = any; |
| 87 | + } |
| 88 | + return 0; |
| 89 | +} |
| 90 | +int msync(void *addr, size_t len, int flags) { |
| 91 | + static int (*real)(void *, size_t, int); |
| 92 | + if (!real) real = dlsym(RTLD_NEXT, "msync"); |
| 93 | + if ((uintptr_t)addr & (EMU_PAGE - 1)) { errno = EINVAL; return -1; } |
| 94 | + return real(addr, len, flags); |
| 95 | +} |
| 96 | +long sysconf(int name) { |
| 97 | + static long (*real)(int); |
| 98 | + if (!real) real = dlsym(RTLD_NEXT, "sysconf"); |
| 99 | + if (name == _SC_PAGESIZE) return EMU_PAGE; |
| 100 | + return real(name); |
| 101 | +} |
| 102 | +int getpagesize(void) { return (int)EMU_PAGE; } |
| 103 | +EOF |
| 104 | + |
| 105 | +cat >"${tmpdir}/premain.c" <<'EOF' |
| 106 | +#define _GNU_SOURCE |
| 107 | +#include <execinfo.h> |
| 108 | +#include <link.h> |
| 109 | +#include <stdio.h> |
| 110 | +#include <string.h> |
| 111 | +#include <unistd.h> |
| 112 | +
|
| 113 | +/* be/src/common/phdr_cache.cpp before main(): the PHDR cache is still NULL, so the |
| 114 | + * Doris hook that the libunwind patch routes dwarf_find_proc_info() through finds |
| 115 | + * nothing. */ |
| 116 | +int doris_unwind_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, |
| 117 | + unsigned long ip) { |
| 118 | + (void)callback; (void)data; (void)ip; |
| 119 | + return 0; |
| 120 | +} |
| 121 | +
|
| 122 | +static void *dummy_buf[4]; |
| 123 | +static int dummy_bt = -1; |
| 124 | +
|
| 125 | +/* Zero the stack below us, like the never-touched region the warm-up ran on, so the |
| 126 | + * uc.regs[30] slot that _Uaarch64_getcontext_trace never writes reads as 0. */ |
| 127 | +static void __attribute__((noinline)) scrub_stack(void) { |
| 128 | + volatile char buf[256 * 1024]; |
| 129 | + memset((char *)buf, 0, sizeof(buf)); |
| 130 | + __asm__ volatile("" ::: "memory"); |
| 131 | +} |
| 132 | +
|
| 133 | +/* bRPC src/bthread/mutex.cpp: "Warm up backtrace before main()." */ |
| 134 | +__attribute__((constructor)) static void warm_up(void) { |
| 135 | + scrub_stack(); |
| 136 | + dummy_bt = backtrace(dummy_buf, 4); |
| 137 | +} |
| 138 | +
|
| 139 | +int main(void) { |
| 140 | + void *buf[32]; |
| 141 | + int n = backtrace(buf, 32); |
| 142 | + printf("premain=%d runtime=%d pagesz=%ld\n", dummy_bt, n, sysconf(_SC_PAGESIZE)); |
| 143 | + return (dummy_bt < 0 || n < 0) ? 1 : 0; |
| 144 | +} |
| 145 | +EOF |
| 146 | + |
| 147 | +extra_libs=() |
| 148 | +[[ -f "${TP_INSTALL_DIR}/lib/libz.a" ]] && extra_libs+=(-lz) |
| 149 | + |
| 150 | +"${cc}" -O2 -g -shared -fPIC -o "${tmpdir}/shim64k.so" "${tmpdir}/shim64k.c" -ldl |
| 151 | +# -u unw_backtrace pulls libunwind's backtrace.o in, whose weak `backtrace` alias then |
| 152 | +# takes over the call, exactly as it does in doris_be. |
| 153 | +"${cc}" -O2 -g -fno-omit-frame-pointer -o "${tmpdir}/premain" "${tmpdir}/premain.c" \ |
| 154 | + -I"${TP_INSTALL_DIR}/include" -L"${TP_INSTALL_DIR}/lib" \ |
| 155 | + -Wl,-u,unw_backtrace -lunwind -llzma "${extra_libs[@]}" -lpthread |
| 156 | + |
| 157 | +if command -v nm >/dev/null 2>&1; then |
| 158 | + nm "${tmpdir}/premain" | grep -Eq ' [WT] backtrace$' || |
| 159 | + fail "backtrace() is not bound to libunwind's unw_backtrace in the test program" |
| 160 | +fi |
| 161 | + |
| 162 | +echo "== native page size ==" |
| 163 | +out="$("${tmpdir}/premain")" || fail "pre-main backtrace() crashed on the native page size: ${out}" |
| 164 | +echo "${out}" |
| 165 | + |
| 166 | +echo "== emulated 64 KiB page size ==" |
| 167 | +out="$(LD_PRELOAD="${tmpdir}/shim64k.so" "${tmpdir}/premain")" || |
| 168 | + fail "pre-main backtrace() crashed on an emulated 64 KiB-page kernel: ${out}" |
| 169 | +echo "${out}" |
| 170 | +[[ "${out}" == *"pagesz=65536"* ]] || fail "the 64 KiB page emulation did not take effect: ${out}" |
| 171 | + |
| 172 | +echo "PASS" |
0 commit comments