Skip to main content

ASM x86 – Arithmetic Commands

· 9 min read
Strider

Hi, in the second part of the x86 assembler series, I want to go into the most important commands in assembler and show small program examples.

We have seen in the last post how the x86 CPU is structured from the registers, what CISC and RSIC and Von-Neumann and Harvard is.

Today we'll look at the most common x86 assembler arithmetic commands. I plan to show you a few small example programs instead of building a man page. Because this is easier for the language than reading through books or something like that.

General structure of an assembly code

Let's get to the general structure of an assembler program.

section .data ;Data like strings etc...

section .bss ;Modifyable variables etc...

section .text ;Program code

global _start ;Entrypoint definition

_start: ;Entrypoint of a assembler program
;do staff

Here we see the general structure of an x86 assembler program, in NASM. The program is divided into 3 sections.

The section .data contains all data like strings like "Hello world".

The section .bss contains all modifiable variables defined there. The variables can be used and modified later in the actual program part.

The third and last section .text contains the actual program or instructions.

We see in the program section the instruction global _start. Here the start/entry point of the program is defined. The name _start, can be replaced by any name. It is important, however, that the name occurs as a jump label. After the instruction "global", we see the jump label _start:. With this we can define individual program sections, to which one can jump at runtime or call like a function. More about this later. Behind the jump label is our program code or the instructions that are to be executed. Comments always start with a semicolon.

Let code something

Ok, let's move on to a real program!

section .text

global _start

_start:
mov eax, 0x1
mov edx, 0x1
add eax, edx

What does the program do here? The program calculates 1+11+1, not very exciting but as an example it is enough. In the first two lines, after the jump mark _start, we see that with the command mov, the register EAX and EDX, get the value 1 assigned as hexadecimal value.

The command is structured like this: "mov target, (what or where)". If we want to assign something to the register, we have to specify the register as the first operand, because the first operand is the destination where the value should go.

The second operand is the source, which can be another register e.g. EBX or a fixed value. If the source is a register, the value is only copied and not moved.

Comparison with C:
mov eax, 0x1 \Leftrightarrow int eax = 0;

The third line of the program, adds on the register EAX, the value from the register EDX. Again, the first operand is the destination and the second the source ("add destination, source"). The source can also be a number or a pointer. We will come to what pointers are later. After the operation, the result is stored in the destination register.

Comparison with C:
add eax, edx \Leftrightarrow eax = eax + edx;

Now if we want to compile the program, we do it with the command: (Linux with nasm and ld)

nasm -f elf32 program.asm && ld -o program -melf_i386 program.o && ./program

Here we use NASM to compile the program, then bind it with LD and then execute it. We will get a segmentationfault directly, because we did not specify anything after the program. Here e.g. an exit is missing.

Let's run the whole thing in GDB. For this we set a breakpoint with break _start. I have made a small video for this:

asciicast

Substracting numbers

The same works with a simple subtraction. Here also a small example 111-1:

;Simple subtraction
section .text

global _start

_start:
mov eax, 0x1
mov edx, 0x1
sub eax, edx

;EAX = 0x0

Multiply & divide numbers

The multiplication as well as division, is structured somewhat differently. Here we have only one operand. The operand is the source. Our destination where the result will be stored later is the EAX register, which now unfolds its special property. A small example for multiplication 2×42 \times 4.

;Simple multiplication
section .text

global _start

_start:
mov eax, 0x2
mov edx, 0x4
mul edx

;EAX = 0x8

With the multiplication, if the result has more than 32Bit length, the register DX is occupied with the more significant part of the number and EAX with the less significant part. The number is then to be read like this (EDX EAX).

;Simple division
section .text

global _start

_start:
mov eax, 0x8
mov ecx, 0x4
div ecx

;EAX = 0x2

With the two commands there are still extensions.but I come to them later. Very important with the division is that the register EDX cannot be used, since EDX stores the remainder of a division.

