#Ultra
1 messages · Page 2 of 1
well its doing something
oh lol
are you using dataclass?
yep
ah
I didn't look that close - only saw the base-10 pointer integer value
dataclasses probably have something to format it in base-16
dataclass is insanely OP
probably, I just printed it to make sure im parsing shit correctly
me when twohundred dataframes with different column sets because different data sources provide differently structured data
lol
lol i found a bug in linux kallsyms
should i fix it or not i wonder
ah wait nvm im just rarted
alright im done with reading in the symbols, filtering them, sorting and discarding stuff outside of text including linux compat
tomorrow i have to start actual token interning tables
def is_linker(self) -> bool:
if LINUX_COMPAT:
if len(self.name) < 8:
return False
if not self.name.startswith("__"):
return False
return any(self.name.startswith(x) for x in [
"__start_",
"__stop_",
"__end_",
]) or any(self.name.endswith(x) for x in [
"_start",
"_end",
])
return self.name.startswith("linker_symbol")
least insane python code
we have nm at home
this is parsing nm output
Looks like my build of linux has 125855 text symbols
at least those that pass the filter
do you prefix linker script symbols with linker_symbol?
Ye
I wonder how well kallsyms compression would work with mangled symbols
a lot of substrings are often repeated
it wont have anything superior because its aiming for linux abi compatibility with userspace
that means all syscalls are 1:1
yeah thats a good compression target
does this mean ultra will get working cuda drivers ported from linux
omg
we're saved
yes
given i implement all syscalls
or well the nvk driver would have to be ported like leo did for managarm
infy flavored linux best os
but all other drivers im gonna do myself
if u read the title post for this thread
it outlines my goals basically
let's gooooo cuda drivers
hmm fuck
the compound token table is somehow sublty different for my script and linux
idk why
have to debug this
hmm the symbol list is exactly the same and sorted identically so this is optimistic at least
and my script decompresses the symbol names correctly also, so compression logic is correct yet somehow produces different shit???

so now its just a matter of serializing this into an assembly file
it was a very subtle thing where tokens with identical scores would choose the last
linux picks the first tho

class TokenTable:
class TokenizedSymbol:
def __init__(self, symbol: Symbol):
self.tokens: List[int] = [ord(c) for c in symbol.canonical_name()]
def __init__(self, symbols: List[Symbol]) -> None:
self.symbols = [TokenTable.TokenizedSymbol(s) for s in symbols]
# This stores two-byte sequences and we want to have O(1) lookups
self.multibyte_tokens: List[int] = [0] * 0x10000 # (UINT16_MAX + 1)
# Top 256 tokens that will end up in kallsyms
#
# These are recursive:
# If the token at index only has 1 entry this is the token
# Otherwise it's a compound token consisting of two sub-tokens
self.best_tokens: List[List[int]] = [[]] * 0x100 # (UINT8_MAX + 1)
for symbol in self.symbols:
self.__add_symbol(symbol, record_for_best=True)
self.__optimize()
def __for_each_token(self, symbol: TokenizedSymbol, addend: int,
record_for_best: bool = False) -> None:
for i in range(0, len(symbol.tokens) - 1):
self.multibyte_tokens[
(symbol.tokens[i + 1] << 8) + symbol.tokens[i]
] += addend
if record_for_best:
for token in symbol.tokens:
self.best_tokens[token] = [token]
def __add_symbol(
self, symbol: TokenizedSymbol, record_for_best: bool = False
) -> None:
self.__for_each_token(symbol, +1, record_for_best)
def __remove_symbol(self, symbol: TokenizedSymbol) -> None:
self.__for_each_token(symbol, -1)
def __most_used_multibyte_token(self) -> Tuple[int, int]:
best_score = -1
best_index = 0
for i, score in enumerate(self.multibyte_tokens):
if score <= best_score:
continue
best_score = score
best_index = i
return (best_index, best_score)
def replace_all_matching_compound(
src: List[int], target: Tuple[int, int], replacement: int
) -> List[int]:
dst: List[int] = src.copy()
i: int = 0
while i < (len(dst) - 1):
if dst[i] != target[0] or dst[i + 1] != target[1]:
i += 1
continue
dst[i:i + 2] = [replacement]
return dst
def __compress_with_compound_token(self, idx: int) -> None:
token = self.best_tokens[idx]
for symbol in self.symbols:
compressed_tokens = TokenTable.replace_all_matching_sequences(
symbol.tokens, (token[0], token[1]), idx
)
if compressed_tokens == symbol.tokens:
continue
self.__remove_symbol(symbol)
symbol.tokens = compressed_tokens
self.__add_symbol(symbol)
def __optimize(self) -> None:
for i, this_token in reversed(list(enumerate(self.best_tokens))):
if this_token:
continue
most_used = self.__most_used_multibyte_token()
if most_used[1] == 0:
# Nothing to optimize, no tokens are used
break
self.best_tokens[i] = [
(most_used[0] >> 0) & 0xFF,
(most_used[0] >> 8) & 0xFF
]
self.__compress_with_compound_token(i)
def unwind_compound(self, tokens: List[int]) -> str:
out_str = ""
for token in tokens:
if len(self.best_tokens[token]) == 1:
out_str += chr(token)
else: # compound token
out_str += self.unwind_compound(self.best_tokens[token])
return out_str
The entire compression algo
my code for it is 190 lines of scheme (not counting struct defs and elf constant defines) 
what are you producing it for exactly?
ah
fuck, the table produced is different for vmlinux
thats probably because i filter or not filter something i should
also it takes like half a minute for linux lmao
python is slow as shit
vaguely similar
ill have to diff the symbols im processing vs their script
holy shit

