You're trying to do what I do in my toy OS, so I'll share some of my setup and hopefully it's useful. Here's my JSON:
{
"arch": "x86_64",
"code-model": "kernel",
"cpu": "x86-64",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"disable-redzone": true,
"features": "-x87,-xsave,-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-fma,-f16c,+soft-float,+shstk",
"link-self-contained": {
"components": [
"linker"
]
},
"linker-flavor": "gnu-lld-cc",
"llvm-target": "x86_64-unknown-none-elf",
"max-atomic-width": 128,
"metadata": {
"description": "Hexeract kernel build environment",
"host_tools": false,
"std": false
},
"panic-strategy": "abort",
"plt-by-default": true,
"position-independent-executables": true,
"pre-link-args": {
"gnu-cc": [
"-m64"
],
"gnu-lld-cc": [
"-m64",
"-nostdlib",
"-Wl,--no-dynamic-linker",
"-z", "cet-report=error"
]
},
"relro-level": "full",
"rustc-abi": "x86-softfloat",
"stack-probes": {
"kind": "inline"
},
"static-position-independent-executables": true,
"supported-sanitizers": [],
"target-pointer-width": 64
}
There's good news and bad news with this. The good news is that it does create a proper PIE kernel, without any need for a special linker script or other shenanigans. It's linked at base address zero, is a DYN elf rather than an EXEC, and has the appropriate PIE flags in the dynamic array.
The bad news is that there are still some relocations to be fixed up. These are almost certainly vtables, since those need absolute addresses in memory.
The relocations are fairly easy, at least. There's only one type of relocation in my kernel file - R_X86_64_RELATIVE. If you're already loading enough of the elf file to parse out program headers, then getting the DYNAMIC segment and parsing the RELA tags to get the relocations is pretty easy. While you're at it, you should also look for a GNU_RELRO segment to make sure your page tables have the right permissions once relocation is completed.
Another thing to consider is kernel modules - My OS is a microkernel, but if you're going to have kernel modules then you'll need to deal with a lot more relocation and symbol machinery to make that happen. It's worth making sure your elf handling code is robust in that case.