The extensions to the commands add, sub, mul and div are a bit more complex but not difficult.

Carry

To the command add, there is also the extension adc. What makes adc different from add? Well the command add, adds two integers with each other, but as soon as the carrybit is set, it is not added to the result. The command adc does exactly that, it simply adds the value from the carry flag to the result. The result looks like this: Carry OP1+OP2OP_{1}+OP_{2}.

Carry = 0, OP1 = 0xFFFFFFFE, OP2 = 0x1
Result = 0|0xFFFFFFFF

Carry = 1, OP1 = 0xFFFFFFFF, OP2 = 0x1
Result = 1|0x0

In the subtraction there is also an extension that includes the carry flag in the calculation. The carry flag is subtracted from the final result. For example, if we calculate 111 - 1 and the carry flag is set, we do not get a 0 but a 1-1 \Leftrightarrow 0xFFFFFFFF, otherwise if the carry flag is not set, we get a 0. A small example with set carry flag 111 - 1 :

;Simple subtraction with carry
section .text

global _start

_start:
mov eax, 0x1
stc; Zum setzen eines Carryflags
mov ecx, 0x1
sbb eax, ecx

;EAX = 0xFFFFFFFF

Here we also get to know a new command, the stc command. With this command, we can set the carry flag.

We have a similar thing with multiplication, with the imul command. The command mul calculates unsigned/signed operands, the command mul however, only with signed operands. There are 3 variants of this command, which I show in the example below.

;Simple multiplication with imul
section .text

global _start

_start:
mov eax, 0x1
mov ecx, 0x2
imul ecx
imul eax, ecx
imul ebx, eax, 0x2

We see with the first command imul, that as with the command mul, we only specify the source operand, which can be a register or an integer.

The second one is a bit more complex, because here we work with 2 operands, which can both be registers. The first operand is our destination, where later the result will be stored. The 2nd operand is again the source.

The third variant is even more interesting, since we work here with three operands, whereby the last operand must be a mandatory integer. The first operand, describes again the destination register. The second operand is multiplied with the third operand. Here again the structure of the variants:

imul source
imul target, source
imul destination, source, source as value

With division we also have an extension, namely that idiv, unlike div, divides singned values.

;Simple division with idiv
section .text

global _start

_start:
mov eax, 0x8
mov ecx, 0x2
idiv ecx

;EAX = 0x4

Logical operations

Now we can add, subtract, multiply and divide. But what we can still do is to round operands, to negate and even to play around with XOR.

The simplest logical operation is negation. Here is a small example:

;Simple negation
section .text

global _start

_start:
mov eax, 0x8
not eax

;EAX = 0xfffffff7

In the example we see that we put the number 8 into the register EAX. With the command "not" we can invert the content from the register. As a result we get the number 0xfffffff7 in the register EAX. This is sometimes useful if you need to increase a value quickly, for example to get a delay in a loop.

The next operation, which is also important, is the rounding. Here we need two operands, once the destination register and a source register or value. We can apply the operation to any register, well almost any, because the EIP register is rather ReadOnly. There is also a small example:

;Simple AND
section .text

global _start

_start:
mov eax, 0x8
and eax, 0xFFFFFFFF

;EAX = 0x8

The logical OR is identical with the locical OR, and can just as much. There is also a small example for this:

;Simple OR
section .text

global _start

_start:
mov eax, 0x8
or eax, 0xFFFFFFFF

;EAX = 0xffffffff

The XOR operation is also a very important operation, because we can also clear registers or set them to 0 with it. Also here it is identical from the application with AND and OR. Here is a small example:

;Simple XOR
section .text

global _start

_start:
mov eax, 0x8
xor eax, 0xFFFFFFFF
xor eax, eax

;EAX = 0xfffffff7
;EAX nach EAX xor EAX = 0x0

That's it for now, I hope you enjoyed it and see you next time 😄