x64 ASM Fundamentals 0x04 – PEMDAS and such


This post is going to cover some basic mathematical operations within intel’s 64 bit Assembly language. We’ll cover –

  • Multiplication
  • Division
  • Addition
  • Subtraction

(the title should really have been MDAS, but that sounded a bit mad so I clickbaited you. ASM doesn’t have parenthesis anyway 🤷‍♀️)

Let’s get to it, budding ASMer.


ASM has a nice, straightforward instruction to add two registers together, or add an “immediate” value to a register’s existing value.

// High Level Language Code

int a = 53;
int b = 42;
int c = a + b; // var plus var
int d = a + 53; // var plus immediate
int e = 53 + 42; // immediate plus immediate

; ASM equivalent to the above

mov RAX, 53
mov RBX, 42
mov RCX, RAX ; put 53 into RCX
add RCX, RBX ; add RBX's value to RCX (aka a + b) RCX is now 95
mov RDX, RAX ; equiv to "d = a"
add RDX, 53  ; equiv to "a + 53", effectively. RDX is now 95
mov RDI, 53
add RDI, 42  ; RDI is now 95

Straightforward operation! Because of the nature of assembly we can’t simply do “add RDI, 53, 42”, which is why a lot of the operations are prefixed by a “mov”, in order to prepopulate the register with an integer.

For clarity “add [destination] [register or immediate]”, destination must be a register.

Copy any paste the above ASM code into an editor, assemble it with nasm and link it with ld (you should know how to do this by now if you’re following along). Run the application in GDB and keep track of the state of the registers to make sure you understand what’s occurring.

What happens if you try to add something to the maximum int value? (AKA mov RAX, 0xffffffffffffffff; add RAX, 1). Why does this happen?


Subtraction works exactly the same as addition! “sub [destination] [register or immediate]”. An example –

int a = 53;
int b = 42;
int c = a - b; // var minus var
int d = a - 53; // var minus immediate
int e = 53 - 42; // immediate minus immediate

; ASM equivalent

mov RAX, 53
mov RBX, 42
mov RCX, RAX
sub RCX, RBX ; var minus var, RCX is now 11
mov RDX, RAX
sub RDX, 53 ; var minus immediate, RDX is now 0
mov RDI, 53
sub RDI, 42 ; immediate minus immediate, via RDI! RDI is now 11

Again, simple stuff.

What happens if you try to subtract something from 0? (AKA mov RAX, 0; sub RAX, 1). Why does this happen?


This is where things begin to get a bit… weird. Whereas addition and subtraction can be achieved with “ADD DST, SRC”, multiplication can be achieved in a few ways:

// High Level Language version
int a = 53;
int b = 2;
int c = a * b;

Using the MUL instruction

; ASM version
mov RAX, 53  ; easy stuff, RAX is now 53
mov RBX, 2   ; easy stuff, RBX is now 2
push RAX     ; save the existing state of RAX, RAX is still 53
mul RBX      ; RAX becomes RAX * RBX, RAX is now 106
mov RCX, RAX ; put RAX (106 ) into RCX
pop RAX      ; restore RAX back to 53 again, like the above C code

Basically, when you perform a multiplication operation using the MUL instruction your destination register is always RAX, and your first argument to MUL is what you’d like to multiply with RAX’s current value.

The “one argument” MUL instruction can also be swapped out for IMUL too.

Using the 2 argument IMUL instruction

It’s also possible to use a different instruction called “IMUL” which is the same as MUL but supports additional arguments (and it’s for signed multiplication).

The 2 argument form is “IMUL DST, SRC”, where DST and SRC and multiplied together into DST.

mov RAX, 53
mov RBX, 2
mov RCX, RAX ; RCX is now 53
imul RCX, RBX ; RCX becomes 106, because it's the destination

This is a slightly more convenient shorthand for the above.

Using the 3 argument IMUL instruction

There’s one final form of the IMUL instruction, which is “IMUL DST, SRC1, SRC2”, which multiplies SRC1 and SRC2 together and puts the result into DST. Even more convenient 😊

mov RAX, 53
mov RBX, 2
imul RCX, RAX, RBX ; RCX becomes 106 because it's the destination

This is the most straightforward version of multiplication.


Division is… even weirder than multiplication to be honest.

The most basic operation looks like the following –

mov RAX, 4
mov RBX, 2
idiv RBX ; RAX is now two, because it was divided by RBX

It gets weirder if the dividend doesn’t evenly divide by the divisor. For example –

mov RAX, 25
mov RBX, 2
idiv RBX ; RAX becomes 12, RBX is still 2 and RDX becomes 1

RDX takes the remainder of a division argument if there is one! 25 / 2 == 12, with a remainder of 1.

If you attempt to perform a division operation again without setting RDX to zero then the following happens –

A SIGFPE (Signal for Floating Point Exception) was thrown, because we performed a divide operation which yielded a remainder without clearing down RDX first! The correct way to perform this operation is –

mov RAX, 25
mov RBX, 2
idiv RBX      ; RAX becomes 12, RDX becomes 1
mov RDX, 0    ; Clear RDX back to 0
mov RAX, 25
mov RBX, 2
idiv RBX      ; RAX becomes 12, RDX becomes 1, no SIGFPE

Weird, eh? But just part of life!


You now have the tools to perform a bunch of simple arithmetic operations in ASM! This should enable you to participate in more crackme style reverse engineering challenges etc.

In the next post, we’ll explore the next basic building block – the ASM equivalent to an IF statement!

Thanks for reading!

Add a Comment

Your email address will not be published. Required fields are marked *

I accept that my given data and my IP address is sent to a server in the USA only for the purpose of spam prevention through the Akismet program.More information on Akismet and GDPR.