SchOS/ldls/stub.c
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

136 lines
3.9 KiB
C

// 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);
}