Pagina para Basadxs

>>> ESTA ES LA PAGINA MAS BASADA DE TODO EL INTERNET - ESO INTENTO <<<
Actualizado: July 19, 2026 10:22 PM GMTCEST

Assembly

Assembly

The Basics

The Working of the CPU

A computer works by having 3 primary components:

  • CPU
  • Memory
  • Input/Output devices

These three components are connected with each other using buses, they transmit all the data between these components.

The way it works, is that when we submit input (using an I/O device), it’s transmitted to the memory using buses, then this data is moved to the CPU for processing, and when it’s finished processing, it’ll move back to the memory using buses, and back again to the I/O device to, for example, be displayed on the screen.

The CPU is made of different components:

  • Control unit: The main purpose of this component is to control what is happening at any time within the processor. Its job is to perform 4 basic functions:
    • Retrieve instructions from memory.
    • Decode instructions for operation.
    • Retrieve data from memory as needed.
    • Store the reserves as necessary.
  • Execution Unit: The main function of the processor is to execute instructions, this function is performed in the execution unit.
  • Internal memory locations: The CPU has internal memory locations called registers, which are interconnected with the execution unit. These registers are capable of storing data elements for processing without having to access the memory storage unit. There are some special kinds of registers called flags which are used to perform status checks on each arithmetic and logical operations in the CPU.
  • Registers of the CPU

    A register is a small memory space in the CPU to hold data for processing instructions. They can store any CPU instruction, any memory address, or any kind of data.

    As we’ll be focusing on 32-bit assembly, the size of the registers are 32-bit, 16-bit, and 8-bit.

    The lower 8-bit registers are called accumulator, base, count, and data, denoted by a, b, c and d respectively.

    If we add 8 more bits to our 8-bit registers, we’ll have 16-bit registers: AX, BX, CX, and DX.

    If we add 16 more bits to our 16-bit registers, they are now called extended registers and their names remain the same, but now have an E at the beginning of them: EAX, EBX, ECX, EDX.

    
    +------+                       +--------------+--------------+
    |32 bit+-----------------------+    16 bit    |    16 bit    |
    +------+                       +--------------+-------+------+
                                                          |
                                                          |
                                                 +--------+--------+
                                                 | 8 bit  |  8 bit |
                                                 /--------+--------\
                                                /                   \
                                               /                     \
                                              /                       \
                                        Higher bits               Lower bits
    

    If we take for example the EAX register:

                                        AX              AX
    +------+                       +--------------+--------------+
    | EAX  +-----------------------+    16 bit    |    16 bit    |
    +------+                       +--------------+-------+------+
                                                          |
                                                   AH     |   AL
                                                 +--------+--------+
                                                 | 8 bit  |  8 bit |
                                                 /--------+--------\
                                                /                   \
                                               /                     \
                                              /                       \
                                        Higher bits               Lower bits
    

    The lower 8-bits are called AL, and the higher 8 bits are called AH. If we combine AH and AL we get AX.

    There are three types of registers in the CPU:

    • General Purpose: We have 8 32-bit registers that are used for storing working data:
      • EAX: Accumulator (space) for operands and results data.
      • EBX: Pointer to data in the data memory segment.
      • ECX: Counter for string and loop operations.
      • EDX: I/O Pointer.
      • EDI: Data pointer for destination index of string operations.
      • ESI: Data pointer for source index of string operations.
      • ESP: Stack pointer.
      • EBP: Stack base pointer.
    • Segment: There are 6 segment registers that are used for referencing memory locations:
      • CS: Code segment contains the pointer to the code segment in memory. The code segment is where the instruction codes are stored in memory.
      • DS, ES, FS, GS: Data segment pointer
      • SS: Stack segment, points to the stack area in memory.
    • Instruction Pointer: Called the program counter, it tells the computer where to go next to execute the next commands and controls the flow of the program.
  • Flags

    To understand the concept of a flag, let’s take a simple example (write an example of using the status flag for a simple addition or other operation.)

    (write examples for each flag defined)

    In 32-bit CPUs, there’s a 32-bit register where the flags are stored. They are a collection of binary indiactors (meaning each one takes only one bit) that represent the current state of the processor. Some of the status flags are:

    • Carry Flag.
    • Zero Flag.
    • Auxiliary Flag.
    • Sign Flag.
    • Overflow Flag.
    • Parity Flag.

    These flags are used by the CPU to make decisions during the execution of the program, such as whether to jump to a different part of the program, or whether to continue with the current operation.

    After the status flags, we have system flags, they are meant to control the overall behaviour of the CPU and its interaction with the system. They are usually set by the operating system rather than individual programs.

    After system flags we have control flags. They are used to control specific behaviour in the processor. There’s only one flag defined: DF (direction flag), it’s used to control the way the strings are handled by the processor.

    The remaining flags are reserved for the system itself.

Memory Layout of a Program

Whenever we write a program, all the data is stored according to this memory layout:

+----------------------------------------------------+
|  Command line arguments and environment variables  |
+----------------------------------------------------+
|                                                    |
|                      Stack                         |
|                                                    |
+-------------------------+--------------------------+
|                         |                          |
|                         |                          |
|                  Space to grow                     |
|                         |                          |
|                         |                          |
+-------------------------+--------------------------+
|                                                    |
|                       Heap                         |
|                                                    |
+----------------------------------------------------+
|     Uninitialised data .bss                        |
+----------------------------------------------------+
|            Initialised data .data                  |
+----------------------------------------------------+
|                    .text                           |
|            executable code instructions            |
+----------------------------------------------------+

In the first segment (going top-down), we have some space reserved for command line arguments and environment variables.

After that we have the stack, which is our function data.

Then we have the heap, which is dynamic memory, where all the data is stored.

Then we have our BSS segment, which is where uninitialised data is stored.

After that, we have our DATA segment, which is where our initialised data will be stored.

And finally, we have the TEXT segment, which are just all the executable code instructions of the program.

Let’s look at a small C program to understand all these segments:

int var1;
int var2 = 4;

int
main (void)
{
  return 0;
}

The variable var1 will be in our BSS segment, as it’s uninitialised. var2 will be in our DATA segment as it’s initialised. The data used in our main function will go to our stack (all the variables we declare inside of it). All the executable code of our program (which will just be the contents inside of the main function) are going to be in the text segment.

Writing Hello World in Assembly

The Structure of an Assembly Program

To code in assembly, we need to create a file ending in *.s, for example: “hello_world.s”.

First, let’s define the sections that our program will use:

.section .data

In the .data section, we’ll define our initialised variables. After that, we’ll define the .bss section for the uninitialised data:

.section .data

.section .bss

Then, we’ll define the text segment:

.section .data

.section .bss

.section .text

Where the actual instructions of our code will be stored.

Then, we’ll need to define our main function (as we do with C), using .globl and providing a label name, and defining that label:

  .section .data

  .section .bss

  .section .text
  .globl _start

_start:

To compile an assembly program there are two steps we need to follow: first, we need to assemble it using the as utility:

