A Simple ELF
The article examines the complexities of creating a simple Linux program, contrasting a standard C version with one using direct system calls, emphasizing that simplicity reduces complexity, not necessarily ease.
Read original articleThe article discusses the complexities involved in creating a simple program in Linux, specifically focusing on the ELF (Executable and Linkable Format) structure. It begins with a seemingly straightforward C program that prints "Hello Simplicity!" but reveals the underlying complexity when examining the compiled output. The author highlights the numerous symbols and sections generated by the compiler, emphasizing that even simple tasks involve significant overhead due to standard libraries and initialization routines. To illustrate the concept of simplicity versus complexity, the author proposes a version of the program that eliminates reliance on the standard library by using system calls directly. This approach involves writing assembly code to handle output and process termination, thus providing a custom entry point. The article concludes that while the new program may not be easier to understand, it is indeed simpler in terms of dependencies and structure, aligning with the notion that simplicity is not synonymous with ease but rather the absence of unnecessary complexity.
- The article explores the complexities of creating a simple program in Linux.
- It contrasts a standard C program with a version that uses system calls directly.
- The author emphasizes that simplicity is about reducing complexity, not necessarily making things easier.
- The discussion includes an analysis of the ELF structure and the overhead introduced by standard libraries.
- The final program demonstrates a custom entry point and direct system calls, showcasing a simpler approach.
Related
Weekend projects: getting silly with C
The C programming language's simplicity and expressiveness, despite quirks, influence other languages. Unconventional code structures showcase creativity and flexibility, promoting unique coding practices. Subscription for related content is encouraged.
Why We Build Simple Software
Simplicity in software development, likened to a Toyota Corolla's reliability, is crucial. Emphasizing straightforward tools and reducing complexity enhances reliability. Prioritizing simplicity over unnecessary features offers better value and reliability.
We Build Simple Software
Simplicity in software development, likened to a Toyota Corolla's reliability, is crucial. Emphasizing straightforward tools, Pickcode aims for user-friendly experiences. Beware of complex software's pitfalls; prioritize simplicity for better value and reliability.
Driving Compilers
The article outlines the author's journey learning C and C++, focusing on the compilation process often overlooked in programming literature. It introduces a series to clarify executable creation in a Linux environment.
In C some things aren't what they seem – The Craft of Coding
The article highlights confusion in C programming, particularly with the non-existent "–>" operator, emphasizing the need for clarity to prevent misinterpretation and errors in coding practices.
- Discussion of minimal ELF file creation techniques and tools, with users sharing links to their projects.
- References to alternative libraries and methods for writing C programs without standard libraries, emphasizing the simplicity of direct system calls.
- Questions about the complexity and purpose of the article, with some commenters expressing confusion over the need for such low-level programming.
- Insights into the Linux system call interface being more accessible compared to other operating systems.
- Comments on the historical context of programming in assembly and the evolution of programming practices.
1. hand-written minimal ELF headers, with enough asm to do `_exit(main(argc, argv))`: https://github.com/DavidBuchanan314/kurl/blob/main/golfed/el... (currently only implemented for aarch64)
2. "Linux Syscall Support" library for conveniently making raw syscalls from C: https://chromium.googlesource.com/linux-syscall-support/
3. To avoid custom linker scripts (which I hate with a passion), I embed my hand-crafted ELF within a regular ELF, and slice it out at the end (using a python script). The "container" ELF is a regular full-fat ELF, potentially including working debug symbols, but the inner ELF has none of the cruft.
Using this technique, I wrote a barely-functional TLS1.3 client that fits in ~3.5KB (see the rest of repo from the first link)
[1] https://github.com/torvalds/linux/tree/master/tools/include/...
[2] https://github.com/boricj/ghidra-delinker-extension/tree/mas...
I've written an article about this idea:
https://www.matheusmoreira.com/articles/linux-system-calls
You can get incredibly far with just this. I wrote a freestanding lisp interpreter with nothing but Linux system calls. It turned into a little framework for freestanding Linux programs. It's been incredibly fun.
Freestanding C is a much better language. A lot of legacy nonsense is in the standard library. The Linux system call interface is really nice to work with. Calling write is not that hard. It's the printf style string building and formatting that I sometimes miss.
It's a webserver written in x86 assembler, which makes raw syscalls. It has no functions, and unmaps the stack so it uses only one 4KB page of memory at runtime.
I did design my own runtime binary executable/dynamic library format which I do embed in an ELF capsule to be loaded by legacy systems. The thing I need to port though is the core user level drivers:vulkan/drm & alsa-lib. The main issue would be the alsa-lib since some part of its API still "requires" a C runtime (you have to call free() on some returned data).
The issue with this "format": it is so much simple, I wonder if it would not be better if each software "dynamic library/user level system interface" should design its own minimal and giga simple "dynamic library" format, taylored for its semantics.
Dunno yet.
On modern hardware architecture, you load position independent memory segment (code and data). You should need its alignment requirement and you are good to go.
Basically, a magic with the alignment, then a table of offsets or re-entrant code (possible on modern hardware architecture which supports try-lock hardware semantics) right after the "header". I chose to use the re-entrant code guarded with an hardware try-lock mechanism, because it is more generic and will be cleaner on the long run than a table of offsets.
Bending the product of code generators (assemblers) into some runtime format was a good idea until most hardware architectures support a hardware try-lock mechanism, then it became really nasty legacy.
I wrote this page for my own compiler that I'm working on, but I think it would be a good complement to this article. Note that the page is not that great on mobile, the extra real estate on desktop really helps.
A printf-hello-world is about 1 KiB. A write-hello-world (syscalls only) is less than 200 bytes. Assembly programming skills not needed to use it.
2. Why is it that exiting at the end of main() requires a system call? Wouldn't a `ret` instruction go "back" to somplace where the OS itself will do cleanup work?
Related
Weekend projects: getting silly with C
The C programming language's simplicity and expressiveness, despite quirks, influence other languages. Unconventional code structures showcase creativity and flexibility, promoting unique coding practices. Subscription for related content is encouraged.
Why We Build Simple Software
Simplicity in software development, likened to a Toyota Corolla's reliability, is crucial. Emphasizing straightforward tools and reducing complexity enhances reliability. Prioritizing simplicity over unnecessary features offers better value and reliability.
We Build Simple Software
Simplicity in software development, likened to a Toyota Corolla's reliability, is crucial. Emphasizing straightforward tools, Pickcode aims for user-friendly experiences. Beware of complex software's pitfalls; prioritize simplicity for better value and reliability.
Driving Compilers
The article outlines the author's journey learning C and C++, focusing on the compilation process often overlooked in programming literature. It introduces a series to clarify executable creation in a Linux environment.
In C some things aren't what they seem – The Craft of Coding
The article highlights confusion in C programming, particularly with the non-existent "–>" operator, emphasizing the need for clarity to prevent misinterpretation and errors in coding practices.