Skip to content

Build Linux language extensions with GCC 13 on the RHEL 9 baseline - #101

Draft
SicongLiu2000 wants to merge 3 commits into
mainfrom
dev/sicongliu/exthost-v3-gcc13-all-languages
Draft

Build Linux language extensions with GCC 13 on the RHEL 9 baseline#101
SicongLiu2000 wants to merge 3 commits into
mainfrom
dev/sicongliu/exthost-v3-gcc13-all-languages

Conversation

@SicongLiu2000

@SicongLiu2000 SicongLiu2000 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Build all Linux language extensions with GCC 13 against a RHEL 9 / glibc 2.34 baseline, and fix the runtime/test issues exposed by end-to-end qualification.

This change:

  • provisions GCC 13 consistently for R, Python, Java, ONNX, EKM, and other native Linux components;
  • adds required native build, archive, ELF-inspection, and crash-diagnostic tools;
  • updates Java and R library-path handling for the RHEL 9 baseline;
  • keeps the unloadable R extension on the process C++ runtime to avoid duplicate static runtime state;
  • fixes Python UTF-16 output tests under -fshort-wchar;
  • fixes the R NCHAR test's temporary-string lifetime;
  • normalizes Windows-style separators in Python package ZIP entries on Linux.

Why

The Linux package must run on Ubuntu 22.04/24.04, RHEL 9/10, and Azure Linux 3 while using one compiler major version for all native extensions. Building on newer distro baselines introduced GLIBC/GLIBCXX requirements unavailable on RHEL 9. GCC 11 also cannot consume the fixed-underlying-type enum declarations exposed by the R 4.5 toolchain.

The selected baseline is UBI 9.6 with GCC 13. This preserves the required compiler while targeting glibc 2.34.

Validation

Validated through the private Data-SQL-Language-Extensions branch pinned to this source:

  • DSLE build 173434936: all required Linux language builds, tests, ELF gates, packaging, and automatic publication succeeded.
  • Qualified package: data-sql-language-extensions-linux version 1.0.0-PR-exthost-v3-gcc13-real-e2e-20260723.20.
  • Package payload gates:
    • native GLIBC baseline at or below 2.34;
    • native GLIBCXX baseline at or below 3.4.29;
    • Python has no runtime Boost or libstdc++ dependency and no GLIBCXX requirement.
  • The same automatically published package was consumed by private mssql-server builds and exercised through DSMainDev PVS across Ubuntu 22.04/24.04, RHEL 9/10, and Azure Linux 3.
  • Language-specific Python, R, Java, ONNX, and EKM suites have clean passing evidence on all five distros.

The PR is intentionally draft while downstream productization and final broad-board signoff are reviewed.

@SicongLiu2000
SicongLiu2000 force-pushed the dev/sicongliu/exthost-v3-gcc13-all-languages branch 9 times, most recently from c2ff604 to a69f918 Compare July 28, 2026 01:38
The Linux extensions are built on Ubuntu 24.04 with GCC 13 and must load on
RHEL 9, which ships glibc 2.34 and libstdc++ 3.4.29. Rather than change where
they are built, this makes the produced binaries meet that floor.

GCC 13 emits a reference to std::ios_base_library_init()@GLIBCXX_3.4.32 into
every translation unit that includes <iostream>, and that single symbol was the
only libstdc++ requirement above the floor. The shipped R sources therefore no
longer include it: the includes in RExtension.cpp and RTypeUtils.cpp were
unused, and Logger.cpp writes through stdio.

The Rcpp and RInside headers also include <iostream> and cannot be changed
here, so the symbol is additionally defined locally. R must not link libstdc++
statically: libR.so already links libstdc++.so.6, and a second static copy puts
two C++ runtimes in one process, which aborts partway through script execution.

language-extensions/common/linux/AbiFloorCompat_linux.cpp collects the local
definitions needed to stay within the floor:

  std::ios_base_library_init  3.4.32  <iostream>, via Rcpp/RInside
  _dl_find_object             2.35    libgcc.a unwinder
  arc4random                  2.36    libstdc++.a
  __isoc23_strtoul            2.38    libstdc++.a
  __isoc23_strtoll/strtoull   2.38    <stdlib.h> C23 redirect

arc4random is backed by getrandom(2) with a /dev/urandom fallback and aborts
rather than returning predictable bytes. _dl_find_object reports "no
information", which makes the libgcc unwinder use its dl_iterate_phdr path -
the same path taken on every glibc older than 2.35. All definitions are hidden,
so the extensions never interpose on glibc for the rest of the host process.