turns out it was correct all along, i was just comparing against stage 1 link
so now my script generates identical token table to linux kallsyms
nice
file gen time
im thinking for my kernel ill just use a .C file, im not sure why you'd do assembly
but ill have GAS for linux compat
or maybe ill do NASM
$ time python3 generate_symbol_tables.py symbols.asm --binary ~/linux/vmlinux --linux-mode
real 0m18.582s
user 0m16.691s
sys 0m0.213s
kinda sad
could I perhaps yoink your script? 
/mnt/d/ultra/scripts$ time ~/linux/scripts/kallsyms ~/linux/System.map > linux.syms
real 0m1.331s
user 0m0.358s
sys 0m0.068s
python is very fast
oof
yeah sure
i now need to add serialization to file and im done with it
$ time python3 generate_symbol_tables.py symbols.asm --binary ../build-clang-x86_64/kernel-x86_64 --linux-mode
real 0m0.478s
user 0m0.243s
sys 0m0.055s
well at least for my kernel its fast

lol
ah i just realized u must use assembly, because you do multiple link stages and at first stage the symbol table is empty but symbols must still be there
in C u cant really make an empty array
or u could do a dummy symbol i guess but optimizations and shit
wdym
static unsigned long symbols[0]; doesnt really work right
you can make it extern
sorry i wasn't really reading back that much, what is the problem exactly
lmfao
this shit is a lot more annoying to make in an abstract manner than i had thought
but at least i can 100% prove that my thing is correct this way by diffing against linux
holy shit its coming together
Tioread16_at literally 2 bytes to encode the entire name
very close to being done
wyd
Kernel symbol table
lol
that'll be easy for you
ur os already more advanced with symbol lookup then most
Its not even integrated into the os yet and the script is not completely finished lol
yea still lmao
also prepare your eyes to get cursed
anyways
why are u so interested in very fast symbol lookup exactly
What am I looking at
cursed shit
failed install of a kde vista theme, with some config i stole
Ah
I'm not tbh, I needed some symbol lookup mechanism, I saw how Linux does it, realized its very fast and smart and also not that complex so went ahead and implemented it
right
makes sense
what are your plans on the VMM design, as well as what are you gonna use for the PMM (buddy?)
is ultra gonna be monolithic or
Pmm is gonna be buddy yeah
makes sense
See my title post
Its a mini Linux project
So yeah ofc
right
I wanna study every subsystem in depth then implement a simplified version of it for my kernel basically
fair fair
have fun implementing evdev

