Skip to main content

A Gentle Introduction to NASM: Why and How to Get Started

Introduction

    As the title suggests, in this post we'll be discussing many things about Assembly language. This is a series I have been wanting to make for a while now. My experience with Assembly Languages started way back in 2018 when I was in College. There we had a particular subject called Computer and Network Fundamentals where we had to tinker with MIPS Assembly (talk about fundamentals). It was love at first sight.

    Now it's been a few years since then, so my skills at programming with assembly language got a bit better (not by much though). In case there's anyone else in this world that would like to start writing a few programs with this wonderful technology, this post and most likely the few following are gonna be of great help, I hope.

What exactly is Assembly Language?

    All this talk about Assembly is great, but what exactly is it? I’m guessing most of you already have a basic understanding of what Assembly language is and how it functions. But just in case you’re not entirely sure, let’s break it down.

    Assembly language is essentially the closest you can get to machine code while still having something that’s human-readable. Think of it as a thin layer of abstraction over the raw binary code that a computer's processor understands. Machine code consists of long strings of ones and zeros, which are pretty tough for humans to read and write. Assembly simplifies this by using mnemonic codes and symbols, making it somewhat easier for us to write instructions directly for the CPU.

    One important thing to note about Assembly is that it’s processor-dependent. This means that Assembly code written for one type of processor, say an Intel iX series CPU, won’t work directly on a different type of processor. But why is that?

    Well, each chip manufacturer designs their processors in a unique way. They all have their own methods for moving and processing data, which results in different ISAs (Instruction Set Architectures). The ISA is basically the set of instructions that a particular processor can execute. Since different processors have different ISAs, the Assembly code written for one won't necessarily be compatible with one another.

    Now, these ISAs generally fall into two main categories: CISC (Complex Instruction Set Computer) and RISC (Reduced Instruction Set Computer). The names pretty much give away their main characteristics.

  • CISC Architectures: These are packed with a broad array of instructions, allowing you to perform complex operations with relatively simple code. However, the trade-off is that each of these instructions might take more CPU cycles to complete. This means the processor might take longer to execute each instruction, but the code can be more concise.

  • RISC Architectures: On the flip side, RISC processors have a much smaller set of instructions. These instructions are simpler and faster to execute, requiring fewer CPU cycles. The downside? You often need more lines of code to perform the same operations that CISC might handle in fewer instructions.

    RISC architectures have been gaining popularity, especially with the advent of Apple Silicon and similar technologies. The reason? Power efficiency. RISC instructions are generally more power-efficient, meaning they generate less heat and can help devices like laptops and smartphones run cooler and last longer on a single battery charge. This is why you see RISC-based processors being used in devices where battery life and energy efficiency are critical.

Why Assembly?

    You might hear some die-hard Assembly enthusiasts say, "For efficiency, duh. It’s like, so obvious..."—and sure, there’s some truth to that. Back in the day, when computing power was much more limited, developers really had to squeeze every last drop of performance out of their hardware. 

    Writing code in Assembly allowed them to do just that, making programs run faster and more efficiently. But let’s be real: those days are largely behind us. Modern hardware is incredibly powerful, and the trade-off between the effort it takes to write Assembly code and the performance gains you get isn't what it used to be.

    Today, you can write a relatively efficient program in a high-level language with much less effort. Tools, compilers, and optimized libraries make it easy to create software that runs quickly and efficiently, without having to dive into the low-level world of Assembly. Plus, hardware has advanced to the point where it's no longer the major bottleneck it once was. We now have multi-core processors, gigabytes of RAM, and solid-state drives that make high-level languages more than capable of handling most tasks.

    So, why learn Assembly then? Well, the truth is, it’s not for everyone. The main reason to dive into Assembly language today is for the sheer enjoyment of it. There’s something uniquely satisfying about working at the metal level of a computer, something that appeals to a particular kind of programmer. It’s challenging, intellectually stimulating, and offers a deep sense of accomplishment when you finally get your code working just right.

    For me, Assembly is fascinating because it forces you to think differently. It strips away the abstractions and brings you closer to how the computer actually operates. You’re not just writing code; you’re engaging with the machine in a direct and intimate way. There’s a thrill in understanding the nitty-gritty details of how things work under the hood, and for those of us who love a good challenge, Assembly provides plenty of them.

    That said, there is an added bonus to learning Assembly: it gives you a much better understanding of how computers work internally. As you write Assembly code, you start to see how the CPU executes instructions, manages memory, and interacts with other components. This deeper understanding can make you a better programmer overall, even if you primarily work in higher-level languages.

