// SPDX-License-Identifier: GPL-2.0-or-later // // Stubs. See ldls/stub.h. // // Two pieces of x86-64 that have to be exactly right, and both are short // enough to read: // // the trampoline 20 bytes generated per symbol, which loads the symbol's // index and jumps to the thunk // the thunk one shared asm block that saves the argument registers, // calls ldls_stub(), and returns whatever it said // // Written from the x86-64 psABI and the instruction encodings in the Intel // manual -- both published, both specifications, and neither one anybody's // source code. #include "stub.h" // --- the thunk --------------------------------------------------------------- // // Reached by `jmp` from a trampoline, with %r11 holding the symbol index and // every argument register still holding what the driver put there. // // The stack arithmetic is the part worth checking. A `call` from the driver // leaves %rsp ≡ 8 (mod 16). Six pushes move it by 48, which is 0 (mod 16), so // it is still ≡ 8 -- and the ABI wants it ≡ 0 immediately before a `call`. // Hence the extra `sub $8`: without it every stub call would enter C with a // misaligned stack, which is survivable right up until something uses SSE and // then is not. __asm__( ".text\n" ".globl ldls_thunk\n" ".hidden ldls_thunk\n" "ldls_thunk:\n" " pushq %r9\n" " pushq %r8\n" " pushq %rcx\n" " pushq %rdx\n" " pushq %rsi\n" " pushq %rdi\n" " movq %rsp, %rsi\n" /* args[0..5] = rdi, rsi, rdx, rcx, r8, r9 */ " movq %r11, %rdi\n" /* the index the trampoline put there */ " subq $8, %rsp\n" " call ldls_stub\n" " addq $8, %rsp\n" " addq $48, %rsp\n" " ret\n" ".size ldls_thunk, .-ldls_thunk\n" ); extern void ldls_thunk(void); // --- the trampolines --------------------------------------------------------- static void put64(unsigned char *p, unsigned long long v) { int i; for (i = 0; i < 8; i++) p[i] = (unsigned char)(v >> (i * 8)); } static void put32(unsigned char *p, unsigned int v) { int i; for (i = 0; i < 4; i++) p[i] = (unsigned char)(v >> (i * 8)); } static int streq(const char *a, const char *b) { while (*a && *a == *b) { a++; b++; } return *a == 0 && *b == 0; } int stub_is_bare(const char *name) { return streq(name, "__fentry__") || streq(name, "__x86_return_thunk") || streq(name, "__x86_indirect_thunk_rax"); } void stub_build(struct stubs *s, unsigned char *code, int n) { int i; s->code = code; s->n = n; for (i = 0; i < n; i++) { unsigned char *p = code + (unsigned long long)i * STUB_SIZE; int j; for (j = 0; j < STUB_SIZE; j++) p[j] = 0xCC; // int3, so a mistake stops here and not later // endbr64. Harmless without CET and required with it -- an indirect jump // to a target that does not begin with this faults on a machine that // enforces control-flow integrity, and a driver's calls through function // pointers are exactly that. p[0] = 0xF3; p[1] = 0x0F; p[2] = 0x1E; p[3] = 0xFA; // movl $i, %r11d -- 41 bb imm32. The upper half of %r11 is zeroed by the // 32-bit form, which is what we want for a small index. p[4] = 0x41; p[5] = 0xBB; put32(p + 6, (unsigned int)i); // movabsq $ldls_thunk, %r10 -- 49 ba imm64 p[10] = 0x49; p[11] = 0xBA; put64(p + 12, (unsigned long long)(void *)ldls_thunk); // jmpq *%r10 -- 41 ff e2 p[20] = 0x41; p[21] = 0xFF; p[22] = 0xE2; } } // A bare `ret`, for the names in stub_is_bare(). void stub_make_bare(unsigned char *p) { int j; for (j = 0; j < STUB_SIZE; j++) p[j] = 0xCC; p[0] = 0xF3; p[1] = 0x0F; p[2] = 0x1E; p[3] = 0xFA; // endbr64 p[4] = 0xC3; // ret } unsigned long long stub_addr(const struct stubs *s, int i) { return (unsigned long long)(s->code + (unsigned long long)i * STUB_SIZE); }