or DRM or any complicated linux interface
honestly ultra will probs be in userspace before nyaux ever runs xorg lol
to be fair
your really good at programming your code is fucking crazy smart
Yeah ill have to do evdev and all netlink stuff
yea fun
Nah
I'm not getting to userspace any time soon
Lol how
oh then what is ur plan exactly then
Well thanks idk
like how many interfaces and subsystems are u gonna impl until u reach userspace
Well I mean I wanna get there as soon as possible, but I need a stable foundation before I get there
Which is gonna take tons of time
Not sure yet exactly
as long as u got a PMM, VMM, VFS, some backing FS, scheduler
If I wanna do anything meaningful with the userspace I need at least an entire vfs and tmpfs
Which is gonna take 99999 years
Dunno
unless u want things like a dentry cache or a page cache itll take u at most 2 days for the VFS itself
tmpfs is lit 15 minutes
its just files in ram
It will probably take me at least a week to study how Linux does vfs and dentry
And page cache
that is fair, i think linux does it in a way quite complicated
I'm trying to make it at least somewhat production quality
But we'll see not sure
Thats kinda far away rn
I need a buddy and heap first
again i disagree but okay lol
We'll see when I get there lol
okay lol
this is the uacpi creator talking
I appreciate your belief in my non retarness
infy@DESKTOP-IGT40G0:/mnt/d/ultra/scripts$ ~/linux/scripts/kallsyms ~/linux/System.map > linuxsymbols.S
infy@DESKTOP-IGT40G0:/mnt/d/ultra/scripts$ python3 generate_symbol_tables.py mysymbols.S --binary ~/linux/vmlinux --linux-mode --gnu-as
infy@DESKTOP-IGT40G0:/mnt/d/ultra/scripts$ diff linuxsymbols.S mysymbols.S
infy@DESKTOP-IGT40G0:/mnt/d/ultra/scripts$ echo "$?"
0
holy fuck
i cant believe it works
nice
400k lines of GAS
(for the linux kernel kallsyms)
it was annoying as fuck for the final touchups
because the original script uses alternate form for some fucking reason
in some formatting
and doesnt in other
and also mixes tabs and spaces
so rn the serializer is hacked with some linux awareness, ill have to get rid of that somehow
other than that the serialization stuff is pretty clean imo
should be easy to implement C/NASM "backends"
if i get rid of GAS-specific hacks for linux compat
or hide them away
the linuxes script is 750 lines and mines only 650
(mine is two orders of magnitude slower
)
or well, one order of magnitude actually
https://pastebin.com/tzumCgE3 this is what the entire thing looks like for my kernel
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
okay so
script cleanup time
GAS backend will be linux-compat exclusive
for my kernel ill have C mode probably
infy@DESKTOP-IGT40G0:~$ cat test.c
#include <stdio.h>
int array[] = {
};
int main() { printf("%p\n", array); }
infy@DESKTOP-IGT40G0:~$ gcc test.c -Wall -Wextra
infy@DESKTOP-IGT40G0:~$ ./a.out
0x614c4ba30014
yeah okay apparently this is allowed
then i dont see why it wouldnt work
yea why wouldnt it be?
im also curious whether u can emit constants like x = text_begin - offset
infy@DESKTOP-IGT40G0:~$ cat test.c
#include <stdio.h>
#include <stdint.h>
uint8_t data;
uint8_t *offset = &data + 123;
int main() { printf("%p\n", offset); }
infy@DESKTOP-IGT40G0:~$ gcc test.c -Wall -Wextra
infy@DESKTOP-IGT40G0:~$ ./a.out
0x63e754636094
okay lol yeah
then i dont see any blockers to a C backend
what's sizeof(array)
probably one because it needs to have a unique address
0 actually lmao
never seen sizeof return 0
1 int or 1 byte is probably reserved for it
the former apparently
makes sense
because an empty array right next to it is 4 bytes away
it's most likely either the size of 1 element or the element type's alignment
yeah
its strange that sizeof returns 0 tho
because empty structs dont return 0
or well i guess its because of C sizeof rules for arrays
Yooo I found the ultra thread lets go
Enticing ngl
get it
yeah only if the keyboard layout wasnt gay
Nah 1650 or T400 first
idc if I'd ssh into it anyways 
Also will this get lil and/or nvo support 
gcc has an extension to return 0 on sizeof of function type
not function pointer, function
Progress update:
i've been touching grass a bit but im still finishing the kallsyms script
i wanna get rid of linux-specific hacks and put them away
so i have a clean backend for my kernel
and so that someone else can easily add their backend if they want to use the script for their kernel
basically just trying to separate stuff out into classes
and use enums instead of backend-specific strings
wait, why not just use an elf for the symbols instead of a dedicated format?
given the only job of kallsyms is debug anyways
in freebsd /dev/ksyms is literally an elf file with a symbol table and string table
scripts/kallsyms.c generates a whole bunch of tables used for both symbol resolution (when loading modules) and backtrace generation as well
ksymtab can be generated using a section attribute
yeah thats what my backend will be doing
I think the only reason it is not is because they have support for using relative stuff
@red parcel this is what the generated tables look like
as in, having the symbol data be two int32 which are relative to the location
kallsyms and ksymtab are two completely different things
idk what ksymtab is
its what used for module linking (aka EXPORT_SYMBOL)
ah yeah, that one i dont care about
kallsyms is whats used for linking modules and backtraces
its just a set of 8 tables for fast symbol lookup and address -> symbol conversion
and its compressed as well
wdym linking modules? you mean builtin ones? if so they are just ar files that are linked iirc
dynamic modules don't use kallsyms
like kernel modules
they use the ksymtab
but like, why overcomplicate and not just use the symbols that come as part of being an elf file
#1385970208631427173 message
it includes a whole bunch of garbage in it, the table is huge, not compressed, not sorted
so either u clean it up at runtime or pay the price of linear scan every time
I clean it up once at boot and then do a binary search
yeah so u pay a runtime cost plus u probably dont do symbol interning right
which kallsyms does
yeah because who cares
linux does it because they have like 250k symbols inside the kernel easily
and the interning doesn't save that much
or well, it does when you have 100s of thousands of symbols
tbf I hate it just because one of the most annoying parts of the linux build that everytime I get to it I dread it because it takes a few more seconds
and it means that for debugging you need vmlinux-to-elf
yeah their make is kinda annoying
even if u changed literally nothing it still takes 10 seconds to do nothing
and somehow its still one of the best buildsystems for C
ah yeah its because they only allow modules to link against shit thats explicitly exported
idc about that so i can just reuse my kallsyms
oh and also they use kallsyms everywhere for bpf and other types of tracing
well, kallsyms can be quite slow for string -> address
because you need to decompress the strings for the lookup
unless they changed all it works
decompression is just
for i in range(symbol_name):
name += names[name_offsets[i]]
and its like 5-10 chars on average
and the names are sorted ofc
yeah but you need linear search for finding the symbol name
you sure? I think kallsyms is sorted by address
not name
u dont, they're sorted by both address and lexicographically
like i said its 8 separate tables
1 table sorts by offsets from base (aka address), the other sorts by name
well, I know that I had to add sleeps for a kernel module that used kallsyms_lookup_name to resolve symbols because otherwise it would freeze the kernel enough for the sound driver to crash
and u have other aux tables to help convert from name index to address index
nah it should be microseconds now, maybe it was changed recently
is it kallsyms_seqs_of_names?
8 (eight) separate tables??
that thing converts an index of a name to its address
it seems to only be 6.2+
range(symbol_name) 

