epoch: 1735235532
Watching Revolution OS will inspire you to learn Linux and understand what Linux brings to the table when it comes to development.
Von Neumann's work (and of course, including the people who worked under him before his paper drafts), ENIAC, and the realization that someone, at some point, wrote the very first assembly code (with the hope of trying to interact with dead electronic silicon) - all of this inspires me to learn Assembly. Before that, we were using punch cards, which, if I’m not mistaken, were originally used in the textile industry.
It’s not just about writing code for me; it’s about a deep desire to truly understand how machines work at their core. I feel proud and amazed that we, as humans, have created so much from almost nothing. Sometimes, it feels like we’re magicians, pulling inventions out of thin air. All I want is to be a part of this incredible journey. I’m so grateful for being in the right place, as I’m aware of wars going on somewhere on this same planet - it feels like a fateful act that we have the time to spend and understand things. I feel like I’m standing on the shoulders of giants (and no, I’m not getting into why Sir Isaac Newton said “giants” or if he was teasing his nemesis:)).
Learning assembly is like looking under the hood of an immensely powerful, complex machine. You gain a direct understanding of how high-level languages translate their instructions into the very steps a processor executes. It’s the language of the hardware itself-unencumbered by the abstractions you find in C, Python, or Java.
This perspective can be both empowering and awe-inspiring. When you write assembly, you’re moving registers, toggling flags, and manipulating memory as the CPU does it. It’s also a gateway to improving overall coding prowess: once you understand assembly, you’ll be able to optimize high-level code, debug elusive bugs, and foresee performance bottlenecks long before they become an issue.
Outlinining some core areas where assembly gives you deeper insight:(my fvrt being Reverse Engineering)
%r8
through %r15
—to keep more temporary values in registers, while the 32-bit build has to spill some of those values to memory due to fewer available registers.
Below is a naive Fibonacci C example that can lead to such a problem in a memory-constrained environment:
#include <stdio.h>
#include <stdint.h>
static const char banner[] = "Fibonacci Demo\\n";
uint32_t fib(uint32_t n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
int main(void) {
printf("%s", banner);
uint32_t N = 40;
uint32_t result = fib(N);
printf("fib(%u) = %u\\n", N, result);
return 0;
}
In a limited-memory environment, repeatedly calling fib()
can exceed the small stack, inadvertently overwriting the banner
string in read-only memory. Observing the assembly (or linker script) shows exactly how the stack grows and collides with .rodata
. Fixing either the recursion pattern (e.g., using an iterative approach) or reconfiguring the memory map prevents this corruption.
movaps
, mulps
, and addps
, the performance improved significantly. Below is a naive Fourier transform example that illustrates this:
#include <stdio.h>
#include <math.h>
#include <xmmintrin.h>
#define N 8
void dft_naive(const float* inRe, float* outRe, float* outIm, int n) {
for (int i = 0; i < n; i++) {
float sumRe = 0.0f;
float sumIm = 0.0f;
for (int k = 0; k < n; k++) {
float angle = 2.0f * (float)M_PI * i * k / n;
sumRe += inRe[k] * cosf(angle);
sumIm -= inRe[k] * sinf(angle);
}
outRe[i] = sumRe;
outIm[i] = sumIm;
}
}
int main(void) {
float inRe[N] = {1,2,3,4,5,6,7,8};
float outRe[N] = {0};
float outIm[N] = {0};
dft_naive(inRe, outRe, outIm, N);
for (int i = 0; i < N; i++) {
printf("freq[%d] = (%f) + j(%f)\\n", i, outRe[i], outIm[i]);
}
return 0;
}
The naive implementation repeatedly calls cosf()
and sinf()
, which is computationally expensive. Using vectorized instructions like movaps
and mulps
to precompute values and perform batch operations can significantly improve performance, especially on modern SIMD-capable architectures.
%rbx
to remain preserved, while the C compiler reused them. Correcting the calling conventions aligned the two, resolving the crashes.
Taken together, these technical insights don’t just make you a “low-level coder”-they make you a well-rounded programmer who can switch between high-level design and low-level details whenever needed.
[Ilya@m87 ~]$ cat /usr/include/asm/unistd_32.h | grep "exit"
#define __NR_exit 1
#define __NR_exit_group 252
[Ilya@m87 ~]$ cat /usr/include/asm/unistd_64.h | grep "exit"
#define __NR_exit 60
#define __NR_exit_group 231
A fantastic resource to get you started is Jonathan Bartlett’s book, often referenced as “Programming from the Ground Up.” It dives into x86 Linux assembly, guiding you through examples and hands-on exercises in a logical, approachable manner.
One version of the PDF is still active as of epoch:1735236236 and can be found here:(im lazy to write dates, I use `mtime` command for my personal reasons) Programming from the Ground Up (lettersize PDF) .
Bartlett's structured approach helps demystify assembly and correlates it directly to the day-to-day tasks programmers care about-like how function calls work or how data structures fit in memory.
If you’re looking for some informal notes and a place to see how I’ve been exploring (and explaining) some of Bartlett’s code examples, feel free to visit my GitHub repository:
github.com/1darshanpatil/Assembly
Please note, this is a leisure project, so don’t expect a polished tutorial or formal structure. However, it might offer additional perspectives on the foundational concepts-and maybe inspire you to add your own experiments and notes as you learn.