Today, it is not very common to meet programmers working in assembly language (also called assembler language) because high-level languages are much faster to learn and use, are architecture independent (and often portable), and the performance of compiled programs is comparable to programs written in assembly. However, knowledge of assembly is useful in the world of reverse engineering, which is an important blue team security discipline. So it is therefore very useful to learn the code generated by the compiler against common sequences of instructions written in some high-level language.
This post shows how Rust and C++ compile the five basic arithmetic operations (sum, subtraction, multiplication, integer division, remainder of integer division) on 32-bit integer variables.
The reference platform is x86_64; the Rust compiler version is 1.72.0 for Linux; as for C++, the gcc compiler was used in version 11.4.0 also for Linux. The disassembler tool is IDA Free Version 8.3.230608 for Linux x86_64. The compilation of both Rust and g++ is executed with default options of correspondent compilers, namely no optimization is invoked.
In this post the focus is only on the five basic arithmetic operations, so the analysis of the disassembled code will cover only these operations and nothing else; the syntax of assembly code is the Intel syntax (so for two operand instruction, the first operand is the destination, the second operand is the source).
c++ code and related assemblY CODE
The C++ code that implements the five basic arithmetic operations is as follows:
	int v1 = 41;
	int v2 = 7;
	int sum = v1 + v2;
	int sub = v1 - v2;
	int mul = v1 * v2;
	int quo = v1 / v2;
	int rem = v1 % v2;To see the full code visit my space on GitHub at this address: https://github.com/ettoremessina/reverse-engineering/blob/main/linux/arithmetic/gcc/
The corresponding assembly code generated by the compiler is as follows:
	mov     [rbp+var_1C], 29h
	mov     [rbp+var_18], 7
	mov     edx, [rbp+var_1C]
	mov     eax, [rbp+var_18]
	add     eax, edx
	mov     [rbp+var_14], eax
	mov     eax, [rbp+var_1C]
	sub     eax, [rbp+var_18]
	mov     [rbp+var_10], eax
	mov     eax, [rbp+var_1C]
	imul    eax, [rbp+var_18]
	mov     [rbp+var_C], eax
	mov     eax, [rbp+var_1C]
	cdq
	idiv    [rbp+var_18]
	mov     [rbp+var_8], eax
	mov     eax, [rbp+var_1C]
	cdq
	idiv    [rbp+var_18]
	mov     [rbp+var_4], edx
	mov     eax, [rbp+var_14]The variable v1 occupies 4 bytes, is stored on the stack at the relative address [rbp+var_1C]; also the variable v2 occupies 4 bytes, is stored on the stack at the relative address [rbp+var_18]. The instruction “mov [rbp+var_1C], 29h” assigns the constant value 41 (that is 29 in hexadecimal) into [rbp+var_1C] (and following) memory addresses for a total of 4 bytes; it corresponds to C++ code “v1 = 41“.  The same is true for “mov [rbp+var_18], 7” which corresponds to the assignment “v2 = 7” in C++.
