Assembly Notes
Terminologies
B, byteW, word, 2 bytesD, double word, 4 bytesQ, quad word, 8 bytes ($2^3$)T, ten bytes
All C symbols (i.e., functions and global variables) have a underscore prefix appended to them by the C compiler. (This rule is specifically for DOS/Windows, the Linux C compiler does not prepend anything to C symbol names.)
Operating System
Segments exist in RAM, but they are managed and "understood" by the CPU.
physical memory = RAM
When you double-click or run an .exe file, it undergoes a process called Loading. A program cannot run directly on the disk because the CPU can only "talk" to RAM and its own registers at the speeds required for execution.
The Operating System (OS) is effectively the first and most important program loaded into RAM.
swap file in on hard drive
Each process has their own segment on RAM and they are all managed by the OS (which is also on RAM)
- The OS divides the total RAM into two main zones to protect itself from your programs (and to protect your programs from each other).
Kernel Space: This is the "High Ground" where theOS Kernellives. It is strictly protected. If your C or Assembly program tries to read or write to this memory directly, the CPU hardware will immediately trigger a "General Protection Fault" and kill your program.- User Space: This is where your individual
Processes(like your C driver, Java JVM, or a web browser) live. Each process thinks it is the only program in the world.
each segments on RAM has their own corresponding segment registers on CPU.
the memory segment in RAM of each process is further divided into .data, .text, and .bss, heap, stack
heap & stack are on RAM. heap are shared between threads of the same process. Each process has its own heap to work with.
Why stack overflow but heap does not?
- The Heap doesn't have a fixed "limit" like the 1MB stack. If your program needs more space, the OS just gives it more pages of RAM.
- The Stack is a fixed-size, rigid structure allocated the moment a thread is born.
The assembler translate .asm into a binary .obj file. This binary file is organized into sections that correspond to the directives you wrote:
- Header: Information about the file (machine type, number of sections).
.textsection: The actual binary opcodes for your instructions (e.g., 55 for push rbp)..datasection: The raw bytes for your strings and initialized constants.
reverse engineer from binary to assembly or C is very difficult and not viable.
However, reverse from java byte code .class back to .java is actually easy. A Java decompiler can often give you back code that looks almost identical to the original source. This is why Java developers use "Obfuscators" to scramble their code if they want to hide their secrets.
Registers
registers is a special kind of memory built right into the CPU that is very small, but extremely fast to access
Instructions & data are stored in RAM and are loaded into the registers.
There are 16 general-purpose registers. In x86-64, each register is 64 bits wide (6 bytes), and for each of them the lower byte, word and double-word can be addressed individually (incidentally, 1 "word" = 2 bytes, 1 "double-word" = 4 bytes, in case you haven't heard this terminology before).
rsp holds the stack pointer (which is used by instructions like push, pop, call and ret
rsi and rdi serve as source and destination index for "string manipulation" instructions
Another example where certain registers get "special treatment" are the multiplication instructions, which require one of the multiplier values to be in the register rax, and write the result into the pair of registers rax and rdx.
rip holds the address of the next instruction to execute. It is modified by control flow instructions like call or jmp.
rflags holds a bunch of binary flags indicating various aspects of the program's state, such as whether the result of the last arithmetic operation was less, equal or greater than zero. The behavior of many instructions depends on those flags, and many instructions update certain flags as part of their execution. The flags register can also be read and written "wholesale" using special instructions.
AX
Like most x86 registers, RAX (Accumulator Register) is backward-compatible. You can access smaller portions of the register using different names. This allows a 64-bit processor to run code designed for older 32-bit or 16-bit systems.
- Register Name,Size,Description:
RAX, 64 bits, The full register (Quad-word, full 8 bytes).- the 'R' (register) prefix is used for all 64-bit general-purpose registers
EAX, 32 bits, The lower 32 bits (Double-word, the 32 bits - 4 bytes - on the right side).- The "E" in EAX stands for "Extended," which was added when CPUs moved from 16-bit to 32-bit.
AX(accumulator register), 16 bits, The lower 16 bits (Word, the 16 bits - 2 bytes - on the right side).AL, 8 bits, The lowest 8 bits (Byte) from 7-0.AH, 8 bits, Bits 8 through 15 (High Byte ofAX).
BX
BX = base register
rbxfull 8 bytesebxright 4 bytesbxbl,bh
CX
CX stands for the Count Register. Like the other registers we've discussed, it is 16 bits wide and has been extended into ECX (32-bit) and RCX (64-bit).
rcx ecx cx ch cl
DI
DI = Destination Index register
rdi, edi, di, dil
Note: Unlike AX or BX, DI does not have a "high byte" version (there is no DH for DI).
Instructions
The INC and DEC instructions increment or decrement values by one. Since the one is an implicit operand, the machine code for INC and DEC is smaller than for the equivalent ADD and SUB instructions.
inc ecx ; ecx++
dec dl ; dl--
Directives
A directive is an artifact of the assembler, not the CPU. They are generally used to either instruct the assembler to do something or inform the assembler of something. They are NOT translated into machine code.
NASM code passes through a preprocessor just like C. It has many of the same preprocessor commands as C. However, NASM’s preprocessor directives start with a % instead of a # as in C.
The equ directive can be used to define a symbol. Symbols are named constants that can be used in the assembly program. The format is:
symbol equ value
Symbol values can not be redefined later.
The %define directive is similar to C’s #define directive. It is most commonly used to define constant macros just as in C.
%define SIZE 100
mov eax, SIZE
The above code defines a macro named SIZE and shows its use in a MOV instruction. Macros are more flexible than symbols in two ways. Macros can be redefined and can be more than simple constant numbers.
Data directives are used in data segments to define room for memory. There are two ways memory can be reserved. The first way only defines room for data; the second way defines room and an initial value.
- The first method uses one of the
RESXdirectives. TheXis replaced with a letter that determines the size of the object (or objects) that will be stored. - The second method (that defines an initial value, too) uses one of the
DXdirectives. TheXletters are the same as those in theRESXdirectives.
L1 db 0 ; byte labeled L1 with initial value 0
L2 dw 1000 ; word labeled L2 with initial value 1000
L3 db 110101b ; byte initialized to binary 110101 (53 in decimal)
L4 db 12h ; byte initialized to hex 12 (18 in decimal)
L5 db 17o ; byte initialized to octal 17 (15 in decimal)
L6 dd 1A92h ; double word initialized to hex 1A92
L7 resb 1 ; 1 uninitialized byte
L8 db "A" ; byte initialized to ASCII code for A (65)
Double quotes and single quotes are treated the same. Consecutive data definitions are stored sequentially in memory. That is, the word L2 is stored immediately after L1 in memory. Sequences of memory may also be defined.
L9 db 0, 1, 2, 3 ; defines 4 bytes
L10 db "w", "o", "r", ’d’, 0 ; defines a C string = "word"
L11 db ’word’, 0 ; same as L10
For large sequences, NASM’s TIMES directive is often useful. This directive repeats its operand a specified number of times. For example,
L12 times 100 db 0 ; equivalent to 100 (db 0)’s
L13 resw 100 ; reserves room for 100 words
Remember that labels can be used to refer to data in code. There are two ways that a label can be used. If a plain label is used, it is interpreted as the address (or offset) of the data. If the label is placed inside square brackets ([]), it is interpreted as the data at the address. In other words, one should think of a label as a pointer to the data and the square brackets dereferences the pointer just as the asterisk does in C.
In 32-bit mode, addresses are 32-bit. Here are some examples:
mov al, [L1] ; copy byte at L1 into AL
mov eax, L1 ; EAX = address of byte at L1
mov [L1], ah ; copy AH into byte at L1
mov eax, [L6] ; copy double word at L6 into EAX
add eax, [L6] ; EAX = EAX + double word at L6
add [L6], eax ; double word at L6 += EAX
mov al, [L6] ; copy first byte of double word at L6 into AL (line 7)
Line 7 of the examples shows an important property of NASM. The assembler does not keep track of the type of data that a label refers to. It is up to the programmer to make sure that he (or she) uses a label correctly. Later it will be common to store addresses of data in registers and use the register like a pointer variable in C. Again, no checking is made that a pointer is used correctly. In this way, assembly is much more error prone than even C.
Consider the following instruction:
mov [L6], 1 ; store a 1 at L6
This statement produces an operation size not specified error. Why? Because the assembler does not know whether to store the 1 as a byte, word or double word. To fix this, add a size specifier:
mov dword [L6], 1 ; store a 1 at L6
This tells the assembler to store an 1 at the double word that starts at L6. Other size specifiers are: BYTE, WORD, QWORD and TWORD
Input and Output
k
Memory and Addresses
k
Assembler
To assemble the code into object file:
nasm -f object-format first.asm
where object-format is either coff, elf, obj or win32 depending on what C compiler will be used.
Object File (.o, .obj) is binary. It is the direct output of the Compiler (for C) or the Assembler (for your Assembly code). The addresses are relative so you cannot run an object file directly. It contains the .text and .data segments you defined, but they are "orphans" waiting to be joined with others.
The Executable File (.exe) are the "finished product." It is created by the Linker, which takes one or more object files and stitches them together.
Linking is the process of combining the machine code and data in object files and library files together to create an executable file.
For example, to link the code for the first program:
# -c means to just compile, do not attempt to link yet
gcc -c driver.c
# linking
# This creates an executable called first.exe
gcc -o first driver.o first.o asm io.o
It is possible to combine the compiling and linking step. For example,
gcc -o first driver.c first.o asm io.o
Now gcc will compile driver.c into object file and then link.
The -l listing-file switch can be used to tell nasm to create a listing file of a given name. This file shows how the code was assembled.