See all documents
Intel x86 Assembly is difficult to learn but it's extremely useful to have the skillset.
Mon Nov 20 2023 • 2667 words • 8 min read
I'm developing an product which requires me to have fairly good understanding about x86 assembly. While setting up my Netwide Assembler on Windows I encountered numerous challenges since there were not many resources for setting it up on Windows. What is Netwide Assembler? It's an assembler and disassembler for Intel x86 architecture which can be used for writing assembly code.
In his blog post I will go through the process of setting up Netwide Assembler on Windows and provide informations about x86 assembly to help you get started with programming in x86 assembly.
System Properties
Environment Variables
Path
and click on Edit
C:\Users\[USERNAME]\AppData\Local\bin\NASM
Save
and Exit
MSYS2
pacman -S mingw-w64-x86_64-toolchain
System Properties
Environment Variables
Path
and click on Edit
C:\msys64\mingw32\bin
Save
and Exit
Now as Netwide Assembler and MSYS2 has been installed and properly configured we can compile and execute x86 assembly code.
global _main
extern _printf
section .data
message db 'Hello World', 0xA, 0
section .text
push ebp
mov esp, ebp
push message
call _printf
add esp, 4
xor eax, eax
ret
PS C:\Users\husenjan > nasm main.asm -f win32 -o main.o
PS C:\Users\husenjan > gcc main.obj -o main.exe
The nasm
command translates the x86 assembly code onto a object file and the gcc
command compiles the object file to an executable program.
The extern
instruction in x86 assembly is used to import C++ functions into x86 assembly environment.
global _main
extern _printf
; Used for storing strings and variables
section .data
output db 'Hello World', 0
section .text
_main:
; Creating stack
push ebp
mov esp, ebp
; Calling printf
push output
call _printf
add esp, 4
; Exiting program
xor eax, eax
ret
The extern _printf
is importing in printf()
function in C++ and the call _printf
is calling the printf()
function. When calling a function in x86 assembly it's critical to ensure the stack is being handled correctly before and after the function call otherwise it can create problems.
While-loops can become necessary for our application incase certain actions needs to be executed multiple of times. In x86 assembly the while loop can be created using the stack, jump instruction, and comparison instruction.
global _main
extern _printf
; Used for storing strings and variables
section .data
output db 'Hello World', 0xA, 0
section .text
_main:
; Creating stack
push ebp
mov esp, ebp
mov eax, 0
; Creating while-loop
_while_loop:
push eax
; Calling printf
push output
call _printf
add esp, 4
; Incrementing eax
pop eax
inc eax
; Exiting if EAX equals 10
cmp eax, 10
jl _while_loop
; Exiting program
xor eax, eax
ret
In push eax
the EAX register is pushed onto the stack since once call _printf
is executed the EAX register will be modified so once the function is called the pop eax
is executed to restore the original value of EAX register and the value is incremented by one at inc eax
and aslong the EAX register is less than 10 the _while_loop
will continously be executed.
While-loops are great but for-loops are even better because it's much cleaner and it can easily be created using the stack and loop instruction. The loop instruction is essential since it will continue the loop to ECX register becomes null.
global _main
extern _printf
; Used for storing strings and variables
section .data
output db 'Hello World', 0xA, 0
section .text
_main:
; Creating stack
push ebp
mov esp, ebp
; Assigning ECX the value 10
mov ecx, 10
; Creating for-loop
_for_loop:
push ecx
; Calling printf
push output
call _printf
add esp, 4
; Continues to jump to _for_loop if ECX is greater than 0
pop ecx
loop _for_loop
; Exiting program
xor eax, eax
ret
The mov ecx, 10
is assigning the value 10 to ECX register which means it will loop 10 times through _for_loop
and each time the loop _for_loop
is executed the ECX is decremented by 1 and this will continue to ECX register becomes null.
Now as we have familiarized ourselves with while-loops and for-loops it's time to start working with arrays. In x86 assembly a array can be created in .data
section and a while-loop or for-loop can be used to work with the array.
global _main
extern _printf
; Used for storing strings and variables
section .data
array db 10 dup(0)
formatout db '%d', 0xA, 0
section .text
_main:
; Creating stack
push ebp
mov ebp, esp
mov edi, 0
mov ecx, 10
mov ebx, 1
; Looping through array
_loop:
push ecx
; EAX = [array + edi]
mov eax, [array + edi]
mov eax, ebx
add ebx, 1
push ebx
; Calling _printf
push eax
push formatout
call _printf
add esp, 8
; Restoring original values of registers
pop ebx
pop ecx
; Preparing to access next array
add edi, 2
; Jumping to _loop if ECX is greater than 0
loop _loop
xor eax, eax
ret
The array db 10 dup(0)
creates the array and the array can be accessed directly using mov eax, [array_name + edi]
and once EDI register is incremented by 2 the next array is accessible.
Programming in x86 assembly can be difficult and challenging in the beginning. However, the more practise that is done the easier it becomes therefore it's highly recommended to start off slow such as programming a calculator using what has been taught in this blog post. If you don't understand something than I recommend googling the question and researching it to you find the answer since that is the only way to learn difficult things.
Programming a calculator in x86 might seem simple but it's extremely difficult. Here is an simple calculator which I worte in x86 assembly:
global _main
extern _printf
extern _scanf
extern _system
extern _sleep
section .data
cleaner db 'cls', 0
menu_01 db '-- CALCULATOR --', 0xA, 0
menu_02 db '1. Addition', 0xA, 0
menu_03 db '2. Substraction', 0xA, 0
menu_04 db '3. Multiply', 0xA, 0
menu_05 db 'Select one: ', 0
menu_06 db 'Enter an number: ', 0
menu_07 db 'Enter another number: ', 0
menu_08 db '%d + %d = %d', 0xA, 0
menu_09 db '%d - %d = %d', 0xA, 0
menu_10 db '%d * %d = %d', 0xA, 0
integer_01 dd 0
integer_02 dd 0
integer_03 dd 0
formatin_01 db '%d', 0
section .text
_main:
push ebp
mov esp, ebp
push cleaner
call _system
add esp, 4
push menu_01
call _printf
add esp, 4
push menu_02
call _printf
add esp, 4
push menu_03
call _printf
add esp, 4
push menu_04
call _printf
add esp, 4
push menu_05
call _printf
add esp, 4
push integer_01
push formatin_01
call _scanf
add esp, 8
push menu_06
call _printf
add esp, 4
push integer_02
push formatin_01
call _scanf
add esp, 8
push menu_07
call _printf
add esp, 4
push integer_03
push formatin_01
call _scanf
add esp, 8
mov eax, dword[integer_01]
cmp eax, 1
je _addition
cmp eax, 2
je _subscration
cmp eax, 3
je _multiply
_addition:
mov ebx, dword[integer_02]
mov edx, dword[integer_03]
mov eax, ebx
add eax, edx
push eax
push edx
push ebx
push menu_08
call _printf
add esp, 16
jmp _exit
_subscration:
mov ebx, dword[integer_02]
mov edx, dword[integer_03]
mov eax, ebx
sub eax, edx
push eax
push edx
push ebx
push menu_09
call _printf
add esp, 16
jmp _exit
_multiply:
mov ebx, dword[integer_02]
mov edx, dword[integer_03]
mov eax, dword[integer_02]
mul eax
mov edx, dword[integer_03]
push eax
push edx
push ebx
push menu_10
call _printf
add esp, 16
jmp _exit
_exit:
push 3
call _sleep
add esp, 4
jmp _main
ret
Hopefully this blog post has been helpful teaching you about x86 assembly. I'll try to update the blog post more once I'm more familiar with x86 assembly.
© 2024 Husenjan
Made by Husenjan