Module: Rubycc::Preprocess::GlibcVersion
- Defined in:
- lib/rubycc/preprocess/glibc_version.rb
Overview
Measures the glibc minor version of the C library a compile will link against, so <features.h>'s GLIBC/GLIBC_MINOR can describe this host instead of the machine the bundled headers were written on.
rubycc ships one copy of the libc headers for every host (R8), so the version those headers report used to be a constant: 39, the reference platform's. On any other host that constant made version-gated header code ("#if __GLIBC_PREREQ (2, 38)") select a different branch than the host's own gcc does, and in the over-reporting direction — claiming 2.39 on a 2.34 host — the selected branch can reach for a symbol that libc does not have, which surfaces as an undefined symbol at link or dlopen time (docs/development/GAPS.md gap U). The value therefore has to be measured, and the only honest place to measure it is the C library itself: not the host's /usr/include (a distroless target has none, which is the whole reason the bundled headers exist) and not the host's gcc (rubycc must work without one).
What a glibc image states about itself is the set of symbol versions it
defines — GLIBC_2.2.5, GLIBC_2.3, ... GLIBC_2.39 — recorded in its
.gnu.version_d table, which ELFReader reads. The highest GLIBC_2.
That measure has one known way of coming out low: a glibc release that added no new symbol to libc.so.6 gets no version node of its own (2.19, 2.20, 2.21 and 2.37 are the releases where this happened), so on such a host the highest node names the previous release. The result is then conservative rather than wrong in the dangerous direction — a gate may take an older branch, never one whose symbols are missing — but it does differ from what the host's own headers say. Reading the version out of glibc's banner string instead would be exact; it is not done here because that means pattern-matching English prose in .rodata, whereas the version table is a structure the ABI defines.
Which file is "the C library" is a question about the target, never
about this host's own paths: the search runs through LibraryResolver with
the target's own system directories, exactly as the -lc of a real link
would, so a cross compile measures the cross libc and a host with no libc
at all measures nothing and falls back (see .minor_for).
Constant Summary collapse
- LIBC_LIBRARY =
The library request that finds the C library. Deliberately the same spelling a link uses, so this reads whatever
-lcwould reach — including through the GNU-ld script glibc installs as libc.so. "c"- RELEASE_VERSION =
A version node naming a whole glibc release: "GLIBC_2.39" and kin. The three-component names ("GLIBC_2.2.5") and the non-release ones ("GLIBC_PRIVATE", "GLIBC_ABI_DT_RELR") are not releases whose minor number GLIBC_MINOR could carry, so only this shape counts. The major number is pinned at 2 for the same reason GLIBC is: glibc has carried it since 1997, and a 3.x would be a different ABI whose minor number this measurement should not be feeding to a 2.x header anyway. Naming glibc's own spelling here is not the hard-coded host identity test/test_platform_literals.rb guards against: this module measures glibc specifically, and a libc that spells its versions otherwise is precisely the "not measurable" case (see .minor_in).
/\AGLIBC_2\.(\d+)\z/
Class Method Summary collapse
-
.libc_images(target) ⇒ Object
The shared objects a
-lclink fortargetwould bind against, in the order the linker sees them. -
.measure(target) ⇒ Object
The uncached measurement, kept separate so a caller (and the tests) can ask for a fresh reading.
-
.minor_for(target) ⇒ Object
The measured glibc minor version for
target(an architecture name or triple, as LibraryResolver takes), or nil when this host has no glibc to measure — no C library on the target's search path, a libc that is not glibc (musl defines no version nodes), or one this reader cannot parse. -
.minor_in(path) ⇒ Object
The highest glibc release version the shared object at
pathdefines, or nil when it defines none — which is the answer for a musl libc, for a relocatable object, and for anything that is not an ELF file this reader accepts.
Class Method Details
.libc_images(target) ⇒ Object
The shared objects a -lc link for target would bind against, in
the order the linker sees them. A target with no C library on its
search path is not an error here (a cross target whose sysroot is not
installed, a musl image with no glibc): it simply cannot be measured.
116 117 118 119 120 |
# File 'lib/rubycc/preprocess/glibc_version.rb', line 116 def libc_images(target) Link::LibraryResolver.resolve([LIBC_LIBRARY], target: target).needed rescue Link::LinkError, SystemCallError [] end |
.measure(target) ⇒ Object
The uncached measurement, kept separate so a caller (and the tests)
can ask for a fresh reading. The first library that states a version
settles it: the -lc resolution yields libc itself ahead of the
loader it names, and a glibc loader repeats libc's versions rather
than adding to them.
92 93 94 95 96 97 98 |
# File 'lib/rubycc/preprocess/glibc_version.rb', line 92 def measure(target) libc_images(target).each do |path| minor = minor_in(path) return minor if minor end nil end |
.minor_for(target) ⇒ Object
The measured glibc minor version for target (an architecture name or
triple, as LibraryResolver takes), or nil when this host has no glibc
to measure — no C library on the target's search path, a libc that is
not glibc (musl defines no version nodes), or one this reader cannot
parse. The caller decides what an unmeasurable host means; the
preprocessor falls back to the value the bundled <features.h> carries.
Memoized per target for the life of the process: a translation unit must not pay a 2 MB read of libc for a value that cannot change while the compiler runs (N1). nil is memoized too, so a host without a libc searches once rather than once per unit.
80 81 82 83 84 85 |
# File 'lib/rubycc/preprocess/glibc_version.rb', line 80 def minor_for(target) cache = (@minor_for ||= {}) return cache[target] if cache.key?(target) cache[target] = measure(target) end |
.minor_in(path) ⇒ Object
The highest glibc release version the shared object at path defines,
or nil when it defines none — which is the answer for a musl libc, for
a relocatable object, and for anything that is not an ELF file this
reader accepts. An unreadable file is likewise "cannot measure", not
an error: this runs on the way to a compile that must still happen.
105 106 107 108 109 110 |
# File 'lib/rubycc/preprocess/glibc_version.rb', line 105 def minor_in(path) reader = ObjFile::ELFReader.read_file(path) reader.version_definitions.filter_map { |v| v.name[RELEASE_VERSION, 1]&.to_i }.max rescue ObjFile::ELFFormatError, SystemCallError, IOError nil end |