So, to sum it up: if you’re drawn to the idea of mastering a difficult skill, if you enjoy tinkering with the inner workings of your computer, or if you simply love a good challenge, Assembly language might just be for you. And who knows? Along the way, you might even come to appreciate the elegance and beauty of coding at the lowest level.

What about NASM?

Basics

    To compile any Assembly program you'll need, well, an assembler. NASM (Netwide Assembler) is one of the most straightforward options out there. NASM uses Intel syntax, which many find simpler and more intuitive compared to alternatives like AT&T syntax (let’s be honest, if you’re a fan of AT&T you're a psycho). The syntax in NASM is clean, without any of the quirky symbols you might find in other assembly languages, making your code easier to read and understand.

    However, there's a small catch: NASM is specifically designed for Intel processors. So, if you’re working with a different architecture (like ARM), you might need to look elsewhere for an assembler that fits your needs.

    One of NASM's strengths is its flexibility. It comes with a variety of configurations and flags that allow you to tailor the compilation process to the specific operating system you’re working on. For this blog series, I'll be writing all the code on Manjaro Linux, running on my trusty Intel i5 processor from the 9th generation (yes, I’m still rocking a laptop from 2018—it's a Coffee Lake architecture, and it’s been serving me well!).

    All the code snippets I'll share will be available in a GitHub repository, so you can easily follow along. I’ve also included a bash script in the repo to help you compile any NASM program you like, provided you’re running a similar setup to mine. This should make it easier for you to get started without having to fiddle around too much with configurations.

A bit of history

    I find the history of NASM pretty fascinating, so I’m going to take a moment to share it with you. If this isn't your cup of tea, feel free to skip ahead.

    As we mentioned earlier, NASM stands for Netwide Assembler. Now, you might be wondering—if Assembly programs are typically chip-dependent, why is it Netwide? It’s true that ASM code is usually specific to a particular processor architecture, but there's more to the story. Many libraries or helpers that can make your coding life easier,  as well as some system calls are often OS-dependent. Additionally, different operating systems interpret machine code in unique ways, so having an assembler that can compile code for multiple platforms is a pretty big deal—especially back in 1996 when NASM first came onto the scene.

    So yes, NASM has been around for quite some time. The project was initially started by Simon Tatham and Julian Hall. These two developers set out to create something more than just another assembler—they wanted a tool that could be used across various platforms, and they succeeded.

    One of the coolest aspects of NASM from the very beginning was its simplicity. At the time, many assemblers were either too complex or too niche, making them hard for beginners to grasp. NASM, however, was designed to be user-friendly, with a clean and straightforward syntax. This approach made it a favorite among those just starting out with assembly language (like us!).

    That simplicity is exactly why NASM has become the go-to choice for beginners looking to learn assembly, and it’s the main reason I chose it to guide you through the basics.

Where to start?

    You see, there’s a ton of info out there on Assembly language. You can dive in and figure out a lot on your own—no problem. The problem? Assembly is a bit of a niche area. This means you’ll often find yourself chasing down random rabbit holes just to nail down one tiny detail. It’s part of the fun (and frustration) of learning Assembly! Thankfully, the basics are pretty well-known and easy to find, so getting started isn’t too bad.

    Now, before we dive into the deep end with NASM, I want to share some of the resources that helped me get my feet wet. These are solid starting points that cover the essentials. My goal with this blog series is to take you beyond the basics and distill all the knowledge I’ve picked up into one easy-to-access spot. To do that, I’m going to assume you’ve already got a handle on the fundamentals—things like instructions, registers, labels, and the like.

Without further ado, here’s the list of goodies I promised:

    Of course, there’s a ton more out there, but I don’t want to overload you right off the bat. With these resources, you’ll be in good shape to follow along with the upcoming posts, where we’ll really get our hands dirty with NASM. We’re talking actual code, real examples—the whole shebang.

Setting up the environment

      Okay, you now have a good grasp on what assembly language is and are not scared enough to turn away. Good! That means I can tell you all about setting up the environment you'll need to start this awesome journey.

    First off is, obviously, NASM. It can be installed easily with any packet manager out there, or as an executable file on Windows.

Windows

For Windows users, installing NASM is pretty straightforward:

  1. Using the NASM Installer:

    • Head over to the NASM official website, and download the latest Windows installer.
    • Run the installer, follow the prompts, and NASM will be installed on your system.
  2. Using Chocolatey (Package Manager):

    • If you’re using Chocolatey, simply open your command prompt as an administrator and run: 
    choco install nasm

Linux

    This is going to greatly vary between distros. I'm going to give you a list of all the basic ones such as Debian, RHEL, Arch, etc. If yours is not among these, I assume you know what you're doing and are capable enough to install a package on your own.

Ubuntu/Debian-based Distros:

  • Open your terminal and run:
    sudo apt-get install nasm

  Arch-based Distros:

  • Open your terminal and run:
    sudo pacman -S nasm

  Fedora/RHEL Distros:

  • For Fedora open your terminal and run:
  • sudo dnf install nasm
  •  For RHEL (you might need EPEL enabled):
    sudo yum install nasm

  Alpine-based Distros:

  • In Alpine and Alpine-based distros run the following:
  • sudo apk add nasm

Gentoo:

  • For Gentoo, you can install NASM using:
    sudo emerge dev-lang/nasm

MacOS

    The easiest way to install anything on MacOS is to use Homebrew. If you do not have it installed, run the following command:

    /bin/bash -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
        

 Then you can run an installation command like so:

    brew install nasm
    

Verify the installation

    After the installation process is completed, it's always a good practice to check if it's installed correctly. To do that, run the following command regardless of the OS you're rocking.

    nasm -v

 The output should look like this:

    If the output is nothing like this and tells you the command nasm can't be found, try to reload your terminal, sometimes the new packages you install aren't always picked up immediately.

 Compiling your code

    I’m going to dive into this more in future posts, but I wanted to give you a little sneak peek at something that’ll make your life a whole lot easier when working with Assembly code.

    So, here’s the deal: when you’re working with Assembly, there are a couple of steps to go from code to something you can actually run. First, you compile your code with NASM, and then you’ve got to use a linker to turn that into an executable. It’s not rocket science, but it’s still a bit of a hassle to do every single time.

    To save you some time and sanity, I’ve cooked up a little bash script that handles all of that for you in one go. Say hello to cmplasm —your new best friend when it comes to compiling Assembly code.

    The idea is super simple: instead of running a bunch of commands every time, you just use one command, and boom—you’ve got yourself an executable. No fuss, no muss.

    I’ve put cmplasm up on my GitHub repository, complete with documentation on how to use it. Feel free to check it out, get comfy with it, and start streamlining your workflow. Or, if you’d rather wait for a more in-depth walkthrough, stay tuned! In the next post, I’ll break down exactly how cmplasm works and how it’ll make your Assembly journey a whole lot smoother.

To be continued...

    Alright, that’s a wrap for this intro! We’ve covered the basics, and you’ve got a solid list of resources to get you started. Now that you’ve got a good foundation, it’s time to take things up a notch. In the next post, we’re going to roll up our sleeves and dive into some actual Assembly code.

    But don’t worry—we’re not just going to stop at the usual “Hello, World!” Instead, we’re going to tackle something a bit more interesting that’ll give you a real taste of what Assembly programming is all about. Think of it as dipping your toes into the deep end.

    So, get your environment set up, review those basics, and get ready for the fun stuff. Next up: we get our hands dirty with some real NASM programming. See you in the next post!

Comments

  1. Introduction has an "I" at the start

    ReplyDelete
  2. Seems to be an Android rendering issue. On PC browser looks good.

    ReplyDelete

Post a Comment

Popular Posts

The magic of eBPF III: Development playground

Introduction  At some point, we had to dive into developing programs in eBPF, and that time has finally come. In this post, we'll explore several different approaches to writing eBPF programs, including powerful tools like Cilium and BCC. I'll highlight the methods that I find most efficient and convenient, because as developers, our goal is to write code quickly and effectively, without unnecessary complications. So let's get straight to the point and see how we can streamline our eBPF development workflow.  I think I should clarify, my go-to method of coding eBPF programs is with Cilium and their bpf2go library. A spectacular and simple way of coding programs in kernelspace, with C like syntax, and a very comfortable way of adapting the userspace with Golang. It turns out that all you need to do that is the big brain of the people in Cilium. I won't spoil anything just yet, but keep in mind that all my tinkering with eBPF has been done with bpf2go .   I st

The magic of eBPF I: What is this?

 Introduction     This post has been mainly inspired because I've been tinkering with eBPF for the past few months, getting to know it works. Now that I have what could be consider "Solid" knowledge on the matter, I thought to share and tell my experience and the possibilities I foresee with this technology.     If I say that the technology world, especially IT, is in constant evolution and change I surprise no one however, it's been a long time since we get something so potentially game-changing as eBPF is. Originally, it was designed to filter network packets, but now has grown into an incredibly versatile and powerful tool that enables developers and security engineers to run sandboxed programs in the Kernel space.     I'm fully aware that the moment you read Kernel a shiver was sent down your spine, and if it's not the case, I'm glad to meet another fellow low-level enthusiast. Regardless, you shouldn't be scared of any of this. To you as a pro