I encountered what appears to be an extended ELF section-index handling issue in Zig 0.16.0.
Environment
- Zig 0.16.0
- Native
x86_64-linux-gnu - Ubuntu 26.04.1 under WSL2
- RayClay 0.8.11 compiled as a single-header C implementation
The RayClay header is compiled with:
-std=gnu11
-DRAYCLAY_IMPLEMENTATION
-DRC_GLFW_X11=1
-DRC_GLFW_WAYLAND=1
-ffunction-sections
-fdata-sections
Initial failure
A Debug zig build produced undefined-symbol errors for RayClay functions, including:
undefined symbol: rcRunApp
The same happened for many other public RayClay functions.
Initially, this looked like the implementation object had not been compiled or attached to the Zig module. However, rayclay.o existed and contained the missing symbols:
rcGetStyle GLOBAL DEFAULT section 291
rcRunApp GLOBAL DEFAULT section 2140
rcAppRequestClose GLOBAL DEFAULT section 2146
For example:
readelf -sW rayclay.o | grep rcRunApp
produced:
44719: 0000000000000000 2109 FUNC GLOBAL DEFAULT 2140 rcRunApp
Therefore, the symbol was present in the object, but Zig’s linker behaved as though it were undefined.
Unusually large ELF section table
The Debug object had 78,010 sections:
Number of section headers: 0 (78010)
The zero is ELF extended section numbering, not an actual zero section count. The object also had a .symtab_shndx section.
The normal ELF section-index range becomes reserved starting at 0xff00, so this object requires extended section and symbol indexes.
The section breakdown was approximately:
| Category | Count |
|---|---|
.text.* and text relocations |
3,377 |
.data.* and data relocations |
72,482 |
.rodata.* and relocations |
1,071 |
.bss.* |
1,054 |
| Debug and other sections | 26 |
| Total | 78,010 |
The largest contributors were:
36,252 .data.* sections
36,230 .rela.data.* sections
Many were compiler-generated entries such as:
.data..L__unnamed_23
.rela.data..L__unnamed_23
.data..L__unnamed_24
.rela.data..L__unnamed_24
The Debug object also referenced UBSan handlers:
__ubsan_handle_type_mismatch_v1
__ubsan_handle_pointer_overflow
__ubsan_handle_add_overflow
__ubsan_handle_out_of_bounds
My current understanding is that Debug C compilation generates a large amount of UBSan metadata for this approximately 80,000-line implementation header. At -O0, most of that metadata remains, and -fdata-sections places each metadata object in a separate ELF section. Objects containing references also receive separate relocation sections.
GNU readelf can parse the object and find the RayClay symbols, but Zig’s default linker fails to resolve them.
Bundled LLD result
I temporarily restored the original -fdata-sections configuration and enabled Zig’s bundled LLD.
The goal was to determine whether the issue was specific to Zig’s default ELF linker while retaining the existing data-section layout.
Instead, the bundled LLD process terminated with SIGSEGV.
I do not have a symbolized backtrace, so I cannot say exactly where LLD crashed. The crash appears correlated with the same 78,010-section object and its extended section-index tables.
Working workaround
Changing only the Debug build to:
-fno-data-sections
reduced the object from 78,010 sections to 3,509 sections:
| Category | Count |
|---|---|
.text.* and text relocations |
3,377 |
| Writable data and relocations | 4 |
| Read-only data and relocations | 108 |
| BSS | 1 |
| Debug and other sections | 19 |
| Total | 3,509 |
This does not disable UBSan or remove the generated metadata. It groups compatible data objects into shared sections instead of creating a separate section for every object.
The resulting Debug build links and runs successfully.
The current workaround is:
if (optimize == .Debug)
"-fno-data-sections"
else
"-fdata-sections"
Release behavior
The issue only occurred in Debug.
The release C objects did not contain references to __ubsan_* handlers and remained far below the extended-index threshold:
| Mode | Flag | Sections | Result |
|---|---|---|---|
| Debug | -fdata-sections |
78,010 | Undefined-symbol failure |
| Debug | -fno-data-sections |
3,509 | Works |
| ReleaseSafe | -fdata-sections |
3,100 | Works |
| ReleaseFast | -fdata-sections |
2,914 | Works |
| ReleaseSmall | -fdata-sections |
Below threshold | Works |
I also tested ReleaseFast with -fno-data-sections. It worked, but the final executable was slightly larger:
ReleaseFast with -fdata-sections: 3,912,152 bytes
ReleaseFast with -fno-data-sections: 3,920,520 bytes
For that reason, I limited the workaround to Debug.
Questions
- Is extended ELF section numbering or
SHT_SYMTAB_SHNDXhandling a known limitation in Zig 0.16’s default Linux linker? - Is the bundled LLD
SIGSEGVlikely a separate Zig/LLD integration bug? - Is using
-fno-data-sectionsonly for Debug the recommended workaround? - Is there a linker option that allows Zig to correctly consume this object while retaining
-fdata-sections? - Would a reduced reproducer or the original object file be useful for an issue report?
The final Debug and release executables otherwise build and run correctly on both the Wayland and X11 GLFW backends.