as hello_world.s -o program.o

With the -o flag we are specifying the name of the output object file that will be generated (hence the *.o extension). Once the program is assembled, we need to link it using ld:

ld program.o -o program.out

The syntax is pretty much the same, we specify the object files that we want to link, and then we specify a name for the output.

Once we have the output file program.out if we try to run it we’d run into an error, it can be either a message stating that we cannot execute a binary file, or we’d get a segmentation fault (which is what is called undefined behaviour), but that happens only because our program doesn’t have any instructions, and it’s not gracefully exiting.

System Calls

In an operating system there are some pre-built tasks which are called system calls, they are useful to allow programs to perform operations that are reserved (or implemented) at the operating system level, for example, creating files, outputting to a file, allocating memory, etc.

For example, if we have this very simple C program:

#include <stdio.h>

int
main (void)
{
  printf ("Hello, World!\n");

  return 0;
}

The way that it works to print out something to the screen is using the write system call, which is used to write data into a file.

In Linux there are 3 pre-defined streams that allow us to manage data:

  • stdin is the keyboard input.
  • stdout is the output to the screen.
  • stderr is for outputting errors.

Each of these files have an unique ID that is constant between all programs: for stdin it’s 0, for stdout it’s 1, and for stderr it’s 2.

So to write something onto the screen, we need to write into the file descriptor 1 (as it’s the file descriptor for the stdout).

Hello World in Assembly

In order to write hello world in assembly to the screen we need to know two things:

  • What is the system call number for the write syscall.
  • What are the arguments required for this system call.

To know the system call number that we need to call, we can do so reading the contents of the file located in /usr/include/asm/unistd_32.h. In that file, we’ll find the following line:

#define __NR_write 4

Meaning that the system call number for write is 4.

For now, that’s enough for us to start writing our assembly program, so we’ll start from the basic structure of a program:

  .section .data

  .section .bss

  .section .text
  .globl _start
_start:

In the data section, we’ll define a msg variable that will hold the value of the string we want to print, which in this case is “Hello, World!\n”. In assembly we define variables as labels, so we can just do:

msg:
  .ascii "Hello, World\n"

In the msg label, we’ve defined data of type ascii using .ascii with a value of “Hello, World\n”.

Now, in our _start label we need to specify which system call we are using. For that, we are going to use the mov instruction:

movl $4, %eax # syscall number

What the mov instruction does is to move data from the source to the destination, in our case our source is 4, and our destination is the eax register (as this one must hold the syscall number we want to call). Notice that we need to use the $ sign with number literals, and % with registers.

Once we are finished specifying the syscall number we want to call, we need to specify the other arguments for the systemcal call we are intending to use. The write system call takes three arguments:

  • The file descriptor into ebx.
  • The address of the message that we want to write into the specified file into ecx.
  • The length of our message into ecx.

After specifying those parameters we’ll have a _start label similar to this one:

_start:
  movl $4, %eax # syscall number
  movl $1, %ebx # file descriptor to write to
  movl $msg, %ecx # pointer to our message
  movl $13, %edx # buffer size in bytes

Finally, once we are finished specifying the parameters, we are ready now to actually do the system call. We do so with the int keyword and specify the number of the system call we want to execute. The write system call is part of the 0x80 system call, so we’d specify it like this:

int $0x80

Now, we can assemble, link and execute our program like this:

as -32 hello_world.s -o hello_world.o
ld -m elf_i386 hello_world.o -o hello_world.out
./hello_world.out

And we’d get an output similar to this one:

Hello, World
segmentation fault (core dumped)

Again, we are having the segmentation fault we had earlier. As explained before, this happens because we are not exiting gracefully the program, to do so we have another syscall which is the exit system call.

To implement this exit systemcall, refer back to the unistd_32.h file:

#define __NR_exit 1

So now, we know that the exit syscall is 0. And to call it from our assembly program, we also need to specify an exit status code:

movl $1, %eax
movl $0, %ebx
int $0x80

This is how our entire program looks like:

  .section .data
msg:
  .ascii "Hello, World\n"

  .section .bss

  .section .text
  .globl _start

_start:
  movl $4, %eax
  movl $1, %ebx
  movl $msg, %ecx
  movl $13, %edx
  int $0x80

  movl $1, %eax
  movl $0, %ebx
  int $0x80

Debugging our Program

To debug our program we’ll use the GNU Debugger (GBD). To use it, we need to first assemble and link our program. Once that’s done, we can launch it like this:

gdb ./program.out

After we execute GDB, we’ll get a different prompt where we can input different commands:

(gdb) commands go here

What we need to do first, is to disassemble our _start label:

(gdb) disassemble _start

And we’ll get an output similar to this:

Dump of assembler code for function _start:
   0x0000000000401000 <+0>:   mov    $0x4,%eax
   0x0000000000401005 <+5>:   mov    $0x1,%ebx
   0x000000000040100a <+10>:  mov    $0x402000,%ecx
   0x000000000040100f <+15>:  mov    $0xd,%edx
   0x0000000000401014 <+20>:  int    $0x80
   0x0000000000401016 <+22>:  mov    $0x1,%eax
   0x000000000040101b <+27>:  mov    $0x0,%ebx
   0x0000000000401020 <+32>:  int    $0x80
End of assembler dump.

Which is an identical replica of our code (with the difference of msg being its address, instead of the name of the label).

To execute our code step by step we can place a breakpoint in our _start label by doing:

(gdb) break * _start

Notice that we can use the address that the disassemble output returned to us, instead of specifying _start we could also use 0x0000000000401000.

To run our program (so it stops at the breakpoint), we can use the run command:

(gdb) run
Breakpoint 1, 0x0000000000401000 in _start ()

As our program stopped at the movl $4, %eax it hasn’t been executed yet, and will be the next instruction to be executed once the program finishes running. We can confirm that by running:

(gdb) info registers
rax            0x0                 0
rbx            0x0                 0
rcx            0x0                 0
rdx            0x0                 0
rsi            0x0                 0
rdi            0x0                 0
rbp            0x0                 0x0
rsp            0x7fffffffdcd0      0x7fffffffdcd0
r8             0x0                 0
r9             0x0                 0
r10            0x0                 0
r11            0x0                 0
r12            0x0                 0
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
rip            0x401000            0x401000 <_start>
eflags         0x202               [ IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
pl3_ssp        <unavailable>
fs_base        0x0                 0
gs_base        0x0                 0

Even though my operating system is 64 bits, the equivalent of eax is rax and the same with the other registers. We can see that its value is 0.

To execute our next instruction we can use the ni command (which stands for next instruction):

(gdb) ni
0x0000000000401005 in _start ()

If we execute info registers again:

rax            0x4                 4

If we run ni again, then the instruction where we move 1 into ebx will be executed, changing the value of ebx (which can be confirmed with info registers). If we do it again, then we’ll be at the instruction where we move the address of our message into ecx. When we execute info registers:

rcx            0x402000            4202496

We can see the address of our message. To view the contents of that address, we can usee the x/s command in GDB (which is used to print strings) like this:

(gdb) x/s 0x402000
0x402000:     "Hello, World\n"

If we want to print a single character at an address, instead of the entire string, we can use the x/c command instead. If we execute ni one more time, then the value of the edx register will change as well.

The next instruction after we move the length of our message into the edx register is to execute an interrupt to trigger a system call, if we execute ni one more time, this will be the output:

(gdb) ni
Hello, World
0x0000000000401016 in _start ()

If we keep going instruction per instruction, we’ll reach the exit point of our program, and we’ll get the following output:

(gdb) ni
[Inferior 1 (process 8974) exited normally]

Using C Functions in Assembly

We can use C functions from assembly, let’s try to use the printf function in a new program. We’ll start it with the basic structure:

  .section .data
msg:
  .ascii "Hello, World!\n"

  .section .text
  .globl _start
_start:

In the _start label we must first push the data into the stack (in our case our msg label), and then call the printf function:

_start:
  pushl $msg
  call printf

And after that, we need to exit our program. We can do it using the exit function by pushing its argument into the stack as well:

pushl $0
call exit

Now we need to assemble it and link it:

as program.s -o program.o
ld -lc -dynamic-linker /lib/ld-linux.so.2 program.o -o program.out

If it doesn’t work, we can rename our _start label to main, and compile it using GCC:

gcc program.s -o program.out

If we are using a 64 bit operating system, we must specify the -m32 and -no-pie flags:

gcc -m32 -no-pie program.s -o program.out

Moving Data

Defining Data

In assembly there are different datatypes we can use:

Directive Data Type
.ascii Text string
.asciz Null-terminated text string
.byte Byte value
.double Double-precision floating-point number
.float Single-precision floating-point number
.int 32-bit integer number
.long 32-bit integer number (same as .int)
.octa 16-byte integer number
.quad 8-byte integer number
.short 16-bit integer number
.single Single-precision floating-point number (same as float)

To define variables in C, for example, we do it this way:

char *string = "Hello, World!\n";
int a = 3;
float pi = 3.14159;

To define those same variables in assembly, we would do it this way:

  .section .data

string:
  .ascii "Hello, World!\n"

a:
  .int 3

pi:
  .float 3.14159

Using static symbols

Consider the following C program:

#include <stdio.h>

#define MSG "Hello, World!\n"

int
main (void)
{
  printf (MSG);

  return 0;
}

Now, with a basic assembly program:

  .section .data
_msg:
  .ascii "Hello, World\n"

  .section .text

  .globl _start

_start:
  movl $4, %eax
  movl $1, %ebx
  movl $msg, %ecx
  movl $13, %edx
  int $0x80

  movl $1, %eax
  movl $0, %ebx
  int $0x80

To declare a static symbol (to replace the 13 which is the length of our message), in the .data section, we can use the .equ directive to create a static symbol:

.section .data
.equ STRING_LEN, 13

And then we can use it like this:

movl $STRING_LEN, %edx

Defining data in .bss

Let’s start with a basic program:

  .section .bss

  .section .text
  .globl _start
_start:

To define data in our .bss section we need the .comm directive, it means Common Memory Area, and we use it like this:

.section .bss
.comm buffer, 16

In that line, we are creating a variable called buffer, with a size in bytes of 16.

Now, with a variable created, in our _start label we will read input from the user, then we will write the contents to the screen, and finally we will exit the program:

  .section .bss
  .comm buffer, 16

  .section .text
  .globl _start
_start:
  # read syscall

  # write syscall

  # exit syscall

To know what input the read syscall, we can execute man 2 read and we’ll get a man page, with a function definition like this:

ssize_t read(int fd, void buf[count], size_t count);

The first input is the file descriptor, then the buffer pointer, and finally the buffer size, which in this case is 16 bytes. And finally, to get the syscall number we can again check the file located at /usr/include/asm/unistd_32.h.

# ..._start
movl $3, %eax # read syscall number
movl $0, %ebx # stdin fd
movl $buffer, %ecx # buf
movl $16, %edx # count
int $0x80

Now that we got the input from the user into the buffer variable, we can now print it out to the screen, and then exit our program:

# [...]
movl $4, %eax
movl $1, %ebx
movl $buffer, %ecx
movl $16, %edx
int $0x80

# exit the program
movl $1, %eax
movl $0, %ebx
int $0x80

Moving Data

In assembly there are different instructions for moving data, they have the form of movX, with X being the size of the data we are moving. For example:

  • movl - moving 32-bit long data.
  • movw - moving 16-bit long data.
  • movb - moving 8-bit long data

And the instruction follows this form: movX src, dst

For example:

movl $4, %eax

That instruction will set the value of the eax register to 4.

For example:

  .section .text
  .globl _start
_start:
  movl $190, %eax
  movw $3, %bx
  movb $1, %cl

We can also use it to set the values of variables:

  .section .bss
  .comm buffer, 8

  .section .text
  .globl _start
_start:
  movl $100, %buffer # set buffer value of 100

  movl buffer, %ecx # set ecx with the value of buffer

  movl $buffer, %edx # set edx with the value of buffer (with the $ symbol we move the address of the data)
  movl $500, %eax # set eax to 500
  movl %eax, (%ebx) # set the value of eax to the address that is inside of ebx, changing the value of buffer

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

Accessing and Moving Indexed Values

Let’s see how to create, and use arrays in assembly.

As usual, let’s start with our basic program structure, and we’ll create a label in our data section which will hold an initialised array like this:

  .section .data
numbers:
  .int 10, 20, 30, 40, 50, 60

  .section .text
  .globl _start
_start:
  # program goes here

We can define labels that contain multiple elements like that, just by separating them with a comma.

To access these elements we must use a formula, in which we need to specify the base address, the offset address, the index, and the size of each individual element (in bytes). We can do it like this:

_start:
  # base_address (offset_address, index, size)
  # numbers(, 2, 4)
  movl $2, %edi # move the index into the edi register (it should be moved to the edi register)
  movl numbers(, %edi, 4), %eax # move 30 into the %eax register

  # exit the program
  movl $1, %eax
  movl $0, %ebx
  int $0x80

In our specific case, the offset address would be zero, so we can skip it by not specifying a number. For this example, we will be accessing the number 30 so its index would be 2 (as they are counted from zero), and as we know the size of an integer is 4 bytes, so we specify it as its size.

Now let’s check with GDB if the number 30 is indeed inside of the eax register. After we assemble, and link our program, we need to run GDB like this:

gdb ./arrays.out

Let’s get started by disassembling the program so we can get the address of our _start label and set a breakpoint where we move the number 30 into the eax register (our second instruction):

(gdb) disassemble _start
Dump of assembler code for function _start:
   0x08049156 <+0>:   mov    $0x2,%edi
   0x0804915b <+5>:   mov    0x804c00c(,%edi,4),%eax
   0x08049162 <+12>:  mov    $0x1,%eax
   0x08049167 <+17>:  mov    $0x0,%ebx
   0x0804916c <+22>:  int    $0x80
End of assembler dump.
(gdb) break * 0x0804915b
Breakpoint 1 at 0x804915b
(gdb) run

Breakpoint 1, 0x0804915b in _start ()

By the time when we reach our move instruction to the eax register, the move to the edi must’ve happened, so we can check it by using info registers:

(gdb) info registers
[...]
edi            0x2                 2

We can see the values of our array using the examine command like this: x/Nd &numbers, where N represents the number of elements we want to see, as our array has 6 elements, we would run:

(gdb) x/6d &numbers
0x804c00c:    10      20      30      40
0x804c01c:    50      60

So, in our next instruction 30 must be moved into the eax register. We can move to the next instruction using ni and check the registers again:

(gdb) ni
0x08049162 in main ()
(gdb) info registers
eax            0x1e                30

Direct and Indirect Addressing

Let’s consider the following to be our memory region:

+-------------------------+
|                         |
|        0xAAAA           |
|                         |
+-------------------------+
|                         |
|        0xBBBB           |
|                         |
+-------------------------+
|                         |
|        0xCCCC           |
|                         |
+-------------------------+
|                         |
|                         |
|                         |
|                         |
|                         |
|                         |
|                         |
|                         |
|                         |
|                         |
|                         |
+-------------------------+

Suppose we have three memory addresses: 0xAAAA, 0xBBBB, and 0xCCCC. If, for example, we want to move the value 5 into the memory address 0xAAAA, we can do it by just specifying that address like this:

movl $5, 0xAAAA

In that instruction we are moving the value 5 directly into the address 0xAAAA, that’s called direct memory access.

If we cannot access the memory address 0xAAAA, meaning we cannot access that address directly, in that case we’ll perform two steps to move 5 into that memory address. The first step would be to move that memory address into one of our general purpose registers:

movl $0xAAAA, %eax

%eax now contains a pointer to that address. And the last step would be to move the 5 into the address that %eax points to like this:

movl $5, (%eax)

Whenever we move memory indirectly we need to enclose the register in brackets, meaning that the register contains a pointer. That is called indirect memory addressing.

An entire program using indirect memory addressing would look like this:

  .section .data
number:
  .int 0

  .section .text
  .globl _start
_start:
  # direct addressing
  movl $5, number

  # indirect addressing
  movl $number, %eax # move address to %eax (we use $ to move the memory address)
  movl $10, (%eax)

Let’s debug our program to make sure that it works (first assemble it and link it).

(gdb) disassemble start
Dump of assembler code for function start:
   0x08049156 <+0>:   movl   $0x5,0x804c00c
   0x08049160 <+10>:  mov    $0x804c00c,%eax
   0x08049165 <+15>:  movl   $0xa,(%eax)
End of assembler dump.
(gdb) break * 0x08049156
Breakpoint 1 at 0x8049156
(gdb) run

Breakpoint 1, 0x08049156 in start ()
(gdb) ni
0x08049160 in start ()
(gdb) x/x &numbers
0x804c00c:    0x00000005
(gdb) ni
0x08049165 in start ()
(gdb) info reg $eax
eax            0x804c00c           134529036
(gdb) ni
0x0804916b in ?? ()
(gdb) x/d &numbers
0x804c00c:    10
(gdb)

Indirect Address Pointer

Consider the following program:

  .section .data
numbers:
  .int 4, 8

  .section .text
  .globl _start
_start:
  movl $numbers, %eax
  movl $2, (%eax)
  movb $9, 2(%eax)
  movw $3, 6(%eax)

There we have a variable numbers with two integer values: 4, and 8. Meaning that it’s a total of 8 bytes in size.

It’ll be represented in memory like this:

+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 8 | 0 | 0 | 0 | 4 |
+---+---+---+---+---+---+---+---+
  7   6   5   4   3   2   1   0

In the _start label, we are moving the memory address of numbers into the %eax register. In our next instruction we are moving 4 bytes (containing 2) into the address stored in the %eax register. And now it’ll be represented in memory like this:

+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 8 | 0 | 0 | 0 | 2 |
+---+---+---+---+---+---+---+---+
  7   6   5   4   3   2   1   0

In our next instruction, where we are moving a single byte using movb, we’ll move a single byte containing 9 into the second index of the specified address. Notice that as %eax points to the base address (meaning byte 0), the result of that operation will result in:

+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 8 | 0 | 9 | 0 | 2 |
+---+---+---+---+---+---+---+---+
  7   6   5   4   3   2   1   0

Only the second index will be changed, as we are moving a single byte.

In the next instruction, we are moving two bytes as we used movw, it’ll move two bytes containing the value 3 into the sixth byte of our variable, resulting in the following memory layout:

+---+---+---+---+---+---+---+---+
| 0 | 3 | 0 | 8 | 0 | 9 | 0 | 2 |
+---+---+---+---+---+---+---+---+
  7   6   5   4   3   2   1   0

Creating Stack Frames

Let’s start with a basic assembly program:

  .section .text
  .globl _start
_start:
  # create stack frame

  movl %esp, %ebp # copy the esp address to ebp
  subl $8, %esp # create 8 bytes in memory in our stack frame (subtrack means move upwards)

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

Now we can check it using GDB:

(gdb) disassemble _start
Dump of assembler code for function _start:
   0x08049156 <+0>:   mov    %esp,%ebp
   0x08049158 <+2>:   sub    $0x8,%esp
   0x0804915b <+5>:   mov    $0x1,%eax
   0x08049160 <+10>:  mov    $0x0,%ebx
   0x08049165 <+15>:  int    $0x80
End of assembler dump.
(gdb) b * 0x08049156
Breakpoint 1 at 0x8049156
(gdb) run
Breakpoint 1, 0x08049156 in main ()
(gdb) ni
0x08049158 in _start ()
(gdb) info registers
esp            0xffffcdec          0xffffcdec
ebp            0xffffcdec          0xffffcdec
(gdb) p/d $ebp-$esp
$1 = 0 # they point to the same address (this will get the size of our stack frame)
(gdb) ni
0x0804915b in _start ()
(gdb) info registers
esp            0xffffcde4          0xffffcde4
ebp            0xffffcdec          0xffffcdec
(gdb) x/8bx $esp
0xffffcde4:   0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
(gdb)

Before we start adding and removing data to our stack, let’s review something first. Let’s consider the following is our stack:


+------------------------------<----------> ESP
|                              |
|                              |
|                              |
+------------------------------+
|                              |
|                              |
|                              |
+------------------------------+
|                              |
|                              |
|                              |
+------------------------------+
|                              |
|                              |
|                              |
+------------------------------+
|                              |
|                              |
|                              |
+------------------------------+
|                              |
|                              |
|                              |
+------------------------------+
|                              |
|                              |
|                              |
+------------------------------+---------> EBP

To add something into the stack, we’ll need to use the push instruction. Whenever we add data to our stack, the esp register will point at the top of it (the latest item added to the stack).

To remove data from the stack, we’ll use the pop instruction. Let’s continue from the previous program:

  .section .text
  .globl _start
_start:
  # create stack frame

  movl %esp, %ebp # copy the esp address to ebp
  subl $8, %esp # create 8 bytes in memory in our stack frame (subtrack means move upwards)

  # add data into the stack frame
  movl $128, %eax # to push data, it must be in a register
  pushl %eax

  movl $64, %ebx # add more data, 8 bytes that were allocated are used now
  pushl %ebx

  # remove data from stack
  popl %ebx
  popl %eax

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

Data Exchange Instructions

Let’s get started with a simple program:

  .section .text
  .globl _start
_start:
  movl $3, %eax
  movl $5, %ebx

  xchg %eax, %ebx # exchange data between eax and ebx

  movl $0x12345678, %ecx
  bswap %ecx # swap the order of the bytes in %ecx

  # exit program
  movl $1, %eax
  movl $0, %ebx
  int $0x80

The xchg instruction will set the value of %eax to the value of %ebx and vice-versa.

And bswap will swap the order of the bytes in the register that we specify.

Setting and Clearing the Carry Flag

Let’s see how we can enable and disable flags. Let’s consider the following program:

  .section .text
  .globl _start
_start:
  # set the carry flag (CF)
  stc # Set Carry Flag

  # clear carry flag (CF)
  clc # CLear Carry

  # exit program
  movl $1, %eax
  movl $0, %ebx
  int $0x80

And now we can check it with GDB:

(gdb) disassemble main
Dump of assembler code for function main:
   0x08049156 <+0>:   stc
   0x08049157 <+1>:   clc
   0x08049158 <+2>:   mov    $0x1,%eax
   0x0804915d <+7>:   mov    $0x0,%ebx
   0x08049162 <+12>:  int    $0x80
End of assembler dump.
(gdb) b * 0x08049156
Breakpoint 1 at 0x8049156
(gdb) display /5i $eip
(gdb) display / $eflags
(gdb) run

Breakpoint 1, 0x08049156 in main ()
1: x/5i $eip
=> 0x8049156 <main>:  stc
   0x8049157 <main+1>:        clc
   0x8049158 <main+2>:        mov    $0x1,%eax
   0x804915d <main+7>:        mov    $0x0,%ebx
   0x8049162 <main+12>:       int    $0x80
2: $eflags = [ PF ZF IF ]
(gdb) (gdb) ni
0x08049157 in main ()
1: x/5i $eip
=> 0x8049157 <main+1>:        clc
   0x8049158 <main+2>:        mov    $0x1,%eax
   0x804915d <main+7>:        mov    $0x0,%ebx
   0x8049162 <main+12>:       int    $0x80
   0x8049164 <_fini>: push   %ebx
2: $eflags = [ CF PF ZF IF ]
(gdb) ni
0x08049158 in main ()
1: x/5i $eip
=> 0x8049158 <main+2>:        mov    $0x1,%eax
   0x804915d <main+7>:        mov    $0x0,%ebx
   0x8049162 <main+12>:       int    $0x80
   0x8049164 <_fini>: push   %ebx
   0x8049165 <_fini+1>:       sub    $0x8,%esp
2: $eflags = [ PF ZF IF ]
(gdb)

Setting and Clearing the Overflow Flag in assembly

Consider the following program:

  .section _text
  .globl _start
_start:

  # enable overflow flag (OF)
  movl $0x7fffffff, %eax
  addl $1, %eax # overflow register

  # clear OF flag
  xorl %eax, %eax # remove data from eax

  # exit program
  movl $1, %eax
  movl $0, %ebx
  int $0x80

And now let’s check it on GDB:

(gdb) disassemble main
Dump of assembler code for function main:
   0x08049156 <+0>:   mov    $0x7fffffff,%eax
   0x0804915b <+5>:   add    $0x1,%eax
   0x0804915e <+8>:   xor    %eax,%eax
   0x08049160 <+10>:  mov    $0x1,%eax
   0x08049165 <+15>:  mov    $0x0,%ebx
   0x0804916a <+20>:  int    $0x80
End of assembler dump.
(gdb) b * 0x08049156
Breakpoint 1 at 0x8049156
(gdb) display /4i $eip
(gdb) display / $eflags
(gdb) run

Breakpoint 1, 0x08049156 in main ()
1: x/4i $eip
=> 0x8049156 <main>:  mov    $0x7fffffff,%eax
   0x804915b <main+5>:        add    $0x1,%eax
   0x804915e <main+8>:        xor    %eax,%eax
   0x8049160 <main+10>:       mov    $0x1,%eax
2: $eflags = [ PF ZF IF ]
(gdb) set $eflags=0x0
(gdb) ni
0x0804915b in main ()
1: x/4i $eip
=> 0x804915b <main+5>:        add    $0x1,%eax
   0x804915e <main+8>:        xor    %eax,%eax
   0x8049160 <main+10>:       mov    $0x1,%eax
   0x8049165 <main+15>:       mov    $0x0,%ebx
2: $eflags = [ IF ]
(gdb) ni
0x0804915e in main ()
1: x/4i $eip
=> 0x804915e <main+8>:        xor    %eax,%eax
   0x8049160 <main+10>:       mov    $0x1,%eax
   0x8049165 <main+15>:       mov    $0x0,%ebx
   0x804916a <main+20>:       int    $0x80
2: $eflags = [ PF AF SF IF OF ]
(gdb) i r eax
eax            0x80000000          -2147483648
(gdb) ni
0x08049160 in main ()
1: x/4i $eip
=> 0x8049160 <main+10>:       mov    $0x1,%eax
   0x8049165 <main+15>:       mov    $0x0,%ebx
   0x804916a <main+20>:       int    $0x80
   0x804916c <_fini>: push   %ebx
2: $eflags = [ PF ZF IF ]
(gdb) c

Setting and clearing the Parity Flag

Consider this program:

  .section .text
  .globl _start
_start:
  # enable parity flag (PF)
  movb $0xAA, %al # 0xAA = 0b10101010
  test %al, %al # AND

  # clear PF
  movb $0x57, %al
  test %al, %al

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

Now, we can check it in GDB:

(gdb) disassemble main
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    $0xaa,%al
   0x08048338 <+2>:     test   %al,%al
   0x0804833a <+4>:     mov    $0x57,%al
   0x0804833c <+6>:     test   %al,%al
   0x0804833e <+8>:     mov    $0x1,%eax
   0x08048343 <+13>:    mov    $0x0,%ebx
   0x08048348 <+18>:    int    $0x80
End of assembler dump.
(gdb) b * 0x08048336
Breakpoint 1 at 0x8048336
(gdb) display / $eflags
1: $eflags = <error: No registers.>
(gdb) display /4i $eip
2: x/4i $eip
<error: No registers.>
(gdb) run

Breakpoint 1, 0x08048336 in main ()
1: $eflags = [ PF ZF IF ]
2: x/4i $eip
=> 0x8048336 <main>:    mov    $0xaa,%al
   0x8048338 <main+2>:  test   %al,%al
   0x804833a <main+4>:  mov    $0x57,%al
   0x804833c <main+6>:  test   %al,%al
(gdb) set $eflags=0x0
(gdb) ni
0x08048338 in main ()
1: $eflags = [ IF ]
2: x/4i $eip
=> 0x8048338 <main+2>:  test   %al,%al
   0x804833a <main+4>:  mov    $0x57,%al
   0x804833c <main+6>:  test   %al,%al
   0x804833e <main+8>:  mov    $0x1,%eax
(gdb) ni
0x0804833a in main ()
1: $eflags = [ PF SF IF ]
2: x/4i $eip
=> 0x804833a <main+4>:  mov    $0x57,%al
   0x804833c <main+6>:  test   %al,%al
   0x804833e <main+8>:  mov    $0x1,%eax
   0x8048343 <main+13>: mov    $0x0,%ebx
(gdb) ni
0x0804833c in main ()
1: $eflags = [ PF SF IF ]
2: x/4i $eip
=> 0x804833c <main+6>:  test   %al,%al
   0x804833e <main+8>:  mov    $0x1,%eax
   0x8048343 <main+13>: mov    $0x0,%ebx
   0x8048348 <main+18>: int    $0x80
(gdb) ni
0x0804833e in main ()
1: $eflags = [ IF ]
2: x/4i $eip
=> 0x804833e <main+8>:  mov    $0x1,%eax
   0x8048343 <main+13>: mov    $0x0,%ebx
   0x8048348 <main+18>: int    $0x80
   0x804834a:   add    %al,(%eax)
(gdb) c

Setting and clearing the Sign Flag

Consider the following program:

  .section .text
  .globl main
main:
  # set the sign flag
  movl $-1, %eax
  test %eax, %eax

  # clear the sign flag
  movl $42, %eax
  test %eax, %eax

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

Now, let’s check it in GDB:


(gdb) disassemble main
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    $0xffffffff,%eax
   0x0804833b <+5>:     test   %eax,%eax
   0x0804833d <+7>:     mov    $0x2a,%eax
   0x08048342 <+12>:    test   %eax,%eax
   0x08048344 <+14>:    mov    $0x1,%eax
   0x08048349 <+19>:    mov    $0x0,%ebx
   0x0804834e <+24>:    int    $0x80
End of assembler dump.
(gdb) b * 0x08048336
Breakpoint 1 at 0x8048336
(gdb) display / $eflags
1: $eflags = <error: No registers.>
(gdb) run

Breakpoint 1, 0x08048336 in main ()
1: $eflags = [ PF ZF IF ]
(gdb) set $eflags=0x0
(gdb) ni
0x0804833b in main ()
1: $eflags = [ IF ]
(gdb) ni
0x0804833d in main ()
1: $eflags = [ PF SF IF ]
(gdb) ni
0x08048342 in main ()
1: $eflags = [ PF SF IF ]
(gdb) ni
0x08048344 in main ()
1: $eflags = [ IF ]
(gdb) c

Setting and clearing the zero flag

  .section .text
  .globl main
main:
  # set zero flag (ZF)
  movl $0, %eax
  test %eax, %eax

  # clear ZF
  movl $42, %eax
  test %eax, %eax

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

Controlling Execution Flow

Understanding EIP register

Consider the following program:

  .section .text
  .globl main
main:
  # moving data
  movl $4, %eax
  movl $3, %ebx
  movl $5, %ecx
  movl $7, %edx

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

And let’s check it in GDB:

(gdb) disassemble main
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    $0x4,%eax
   0x0804833b <+5>:     mov    $0x3,%ebx
   0x08048340 <+10>:    mov    $0x5,%ecx
   0x08048345 <+15>:    mov    $0x7,%edx
   0x0804834a <+20>:    mov    $0x1,%eax
   0x0804834f <+25>:    mov    $0x0,%ebx
   0x08048354 <+30>:    int    $0x80
End of assembler dump.
(gdb) b * 0x08048336
Breakpoint 1 at 0x8048336
(gdb) run

Breakpoint 1, 0x08048336 in main ()
(gdb) info registers
edi            0xf7ffcc60          -134230944 # pointer to next instruction to be executed
(gdb) disassemble main
Dump of assembler code for function main:
=> 0x08048336 <+0>:     mov    $0x4,%eax # eip position
   0x0804833b <+5>:     mov    $0x3,%ebx
   0x08048340 <+10>:    mov    $0x5,%ecx
   0x08048345 <+15>:    mov    $0x7,%edx
   0x0804834a <+20>:    mov    $0x1,%eax
   0x0804834f <+25>:    mov    $0x0,%ebx
   0x08048354 <+30>:    int    $0x80
End of assembler dump.
(gdb)

Jump Instruction

Consider the following program:

  .section .text
  .globl main
main:
  movl $1, %eax
  movl $2, %ebx

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

Now, let’s implement jumps.

In assembly we have an instruction jmp which will jump to any memory location, it takes only one argument which is the address that we want to jump to (or more practically a label):

  .section .text
  .globl main
main:
  movl $1, %eax
  movl $2, %ebx

  jmp jump_here

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80o

jump_here:
  movl $100, %eax
  movl $200, %ebx

As we are jumping to jump_here before the system exit call, it won’t be executed.

Let’s check it in GDB:

(gdb) disassemble main
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    $0x1,%eax
   0x0804833b <+5>:     mov    $0x2,%ebx
   0x08048340 <+10>:    jmp    0x804834e <jump_here>
   0x08048342 <+12>:    mov    $0x1,%eax
   0x08048347 <+17>:    mov    $0x0,%ebx
   0x0804834c <+22>:    int    $0x80
End of assembler dump.
(gdb) disassemble jump_here
Dump of assembler code for function jump_here:
   0x0804834e <+0>:     mov    $0x64,%eax
   0x08048353 <+5>:     mov    $0xc8,%ebx
End of assembler dump.
(gdb) b * 0x08048340
Breakpoint 1 at 0x8048340
(gdb) run

Breakpoint 1, 0x08048340 in main ()
(gdb) disassemble main
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    $0x1,%eax
   0x0804833b <+5>:     mov    $0x2,%ebx
=> 0x08048340 <+10>:    jmp    0x804834e <jump_here>
   0x08048342 <+12>:    mov    $0x1,%eax
   0x08048347 <+17>:    mov    $0x0,%ebx
   0x0804834c <+22>:    int    $0x80
End of assembler dump.
(gdb) ni
0x0804834e in jump_here ()
(gdb) disassemble main
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    $0x1,%eax
   0x0804833b <+5>:     mov    $0x2,%ebx
   0x08048340 <+10>:    jmp    0x804834e <jump_here>
   0x08048342 <+12>:    mov    $0x1,%eax
   0x08048347 <+17>:    mov    $0x0,%ebx
   0x0804834c <+22>:    int    $0x80
End of assembler dump.
(gdb) disassemble jump_here
Dump of assembler code for function jump_here:
=> 0x0804834e <+0>:     mov    $0x64,%eax
   0x08048353 <+5>:     mov    $0xc8,%ebx
End of assembler dump.
(gdb)

The call instruction

Consider the following program:

  .section .text
  .globl main
main:
  movl $3, %eax
  movl $6, %ebx

  call location 1

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

location1:
  movl $99, %eax
  movl $77, %ebx
  ret

The difference between jmp and call, is that it will return back to the execution point once the instructions in the label we have called reaches ret. Therefore, once the line movl $77, %eax is executed the %eip will point back to the main label and the exit system call will be executed.

Conditional Jump

In assembly we have an instruction cmp which works like this:

cmp source, destination

That instruction allows us to compare two values, and get a result. The way that this instruction works is by subtracting the source to the destination, as if we wrote this:

cmp %eax, %ebx # equivalent to %ebx - %eax

When the subtraction is executed, in case they are equal the result would be zero (setting the ZF). In case that the result of the subtraction is not zero, then the ZF won’t be set.

Now, let’s look at it in a real program:

  .section .data
msg_success:
  .ascii "eq"
msg_failure:
  .ascii "ne"

  .section .text
  .globl main
main:
  movl $4, %eax
  movl $8, %ebx

  cmp %eax, %ebx
  je jump_equals
  jne jump_not_equals

jump_equals:
  movl $4, %eax
  movl $1, %ebx
  movl $msg_success, %ecx
  movl $2, %edx
  int $0x80

  jmp exit

jump_not_equals:
  movl $4, %eax
  movl $1, %ebx
  movl $msg_failure, %ecx
  movl $2, %edx
  int $0x80

exit:
  movl $1, %eax
  movl $0, %ebx
  int $0x80

That program uses the je (jump if equals) and jne (jump if not equals) instructions which will be used to jump to either jump_equals in case the values of %eax and %ebx are equal, and jump_not_equals in case they are not.

Using Zero Flag as a Conditional Jump

Consider the following program:

  .section .data
msg:
  .ascii "zero"

  .section .text
  .globl main
main:
  movl $4, %eax
  subl $4, %eax # %eax is now 0
  jz jump_here # jump if ZF is set

exit:
  movl $1, %eax
  movl $0, %ebx
  int $0x80

jump_here:
  movl $4, %eax
  movl $1, %ebx
  movl $msg, %ecx
  movl $4, %edx
  int $0x80

  jmp exit

jz will jump to the specified label if the ZF is set.

Using Overflow Flag as a Conditional Jump

Consider the following program:

  .section .data
msg:
  .ascii "of"

  .section .text
  .globl main
main:
  movl $0x7fffffff, %eax
  addl $1, %eax # this should set the OF
  jo jump_here

exit:
  movl $1, %eax
  movl $0, %ebx
  int $0x80

jump_here:
  movl $4, %eax
  movl $1, %ebx
  movl $msg, %ecx
  movl $2, %edx
  int $0x80

  jmp exit

jo will jump to the specified label if the OF flag is set.

Parity Flag as a Conditional Jump

Consider the following program:

  .section .data
msg:
  .ascii "pf"

  .section .text
  .globl main
main:
  movl $4, %eax
  subl $1, %eax # this should set the PF
  jp jump_here

exit:
  movl $1, %eax
  movl $0, %ebx
  int $0x80

jump_here:
  movl $4, %eax
  movl $1, %ebx
  movl $msg, %ecx
  movl $2, %edx
  int $0x80

  jmp exit

jp will jump to the specified label if the PF is set.

Using Numbers

Types of Numbers Used in Assembly

In assembly we have different number types:

  • Integers
    • Unsigned
    • Signed
  • SIMD Integers
    • MMX Integers (Multimedia Extension)
    • SSE Integers (Streaming SIMD Extensions)
  • Binary Coded Decimal
  • Floating Point Numbers

Signed and Unsigned Integers

Signed numbers, are those that have a sign, basically, those that are negative. Some examples could be:

  • -2
  • -10
  • -100

And we can think of unsigned numbers, as positive integers:

  • 2
  • 10
  • 100

The way that signed numbers are stored in memory is like this. Suppose this is a 32-bit register:


 7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

If we take the lowest 8-bits:


 7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--/--+--+--+--+--+--+--+--/
                                                                       /                       /
                                                                      /                       /
                                                                     /                       /
                                                                    /                       /
                                                                   /                       /
                                                                  /                       /
                                                                 /                       /
                                                                +--+--+--+--+--+--+--+--+
                                                                |  |  |  |  |  |  |  |  |
                                                                +--+--+--+--+--+--+--+--+
                                                              -128 -3 -2 -1 0  1  2  3  127

We can store an integer ranging from -128 to 127 (having 128 negative values, and 128 positive values - including zero). If we want to store -129, for example, it won’t be possible as our 8-bit register won’t be able to hold it.

But now, let’s suppose we want to store -27:


 7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--/
                                                                       /  /                    /
                                                                      /  /                    /
                                                                     /  /                    /
                                                                    /  /                    /
                                                                   /  /                    /
                                                                  /  /                    /
                                                                 /  /                    /
                                                                +--+--+--+--+--+--+--+--+
                                                                |--|       7 bit        |
                                                                ++-+--+--+--+--+--+--+--+
                                                                 |                   |
                                                                 |                   |
                                                           Used for the sign    Other 7 bits store
                                                                                      the value

In the 8-bit that we have available to store the number, the highest bit will be used to store its sign (1 if it’s negative, 0 if it’s positive), and the other 7 bits will be used to store the actual value.

Unsigned integers

Consider a 32-bit register:


 7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

How an unsigned number is stored into memory is like this:


 7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--/--+--+--+--+--+--+--+--/
                                                                       /                       /
                                                                      /                       /
                                                                     /                       /
                                                                    /                       /
                                                                   /                       /
                                                                  /                       /
                                                                 /                       /
                                                                +--+--+--+--+--+--+--+--+
                                                                |                       |
                                                                +--+--+--+--+--+--+--+--+
                                                                 0  1  2  3  4  5  6  255

When storing an unsigned integer we have an extra bit to store data, resulting in a number ranging from 0, to 255 (having 256 numbers to store).

Using unsigned integers

Consider the following program:

  .section .data
number:       .byte 256 # overflow 0 ... 255

  .section .text
  .globl main
main:
  movb number, %al # moving 256 into al register

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

We might get warnings like this:

test.s: Assembler messages:
test.s:2: Warning: value 0x100 truncated to 0x0

It means that our 256 was replaced with 0. So the value of number will be 0.

How a CPU Stores the Numbers in Memory

The way that CPU stores numbers in memory is the following:


   Decimal
+----------+              +------------+                     +------------+
| 12345678 +--------------+  0xbc614e  +---------------------+  0x4e61bc  |
+----------+              +------------+                     +------------+
              Convert to                    Store in reverse
                 Hex                              order

How to Use Signed Numbers

Consider the following program:

  .section .data
number1:      .int -27
number2:      .int -3

  .section .text
  .globl main
main:
  movl number1, %eax
  movl number2, %ebx
  addl %eax, %ebx # result stored in %ebx

  # exit
  movl $1, %eax
  movl $0, %ebx
  int $0x80

And let’s check it in GDB:

(gdb) disassemble main
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    0x804b008,%eax
   0x0804833b <+5>:     mov    0x804b00c,%ebx
   0x08048341 <+11>:    add    %eax,%ebx
   0x08048343 <+13>:    mov    $0x1,%eax
   0x08048348 <+18>:    mov    $0x0,%ebx
   0x0804834d <+23>:    int    $0x80
End of assembler dump.
(gdb) b * 0x08048341
Breakpoint 1 at 0x8048341
(gdb) run

Breakpoint 1, 0x08048341 in main ()
(gdb) info reg
eax            0xffffffe5          -27
ebx            0xfffffffd          -3
(gdb) ni
0x08048343 in main ()
(gdb) disassemble
Dump of assembler code for function main:
   0x08048336 <+0>:     mov    0x804b008,%eax
   0x0804833b <+5>:     mov    0x804b00c,%ebx
   0x08048341 <+11>:    add    %eax,%ebx
=> 0x08048343 <+13>:    mov    $0x1,%eax
   0x08048348 <+18>:    mov    $0x0,%ebx
   0x0804834d <+23>:    int    $0x80
End of assembler dump.
(gdb) p/d $ebx
$1 = -30
(gdb)

SIMD registers

SIMD registers have two types:

  • MMX (Multimedia Extension): 64-bit in size.
  • SSE (Streaming SIMD Extensions): 128-bit in size.

These registers are used to store different size of integers.

  • MMX Registers in assembly

    MMX registers range from mm0 to mm7. In these 64-bit registers, we can store 8 different 8-byte integers. We can also store 4 16-bit integers. We can store 2 32-bit integers.

    We can move data into these registers like this:

    movq source, destination
    

    Now, let’s see it in an actual program:

      .section .data
    value1:       .int 1, 2
    
      .section .text
      .globl main
    main:
      movq value1, %mm0
    
      # exit
      movl $1, %eax
      movl $0, %ebx
      int $0x80
    

    Let’s check it in GDB:

    (gdb) disassemble main
    Dump of assembler code for function main:
       0x08048336 <+0>:     movq   0x804b008,%mm0
       0x0804833d <+7>:     mov    $0x1,%eax
       0x08048342 <+12>:    mov    $0x0,%ebx
       0x08048347 <+17>:    int    $0x80
    End of assembler dump.
    (gdb) b * 0x08048336
    Breakpoint 1 at 0x8048336
    (gdb) run
    
    Breakpoint 1, 0x08048336 in main ()
    (gdb) disassemble
    Dump of assembler code for function main:
    => 0x08048336 <+0>:     movq   0x804b008,%mm0
       0x0804833d <+7>:     mov    $0x1,%eax
       0x08048342 <+12>:    mov    $0x0,%ebx
       0x08048347 <+17>:    int    $0x80
    End of assembler dump.
    (gdb) p/x $mm0
    $1 = {uint64 = 0x0, v2_int32 = {0x0, 0x0}, v4_int16 = {0x0, 0x0, 0x0, 0x0}, v8_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}
    (gdb) ni
    0x0804833d in main ()
    (gdb) p/x $mm0
    $2 = {uint64 = 0x200000001, v2_int32 = {0x1, 0x2}, v4_int16 = {0x1, 0x0, 0x2, 0x0}, v8_int8 = {0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0}}
    (gdb)
    
  • Understanding SSE Registers

    SSE Registers (Streaming SIMD [Simple Instruction Multiple Data] Extensions) are 128-bit sized registers. Their names are:

    • xmm0
    • xmm1
    • xmm2
    • xmm3
    • xmm4
    • xmm5
    • xmm6
    • xmm7

    We can store 16 single byte integers, we can store 8 16-bit integers, we can store 4 32-bit integers, we can store 2 64-bit integers.

    We can move data into these registers like this:

    movdqa source, destination
    

    movdqa stands for mov double quadword aligned. And we can also use movdqu which stands for move double quadword unaligned.

  • Using SSE Registers

    Consider the following program:

      .section .data
    value1:       .int 0, 1, 2, 3
    value2:       .byte 0, 1, 2, 3, 4, 5, 6, 7
    
      .section .text
      .globl main
    main:
      movdqu value1, %xmm0
      movdqu value2, %xmm1
    
      # exit
      movl $1, %eax
      movl $0, %ebx
      int $0x80
    

    Let’s check it in GDB:

    (gdb) disassemble main
    Dump of assembler code for function main:
       0x08048336 <+0>:     movdqu 0x804b008,%xmm0
       0x0804833e <+8>:     movdqu 0x804b018,%xmm1
       0x08048346 <+16>:    mov    $0x1,%eax
       0x0804834b <+21>:    mov    $0x0,%ebx
       0x08048350 <+26>:    int    $0x80
    End of assembler dump.
    (gdb) b main
    Breakpoint 1 at 0x8048336
    (gdb) run
    
    Breakpoint 1, 0x08048336 in main ()
    (gdb) disassemble
    Dump of assembler code for function main:
    => 0x08048336 <+0>:     movdqu 0x804b008,%xmm0
       0x0804833e <+8>:     movdqu 0x804b018,%xmm1
       0x08048346 <+16>:    mov    $0x1,%eax
       0x0804834b <+21>:    mov    $0x0,%ebx
       0x08048350 <+26>:    int    $0x80
    End of assembler dump.
    (gdb) ni
    0x0804833e in main ()
    (gdb) p/x $xmm0
    $1 = {v8_bfloat16 = {0x0, 0x0, 0x1, 0x0, 0x2, 0x0, 0x3, 0x0}, v8_half = {0x0, 0x0, 0x1, 0x0, 0x2, 0x0, 0x3, 0x0}, v4_float = {0x0, 0x1, 0x2, 0x3}, v2_double = {0x100000000, 0x300000002}, v16_int8 = {0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0x1, 0x0,
        0x2, 0x0, 0x3, 0x0}, v4_int32 = {0x0, 0x1, 0x2, 0x3}, v2_int64 = {0x100000000, 0x300000002}, uint128 = 0x3000000020000000100000000}
    (gdb) ni
    0x08048346 in main ()
    (gdb) p/x $xmm1
    $2 = {v8_bfloat16 = {0x100, 0x302, 0x504, 0x706, 0x0, 0x0, 0x0, 0x0}, v8_half = {0x100, 0x302, 0x504, 0x706, 0x0, 0x0, 0x0, 0x0}, v4_float = {0x3020100, 0x7060504, 0x0, 0x0}, v2_double = {0x706050403020100, 0x0}, v16_int8 = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
      v8_int16 = {0x100, 0x302, 0x504, 0x706, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x3020100, 0x7060504, 0x0, 0x0}, v2_int64 = {0x706050403020100, 0x0}, uint128 = 0x706050403020100}
    

BCD Data Types