Then the value of the variable v1 (i.e., the 4 bytes from [rbp+var_1C]) is copied to the edx register, and then the value of the variable v2 (i.e., [rbp+var_18]) is also copied to the eax register; the sum operation is implemented by the “add eax, edx” instruction, which sums the eax and edx registers and saves the result of the sum in the eax register. Then the value of the eax register (of length 4 bytes) is copied into memory (of the stack) from [rbp+var_14], which corresponds to the variable called sum in the C++ code.
Similar assembly code for subtraction between v1 and v2 with the difference that the assembly instruction implementing it is “sub eax, [rbp+var_18]” and that the result is saved on “[rbp+var_10]” which corresponds to the variable called sub in the C++ code. And also similar assembly code for multiplication between v1 and v2 with the difference that the assembly instruction implementing it is “imul eax, [rbp+var_18][rbp+” which corresponds to the variable called var_18mul in the C++ code.
For integer division the code is a little different:  the “mov eax, [rbp+var_1C]” instruction copies the value of variable v1 (i.e., the 4 bytes of memory in [rbp+var_1C]) to the eax register, then follows the cdq instruction (that means convert double word to quad word from eax to edx:eax registers) to which, if bit 31 of eax is at 1 writes ffffffffh to edx while if bit 31 of eax is 0 writes 00000000h to edx. The instruction that implements the division is “idiv [rbp+var_18]” which performs the division between edx:eax and the variable v2 (i.e., [rbp+var_18]) and saves the result to eax. Then the result is saved on “[rbp+” which corresponds to the variable called var_8quo in the C++ code.
The implementation of the remainder of the division is very similar to integer division, in fact idiv simultaneously computes both the result of integer division (which is saved on eax) and the remainder of the division (which is saved on edx); the compiler generates code that is quite similar to integer division but then saves on [rbp+var_4] (i.e., the variable rem in C++ code) the value of edx, which is precisely the remainder.
Note: An optimizer would have merged the two instruction sequences of integer division and remainder division by invoking idiv only once, but because of the above, the optimizations were not intentionally invoked.
Note: There are no overflow control mechanisms; the logic of the operations is circular, so in the event of an overflow in sum or multiplication (for example, if v1 and v2 were very large) simply the overflow bits would be lost and the eax register would end up holding only the bits that fell within the size of the eax register.
RUST code and related assemblY CODE
The Rust code that implements the five basic arithmetic operations is as follows:
	let mut v1: u32 = 0;
	v1 = 41;
	let mut v2: u32 = 0;
	v2 = 7;
	let sum: u32 = v1 + v2;
	let sub: u32 = v1 - v2;
	let mul: u32 = v1 * v2;
	let quo: u32 = v1 / v2;
	let rem: u32 = v1 % v2;To see the full code visit my space on GitHub at this address:
The corresponding assembly code generated by the compiler is as follows: https://github.com/ettoremessina/reverse-engineering/tree/main/linux/arithmetic/rust/
Note: If you compile this code you get a warning (initial assignment to the two variables not needed); if you modify the code to remove that assignment the compiler ignores the mut modifier and the Rust code you would get would not be equivalent to the C++ code above.
	//....addition
	mov     [rsp+118h+var_F4], 0
	mov     [rsp+118h+var_F4], 29h
	mov     [rsp+118h+var_F0], 0
	mov     [rsp+118h+var_F0], 7
	mov     eax, [rsp+118h+var_F4]
	add     eax, [rsp+118h+var_F0]
	mov     [rsp+118h+var_F8], eax
	setb    al
	test    al, 1
	jnz     short loc_8ADE
	//....subtraction
	mov     eax, [rsp+118h+var_F4]
	mov     ecx, [rsp+118h+var_F0]
	mov     edx, eax
	sub     edx, ecx
	mov     [rsp+118h+var_FC], edx
	cmp     eax, ecx
	setb    al
	test    al, 1
	jnz     short loc_8B19
	//....multiplication
	mov     eax, [rsp+118h+var_F4]
	mul     [rsp+118h+var_F0]
	mov     [rsp+118h+var_100], eax
	seto    al
	test    al, 1
	jnz     short loc_8B5B
	//....division
	mov     eax, [rsp+118h+var_F0]
	mov     [rsp+118h+var_104], eax
	cmp     eax, 0
	setz    al
	test    al, 1
	jnz     short loc_8BA9
	mov     ecx, [rsp+118h+var_104]
	mov     eax, [rsp+118h+var_108]
	xor     edx, edx
	div     ecx
	mov     [rsp+118h+var_E0], eax
	//....remainder
	mov     eax, [rsp+118h+var_F0]
	mov     [rsp+118h+var_10C], eax
	cmp     eax, 0
	setz    al
	test    al, 1
	jnz     loc_8D16
	mov     ecx, [rsp+118h+var_10C]
	mov     eax, [rsp+118h+var_110]
	xor     edx, edx
	div     ecx
	mov     [rsp+118h+var_DC], edx
The variable v1 occupies 4 bytes, is stored on the stack at the relative address [; also the variable rsp+118h+var_F4]v2 occupies 4 bytes, is stored on the stack at the relative address [. The instruction “rsp+118h+var_F0]mov [rsp+118h+var_F4], 29h[ (and following) memory addresses for a total of 4 bytes; it corresponds to Rust code “rsp+118h+var_F4v1 = 41“.  The same is true for “mov [” which corresponds to the assignment “rsp+118h+var_F0], 7v2 = 7” in Rust.
Then there is a check for overflow, in fact the instruction “setb al” copies the CF (carry flag) into the al recorder and “test al, 1” sets the zero flag to 1 if the al register contains 1 (to check whether there is a carry over beyond the highest representable integer), 0 otherwise. The following “jnz short ” jumps to a piece of code (not shown in assembly code) to handle the overflow when loc_8ADEal equals 1.
For subtraction the values of variables v1 and v2 are respectively copied into eax and ecx registers by the two instructions “mov eax, [rsp+118h+var_F4]” and “mov ecx, [rsp+118h+var_F0]“, then the value of eax (that is the same of v1 variable) is copied in edx register by “mov edx, eax” and the subtraction is performed by “sub edx, ecxedx and ecx in edx. Finally the result that is in edx register is saved on “[” which corresponds to the variable called rsp+118h+var_FC]sub in the Rust code. Also here there is a check for overflow (that is an underflow in this case) as above.
For multiplication between v1 and v2, the variable v1 is copied in [rsp+118h+var_F4], then  with the assembly instruction ithat implements the multiplication is “mul [rsp+118h+var_F0]eax with the operand [rsp+118h+var_F0] (thus the variable v2); the code saves the value in eax itself in “[rsp+118h+var_100]mul in the Rust code. Also here there is a check for overflow as above.
For integer division the code is a little different: first there is a check of the divisor other than 0, the jump to loc_8BA9 manages the division by zero case (not shown in assembly code); then it copies the values of v1 and v2 into memory at addresses [rsp+118h+var_104] and [rsp+118h+var_108] (now shown in assembly code), then it copies these two last values from memory to ecx and eax registers. The instruction “xor edx, edx” set the edx register to 0 and the division is performed by “div ecx” that saves the integer division between eax and ecx in eaxeax[rsp+118h+var_E0]quo
The implementation of the remainder of the division is very similar to integer division, in fact first there is a check of the divisor other than 0, the jump to loc_8D16v1 and v2 into memory at addresses [rsp+118h+var_10C] and [rsp+118h+var_110] (now shown in assembly code), then it copies these two last values from memory to ecx and eax registers. The instruction “xor edx, edx” set the edx register to 0 and the division is performed by “div ecx” that saves the integer division between eax and ecx in eax (non interesting here) and the remainder in edx (and it is interesting). Finally the result that is in edx register is saved on “[rsp+118h+var_DC]rem
Note: An optimizer would have merged the two instruction sequences of integer division and remainder division by invoking div only once (and even once only the divisor control equal to 0), but because of the above, the optimizations were not intentionally invoked.

 
            
 
            
 
            
 
            
