From b42238b2fb8ea5101b9635d600809bfc05f2a3fe Mon Sep 17 00:00:00 2001 From: Talaria <324092415+talaria0101@users.noreply.github.com> Date: Thu, 22 Oct 2026 08:59:32 +0000 Subject: [PATCH] Give the ppc64le resolver a real ABI frame header glfwd_resolve_asm saved the argument registers at 0..64(sp), which is the back chain, CR, LR and TOC save area plus the parameter save area. A C callee writes its caller's LR slot at 16(sp) and the TOC slot at 24(sp), so glfwd_resolve_one, which is not a leaf, overwrote the saved r5 and r6 on the first call through every slot. The first call of any GL entry point taking a third or fourth integer argument was handed garbage. Reported in issue 59 on the gtk3-demo ppc64le AppImage: the shipped gl-fwd.so exits 139 when preloaded, one built from this change stays alive. Not rerun here. Registers are now saved above the 96-byte callee area (32-byte ELFv2 header plus a 64-byte parameter area). The frame is 288 bytes. make gl-fwd-qemu-check gains a first-call test with eight live integer arguments; it fails on the old frame (ppc64le) and passes on the new one. The ELFv1 big-endian ppc64 trampoline block is removed. ppc64 is not built (see docs/limits.md), nothing ran that block, and a ppc64 ELFv1 compile now stops at the existing "needs a tail-jump trampoline" error. --- docs/building.md | 4 +- docs/report/10-measured-versus-assumed.md | 3 +- src/Makefile | 10 +- src/gl-fwd.c | 229 ++++++---------------- 4 files changed, 67 insertions(+), 179 deletions(-) diff --git a/docs/building.md b/docs/building.md index 73c3463..c053639 100644 --- a/docs/building.md +++ b/docs/building.md @@ -221,7 +221,9 @@ The qemu check is stronger than a smoke run: it passes one argument of every register class through a trampoline (integer, long, double, pointer), calls each entry point twice so the patched slot is taken as well as the resolver, checks a double return value, and calls a name the target does not define so -the absent stub is exercised. On ppc64le it runs the ELFv2 global-entry +the absent stub is exercised. It also makes the first call of a slot with +all eight integer argument registers live, which the resolver must save and +restore across a C call. On ppc64le it runs the ELFv2 global-entry TOC rebuild. ⚠ qemu emulates the instructions, not a memory model. Real silicon is what diff --git a/docs/report/10-measured-versus-assumed.md b/docs/report/10-measured-versus-assumed.md index cf597f6..6352368 100644 --- a/docs/report/10-measured-versus-assumed.md +++ b/docs/report/10-measured-versus-assumed.md @@ -66,7 +66,8 @@ E76b ABSENT entry point called: t_absent rebuilds the TOC from r12. The qemu vehicle passes one argument of every register class (integer, long, double, pointer) and verifies each value inside the target, calls twice so both the resolver and the patched slot are taken, - checks a double return, and exercises the absent stub. Measured with the + checks a double return, makes the first call of a slot with eight live + integer arguments, and exercises the absent stub. Measured with the Debian bullseye cross sysroots (glibc 2.31) for riscv64 and ppc64le, and the trixie sysroot (glibc 2.41) for loongarch64 under qemu 10.0, because qemu 7.2 faults on that glibc. The full artefact set also builds diff --git a/src/Makefile b/src/Makefile index a963c36..3001c76 100644 --- a/src/Makefile +++ b/src/Makefile @@ -261,7 +261,9 @@ gl-fwd-asm-check: # ppc64le. The vehicle passes one argument of every # register class through a trampoline, calls twice so the patched slot is # taken too, checks a double return value, and calls a name the target does -# not define so the absent stub is exercised; m.c verifies each value and +# not define so the absent stub is exercised, and makes the first call of +# a slot with all eight integer argument registers live, which the resolver +# must save and restore across a C call; m.c verifies each value and # prints "all passed" only when every one held. # # The emulator is named rather than reached through binfmt_misc: a binfmt @@ -295,9 +297,9 @@ gl-fwd-qemu-check: continue; \ fi; \ d=$$(mktemp -d); \ - printf 'long t_args(int a, long b, double c, const char *d) {\n if (a != 7) return 1;\n if (b != 0x123456789L) return 2;\n if (c != 2.718281828) return 3;\n if (!d || d[0] != 120 || d[1] != 121) return 4;\n return a + b + (long)c + d[2];\n}\ndouble t_fret(int a) { return a * 1.5; }\nvoid t_one(void) {}\n' > $$d/tgt.c; \ - printf '#ifndef GLFWD_SONAME\n#define GLFWD_SONAME "libtgt.so"\n#define GLFWD_COUNT 5\n#endif\nGLFWD_SYM(0, t_args)\nGLFWD_SYM(1, t_fret)\nGLFWD_SYM(2, t_one)\nGLFWD_SYM(3, t_absent)\nGLFWD_SYM(4, t_absent_f)\n' > $$d/tgt-fwd.h; \ - printf '#include \nextern long t_args(int, long, double, const char *);\nextern double t_fret(int);\nextern void t_one(void);\nextern long t_absent(int);\nextern double t_absent_f(int);\nint main(void) {\n\tconst char *s = "xyz";\n\tlong r1 = t_args(7, 0x123456789L, 2.718281828, s);\n\tlong r2 = t_args(7, 0x123456789L, 2.718281828, s);\n\tif (r1 != r2) return 1;\n\tdouble d1 = t_fret(4), d2 = t_fret(4);\n\tif (d1 != 6.0 || d2 != 6.0) return 2;\n\tt_one();\n\tif (t_absent(9) != 0) return 3;\n\tif (t_absent_f(9) != 0.0) return 4;\n\tputs("all passed");\n\treturn 0;\n}\n' > $$d/m.c; \ + printf 'long t_args(int a, long b, double c, const char *d) {\n if (a != 7) return 1;\n if (b != 0x123456789L) return 2;\n if (c != 2.718281828) return 3;\n if (!d || d[0] != 120 || d[1] != 121) return 4;\n return a + b + (long)c + d[2];\n}\ndouble t_fret(int a) { return a * 1.5; }\nvoid t_one(void) {}\nlong t_many(long a, long b, long c, long d, long e, long f, long g, long h) {\n return (a==1 && b==2 && c==3 && d==4 && e==5 && f==6 && g==7 && h==8) ? 0 : 1;\n}\n' > $$d/tgt.c; \ + printf '#ifndef GLFWD_SONAME\n#define GLFWD_SONAME "libtgt.so"\n#define GLFWD_COUNT 6\n#endif\nGLFWD_SYM(0, t_args)\nGLFWD_SYM(1, t_fret)\nGLFWD_SYM(2, t_one)\nGLFWD_SYM(3, t_absent)\nGLFWD_SYM(4, t_absent_f)\nGLFWD_SYM(5, t_many)\n' > $$d/tgt-fwd.h; \ + printf '#include \nextern long t_args(int, long, double, const char *);\nextern double t_fret(int);\nextern void t_one(void);\nextern long t_absent(int);\nextern double t_absent_f(int);\nextern long t_many(long, long, long, long, long, long, long, long);\nint main(void) {\n\t/* First call of a slot, all eight argument registers live: the resolver must hand every one back intact. */\n\tif (t_many(1, 2, 3, 4, 5, 6, 7, 8) != 0) return 5;\n\tconst char *s = "xyz";\n\tlong r1 = t_args(7, 0x123456789L, 2.718281828, s);\n\tlong r2 = t_args(7, 0x123456789L, 2.718281828, s);\n\tif (r1 != r2) return 1;\n\tdouble d1 = t_fret(4), d2 = t_fret(4);\n\tif (d1 != 6.0 || d2 != 6.0) return 2;\n\tt_one();\n\tif (t_absent(9) != 0) return 3;\n\tif (t_absent_f(9) != 0.0) return 4;\n\tputs("all passed");\n\treturn 0;\n}\n' > $$d/m.c; \ cp gl-fwd.c ld-conf.h cld-env.h $$d/; \ if $$t-gcc -shared -fPIC -O2 $$d/tgt.c -o $$d/libtgt.so -Wl,-soname,libtgt.so -lm \ && (cd $$d && $$t-gcc -shared -fPIC -O2 -DGLFWD_TABLE='"tgt-fwd.h"' \ diff --git a/src/gl-fwd.c b/src/gl-fwd.c index febb911..01574ac 100644 --- a/src/gl-fwd.c +++ b/src/gl-fwd.c @@ -545,69 +545,73 @@ __asm__(".abiversion 2\n" /* r3-r10 are the argument registers, f1-f13 the floating-point ones (the * ABI allows float arguments past f8 in the excess slots, and saving all * thirteen costs less than proving an entry point cannot use them), and r11 - * carries the static chain, as %r10 does on x86-64. The parameter save area - * at 0(sp) belongs to the caller of glfwd_resolve_one, which takes one - * argument in a register and never writes it. The stack stays 16-byte - * aligned through the C call. */ + * carries the static chain, as %r10 does on x86-64. + * + * The bottom of the frame belongs to glfwd_resolve_one, not to this code: the + * back chain at 0(sp), the CR save at 8(sp), the LR save at 16(sp) and the + * TOC save at 24(sp) are all written by a C callee, and the parameter save + * area above them is the callee's spill space. The 32-byte ELFv2 header plus + * a 64-byte parameter area is 96 bytes, so registers are saved from 96(sp). + * The stack stays 16-byte aligned through the C call. */ __asm__(".text\n" ".globl glfwd_resolve_asm\n" ".hidden glfwd_resolve_asm\n" ".type glfwd_resolve_asm,@function\n" "glfwd_resolve_asm:\n" - " stdu 1, -192(1)\n" - " std 3, 0(1)\n" + " stdu 1, -288(1)\n" + " std 3, 96(1)\n" " mr 3, 0\n" " mflr 0\n" - " std 0, 184(1)\n" - " std 4, 8(1)\n" - " std 5, 16(1)\n" - " std 6, 24(1)\n" - " std 7, 32(1)\n" - " std 8, 40(1)\n" - " std 9, 48(1)\n" - " std 10, 56(1)\n" - " stfd 1, 64(1)\n" - " stfd 2, 72(1)\n" - " stfd 3, 80(1)\n" - " stfd 4, 88(1)\n" - " stfd 5, 96(1)\n" - " stfd 6, 104(1)\n" - " stfd 7, 112(1)\n" - " stfd 8, 120(1)\n" - " stfd 9, 128(1)\n" - " stfd 10, 136(1)\n" - " stfd 11, 144(1)\n" - " stfd 12, 152(1)\n" - " stfd 13, 160(1)\n" - " std 11, 168(1)\n" + " std 0, 272(1)\n" + " std 4, 104(1)\n" + " std 5, 112(1)\n" + " std 6, 120(1)\n" + " std 7, 128(1)\n" + " std 8, 136(1)\n" + " std 9, 144(1)\n" + " std 10, 152(1)\n" + " stfd 1, 160(1)\n" + " stfd 2, 168(1)\n" + " stfd 3, 176(1)\n" + " stfd 4, 184(1)\n" + " stfd 5, 192(1)\n" + " stfd 6, 200(1)\n" + " stfd 7, 208(1)\n" + " stfd 8, 216(1)\n" + " stfd 9, 224(1)\n" + " stfd 10, 232(1)\n" + " stfd 11, 240(1)\n" + " stfd 12, 248(1)\n" + " stfd 13, 256(1)\n" + " std 11, 264(1)\n" " bl glfwd_resolve_one\n" " mr 12, 3\n" " mtctr 3\n" - " ld 11, 168(1)\n" - " lfd 1, 64(1)\n" - " lfd 2, 72(1)\n" - " lfd 3, 80(1)\n" - " lfd 4, 88(1)\n" - " lfd 5, 96(1)\n" - " lfd 6, 104(1)\n" - " lfd 7, 112(1)\n" - " lfd 8, 120(1)\n" - " lfd 9, 128(1)\n" - " lfd 10, 136(1)\n" - " lfd 11, 144(1)\n" - " lfd 12, 152(1)\n" - " lfd 13, 160(1)\n" - " ld 3, 0(1)\n" - " ld 4, 8(1)\n" - " ld 5, 16(1)\n" - " ld 6, 24(1)\n" - " ld 7, 32(1)\n" - " ld 8, 40(1)\n" - " ld 9, 48(1)\n" - " ld 10, 56(1)\n" - " ld 0, 184(1)\n" + " ld 11, 264(1)\n" + " lfd 1, 160(1)\n" + " lfd 2, 168(1)\n" + " lfd 3, 176(1)\n" + " lfd 4, 184(1)\n" + " lfd 5, 192(1)\n" + " lfd 6, 200(1)\n" + " lfd 7, 208(1)\n" + " lfd 8, 216(1)\n" + " lfd 9, 224(1)\n" + " lfd 10, 232(1)\n" + " lfd 11, 240(1)\n" + " lfd 12, 248(1)\n" + " lfd 13, 256(1)\n" + " ld 3, 96(1)\n" + " ld 4, 104(1)\n" + " ld 5, 112(1)\n" + " ld 6, 120(1)\n" + " ld 7, 128(1)\n" + " ld 8, 136(1)\n" + " ld 9, 144(1)\n" + " ld 10, 152(1)\n" + " ld 0, 272(1)\n" " mtlr 0\n" - " addi 1, 1, 192\n" + " addi 1, 1, 288\n" " bctr\n" ".size glfwd_resolve_asm, .-glfwd_resolve_asm\n"); /* The trampoline gets the table through r2 (its own TOC, valid at the local @@ -630,127 +634,6 @@ __asm__(".text\n" " mtctr 12\n" \ " bctr\n" \ ".size " #n ", .-" #n "\n"); -#elif defined(__powerpc64__) -/* ELFv1, the big-endian ppc64 ABI. Function pointers are three-word - * DESCRIPTORS in .opd (entry, TOC, environment pointer), dlsym returns - * descriptors, and a call through one sets r2 and r11 from the descriptor - * before reaching the code. So every exported symbol here gets a .opd entry, - * and every table slot holds a descriptor address, ours or the target's. - * Assembled here, and RUN under qemu-user against a ppc64 sysroot. See - * `make gl-fwd-qemu-check` and ../docs/report/10-measured-versus-assumed.md 10. - * Not run on ppc64 silicon. */ -__asm__(".section .opd,\"aw\"\n" - ".align 3\n" - ".globl glfwd_absent\n" - ".hidden glfwd_absent\n" - "glfwd_absent:\n" - " .quad .Lglfwd_absent_body, .TOC., 0\n" - ".previous\n" - ".text\n" - ".type .Lglfwd_absent_body,@function\n" - ".Lglfwd_absent_body:\n" - " stdu 1, -16(1)\n" - " li 0, 0\n" - " std 0, 0(1)\n" - " lfd 1, 0(1)\n" - " li 3, 0\n" - " addi 1, 1, 16\n" - " blr\n" - ".size .Lglfwd_absent_body, .-.Lglfwd_absent_body\n"); -__asm__(".section .opd,\"aw\"\n" - ".align 3\n" - ".globl glfwd_resolve_asm\n" - ".hidden glfwd_resolve_asm\n" - "glfwd_resolve_asm:\n" - " .quad .Lglfwd_resolve_asm_body, .TOC., 0\n" - ".previous\n" - ".text\n" - ".type .Lglfwd_resolve_asm_body,@function\n" - ".Lglfwd_resolve_asm_body:\n" - " stdu 1, -192(1)\n" - " std 3, 0(1)\n" - " mr 3, 0\n" - " mflr 0\n" - " std 0, 184(1)\n" - " std 4, 8(1)\n" - " std 5, 16(1)\n" - " std 6, 24(1)\n" - " std 7, 32(1)\n" - " std 8, 40(1)\n" - " std 9, 48(1)\n" - " std 10, 56(1)\n" - " stfd 1, 64(1)\n" - " stfd 2, 72(1)\n" - " stfd 3, 80(1)\n" - " stfd 4, 88(1)\n" - " stfd 5, 96(1)\n" - " stfd 6, 104(1)\n" - " stfd 7, 112(1)\n" - " stfd 8, 120(1)\n" - " stfd 9, 128(1)\n" - " stfd 10, 136(1)\n" - " stfd 11, 144(1)\n" - " stfd 12, 152(1)\n" - " stfd 13, 160(1)\n" - " bl glfwd_resolve_one\n" - " ld 2, 8(3)\n" - " ld 11, 16(3)\n" - " ld 0, 0(3)\n" - " mtctr 0\n" - " lfd 1, 64(1)\n" - " lfd 2, 72(1)\n" - " lfd 3, 80(1)\n" - " lfd 4, 88(1)\n" - " lfd 5, 96(1)\n" - " lfd 6, 104(1)\n" - " lfd 7, 112(1)\n" - " lfd 8, 120(1)\n" - " lfd 9, 128(1)\n" - " lfd 10, 136(1)\n" - " lfd 11, 144(1)\n" - " lfd 12, 152(1)\n" - " lfd 13, 160(1)\n" - " ld 3, 0(1)\n" - " ld 4, 8(1)\n" - " ld 5, 16(1)\n" - " ld 6, 24(1)\n" - " ld 7, 32(1)\n" - " ld 8, 40(1)\n" - " ld 9, 48(1)\n" - " ld 10, 56(1)\n" - " ld 0, 184(1)\n" - " mtlr 0\n" - " addi 1, 1, 192\n" - " bctr\n" - ".size .Lglfwd_resolve_asm_body, .-.Lglfwd_resolve_asm_body\n"); -/* The caller's descriptor hop has already set 2 to OUR TOC and 11 to our - * (zero) environment pointer, so unlike ELFv2 there is no global entry to - * rebuild anything: the body starts on the table at once. The slot holds a - * DESCRIPTOR address, and the three loads walk it into the registers a call - * through that descriptor would have set: r2, r11, then the code address. - * The index rides in r0, which no argument occupies. */ -#define GLFWD_TRAMPOLINE(i, n) \ - __asm__(".text\n" \ - ".p2align 4\n" \ - ".type .L" #n "_body,@function\n" \ - ".L" #n "_body:\n" \ - " li 0, " #i "\n" \ - " ld 12, glfwd_tab@got(2)\n" \ - " ld 12, 8*" #i "(12)\n" \ - " ld 2, 8(12)\n" \ - " ld 11, 16(12)\n" \ - " ld 12, 0(12)\n" \ - " mtctr 12\n" \ - " bctr\n" \ - ".size .L" #n "_body, .-.L" #n "_body\n" \ - ".section .opd,\"aw\"\n" \ - ".align 3\n" \ - ".globl " #n "\n" \ - ".type " #n ",@function\n" \ - #n ":\n" \ - " .quad .L" #n "_body, .TOC., 0\n" \ - ".size " #n ", 24\n" \ - ".previous\n"); #else #error "gl-fwd needs a tail-jump trampoline for this architecture" #endif