This is an automated email from the ASF dual-hosted git repository. yiguolei pushed a commit to branch tmp_4.1.4-rc04 in repository https://gitbox.apache.org/repos/asf/doris.git
commit ad35a140c7fd0b842f18c23300bac581f7d04326 Author: yiguolei <[email protected]> AuthorDate: Wed Sep 9 21:18:19 2026 +0800 [bugfix](glibc) fix glibc compatible bugs (#67694) ### What problem does this PR solve? pick https://github.com/apache/doris/pull/67701/changes Rust std weak-links this glibc 2.18 entry point and has an internal fallback when it is absent. Since the LDB sysroot exposes it, the final linker would otherwise record GLIBC_2.18. See https://github.com/rust-lang/rust/issues/57497 for more details. Problem Summary: ### Release note None ### Check List (For Author) - Test <!-- At least one of them must be included. --> - [ ] Regression test - [ ] Unit Test - [ ] Manual test (add detailed scripts or steps below) - [ ] No need to test or manual test. Explain why: - [ ] This is a refactor/code format and no logic has been changed. - [ ] Previous test can cover this change. - [ ] No code files have been changed. - [ ] Other reason <!-- Add your reason? --> - Behavior changed: - [ ] No. - [ ] Yes. <!-- Explain the behavior change --> - Does this need documentation? - [ ] No. - [ ] Yes. <!-- Add document PR link here. eg: https://github.com/apache/doris-website/pull/1214 --> ### Check List (For Reviewer who merge this PR) - [ ] Confirm the release note - [ ] Confirm test cases - [ ] Confirm document - [ ] Add branch pick label <!-- Add branch pick label that this PR should merge into --> --- be/CMakeLists.txt | 10 ++ be/cmake/check_glibc_compatibility.cmake | 70 +++++++++++++ be/src/glibc-compatibility/lance_symbol_versions.c | 115 ++++++++++++++++++++- be/src/glibc-compatibility/musl/expf.c | 80 ++++++++++++++ be/src/service/CMakeLists.txt | 22 +++- 5 files changed, 293 insertions(+), 4 deletions(-) diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt index 389c3883b29..555b2508f9d 100644 --- a/be/CMakeLists.txt +++ b/be/CMakeLists.txt @@ -725,7 +725,17 @@ if (GLIBC_COMPATIBILITY) # Keep lance_c here instead of COMMON_THIRDPARTY: placing its required libm there # would resolve -lm symbol before Doris compatibility is scanned, preventing # the linker from selecting Doris' optimized implementations. + # lance_c can also introduce single-precision math references after the + # compatibility archive has already been scanned. As ClickHouse does for its + # compatibility archive, force the GLIBC_2.27 math symbols to be undefined up + # front so the linker extracts Doris' musl implementations instead of binding + # late references to the toolchain's libm. set(DORIS_LINK_LIBS ${DORIS_LINK_LIBS} + -Wl,-u,logf + -Wl,-u,powf + -Wl,-u,expf + -Wl,-u,exp2f + -Wl,-u,log2f glibc-compatibility-explicit glibc-compatibility -lm diff --git a/be/cmake/check_glibc_compatibility.cmake b/be/cmake/check_glibc_compatibility.cmake new file mode 100644 index 00000000000..435419bf07d --- /dev/null +++ b/be/cmake/check_glibc_compatibility.cmake @@ -0,0 +1,70 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +if (NOT DEFINED ARTIFACT OR NOT EXISTS "${ARTIFACT}") + message(FATAL_ERROR "Cannot check glibc compatibility: '${ARTIFACT}' does not exist") +endif() + +if (NOT DEFINED BASELINE OR BASELINE STREQUAL "") + message(FATAL_ERROR "Cannot check glibc compatibility: BASELINE is not set") +endif() + +if (NOT DEFINED OBJDUMP OR OBJDUMP STREQUAL "" OR NOT EXISTS "${OBJDUMP}") + find_program(OBJDUMP NAMES objdump llvm-objdump REQUIRED) +endif() + +execute_process( + COMMAND "${OBJDUMP}" -T "${ARTIFACT}" + RESULT_VARIABLE objdump_result + OUTPUT_VARIABLE dynamic_symbol_table + ERROR_VARIABLE objdump_error) + +if (NOT objdump_result EQUAL 0) + message(FATAL_ERROR + "Cannot read the dynamic symbol table from '${ARTIFACT}': ${objdump_error}") +endif() + +string(REPLACE "\n" ";" dynamic_symbol_lines "${dynamic_symbol_table}") +set(incompatible_symbols) + +foreach (symbol_line IN LISTS dynamic_symbol_lines) + # Only undefined symbols are runtime requirements on the target system. + if (symbol_line MATCHES "\\*UND\\*") + string(REGEX MATCH "GLIBC_[0-9]+(\\.[0-9]+)+" symbol_version "${symbol_line}") + if (NOT "${symbol_version}" STREQUAL "") + string(REGEX REPLACE "^GLIBC_" "" numeric_version "${symbol_version}") + if ("${numeric_version}" VERSION_GREATER "${BASELINE}") + string(REGEX MATCH "[^ \t]+$" symbol_name "${symbol_line}") + list(APPEND incompatible_symbols "${symbol_version} ${symbol_name}") + endif() + endif() + endif() +endforeach() + +if (NOT "${incompatible_symbols}" STREQUAL "") + list(REMOVE_DUPLICATES incompatible_symbols) + list(SORT incompatible_symbols) + string(JOIN "\n " formatted_symbols ${incompatible_symbols}) + message(FATAL_ERROR + "${ARTIFACT} requires glibc symbols newer than GLIBC_${BASELINE}:\n" + " ${formatted_symbols}\n" + "The production BE must remain runnable on CentOS 7 (glibc ${BASELINE}).") +endif() + +message(STATUS + "glibc compatibility check passed: ${ARTIFACT} requires no symbols newer than " + "GLIBC_${BASELINE}") diff --git a/be/src/glibc-compatibility/lance_symbol_versions.c b/be/src/glibc-compatibility/lance_symbol_versions.c index b0594dce06b..66b9cdae5cb 100644 --- a/be/src/glibc-compatibility/lance_symbol_versions.c +++ b/be/src/glibc-compatibility/lance_symbol_versions.c @@ -18,8 +18,12 @@ #define _GNU_SOURCE #include <fcntl.h> +#include <pthread.h> #include <spawn.h> +#include <stdlib.h> +#include <sys/syscall.h> #include <sys/uio.h> +#include <unistd.h> #if defined(__x86_64__) #define DORIS_GLIBC_BASE_VERSION "GLIBC_2.2.5" @@ -41,6 +45,76 @@ // glibc symbol, so it cannot recurse back into the hidden wrapper. #define DORIS_HIDDEN __attribute__((visibility("hidden"))) +typedef void (*doris_tls_destructor)(void*); + +struct doris_tls_destructor_entry { + doris_tls_destructor destructor; + void* object; + struct doris_tls_destructor_entry* next; +}; + +static pthread_key_t doris_tls_destructor_key; +static pthread_once_t doris_tls_destructor_once = PTHREAD_ONCE_INIT; + +static void doris_run_tls_destructors(void* value) { + struct doris_tls_destructor_entry* entry = value; + while (entry != NULL) { + struct doris_tls_destructor_entry* next = entry->next; + doris_tls_destructor destructor = entry->destructor; + void* object = entry->object; + + // Publish the remainder before invoking the destructor. A destructor + // may register another TLS destructor, which must run before the older + // entries that are still pending. + if (pthread_setspecific(doris_tls_destructor_key, next) != 0) { + abort(); + } + free(entry); + destructor(object); + entry = pthread_getspecific(doris_tls_destructor_key); + } +} + +static void doris_run_main_thread_tls_destructors(void) { + doris_run_tls_destructors(pthread_getspecific(doris_tls_destructor_key)); +} + +static void doris_init_tls_destructor_key(void) { + if (pthread_key_create(&doris_tls_destructor_key, doris_run_tls_destructors) != 0) { + abort(); + } + // pthread_key_create() arranges for doris_run_tls_destructors() to be + // called automatically when an ordinary worker thread exits. However, + // returning from main() (which is equivalent to exit()) does not run the + // initial thread's pthread key destructors. Register an atexit handler so + // TLS destructors belonging to the thread that performs normal process + // termination are still invoked. + // + // This relies on Doris normally terminating the process from its initial + // thread. If another thread calls exit(), atexit handlers execute in that + // thread and pthread_getspecific() observes that thread's TLS state. + // atexit handlers are not invoked by abort(), _exit(), fatal signals, or + // SIGKILL; those paths are already abnormal process termination. + // + // Unlike glibc, this fallback puts the initial thread's TLS cleanup in the + // same LIFO list as atexit callbacks and static-object destructors. A static + // object initialized after this handler is registered is therefore destroyed + // before the TLS objects. We accept this limitation because Doris normally + // terminates with _exit(), and normal destructor processing is currently used + // only when enable_graceful_exit_check is enabled for sanitizer leak checks. + // TLS destructors on that diagnostic path must not access static-lifetime + // objects that may already have been destroyed. If graceful exit becomes a + // production path, the initial thread's TLS destructors must instead be run + // explicitly before the atexit/static-destructor list. + // + // A registration failure means that normal main-thread TLS cleanup cannot + // be guaranteed, so fail immediately instead of continuing with a partially + // installed compatibility implementation. + if (atexit(doris_run_main_thread_tls_destructors) != 0) { + abort(); + } +} + extern __typeof__(posix_spawnp) __doris_old_posix_spawnp; DORIS_GLIBC_SYMVER(__doris_old_posix_spawnp, posix_spawnp, DORIS_GLIBC_BASE_VERSION); @@ -53,8 +127,8 @@ DORIS_GLIBC_SYMVER(__doris_old_posix_spawn_file_actions_destroy, posix_spawn_fil DORIS_GLIBC_BASE_VERSION); extern __typeof__(posix_spawn_file_actions_adddup2) __doris_old_posix_spawn_file_actions_adddup2; -DORIS_GLIBC_SYMVER(__doris_old_posix_spawn_file_actions_adddup2, - posix_spawn_file_actions_adddup2, DORIS_GLIBC_BASE_VERSION); +DORIS_GLIBC_SYMVER(__doris_old_posix_spawn_file_actions_adddup2, posix_spawn_file_actions_adddup2, + DORIS_GLIBC_BASE_VERSION); extern __typeof__(preadv) __doris_old_preadv; DORIS_GLIBC_SYMVER(__doris_old_preadv, preadv, DORIS_GLIBC_PREADV_VERSION); @@ -90,3 +164,40 @@ DORIS_HIDDEN ssize_t splice(int fd_in, off64_t* offset_in, int fd_out, off64_t* size_t length, unsigned int flags) { return __doris_old_splice(fd_in, offset_in, fd_out, offset_out, length, flags); } + +// Rust std weak-links copy_file_range and otherwise issues the syscall itself. +// Provide that syscall path locally so linking on glibc 2.27 does not attach a +// GLIBC_2.27 version requirement. Old kernels return ENOSYS and Rust falls back +// to its generic copy loop. +DORIS_HIDDEN ssize_t copy_file_range(int fd_in, off64_t* offset_in, int fd_out, off64_t* offset_out, + size_t length, unsigned int flags) { + return (ssize_t)syscall(SYS_copy_file_range, fd_in, offset_in, fd_out, offset_out, length, + flags); +} + +// Rust std weak-links this glibc 2.18 entry point and has an internal fallback +// when it is absent. Since the LDB sysroot exposes it, the final linker would +// otherwise record GLIBC_2.18. Supply equivalent pthread-key based registration +// locally. All callers are linked into doris_be, so dso_symbol tracking for +// dlclose is intentionally unnecessary. +// See https://github.com/rust-lang/rust/issues/57497 for more details. +DORIS_HIDDEN int __cxa_thread_atexit_impl(doris_tls_destructor destructor, void* object, + void* dso_symbol) { + (void)dso_symbol; + if (pthread_once(&doris_tls_destructor_once, doris_init_tls_destructor_key) != 0) { + abort(); + } + + struct doris_tls_destructor_entry* entry = malloc(sizeof(*entry)); + if (entry == NULL) { + abort(); + } + entry->destructor = destructor; + entry->object = object; + entry->next = pthread_getspecific(doris_tls_destructor_key); + if (pthread_setspecific(doris_tls_destructor_key, entry) != 0) { + free(entry); + abort(); + } + return 0; +} diff --git a/be/src/glibc-compatibility/musl/expf.c b/be/src/glibc-compatibility/musl/expf.c new file mode 100644 index 00000000000..f9fbf8e727d --- /dev/null +++ b/be/src/glibc-compatibility/musl/expf.c @@ -0,0 +1,80 @@ +/* + * Single-precision e^x function. + * + * Copyright (c) 2017-2018, Arm Limited. + * SPDX-License-Identifier: MIT + */ + +#include <math.h> +#include <stdint.h> +#include "libm.h" +#include "exp2f_data.h" + +/* +EXP2F_TABLE_BITS = 5 +EXP2F_POLY_ORDER = 3 + +ULP error: 0.502 (nearest rounding.) +Relative error: 1.69 * 2^-34 in [-ln2/64, ln2/64] (before rounding.) +Wrong count: 170635 (all nearest rounding wrong results with fma.) +Non-nearest ULP error: 1 (rounded ULP error) +*/ + +#define N (1 << EXP2F_TABLE_BITS) +#define InvLn2N __exp2f_data.invln2_scaled +#define T __exp2f_data.tab +#define C __exp2f_data.poly_scaled + +static inline uint32_t top12(float x) +{ + return asuint(x) >> 20; +} + +float expf(float x) +{ + uint32_t abstop; + uint64_t ki, t; + double_t kd, xd, z, r, r2, y, s; + + xd = (double_t)x; + abstop = top12(x) & 0x7ff; + if (predict_false(abstop >= top12(88.0f))) { + /* |x| >= 88 or x is nan. */ + if (asuint(x) == asuint(-INFINITY)) + return 0.0f; + if (abstop >= top12(INFINITY)) + return x + x; + if (x > 0x1.62e42ep6f) /* x > log(0x1p128) ~= 88.72 */ + return __math_oflowf(0); + if (x < -0x1.9fe368p6f) /* x < log(0x1p-150) ~= -103.97 */ + return __math_uflowf(0); + } + + /* x*N/Ln2 = k + r with r in [-1/2, 1/2] and int k. */ + z = InvLn2N * xd; + + /* Round and convert z to int, the result is in [-150*N, 128*N] and + ideally ties-to-even rule is used, otherwise the magnitude of r + can be bigger which gives larger approximation error. */ +#if TOINT_INTRINSICS + kd = roundtoint(z); + ki = converttoint(z); +#else +# define SHIFT __exp2f_data.shift + kd = eval_as_double(z + SHIFT); + ki = asuint64(kd); + kd -= SHIFT; +#endif + r = z - kd; + + /* exp(x) = 2^(k/N) * 2^(r/N) ~= s * (C0*r^3 + C1*r^2 + C2*r + 1) */ + t = T[ki % N]; + t += ki << (52 - EXP2F_TABLE_BITS); + s = asdouble(t); + z = C[0] * r + C[1]; + r2 = r * r; + y = C[2] * r + 1; + y = z * r2 + y; + y = y * s; + return eval_as_float(y); +} diff --git a/be/src/service/CMakeLists.txt b/be/src/service/CMakeLists.txt index b9bcef0b7b8..1f52d4f7a60 100644 --- a/be/src/service/CMakeLists.txt +++ b/be/src/service/CMakeLists.txt @@ -46,13 +46,31 @@ if (${MAKE_TEST} STREQUAL "OFF" AND ${BUILD_BENCHMARK} STREQUAL "OFF") endif () pch_reuse(doris_be) - # This permits libraries loaded by dlopen to link to the symbols in the program. - set_target_properties(doris_be PROPERTIES ENABLE_EXPORTS 1) + # Keep doris_be symbols private. Java UDF callbacks are registered explicitly through JNI and + # Python UDFs run out of process, so neither requires executable exports. Exporting all symbols + # would also let dlopened libraries bind to private copies from static dependencies. target_link_libraries(doris_be ${DORIS_LINK_LIBS} ) + if (OS_LINUX AND GLIBC_COMPATIBILITY) + # Use TARGET_FILE rather than an install/output directory so this check + # follows doris_be if its build location or output name changes. + set(GLIBC_COMPATIBILITY_CHECK + "${BASE_DIR}/cmake/check_glibc_compatibility.cmake") + set_property(TARGET doris_be APPEND PROPERTY LINK_DEPENDS + "${GLIBC_COMPATIBILITY_CHECK}") + add_custom_command(TARGET doris_be POST_BUILD + COMMAND "${CMAKE_COMMAND}" + "-DARTIFACT=$<TARGET_FILE:doris_be>" + "-DOBJDUMP=${CMAKE_OBJDUMP}" + "-DBASELINE=2.17" + -P "${GLIBC_COMPATIBILITY_CHECK}" + COMMENT "Checking that doris_be requires no glibc symbols newer than GLIBC_2.17" + VERBATIM) + endif() + install(DIRECTORY DESTINATION ${OUTPUT_DIR}/lib/) install(TARGETS doris_be DESTINATION ${OUTPUT_DIR}/lib/) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