oh yeah so I guess its a 6.2 addition
yeah without the range lol
was typing something else first
ok yeah I didn't know about that one lol
i guess it was slow af before
when I last looked at it was v4.something
now u basically binary search for your name
when u find it, u grab its index
then convert it to the index of the address
yeah that is fine then
and grab the address
and the fact you can do GPL symbols :^)
really?
what kind of namespace
a string
most useful feature
like u can put global symbols behind a namespace?
seems like you need to use MODULE_IMPORT_NS for explicit stuff
and of course there is something to do it automatically
interesting
and you can set a default namespace for your module
i guess if u have 99999 modules all with their own global symbols u must have some sort of ksymtab
otherwise u will have collisions
for modules that have multiple C files that need to call into each other
yeah
why the hell do they have a lexer in genksyms
does it actually scan for EXPORT_SYMBOL
in every c file
damn, apparently linux has symbols in the kallsyms for function padding as well
prefixed by __pfx_
wdym?
the alignment nops the compiler provides for functions get their own symbol
lol
well the ksyms is not super smart, it just includes everything that lies inside the text section
i know because my shit is not super smart and the thing it produces is identical
functions will have PADDING_BYTES of NOP in front of them. Unwinders
and other things that symbolize code locations will typically
attribute these bytes to the preceding function.
Given that these bytes nominally belong to the following symbol this
mis-attribution is confusing.
this one
i wonder if they have logic to convert __pfx to the actual function
but its unlikely that a function faults at nop lol
why is this so complex lol
i guess this is the thing that produces ksymtab

why does it need to fucking parse C lol
I mean, there is no way it parses the entire included source tree when generating kallsyms
kallsyms is one source file
this is ksymtab
what the hell is going on
ooo I remember something
ksymtab has a crc32 table as well
of the actual code of the function
I think the idea of it is to check its the same function you linked against or something?
I don't remember exactly
yeah I have no idea what it is doing there
I forgot linux added the cleanups macros lol
is this in the actual kernel?
yeah
damn
what exactly does scoped_guard(rcu) do?
kernel dev writes userspace code lore
When CONFIG_MODVERSIONS is enabled, symbol versions for modules
are typically calculated from preprocessed source code using the
**genksyms** tool.
Documentation/kbuild/gendwarfksyms.rst
this cannot be real
bruh why is it from the source code
I think it just does rcu_read_lock and rcu_read_unlock
cool
However, this is incompatible with languages such
as Rust, where the source code has insufficient information about
the resulting ABI. With CONFIG_GENDWARFKSYMS (and CONFIG_DEBUG_INFO)
selected, **gendwarfksyms** is used instead to calculate symbol versions
from the DWARF debugging information, which contains the necessary
details about the final module ABI.
apparently theres also a dwarf version
i wonder what would be the simplest way to do module versioning
or maybe u just dont do it
at work we have a module system where we also export and do it manually
so we do something like EXPORT(symbol_name, numeric_version)
and it just does a bit of name aliasing
so whoever imports essentially does symbol_name_v1 and so on
its manual but u can also skip a version bump if its a bug fix and not a behavior change for example right
for core functions thats probably not that often
do u not use linux?
its for embedded stuff that don't run linux :^)
u have ur own kernel? thats cool
its built on top of existing rtoses but yeah
i will probably just do numeric_version as well when I get around to that
tbh
you could just have the module include the kernel version in the header or something
and verify it at load time
fair lol
kernels really don't need backwards/forward module compat
im wondering why they didnt just do that
unless you have OEM drivers ig
because they have out of tree modules i guess?
yeah
but those are built against headers that come from a specific version anyway
yeah im not sure what problem is being solved here
maybe we're forgetting some use case or edge case lol
we have wrote kernel modules that work between linux majors, but only because they don't use the structs whenever possible lol
lol yeah
and for them I wouldn't want symbol versioning anyways since the idea is to be generic
modules that support a lot of kernel versions tend to have tons of ifdefs
especially one that is generated from the freaking source code
yeah
the nt module style is pretty interesting in that regard
that u just use explicitly filled calback tables
or ioctls or wahtever they call them
I sure do like it when a subsystem has a function that is a dispatcher for other functions
lmao
yeah, no C parsing required
with userspace
to find out if it works
is this like ntdll
yeah
but a bit weirder since it also accesses some kernel structs directly
very interesting
the kernel has symbols that contain offsets in structs
so process enumeration is looking at the kernel structures directly that the offsets are exported in the symbol list
iirc
tbh I have no idea how their kernel modules work
I don't think I saw their source code having EXPORT like macro
@willow fern sorry to ping, do u happen to know how freebsd dynamic modules work? how do they do versioning/export?
linux literally parses .c files for that

I think it just uses elfs (?)
id hope so lol
lol
did u find some code for versioning?
nope not seeing anything for that
:^)
even freebsd developers use it
they dir structure is so weird i cant even find C code lmfao
like they have sys/modules
but there is no code inside there
just makefiles
wtf?
the elf code is under sys/kern/link_elf_obj.c
see the manual pages, it's freebsd not linux so they are actually useful https://man.freebsd.org/cgi/man.cgi?query=module&sektion=9
ah
damn
but tldr is any non-static function is exported, and there are depend directives so you can specify version requirements
as to this
the actual code is in e.g. sys/dev/somedriver/somedriver.c and the counterpart in sys/modules is just the case for building it as a module (rather than a static part of the kernel)
so if i have module a with do_bar in bar.c used by foo.c, and module b with do_bar they will collide?
even if module a doesnt consider it an exported api
makes sense
probably, but i would suspect that module-local symbols are searched first
i meant more like if module c is loaded that wants do_bar
if u load just those 2 they wont collide because the symbols wont have UNDEF or whatever
the kernel would search its symbol table, and will pick the first match probably
it looks as though it won't look except at what you MODULE_DEPEND() on
ohhhhh
so they basically use namespaces
like linux recently introduced
ok that makes sense
DRIVER_MODULE(bytgpio, acpi, bytgpio_driver, 0, 0);
MODULE_DEPEND(bytgpio, acpi, 1, 1, 1);
MODULE_DEPEND(bytgpio, gpiobus, 1, 1, 1);
ACPI_PNP_INFO(bytgpio_gpio_ids);
thats cool
i cant find what DRIVER_MODULE does tho
it seems to mention acpi twice
also whats interesting is version numbers are hardcoded
@prime wraith

