I have two specific questions:
- Key features (symbol visibility, section groups, SHF_MERGE, etc) were all available as of April 2001. Where can we find the discussion mailing lists? Are they still available?
- How does the ABI end up being “All rights reserved” by SCO? Tool Interface Standard (TIS) Portable Formats Specification, version 1.2 effectively put the specification in the public domain.
While IEEE 754 is an issue because it's not freely available for most people, the only document that has truly stumped me is the ELF specification. From your blog post, it seems like my failure to find the most up-to-date specification is simply because one doesn't exist.
* ISA manual * ELF (generic ABI, psABI (e.g. x86-64-psABI), OSABI) * DWARF * Floating-point * Language standards * Itanium C++ ABI
and probably a few other stuff.
> it seems like my failure to find the most up-to-date specification is simply because one doesn't exist.
While the up-to-date specification is unavailable, hopefully it is not too bad because all essential features have been completed as of 2001:)
RELR is a linker and loader feature, not on the compiler side. Compressed debug sections are a pretty natural extension.
The written specifications for them are often a bit of a fairy tale. The real specification is whatever the local C compiler generates.
The other gatekeeper is what the linker will accept. That can sometimes be a nightmare.
> Second draft published May 3, 1999. > ... > New dynamic section tags DT_RUNPATH and DT_FLAGS added. Dynamic section tag DT_RPATH moved to level 2.
In glibc, DT_RPATH and DT_RUNPATH have different semantics regarding precedence related to LD_LIBRARY_PATH. In FreeBSD rtld and musl, DT_RPATH and DT_RUNPATH are equivallent.
Text of the spec is copyrighted by the SCO, they did not put it in public domain. That's what "All rights reserved" was intended to mean even though this phrase has not been meaning anything since 2000.
Has anyone looked at creating a new object format for Linux? A non-open spec seems a minor issue, really, in an era when we put binary blobs in the kernel (hi Nvidia.) But the more decades I work in closed source, the more I value open source, and believe keeping _everything_ open.
(Personally, that makes me a bit uneasy: for instance, if the copyrighted spec lists the file sections in a certain order, and your implementation happens to output them in the same order even if it doesn't have to, then have you infringed on the owner's copyright of that particular arrangement?)
Meanwhile, they could have gotten a patent on (some parts of) the format, but that doesn't seem to be the case here.
> In practice, achieving consensus among major toolchain vendors (GNU and LLVM) may be sufficient, even without formal approval from the generic ABI.
That makes it sound easy. GNU projects seem to be very conservatively maintained. LLVM has around one trillion open pull requests.
I came up with a little mechanism to get the kernel to automatically load into memory data embedded into the program. I used it in my programming language to embed code into the interpreter so that they can be automatically loaded and executed.
https://www.matheusmoreira.com/articles/self-contained-lone-...
Only the maintainer of the relatively new mold linker cared to implement a helpful feature to make this kind of thing possible and easy. I requested the same feature in GNU ld and the idea wasn't exactly received with enthusiasm. I'm not sure LLVM ever received it at all.
And that was just a linker option to add some extra PT_NULL segments for easy and efficient patching. Can't even imagine the effort it would take to actually change something about this ABI.
$ LANG=en_US objdump -x /bin/bash
/bin/bash: file format elf64-x86-64
/bin/bash
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000032ef0
Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
filesz 0x00000000000002d8 memsz 0x00000000000002d8 flags r--
INTERP off 0x0000000000000318 vaddr 0x0000000000000318 paddr 0x0000000000000318 align 2**0
filesz 0x000000000000001c memsz 0x000000000000001c flags r--
LOAD off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
filesz 0x000000000002e188 memsz 0x000000000002e188 flags r--
LOAD off 0x000000000002f000 vaddr 0x000000000002f000 paddr 0x000000000002f000 align 2**12
filesz 0x00000000000def6d memsz 0x00000000000def6d flags r-x
LOAD off 0x000000000010e000 vaddr 0x000000000010e000 paddr 0x000000000010e000 align 2**12
filesz 0x0000000000039b08 memsz 0x0000000000039b08 flags r--
LOAD off 0x0000000000148a90 vaddr 0x0000000000149a90 paddr 0x0000000000149a90 align 2**12
filesz 0x000000000000bbc0 memsz 0x0000000000016b28 flags rw-
DYNAMIC off 0x000000000014b4c0 vaddr 0x000000000014c4c0 paddr 0x000000000014c4c0 align 2**3
filesz 0x0000000000000200 memsz 0x0000000000000200 flags rw-
NOTE off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3
filesz 0x0000000000000030 memsz 0x0000000000000030 flags r--
NOTE off 0x0000000000000368 vaddr 0x0000000000000368 paddr 0x0000000000000368 align 2**2
filesz 0x0000000000000044 memsz 0x0000000000000044 flags r--
0x6474e553 off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3
filesz 0x0000000000000030 memsz 0x0000000000000030 flags r--
EH_FRAME off 0x00000000001278a8 vaddr 0x00000000001278a8 paddr 0x00000000001278a8 align 2**2
filesz 0x000000000000472c memsz 0x000000000000472c flags r--
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
RELRO off 0x0000000000148a90 vaddr 0x0000000000149a90 paddr 0x0000000000149a90 align 2**0
filesz 0x0000000000003570 memsz 0x0000000000003570 flags r--
...
$ hd -s 0x338 -n 0x30 /bin/bash
00000338 04 00 00 00 20 00 00 00 05 00 00 00 47 4e 55 00 |.... .......GNU.|
00000348 02 00 00 c0 04 00 00 00 03 00 00 00 00 00 00 00 |................|
00000358 02 80 00 c0 04 00 00 00 01 00 00 00 00 00 00 00 |................|
00000368
$ hd -s 0x368 -n 0x44 /bin/bash
00000368 04 00 00 00 14 00 00 00 03 00 00 00 47 4e 55 00 |............GNU.|
00000378 7a 64 08 ba 82 a2 d8 6d d9 8f 1f 75 ac 8e dc b6 |zd.....m...u....|
00000388 95 f6 fd 60 04 00 00 00 10 00 00 00 01 00 00 00 |...`............|
00000398 47 4e 55 00 00 00 00 00 03 00 00 00 02 00 00 00 |GNU.............|
000003a8 00 00 00 00 |....|
000003ac
Plus, of course, you can always leave the section table in as well, with SHT_NOTE sections in it.I considered using them. Ended up not using them but they did inspire the actual solution.
At the time I wasn't able to find much information about those things and how they're implemented and used in practice. Couldn't be sure if they were a good fit for what I wanted to do.
The best functional description of these notes I've ever found actually came from some old Linux standard base document. Apparently people would use their company's name as the note's name and enumerate a bunch of metadata types that are specific to their company. To me that sounded a lot like the "email the guys in charge to ask them for their blessing" situation described in TFA. I'm just a random guy and I just wasn't gonna show up out of nowhere in somebody's mailbox asking for some official designation for my hobby language.
There was no practical reason to use these things either. Turns out the kernel doesn't care about PT_NOTE segments at all. They don't get loaded into memory automatically. I'd still need to add a PT_LOAD segment for the data even if I had used this stuff.
The PT_LOAD segments are the only ones that really matter. Those are the segments the kernel cares about. Ideally I would have been able to simply use a PT_LOAD segment for the data and nothing else. When I tried it though, I discovered they had two limitations: alignment requirements and lack of type information. They have to be aligned to some page boundaries which obfuscates the true size of the data they contain. I also can't easily tell them apart from each other, I'd need to put some header in the data itself in order to find the correct segment which risks false positives.
Those two problems are solved by the PT_LONE segment. It describes the true size of the data block inside the PT_LOAD. It provides a unique magic number for finding it while iterating over the program header table. No possibility for mistakes. When it's found the program gets a pointer and a size and the data is already in memory because it's contained inside a properly aligned PT_LOAD segment. This makes the kernel do a ton of heavy lifting before the program has even begun execution.
This is what the PT_NOTE segment boils down to anyway. It's just a memory area. The standard just happens to mandate the presence of a header structure on those segments. The PT_LONE segment doesn't have that requirement so I just replaced that opaque binary header with S-expressions that my interpreter simply parses instead. Much nicer in my opinion.
> you can always leave the section table in as well
That was the first thing I tried. The objcopy tool even has options that let you embed arbitrary files inside the binary by putting them into a section. Seemed incredibly easy at first.
Turns out that ELF sections are irrelevant to the kernel: it only maps in the PT_LOAD segments. Had to ask lots of questions on stackoverflow and read the Linux ELF loader source code to figure this stuff out. Then there's the fact that linkers don't generate PT_LOAD segments covering the program's sections. Apparently only the dynamic linker uses that stuff so there was never any reason to make the kernel load it. It's just how things are done and it doesn't look like things are about to change to accomodate other use cases.
Since this is not done, anyone who wants to embed data into the ELF has to somehow find and open the running program's own executable, read the thing into memory and parse its contents a second time. This is just bad on so many levels. It's fragile and duplicates work the kernel has already done.
The proper solution is to make the kernel load the data as part of the program's memory image. That requires editing the segments, not the sections. Tools for editing sections are quite featureful. Tools for editing the segments didn't actually exist.
So I made a little tool to patch this data into an ELF file. This turned out to be very hard and annoying. There were two possibilities: either move the table to the end of the file, or reinvent the linker. The first option leaves a huge hole in the file where the original used to be but it does allow adding entries without relinking everything. The second option doesn't waste space but is orders of magnitude more complexity than I had signed up for.
Took a third option by asking for linker support instead. The mold maintainer added a couple lines of code and suddenly my ELF files had PT_NULL entries tailor made for patching which is just awesome and the best possible solution.
I asked GNU ld maintainers to add this feature as well. They suggested I try using linker scripts instead. I had actually tried that before emailing them and it's actually what motivated me to just ask for this. I seriously hope I never have to write linker scripts ever again.
I opened an issue on the LLVM issue tracker but never heard back. I also tried to implement it in the patchelf utility but couldn't figure out how it works.
I have analyzed a few object file formats in another blog post https://maskray.me/blog/2024-01-14-exploring-object-file-for... (HN discussion: https://news.ycombinator.com/item?id=38998914)
ELF is technically not complicated and simpler than COFF and Mach-O.
> Most Unix-on-Intel binary packages are already largely similar. Almost all such operating systems use the "ELF" binary 'packaging'; the various operating systems have small but significant differences, though, that make each system's ELF binary unusable on others'.
Though the scope has diminished with the decline of proprietary unixen, there is a nice bright spot with APE binaries ( https://justine.lol/ape.html ) which are in some regards even more portable since they work on Darwin (which natively uses Mach-O) and NT (which natively uses PE).
- 2011- Xinous, but Xinous has stopped updating https://www.sco.com/developers/gabi/latest/contents.html . It seems that Xinous has moved on from System V based OpenServer/UnixWare. The newer OpenServer seems to be based on FreeBSD. They likely no longer care about the System V ABI.
Nowadays, people make proposals to the generic-abi Google Group. Essentially, a proposal is considered "standardized" if it receives approval from GNU, LLVM, and Ali Bahrami (Solaris representative).
Many GNU extensions are implemented by LLVM and adopted by BSD and newer ELF-based OSes. For extensions that are considered generic enough, it's recommended to propose them through the generic-abi Google Group. psABI documents generally prefer generic extensions over GNU or LLVM-specific ones.