The VikiD Truth

Knowledge base / Computing / C & systems languages

Algorithms | Apple | AssemblyLanguage | BASH | Bash Cookbook | C-Programmming | CommonLisp | Computer Security | CuisSmalltalk | Databases | Emacs | Exploitation | Firewalls | Fortran | Functional Programming | Git | GNUSmalltalk | Haskell | Social Engineering | Javascript | Julia | Linux | Machine Learning | Metasploit |Machine Learning | Networking | Operating Systems | Open Data Science | Odin | Picolisp | Pharo Smalltalk | Purescript | Python | Racket | Scheme | Unity | vim | V | Web Development | Windows WSL | Zig

Assembly Languagess

Here is the truth about assembly: it is not a language so much as a set of labels for the machine’s own instructions. Every program you have ever run, in Python, PicoLisp or JavaScript, ends up as these instructions. Assembly is just the version written for people to read.

What it is, in plain words

A CPU understands only machine code: numbers that encode instructions like “add these two registers” or “jump to this address if the last result was zero”. Assembly language gives each instruction a short name (a mnemonic) such as mov, add, cmp, jne, call, ret, and lets you use labels instead of raw addresses. An assembler (GNU as, NASM, FASM) translates the text into machine code, almost one line to one instruction.

There is no single assembly language. Each CPU family has its own instruction set architecture (ISA):

The notes at the bottom of this page are 32-bit x86 (ebp, esp). On 64-bit x86 the same registers are rbp and rsp, there are sixteen general registers instead of eight, and function arguments arrive in registers (rdi, rsi, rdx, rcx, r8, r9) rather than on the stack.

Why it matters

An everyday analogy: a calculator with a notepad

Imagine a calculator with a few memory buttons (registers), a big notepad (RAM) and a list of numbered instructions you follow one line at a time. “Copy the number from page 40 into memory A. Add memory B. If the result is zero, skip to line 12.” That is all a CPU does, billions of times a second. Assembly is writing out that list of instructions yourself.

A hands-on example: hello, world with no C library

# hello.s  -- x86-64 Linux, GNU assembler (AT&T syntax)
        .globl _start
        .section .rodata
msg:    .ascii "Hello from asm\n"
len = . - msg

        .text
_start:
        mov  $1, %rax          # syscall number 1 = write
        mov  $1, %rdi          # fd 1 = stdout
        lea  msg(%rip), %rsi   # buffer
        mov  $len, %rdx        # length
        syscall

        mov  $60, %rax         # syscall 60 = exit
        xor  %rdi, %rdi        # status 0
        syscall
as hello.s -o hello.o && ld hello.o -o hello && ./hello
ls -l hello          # a few hundred bytes, no libc at all
strace ./hello       # exactly two system calls

Then go the other way and see what C becomes:

echo 'int sq(int x){return x*x;}' > sq.c
gcc -O2 -S -masm=intel sq.c -o - | grep -A3 'sq:'
#   sq:
#       imul  edi, edi
#       mov   eax, edi
#       ret

The argument arrived in edi, the result leaves in eax. That is the System V calling convention used on Linux, macOS and the BSDs.

Common misconceptions

Connections

Some Notes:

ebp is known as the base pointer or the frame pointer.

 1. On entry to your function, you push it (to save the value for the calling function).

 2. Then, you copy esp, the stack pointer, into ebp, so that ebp now points to your function's stack frame.

 3. At the end of your function, you then pop ebp so that the calling function's value is restored.

For some clarification on exactly what is going on -
the push instruction:

 1. puts the value from the specified register (ebp in this case), onto the stack, and
 2. decrements the stack pointer by the appropriate amount.

The pop operation is the opposite -

 1. it increments the stack pointer and takes a value from the stack and
 2. puts it in the specified register.

Last edited · 12 revisions

Tags: #Computing