Short Answer
This is possible, which is exactly what ld's -R options does.
Example
This small C example shows how to craft such static overlay binaries using modern GNU toolchains.
In the example, we use GCC 11.1.0 and GNU Binutils 2.36.1, which targets to PowerPC.
This should also work on other targets with some modifications.
Sources
1.c:
extern int boot();
void loop() {
while (1);
}
void start() {
boot();
__builtin_unreachable();
}
2.c:
extern void loop();
int boot() {
loop();
return 0;
}
Linker Scripts
1.ld:
OUTPUT_FORMAT("elf32-powerpc", "elf32-powerpc",
"elf32-powerpc")
SECTIONS
{
. = 0x10000;
.text BLOCK(0x10) : {
*(.text*)
}
.data BLOCK(0x10) : {
*(.rodata*)
*(.data*)
}
.bss BLOCK(0x10) : {
*(.bss*)
}
/DISCARD/ : {
*(.eh_frame*)
}
}
2.ld:
OUTPUT_FORMAT("elf32-powerpc", "elf32-powerpc",
"elf32-powerpc")
SECTIONS
{
. = 0x20000;
.text BLOCK(0x10) : {
*(.text*)
}
.data BLOCK(0x10) : {
*(.rodata*)
*(.data*)
}
.bss BLOCK(0x10) : {
*(.bss*)
}
/DISCARD/ : {
*(.eh_frame*)
}
}
Shell Input
cc1 -O3 -mno-eabi -fomit-frame-pointer -fno-stack-protector -fno-exceptions -ffunction-sections -fdata-sections -ffreestanding -fno-ident 1.c -o 1.s
cc1 -O3 -mno-eabi -fomit-frame-pointer -fno-stack-protector -fno-exceptions -ffunction-sections -fdata-sections -ffreestanding -fno-ident 2.c -o 2.s
as -o 1.o 1.s
as -o 2.o 2.s
ld -r -nostdlib --no-eh-frame-hdr -T 1.ld -Map 1.plf.map -o 1.plf 1.o
ld -r -nostdlib --no-eh-frame-hdr -T 2.ld -Map 2.plf.map -o 2.plf 2.o
ld -s -e start -nostdlib --no-eh-frame-hdr -T 1.ld -R 2.plf -Map 1.elf.map -o 1.elf 1.plf
ld -s -nostdlib --no-eh-frame-hdr -T 2.ld -R 1.plf -Map 2.elf.map -o 2.elf 2.plf
Outputs
1.elf (loaded at 0x10000)
0x10000: ; loop
b 0x10000 ; loop
0x10004: ; start
stwu r1, -8(r1)
mflr r0
stw r0, 12(r1)
bl 0x20000 ; boot
2.elf (loaded 0x20000)
0x20000: ; boot
stwu r1, -8(r1)
mflr r0
stw r0, 12(r1)
bl 0x10000 ; loop
lwz r0, 12(r1)
li r3, 0
addi r1, r1, 8
mtlr r0
blr
The 1.elf and 2.elf are what we want.
Notes
- From binutils 2.25, one may use
objcopy --extract-symbol to build an object file with only symbols and address. For example, objcopy --extract-symbol 1.plf 1.sym and link 2.elf with 1.sym. This would significantly reduce size of input files of ld in case there are a lot of overlays.
- Jumps may out of range depend on base address of binaries and architecture's addressing mode. For instance,
jal in MIPS cannot alter the top 4 bits of program counter, and thus one may need to add something like __attribute__((far)) for such long calls to make the compiler to use other ways (e.g., jr) instead.
- Some sections should always stand inside the main binary to work correctly, which may requires extra works during linking process. This includes exceptions (unwinding) in standard C++, and global constant tables (i.e.,
.sdata, .sbss, .lit4, ...) on some targets like PowerPC (eabi) or MIPS (-G n) for efficient data access with constant register(s).
- Things could be more complicated with MinGW since it's
ld seems behave oddly. One may want to employ GNU toolchains that target corresponding unix-like platforms to workaround it at the time of writing.