[PATCH 2/2] z80-elf: support complex relocation symbols

H. Peter Anvin hpa@zytor.com
Fri Oct 24 16:58:23 GMT 2025


On October 24, 2025 5:44:17 AM PDT, Ismael Luceno <ismael@iodev.co.uk> wrote:
>On 24/Oct/2025 13:58, Jan Beulich wrote:
><...>
>> Hmm, this might have been useful back when there were actually 16-bit programs.
>> With the little use of 16-bit code nowadays, I don't expect it would be overly
>> useful (but feel free to educate me). Such 16-bit code is commonly embedded in
>> bigger 32- or 64-bit binaries (boot loaders, kernels etc), and the 16-bit code then
>> often doesn't live at a linked address which matches its final resting place. IOW
>> segbase wouldn't be a link-time constant. It may not even be a load-time constant,
>> as that code (see e.g. Linux'es and Xen's real-mode trampolines) still needs moving
>> into an available low-memory location, which is runtime determined.
>
>I'd say this is useful when writting x86 firmware, 16-bit code is
>unavoidable for things like the bootblock, early firmware startup, and
>the SMM code at the very least. Unfortunately, so far the only option
>has been legacy or custom formats. 

There has been at least one request for a dedicated ELF relocation for this (this is an absolute minimum, in practice I believe at least two more are needed*), but it is complicated by the sheer fact that i386 uses REL, not RELA, and the relocation wants to be (sym + addend) >> 4, and the addend may not fit into 16 bits.

Either way, the relocation does, for a loadable file, need to be carried into the load format, as you point out, but that is usually handled already by linking with -r and postprocessing the result, as the load format generally isn't ELF but something more like MS-DOS MZ (.exe) format. In this case it would be up to the postprocessor to recognize the relevant expression symbol expressions and turn them into native relocations. 

I am aware of how the Linux kernel does this – I wrote the code! – and I can tell you it is a real hack. It involves magic symbol names which have to be global in order not to get lost, and which are created by a sed script. The Linux kernel code is still rather simple as code+data+bss+stack fits in 64K, what 16-bit programmers call "tiny model."

As far as doing this with "proper" relocations, I once figured it would require, in practice:

R_386_SEG16 (s + a) >> 4
R_386_SUB16 -(s + a)
R_386_SUB32 -(s + a)
R_386_HUGE8 (s + a) & 15

Out of these, SEG16 is the really problematic one, since neither s nor a is guaranteed to fit into 16 bits. Possible to solve that would be to generate a synthetic symbol for a and use stacked relocations, use x32 ELF rather than i386, or enable RELA for i386. It also requires enabling stacked relocations in the linker (for the SUB relocations) which *are* defined in the ELF gABI but not implemented in the x86 linker (because x86 currently do not need them); I have prototyped that part and it is relatively trivial, though. 

However, it seems to me that using expression symbols and pushing this off on the postprocessor is a perfectly viable solution which avoids doing anything new in binutils – it is literally flipping a switch. 

As you point out, having a runtime opt-in flag (opt-out flag for the architectures which use it by default) would be a good idea if the switch is to be flipped across the board, though. 

Now, back to the original patch: I don't personally see a need for adding such a flag for the *specific* case of Z80, which is a legacy architecture at this point (eZ80 notwithstanding.) The sdcc toolchain, which is probably the most commonly used toolchain at this point, already uses expression symbols, but uses a unique object format that even they would like to get away from. 



More information about the Binutils mailing list