Say hello to x64 Assembly [part 2]
Some days ago I wrote the first blog post - introduction to x64 assembly - Say hello to x64 Assembly [part 1] which to my surprise caused great interest:
Terminology and Concepts
As i wrote above, I got many feedback from different people that some parts of first post are not clear, that's why let's start from description of some terminology that we will see in this and next parts.
Register - register is a small amount of storage inside processor. Main point of processor is data processing. Processor can get data from memory, but it is slow operation. That's why processor has own internal restricted set of data storage which name is - register.
Little-endian - we can imagine memory as one large array. It contains bytes. Each address stores one element of the memory "array". Each element is one byte. For example we have 4 bytes: AA 56 AB FF. In little-endian the least significant byte has the smallest address:
0 FFwhere 0,1,2 and 3 are memory addresses.
Big-endian - big-endian stores bytes in opposite order than little-endian. So if we have AA 56 AB FF bytes sequence it will be:
0 AASyscall - is the way a user level program asks the operating system to do something for it. You can find syscall table - here. Stack - processor has a very restricted count of registers. So stack is a continuous area of memory addressable special registers RSP,SS,RIP and etc... We will take a closer look on stack in next parts.
Section - every assembly program consists from sections. There are following sections:
- data - section is used for declaring initialized data or constants
- bss - section is used for declaring non initialized variables
- text - section is used for code
The fundamental data types are bytes, words, doublewords, quadwords, and double quadwords. A byte is eight bits, a word is 2 bytes, a doubleword is 4 bytes, a quadword is 8 bytes and a double quadword is 16 bytes (128 bits).
Now we will work only with integer numbers, so let's see to it. There two types of integer: unsigned and signed. Unsigned integers are unsigned binary numbers contained in a byte, word, doubleword, and quadword. Their values range from 0 to 255 for an unsigned byte integer, from 0 to 65,535 for an unsigned word integer, from 0 to 2^32 – 1 for an unsigned doubleword integer, and from 0 to 2^64 – 1 for an unsigned quadword integer. Signed integers are signed binary numbers held as unsigned in a byte, word and etc... The sign bit is set for negative integers and cleared for positive integers and zero. Integer values range from –128 to +127 for a byte integer, from –32,768 to +32,767 for a word integer,from –2^31 to +2^31 – 1 for a doubleword integer, and from –2^63 to +2^63 – 1 for a quadword integer.
As i wrote above, every assembly program consists from sections, it can be data section, text section and bss section. Let's look on data section.It's main point - to declare initialized constants. For example:
Ok, it is almost all clear here. 3 constants with name num1, num2, msg and with values 100, 50 and "Sum is correct", 10. But what is it db, equ? Actual NASM supports a number of pseudo-instructions:
- DB, DW, DD, DQ, DT, DO, DY and DZ - are used for declaring initialized data. For example:
- RESB, RESW, RESD, RESQ, REST, RESO, RESY and RESZ - are used for declaring non initialized variables
- INCBIN - includes External Binary Files
- EQU - defines constant. For example:
- TIMES - Repeating Instructions or Data. (description will be in next posts)
There is short list of arithmetic instructions:
- ADD - integer add
- SUB - substract
- MUL - unsigned multiply
- IMUL - signed multiply
- DIV - unsigned divide
- IDIV - signed divide
- INC - increment
- DEC - decrement
- NEG - negate
Usually programming languages have ability to change order of evaluation (with if statement, case statement, goto and etc...) and assembly has it too. Here we will see some of it. There is cmp instruction for performing comparison between two values. It is used along with the conditional jump instruction for decision making. For example:
cmp instruction just compares 2 values, but doesn't affect them and doesn't execute anything depend on result of comparison. For performing any actions after comparison there is conditional jump instructions. It can be one of it:
- JE - if equal
- JZ - if zero
- JNE - if not equal
- JNZ - if not zero
- JG - if first operand is greater than second
- JGE - if first operand is greater or equal to second
- JA - the same that JG, but performs unsigned comparison
- JAE - the same that JGE, but performs unsigned comparison
It will be in assembly:
There is also unconditional jump with syntax:
JMP labelFor example:
Here we have can have some code which will be after _start label, and all of this code will be executed, assembly transfer control to .exit label, and code after .exit: will start to execute.
Often unconditional jump uses in loops. For example we have label and some code after it. This code executes anything, than we have condition and jump to the start of this code if condition is not successfully. Loops will be covered in next parts.
Let's see simple example. It will take two integer numbers, get sum of these numbers and compare it with predefined number. If predefined number is equal to sum, it will print something on the screen, if not - just exit. Here is the source code of our example:
Let's go through the source code. First of all there is data section with two constants num1, num2 and variable msg with "Sum is correct\n" value. Now look at 14 line. There is begin of program's entry point. We transfer num1 and num2 values to general purpose registers rax and rbx. Sum it with add instruction. After execution of add instruction, it calculates sum of values from rax and rbx and store it's value to rax. Now we have sum of num1 and num2 in the rax register.
Ok we have num1 which is 100 and num2 which is 50. Our sum must be 150. Let's check it with cmp instruction. After comparison rax and 150 we check result of comparison, if rax and 150 are not equal (checking it with jne) we go to .exit label, if they are equal we go to .rightSum label.
Now we have two labels: .exit and .rightSum. First is just sets 60 to rax, it is exit system call number, and 0 to rdi, it is a exit code. Second is .rightSum is pretty easy, it just prints Sum is correct\n. If you don't understand how it works, see first post.
It was a second part of series 'say hello to x64 assembly', if you will have a questions/suggestions write me a comment.
All source code you can find as everytime - here.