are you going to do kconfig?
what is nvo?
nvidia-open
yep
will see, but im going full NIH in kernel space if i can
neat
nvo is independent of kernel space considerations, mostly 
^
thats cool
just sysdeps + bindings
i'm going to adopt lil, that's for sure 
you will catch me dead before i reinvent gpu drivers
I might just start memeing with sandy + ivy bridge this week if time allows so that I have something to merge in mammogram
Letsgo
I might add it as an optional module why not
If it can be exported via DRM
Maybe also to check vs my code
But I wanted to make an igpu driver if u look at my thread title post
modesetting is less of a priority tho
Mostly interested in Accel
funnily enough for me it's the opposite
i'd like a working modeset, especially multi monitor
yeah same
why like
u can have your bootloader set both to native res
after that u never really need to touch it
you can not
why not?
most GOP drivers don't modeset any secondary screens
so you only get one fb
this might be different on laptops, but on every pc i've ever touched you only ever get an fb on the first monitor
sadge
>>> "%#x" % 0
'0x0'
mfw python doesnt respect alternate form
like cmon
all u had to do was strip the 0x
# enables alternate form in printf formatting
python seems to support it, but incorrectly
for hex numbers it prefixes 0x unless the number is 0
but python adds 0x always
alright my serializer is now completely abstracted away from the backend
idk why im wasting time doing all that stuff
but wahtever
and still matches linux kallsyms (for the vmlinux binary) completely so thats great

thats enough python for me today
Infy out here making the ultimate testing framework
lol
reminds me i should work on a test runner for my project sometime
What does it even do, anyway?
one sec ill post it
produces this https://pastebin.com/gSFR8WwT
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
symbol table for the kernel
the C backend took like 20 minutes to add which is cool
Make sure you emit const char *const varname[] because without that const after the *, the array contains mutable pointers, which I don't think you want.
So this is for in-kernel backtracing?
backtracing and symbol lookup for dynamic modules
I see
nice
its overengineered to have log N for address -> symbol and symbol -> address
- string interning
Are symbols big enough that you'd need this compression?
For unmangled symbols I can hardly imagine.
its not even for that
if u have a lot of symbols using the same prefix for example the prefix gets interned into one byte for all of those symbols
and it automatically interns most frequeuntly occuring tokens
Why not just sort them and binary search? Sounds a lot easier to do.
for example when i add uacpi, all uacpi_ prefixes will turn into 1 byte
they are sorted as well
its just that the names are compressed as well
idk whether to feel inadequate that i just parse the elf symbol table now lmao
For my kernel's module loading I was just gonna make it a dynamic ELF file and call it a day 
lol i mean i wrote an 800 line python script for that
which may be less adequate
technically when i say i parse the elf symbol table i mean i let the zig standard library parse the elf symbol table tbf
using strtab/symtab is fine, i just stole linuxes algorithm because i wanted to be cool