Also in this change:

- restore-packages.sh asserts the GCC major version so the toolchain cannot
  silently vary between languages.
- The Java extension links libstdc++/libgcc statically (it does not load libR,
  so there is no duplicate-runtime hazard) and uses the modern JDK lib/server
  layout instead of the Java 8 jre/lib/amd64/server path.
- Test fixes: a dangling c_str() in the R result-column tests, and UTF-16
  literals and raw byte comparisons in the Python output-parameter tests, which
  were relying on std::wstring under -fshort-wchar.
@SicongLiu2000
SicongLiu2000 force-pushed the dev/sicongliu/exthost-v3-gcc13-all-languages branch from a69f918 to ac09800 Compare July 28, 2026 02:00
… shim

The ABI-floor shim defined std::ios_base_library_init() as an empty function.
That was wrong, and it crashed libPythonExtension on Ubuntu 22.04 and RHEL 9.

GCC 13 changed how the standard streams are constructed. Before 13, every
translation unit including <iostream> emitted its own static std::ios_base::Init
object. GCC 13 emits a call to std::ios_base_library_init() instead and performs
the construction inside libstdc++. Stubbing that call out therefore means the
streams are never constructed at all, and the first write to std::cout or
std::cerr faults.

It only shows up on hosts whose libstdc++ predates the change:

  Ubuntu 22.04  libstdc++.so.6.0.30   no self-init -> crash
  RHEL 9        GCC 11/12 era         no self-init -> crash
  Ubuntu 24.04  GCC 13+               library self-initialises -> fine
  RHEL 10       GCC 13+               library self-initialises -> fine
  AzureLinux 3  GCC 13+               library self-initialises -> fine

which matches the PVS board exactly: the Python satellite core-dumped on
Ubuntu 22.04 and RHEL 9 immediately after receiving its first data chunk, while
all three newer distros passed. libPythonExtension.so does reference
_ZSt4cout@GLIBCXX_3.4 and _ZSt4cerr@GLIBCXX_3.4, so the comment claiming no
shipped source used the standard streams was simply incorrect.

R was unaffected because libR.so is built by the distro toolchain and its own
translation units still emit std::ios_base::Init; libpython3.12 is C and
initialises nothing, so the Python extension had no other initialiser.

Construct a function-local static std::ios_base::Init instead. The constructor
is refcounted and idempotent, the local static guard runs it exactly once no
matter how many translation units call in, and std::ios_base::Init::Init is
GLIBCXX_3.4 - present in every libstdc++ we target - so the ABI floor is
unchanged.
The Python satellite segfaulted on Ubuntu 22.04 and RHEL 9 while passing on
Ubuntu 24.04, RHEL 10 and AzureLinux 3. Two core dumps place the fault inside
the host's own libstdc++, not in our code:

  round 1  Ubuntu 22.04  libstdc++.so.6.0.30  SIGSEGV at +0xa1f8a
  round 2  RHEL 9        libstdc++.so.6.0.29  SIGSEGV at +0xa16ea

Same function, two different builds of the library. The extension is compiled
with GCC 13, so the stream insertion operators are inlined from the GCC 13
headers into this library and then execute against stream objects owned by an
older runtime. Ubuntu 24.04, RHEL 10 and AzureLinux 3 ship a GCC 13-era
libstdc++ and are unaffected.

The satellite log pins it exactly: a passing session's first line after the
handshake is the 'Init' trace emitted by Logger::Log, and on the failing distros
the log stops immediately before it - the process dies on its very first log
call.

The symbol-version ABI gate cannot catch this. Every symbol referenced is within
GLIBCXX_3.4.29 and the gate passes; the dependency is inlined code relying on
newer libstdc++ internals, which no version check can see.

Route the three remaining stream users through stdio, which has no such
coupling: Logger::LogError/LogException/Log and the relay of the user script's
captured stdout/stderr in PythonSession. EKM and the ONNX extension were fixed
the same way earlier. R was never affected because it does not reference the
standard streams, and Java is safe because it links libstdc++ statically.

Also hardens the ZIP normalization added for external libraries, which had two
real defects even though it is not the cause of this crash (an uncaught Python
exception would raise SIGABRT, and both cores are SIGSEGV): entry data was read
after mutating entry.filename, which makes ZipFile.open compare the directory
name against the local header and raise BadZipFile on exactly the archives the
code exists to fix; and bp::exec was unguarded, so any Python exception would
escape into a process where that is fatal. It now reads before renaming, only
rewrites archives that actually contain backslashes, and falls back to the
original package on any failure.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant