TCC developers,
I've discovered a rare bug specific to the AArch64 backend that leads
to incorrect address calculation when accessing a global symbol with a
large offset (addend). It falls on the case with comments saying 'very
unlikely'.
When the addend is larger than 0xffffff, the AArch64 backend enters a
special code path intended to handle such large offsets. However,
within this path, the code incorrectly generates an ADD Xr, Xt, #0
instruction (effectively a MOV Xr, Xt). This instruction overwrites
the symbol's base address (in register Xr) with the large offset value
(loaded into the temporary register Xt), instead of adding the offset
to the base address.
This issue does not affect the x86 or RISC-V backends.
----------
Reproduction code:
#include <stdio.h>
#include <stdint.h>
char big[0x1000000 + 8];
int main(void) {
uintptr_t base = (uintptr_t)big;
uintptr_t p = (uintptr_t)(big + 0x1000000);
printf("base=%p p=%p diff=%#lx\n", (void *)base, (void *)p, (unsigned
long)(p - base));
// diff should be 0x1000000. But in aarch64, p becomes 0x1000000.
return 0;
}
----
The patch is offered as follows:
diff --git a/arm64-gen.c b/arm64-gen.c
index 2038aeba..cc1201dc 100644
--- a/arm64-gen.c
+++ b/arm64-gen.c
@@ -476,7 +476,7 @@ static void arm64_sym(int r, Sym *sym, unsigned long addend)
int t = r ? 0 : 1;
o(0xf81f0fe0 | t); /* str xt, [sp, #-16]! */
arm64_movimm(t, addend & ~0xfffffful); // use xt for addent
- o(0x91000000 | r | (t << 5)); /* add xr, xt, #0 */
+ o(0x8B000000 | (t << 16) | (r << 5) | r); /* add xr, xr, xt */
o(0xf84107e0 | t); /* ldr xt, [sp], #16 */
}
}
This change fixes the issue on AArch64 and produces the correct signed result.
_______________________________________________
Tinycc-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/tinycc-devel