This article describes some Clang header modules
features that apply to #include. These features enforce a
more explicit dependency graph, which provide documentation purposes and
makes refactoring convenient. The benefits of clean header inclusions
are well described in Include
What You Use as well, so I won't repeat them here.
When using C++20 modules, these features apply to
#include in a global module fragment (module;)
but have no effect for import declarations.
Layering check
-fmodules-decluse
For a #include directive, this option emits an error if
the following conditions are satisfied (see
clang/lib/Lex/ModuleMap.cppdiagnoseHeaderInclusion):
The main file is within a module (called "source module", say,
A).
The main file or an included file from the source module includes a
file from another module B.
A does not have a use-declaration of B (no
use B).
For the first condition, -fmodule-map-file= is needed to
load the source module map and -fmodule-name=A is needed to
indicate that the source file is logically part of module
A.
For the second condition, the module map defining B must
be loaded by specifying -fimplicit-module-maps (implied by
-fmodules and -fcxx-modules) or a
-fmodule-map-file=.
In January I wrote Compressed debug
sections. The venerable zlib shows its age and there are
replacements which are better in every metric except adoption and a
larger memory footprint. The obvious choice was Zstandard, but I was not
so confident about adoptinig it and solving the ecosystem issue. At any
rate, I slowly removed some legacy .zdebug support from
llvm-project so that a new format could be more easily introduced.
In GCC and Clang, there are three major options specifying the
architecture and microarchitecture the generated code can run on. The
general semantics are described below, but each target machine may
assign different semantics.
-march=X: (execution domain) Generate code that can use
instructions available in the architecture X
-mtune=X: (optimization domain) Optimize for the
microarchitecture X, but does not change the ABI or make assumptions
about available instructions
-mcpu=X: Specify both -march= and
-mtune= but can be overridden by the two options. The
supported values are generally the same as -mtune=. The
architecture name is inferred from X
The problem led to heated discussions, some clickbait news, and
claims such as "glibc breaks ABI" and "glibc does not prioritize
compatibility with pre-existing applications".
I feel compelled to demystify the accident and wish that people can
stop defamation to glibc.
Like incremental basic determinism, but builds are also independent
of the name of the build directory. Builds of the same source code on
the same machine produce exactly the same output every time, independent
of the location of the source checkout directory or the build
directory.
On 2022-07-07, I added a RISC-V linker relaxation framework in ld.lld
and implemented R_RISCV_ALIGN/R_RISCV_CALL/R_RISCV_CALL_PLT
relaxation. The changes will be included in the next llvm-project
release 15.0.0. This post describes the implementation.
In ISO C++ standards, [support.c.headers.general] says:
Source files that are not intended to also be valid ISO C should not
use any of the C headers.
Then, [depr.c.headers] describes how a C header name.h
is transformed to the corresponding C++ cname header. There
is a helpful example:
[ Example: The header assuredly provides its declarations
and definitions within the namespace std. It may also provide these
names within the global namespace. The header <stdlib.h> assuredly
provides the same declarations and definitions within the global
namespace, much as in the C Standard. It may also provide these names
within the namespace std. — end example ]
"may also" in the wording allows implementations to provide
mix-and-match, e.g. #include <stdlib.h> may provide
std::exit and #include <cstdlib> may
provide ::exit.
libstdc++ chooses to enable global namespace declarations with C++
cname header. For example,
#include <cstdlib> also includes the corresponding C
header stdlib.h and we get declarations in both the global
namespace and the namespace std.
The compiler knows that the declarations in the namespace
std are identical to the ones in the global namespace. The
compiler recognizes some library functions and can optimize them. By
using the compiler can optimize some C library functions in
the namespace std (e.g. many std::mem* and
std::str* functions).
For some C standard library headers, libstdc++ provides wrappers
(libstdc++-v3/include/c_compatibility/) which take
precedence over the glibc headers. The configuration of libstdc++ uses
--enable-cheaders=c_global
by default. if GLIBCXX_C_HEADERS_C_GLOBAL in
libstdc++-v3/include/Makefile.am describes that the 6
wrappers
(complex.h, fenv.h, tgmath.h, math.h, stdatomic.h, stdlib.h)
shadow the C library headers of the same name. For example,
#include <stdlib.h> includes the wrapper
stdlib.h which includes cstdlib, therefore
bringing exit into the namespace std.
Recently I have fixed two glibc rtld bugs related to early GOT
relocation for retro-computing architectures: m68k and powerpc32. They
are related to the obscure PI_STATIC_AND_HIDDEN macro which
I am going to demystify.
In 2002, PI_STATIC_AND_HIDDEN
was introduced into glibc rtld (runtime loader). This macro
indicates whether accesses to the following types of variables need
dynamic relocations.
static specifier: static int a;
(STB_LOCAL)
hidden visibility attribute:
__attribute__((visibility("hidden"))) int a;
(STB_GLOBAL STV_HIDDEN),
__attribute__((weak, visibility("hidden"))) int a;
(STB_WEAK STV_HIDDEN)
PI in the macro name is an abbreviation for "position
independent". This is a misnomer: a code sequence using GOT is typically
position-independent as well.
In -fPIC mode, the compiler assumes that all non-local
STV_DEFAULT symbols may be preemptible at run time. A
GOT-generating relocation is used and the GOT is typically unavoidable
at link time (on some architectures the linker can optimize out the
GOT). This case is not interesting to rtld as rtld does not need to
export such variables.
Excluding these cases (non-local STV_DEFAULT), all other
variables are known to be non-preemptible at compile time. The compiler
can generate code which is guaranteed to avoid dynamic relocations at
link time.
Non-HIDDEN_VAR_NEEDS_DYNAMIC_RELOC
architectures with PC-relative instructions
To avoid dynamic relocations, the most common approach is to generate
PC-relative instructions, as most modern architectures (e.g. aarch64,
riscv, and x86-64) provide. Using PC-relative instructions to reference
variables assumes that the distance from code to data is a link-time
constant. Nowadays this condition is satisfied everywhere except the
rare FDPIC ABI.
Here are some assembly fragments from architectures using PC-relative
instructions. The instructions may not be familar to you, but that is
fine. We can see that there is no GOT related marker. I have added some
comments indicating the relocation type and the referenced symbol.
var in the C code has internal linkage which lowers to the
STB_LOCAL binding. References to such local symbols are
often redirected to the section symbol (.bss): the
link-time behaviors are identical.
Non-HIDDEN_VAR_NEEDS_DYNAMIC_RELOC
architectures without PC-relative instructions
Many older architectures do not have PC-relative instructions.
x86-32 does not have PC-relative instructions, but it provides a way
to avoid a load from a GOT entry. It achieves this with a detour:
compute the address of _GLOBAL_OFFSET_TABLE_ (GOT base
symbol), then add an offset (S-_GLOBAL_OFFSET_TABLE_) to
get the symbol address. _GLOBAL_OFFSET_TABLE_ is computed
this way: compute the address of a location in code, then add an offset
(_GLOBAL_OFFSET_TABLE_ - PC).
You probably see now how the x86-32 ABI was misdesigned: the
involvement of _GLOBAL_OFFSET_TABLE_ is unnecessary. A
relocation with the calculation of S-_GLOBAL_OFFSET_TABLE_
would achieve the same net effect.
The relocations with GOT in their names
just use the GOT as an anchor. They don't indicate a load from a GOT
entry.
powerpc64 does not have PC-relative instructions before POWER10.
Earlier microarchitectures use TOC-relative relocations to compute the
symbol address.
A few older architectures tend to use a load from a GOT entry. The
GOT entry needs a relative relocation (instead of
R_*_GLOB_DAT: the symbol is non-preemptible, so no symbol
search is needed). See All about Global
Offset Table. In glibc, these architecture define
HIDDEN_VAR_NEEDS_DYNAMIC_RELOC.
Some architectures even assume the distance from code to data may not
be a link-time constant (see All about
Procedure Linkage Table). They do not provide a relocation with a
calculation of S-_GLOBAL_OFFSET_TABLE_ or S-P.
# nios2: r22 is a callee-saved register which requires a spill and expensive setup ldw r3, %got(var)(r22) # R_NIOS2_GOT16 var ldw r2, 0(r3) addi r2, r2, 1 stw r2, 0(r3)
# powerpc32: r30 is a callee-saved register which requires a spill and expensive setup lwz 9,.LC0-.LCTOC1(30) lwz 3,0(9) addi 3,3,1 stw 3,0(9)
.section ".got2","aw" # Like a manual GOT section .align 2 .LCTOC1 = .+32768 .LC0: .long .LANCHOR0 # R_PPC_ADDR32 .bss; may become R_PPC_RELATIVE at link time
.section ".bss" .set .LANCHOR0,. + 0 var: .zero 4
The first task of rtld is to relocate itself and bind all symbols to
itself. Afterward, non-preemptible functions and data can be freely
accessed.
On architectures where a GOT entry is used to access a
non-preemptible variable, rtld needs to be careful not to reference such
variables before relative relocations are applied. In
rtld.c, _dl_start has the following code:
1 2 3 4 5 6 7 8 9 10
if (bootstrap_map.l_addr) { // Apply R_*_RELATIVE, R_*_GLOB_DAT, and R_*_JUMP_SLOT. ELF_DYNAMIC_RELOCATE (&bootstrap_map, NULL, 0, 0, 0); }
_rtld_local_ro is a hidden global variable. Taking its
address may be reordered before ELF_DYNAMIC_RELOCATE by the
compiler. On an architecture using a GOT entry to load the address, the
reordering will make the subsequent memory store
(_rtld_local_ro.dl_find_object) to crash, since the GOT
address is incorrect: it's zero or the link-time address instead of the
run-time address.
I was pretty sure there is a relocation bug but was not immediately
clear which piece of code may be at fault.
Nowadays there aren't many choices for powerpc32 images. Void Linux ppc still provides
powerpc32 glibc and musl images. I downloaded one and fed it into qemu,
booted it with
qemu-system-ppc -machine mac99 -m 2047M -cdrom void-live-ppc-20210825.iso -net nic -net user,smb=$HOME/Dev -boot d.
I booted into the 4.4.261 kernel because gdb aborts immediately with
5.13.12 kernel. Daniel Kolesa mentioned this 5.x kernel incompatibility
to me and nobody has looked into it yet.
The live CD provides free space of about 1GiB and I can install
cifs-utils and gdb. Then run ld.so under gdb.
1 2 3 4 5 6
xbps-install -S xbps-install cifs-utils gdb cgdb mkdir ~/Dev mount -t cifs -o vers=3.0 //10.0.2.4/qemu ~/Dev cd ~/Dev/glibc/out/ppc gdb -ex 'directory ../../elf' -ex r elf/ld.so
ld.so has 671 R_68K_RELATIVE relocations
and one R_68K_GLOB_DAT for
__stack_chk_guard@@GLIBC_2.4. The following function is
used to apply a relocation. It is shared by self-relocation and
relocation for other modules. The self-relocation code defines
RTLD_BOOTSTRAP and needs just R_68K_RELATIVE,
R_68K_GLOB_DAT, and R_68K_JMP_SLOT.
if (__builtin_expect (r_type == R_68K_RELATIVE, 0)) *reloc_addr = map->l_addr + reloc->r_addend; else { ... switch (r_type) { case R_68K_COPY: ... case R_68K_GLOB_DAT: case R_68K_JMP_SLOT: *reloc_addr = value; break;
However, somehow many case labels were available for self-relocation.
GCC compiles the switch statement into a jump table which requires
loading an address from GOT. With some clean-up to generic relocation
code, GCC decides to perform loop-invariant code motion and hoists the
load of the jump table address. The hoisted load is before relative
relocations are applied, so the jump table address is incorrect.
The foolproof approach is to add an optimization barrier (e.g.
calling an non-inlinable function after relative relocations are
resolved). That is non-trivial given the code structure. So Andreas
Schwab suggested a simple approach by avoiding the jump table: handle
just the essential relocations.
The faulty code concealed well and I could not have found it without
a debugger. It took me a while to set up a m68k image using q800. The
memory is limited to 1000MiB and the emulation is very slow. Linux 5.19
is expected to gain the support for a virtual Motorola 68000 machine.
With qemu-system-m68k -M virt things will become
better.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# Installation
7z x debian-11.0.0-m68k-NETINST-1.iso install/kernels/vmlinux-5.16.0-5-m68k install/cdrom/initrd.gz mv install/kernels/vmlinux-5.16.0-5-m68k install/cdrom/initrd.gz .
For added fun, consider the addresses of two hidden symbols, like
__ehdr_start and _end. GCC may generate a
vector load using a constant pool that requires dynamic relocation. If
the vector load is reordered before ELF_DYNAMIC_RELOCATE,
the loaded value will be incorrect. To address this, glibc 2.42
introduced inline assembly compiler barriers (https://sourceware.org/bugzilla/show_bug.cgi?id=33088).
Non-HIDDEN_VAR_NEEDS_DYNAMIC_RELOC
architectures with GCC vectorization
For added fun, GCC vectorization can introduce GOT-like loads.
Consider the addresses of two hidden symbols, like
__ehdr_start and _end. GCC may generate a
vector load using a constant pool that requires dynamic relocation.
If the vector load is reordered before
ELF_DYNAMIC_RELOCATE, it may load an incorrect value. This
issue might not cause an immediate crash but could lead to problems
later when link map information is accessed, such as during a
backtrace. For details on how this bug was discovered, see
https://blog.sergiodj.net/posts/gcc-glibc-stack-unwinding-relocations-bug/.
stage 1 (ldso/dlstart.c): only relative relocations are
applied. This allows static variables can be accessed.
stage 2 __dls2: This applies non-relative
relocations.
stage 2b __dls2b: Set up thread pointer with a TLS
stub.
stage 3 __dls3: Load the executable and immediately
loaded shared objects. Apply relocations and possibly relocate rtld/libc
itself again for possible symbol interposition (e.g.
R_*_COPY, interposed malloc implementation).
Each stage uses a PC-relative code sequence to load the address of
the next stage entry point, and then jump to it. This serves as a strong
compiler barrier preventing code reordering.
(In glibc, elf/rtld.cELF_DYNAMIC_RELOCATE (&bootstrap_map, NULL, 0, 0, 0);
is kinda like musl's stage 1 plus stage 2.)
Stage 1 computes the entry of stage 2 with
GETFUNCSYM(&dls2, __dls2, base+dyn[DT_PLTGOT]); where
GETFUNCSYM is defined for every port:
This approach is elegant. It even allows a static or hidden function
call with a dynamic relocation, though I haven't found such an
architecture in my testing.