whatever works lol
next step is CFI stack unwinding so i can use these
ill use .eh_frame
already have it set as LOAD
yeah thats fair
if you get eh frame working youll be ahead of me there, theres some more zig std innards i need to splice into to get it working for me (thread context stuff, zig std assumes that if it doesnt have ucontext or the windows capturecontext api then it cant use eh frame stuff)
so good luck there
ah that sucks
but tbh the guess algorithm works really well
while stack < stack_end:
if addr_in_kernel_text(*stack): print(" > %s", symbol_from_addr(*stack))
stack += 8
assuming stack is the actual trace not the raw machine stack yeah
no, like raw machine stack
most likely the only place you will have ktext addresses is stuff pushed by call
you can still get a backtrace without eh frame without guessing like that, using rbp
that requires no-omit-frame-pointer
assuming your calling convention is sane and uses that
imo its worth it to have the backtraces but ig that depends on whats worth to you
if u dont have CFI yeah
damn actually this profiler is pretty good
i was able to shave off about 5 seconds
(when generating symbols for vmlinux)
whatever i guess, only takes 0.4 seconds for my kernel, which is good enough for me
no low hanging fruit left
caching symbol lengths would probably shave off a few more, but idc
i'll take a look at the script when you are done with it
i like optimizing python code :^)
but if you say there's no low hanging fruits there probably isn't
if you manage to optimzie it
not sure
ncalls tottime percall cumtime percall filename:lineno(function)
19/1 0.000 0.000 18.810 18.810 {built-in method builtins.exec}
1 0.033 0.033 18.810 18.810 generate_symbol_tables.py:1(<module>)
1 0.381 0.381 18.758 18.758 generate_symbol_tables.py:758(main)
1 0.024 0.024 14.451 14.451 generate_symbol_tables.py:319(__init__)
1 0.001 0.001 13.818 13.818 generate_symbol_tables.py:403(__optimize)
192 8.867 0.046 13.620 0.071 generate_symbol_tables.py:377(__compress_with_compound_token)
3151379 3.810 0.000 3.915 0.000 generate_symbol_tables.py:337(__for_each_token)
1638618 0.172 0.000 2.189 0.000 generate_symbol_tables.py:348(__add_symbol)
1512761 0.164 0.000 2.062 0.000 generate_symbol_tables.py:353(__remove_symbol)
378575 0.590 0.000 2.032 0.000 generate_symbol_tables.py:722(emit)
1 0.000 0.000 1.203 1.203 generate_symbol_tables.py:307(make_context)
1 0.005 0.005 1.203 1.203 generate_symbol_tables.py:247(__init__)
1 0.142 0.142 1.199 1.199 generate_symbol_tables.py:176(__init__)
27858052/27858032 0.886 0.000 0.886 0.000 {built-in method builtins.len
basically the core compression stuff is what takes the longest
--no-compress 
linuxes script written in C takes 1.2 seconds, mine takes 12 
very ultra
Why so big 💀
mostly because i decided to support linux so i have two backends and everything is abstracted away into classes
I've a lua script that does that in less than 200 loc
Bruh
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
this is the script atm
Mine just uses nm on a intermediary version of the kernel binary and then generates an .asm file. Then i just assemble that and link it to produce the final binary
yeah this is basically what this does
although it generates .c files
or GAS optionally
if u look closely it calls nm
the idea is u have multiple stages:
- link the kernel against a fake empty symbol table (that this script produces if u provide no binary)
- link the kernel against the new symbol table
- regenerate symbol table
- relink the kernel
- if something shifted, repeat
since it only includes .text stuff its unlikely to shift around
but even if it included every symbol, this would work anyway u just have to filter out the symbol table symbols
im scared of running mypy on this lmfao
inb4 999999999 warnings
infy@DESKTOP-IGT40G0:/mnt/d/ultra/scripts$ python -m mypy --disallow-incomplete-defs --no-implicit-optional ./generate_symbol_tables.py | wc -l
61
infy@DESKTOP-IGT40G0:/mnt/d/ultra/scripts$ python -m flake8 --ignore=E743 ./generate_symbol_tables.py | wc -l
44
how am i this bad still
$ python -m mypy --disallow-incomplete-defs --no-implicit-optional ./generate_symbol_tables.py
Success: no issues found in 1 source file
yay
had to add a bunch of stupid assert isinstance for it to shut up about subclasses
alright, fixed every warning and also 0 global variable left 
will be integrating into the kernel tomorrow and maybe start with CFI
takes only 0.27s for my kernel, so thats great
nice
how can something shift?
ideally the symbol table goes into one of the data sections which is ideally after text (you control the linker script)
so nothing will shift
because you said it yourself, it's only text symbols
and text won't move
Limine does it like this and it works just fine
i've never seen its stack trace fail, ever
no idea, maybe for example some references become farther away because the symbol table is large
so the generated text changes
but yeah if u only include text the odds are small
also linux stores .rodata in .text so for them its very possible
but i dont ofc
it cannot change in that way
because you're just linking
and at compile time the compiler doesn't know
so the generated code that is linked will end up being the same
(this is all without considering LTO of course, but i assume you're just gonna be forcing LTO off for all this)
i dont see why LTO shouldnt be supported
well, if Linux does the stupid, don't follow, lol
maybe with LTO there is a chance it changes
well i mean, with this symbol stuff
because if you enable LTO you won't get any discernible symbols anyways out of the executable
so all of this is pointless
LTO mangles everything
it has symbols, they are just not helpful symbols
if we're talking about plain elf symbols from text
yeah im curious how the backtraces would change with lto
do u expect it to be like one huge function?
it basically is
but its free to support it lol
like its more work to not
well, it's free now that you did the useless work needed to support it as above
therefore it is free for you, now
not in an objective sense
for example it wouldn't be free for Limine without changing the symbols producing code
and that is effort for nothing since, as above, LTO mangles everything
also like, how does runtime linking work for LTO if its all mangled? linux still supports runtime modules there
external symbols aren't done away with
LTO isn't stupid
but anything that isn't to be exported externally gets mangled up
seriously, just try it
so only local symbols basically?
last time i checked its either global or local
that's ELF
how do u mark it as exported externally?
for a normal ELF executable, basically, you don't
well, there is a way
you can link a non-LTO TU (that can be an asm file) in, and the asm file externs and uses all of the symbols you don't want the linker to disappear
i dont think linux does that and it still works somehow?
what works?
module linking at runtime
but that's because modules aren't "normal executables" as i said above
they are objects
and why do you think that is a problem?
because runtime modules want to link against kernel symbols
i cannot speak for Linux or any of the black magic they do to get this working
because i simply do not know
if i am wrong feel free to correct me and i will crucify myself for spreading misinfo :p
sure lol
so just to be clear, what you're saying is enabling LTO might strip some/a lot of global symbols out of existence entirely?
Even non-LTO if you have -Wl,--gc-sections
well for that you would have to enable per-function sections
which im not going to do ofc
config LD_DEAD_CODE_DATA_ELIMINATION
bool "Dead code and data elimination (EXPERIMENTAL)"
depends on HAVE_LD_DEAD_CODE_DATA_ELIMINATION
depends on EXPERT
depends on $(cc-option,-ffunction-sections -fdata-sections)
depends on $(ld-option,--gc-sections)
help
Enable this if you want to do dead code and data elimination with
the linker by compiling with -ffunction-sections -fdata-sections,
and linking with --gc-sections.
This can reduce on disk and in-memory size of the kernel
code and static data, particularly for small configs and
on small systems. This has the possibility of introducing
silently broken kernel if the required annotations are not
present. This option is not well tested yet, so use at your
own risk.
lol
@little aurora I think i solved the mystery
thats basically what it does for all explicitly EXPORT()'ed symbols
#define __EXPORT_SYMBOL_REF(sym) \
.balign 8 ASM_NL \
.quad sym
rewrite CSMWrap in rust
💀
yeah it basically generates this at compile time
just a C file with exports
and this uses inline asm to reference the symbol
which probably prevents LTO from destroying it
it does
because even when you do LTO, you can pass .o files that are not LTO files, which means that to some degree if a symbol has to be used by a non-LTO TU/object it can't just disappear
does global asm() statements u put produce a non LTO-ed .o?
like for example, you can LTO programs using Nyu-EFI based on the template, and they will still work, despite all the shenenigans needed to make PEs out of ELFs
it still works because if the LD script needs a symbol or another TU that is not LTO needs a symbol, the symbol won't be disappeared
uh, no idea honestly
maybe?
global asm() statements are super cringe anyways
if it's worth anything, my personally suggestion is to just force LTO off whenever you want to turn on this type of symbol tracking
that's the main reason why Limine forces LTO off, all the time
because i think the stacktrace being there is always worth it
over the minor speedup granted by LTO
yeah we'll see, for now i dont care about LTO at all, it's not even supported
LTO is basically unity build at the IR level, so anything that a unity build would be able to do LTO can do (to some degree)
For hidden symbols it will obviously be more prone to inline since it knows it won't duplicate it
But it can still happily inline huge functions at times if the compiler thinks it should
I'm relatively sure you can
/* force external linkage */
extern void foo()
{
// ...
}
void bar()
{
asm volatile("call foo" ::: "memory");
}
but not
/* static */ void foo()
{
// ...
}
void bar()
{
asm volatile("call foo" ::: "memory");
}
(assuming C and -fvisibility=hidden)
Man cmake is incredibly powerful:
$ python3 ../build.py
Not rerunning cmake since build directory already exists (--reconfigure)
[ 2%] Built target kernel-link-script
[ 5%] Built target e9-console-objects
[ 7%] Generating kernel_symbols_stub.c
[ 10%] Building C object kernel/CMakeFiles/kernel-x86_64-nosymbols.dir/kernel_symbols_stub.c.o
[ 12%] Linking C executable ../kernel-x86_64-nosymbols
[ 52%] Built target kernel-x86_64-nosymbols
[ 55%] Generating kernel_symbols.c
[ 57%] Building C object kernel/CMakeFiles/kernel-x86_64.dir/kernel_symbols.c.o
[ 60%] Linking C executable ../kernel-x86_64
[100%] Built target kernel-x86_64
i basically cloned my kernel target into two separate targets using literally one macro
macro(ultra_kernel_targets_apply FN)
foreach(TARGET IN LISTS ULTRA_KERNEL_TARGETS)
cmake_language(CALL ${FN} ${TARGET} ${ARGN})
endforeach()
endmacro()
and i literally call
ultra_sources(
entry.c
panic.c
console.c
log.c
io.c
)
And it adds the sources to both targets while also making sure they only have to build once
then I make the final kernel binary depend on the symbol table and thats it
we have 135 symbols, base is at 0xFFFFFFFF80000000 and it seems to get linked in correctly
and the symbol table from the final kernel binary seems to match the one with the stub symbol table, so mint was right
nothing shifted so no two-step relink needed
I'd add a verification step to ensure that the symtab is actually correct, but nice
yeah i think ill add a step to verify it
[ 97%] Generating kernel_symbols_final.c
[100%] Built target ensure-symbol-table-matches
Done
And it adds the sources to both targets while also making sure they only have to build once
That was wrong
So i had to actually change it so my kernel is first generated as a static library
and then linked with the symbol table + module objects into the final binary
as an added bonus that stripped some extra unused symbols which is cool
be careful if you have something like initcall. linker shenanigans.
there is -fwhole-archive or something which I would recommend using unless you eventually want to run into trouble.
speaking from experience.
actually fuck that, I just did
- add_library(${ULTRA_KERNEL_OBJECTS} STATIC)
+ add_library(${ULTRA_KERNEL_OBJECTS} OBJECT)
now all my symbols are back
forgot cmake can just do plain objects as a target
linking together many object files into a single object file also has some pitfalls, though it has been a while since I've messed with that kind of thing.
i also have an instance of that for my module generator lol
id appreciate it if u remember which lmfao
this option doesnt link them together btw, its just an abstraction in cmake, like an object container
that said...
add_custom_command(
OUTPUT
${MODULE_OUTPUT}
DEPENDS
${MODULE_OBJECT_TARGET} ${MODULE_SOURCES}
COMMAND
${CMAKE_LINKER}
-r -T${ULTRA_MODULE_LD_SCRIPT}
$<TARGET_OBJECTS:${MODULE_OBJECT_TARGET}> -o ${MODULE_OUTPUT}
COMMAND_EXPAND_LISTS
)
i do have this
how do i make -r safe
now iirc one of the pitfalls occured when I had multiple subdirectory (every subdirectory would get its own kernel_objs.o, and those would all be recursively merged into a single top-level kernel_objs.o).
oh ok, I shouldnt have any recursive objects i think
even a module with subdirectories will get concatenated into one thing
so long as you don't have any global constructors or initcall-like things, you should avoid most pitfalls.
well i do have those lmfao
in modules?
for runtime modules it just has an entrypoint symbol
a static one works as an initcall yes
yeah, and otherwise it doesn't need recursedive ld -r.
anway ur saying scary things
or any ld -r for that matter
yeah sorry about that. 
not if a module is compiled as builtin, does it?
why -r
to make it one object file
do you then rename it to foo.ko?
ye set(MODULE_OUTPUT "${CMAKE_BINARY_DIR}/${MODULE_NAME}.ko")
just use .so's 
nah fuck sos lmao
slow
linux clone go brrrrr 
real
🆘 💀

runtime LTO
DLTO
alright have we written a JIT VM for GIMPLE yet?
cost of cross-dso calls haven't stopped the userland for using them all over
i mean there is libgccjit
yeah but it's hardly a VM.
i mean for userspace its also important to be able to share .text pages
eh but neither is llvm and it fills a similar niche (and gimple ~= llvm ir)
something like Hotspot but it runs GIMPLE.
so its a tradeoff

with GOT/PLT u can share .text pages among all processes
thats probably a big reason why they do it
or you can do it the NT way and no DLL ASLR.
i mean, your executable depends on a specific libc version anyway right
wrong
with glibc your executable depends on specific versioned symbols
so newer glibc keeps working
oh really
yes you can attach versions to individual symbols
glibc puts in a lot of effort into application forwards compatibility
thats cool
cant wait to be able to run it
'don't break userspace' but applied to userspace.
and the static linker picks the latest one if unspecified, and the dynamic linker then picks the right symbol from the dso when linking
rseq 😿
interesting
return -ENOSYS.
how does plt help with this exactly
u could just relocate in ld.so
like every reference to it
the plt is there to avoid having to write the address in multiple places
yeah
i didnt get this one
if you statically link libc, and then a bug is found and fixed in libc, you need to relink every program to apply the libc bugfix
(same applies to all libraries)
oh u mean that
I was talking more about using got/plt vs plain object linking at runtime lol
is it possible to generate an so that doesnt use that even
i mean yeah i get that, i was bringing that up as another benefit for dsos for user space
yeah fair
i mean you can make a .so that has no dynamic symbols (hidden visibility and no symbols from other libraries)
the only relocs it'd have would be R_RELATIVE
do references to the got/plt need to be relocated?
thought they did now
got entries get R_JUMP_SLOT and R_GLOB_DAT relocs (on x86_64)
depending on whether they're functions or pointers to data
i mean references to the got from .text
no i mean the got array entries themselves
yeah maybe they do. I'm not the NT expert, I'm just parroting what I've heard iirc from folks here.
yeah but do references have relocations as well?
no accessing the got is done via pc-rel addressing
or how does it get away with a hardcoded offset
the index into the got is known at static link time
ohh
the issue before was that with the way PE works, there are hardcoded addresses aaaallllll over the text sections, so itd completely destroy sharing if they did aslr because itd COW almost every single text page
but on 64 bit theres tons of pcrel
so its less of an issue
(pcrel was added to the amd64 architecture specifically to provide microsoft a workaround to this PE issue. no im not joking.)
based nt no plt/got
still an issue for
void baz(...);
const struct foo_operations ops = {
.bar = baz
};
but then again
that isn't .text.
COW'ed .rodata 
is there a problem with that?
yeah they dont need to set it read-only until after loading and linking is done
so its fine
to cow rodata during loading
foo@plt:
jmp [rip + _GLOBAL_OFFSET_TABLE_ + foo@GOTOFF]
...
_GLOBAL_OFFSET_TABLE_:
...
; foo entry
; emit R_JUMP_SLOT reloc for foo at this address
dq 0
...
(or if using -fno-plt foo@plt is inlined into every foo call site)
also NT's dynamic linking scheme came about as a graft onto COFF in like 1989 when ELF either didnt exist yet or was a baby
so it makes sense it works different
would it be like mov rax, addr; call rax?
yeah whatever reg
by coincidence my own dynamic linking scheme works almost identically to PE's. naive minds think alike
naive meaning u have to relocate every reference to some data?
yeah, i alleviated it a bit with code (which by anecdotal data is vastly more common than data) by having the linker generate a jump to the final destination at the end of the text section and rerouting all the jumps to there, so that only that one jump needs to be relocated
which is sort of like a poor man's PLT
the array of addresses is the got
u can also disable this with a linker flag and have it keep the direct jumps, and i use this with kernel modules to avoid the expense of the extra jump since theres only one copy of the kernel anyway
plt is linker-generated thunk code that jumps to rtld (if unresolved) or the actual symbol (if resolved)
ah is it just to support lazy binding?
yea
i always felt lazy binding was of dubious value
i mean depends ig
it came from some SunOS paper where even their own benchmarks on a 80s chip werent thaaat improved
if you compile with -fno-plt you'd get something like call whatever@GOTPCREL(%rip)
so it reuses got to call into stuff?
and some auipc abomination on risc-v
riscv's only improvement on the older riscs btw
auipc good
yaoi pc
riscv didnt learn from 32-bit pae that its terrible to have different format for different pt levels
even with pae you can still do a recursive page table
its only the top-most level that changes anyway
you just put the two page directories into consecutive entries in one of the page directories
and u would realistically populate it at cr3 load time
because it doesnt get invalidated by invlpg
its only 4 pointers afterall
also they added cmpxchg8b to 32 bit chips specifically for updating PTEs atomically on PAE pretty sure
lol yeah probably

