//// program2.c// Basic arithmetic operations and print statements//#include <stdio.h>intmain(){int a =10;int b =5;int addition = a + b;int subtraction = a - b;int multiplication = a * b;int division = a / b;int bitwiseAnd = a & b;int bitwiseOr = a | b;int bitwiseNot =~a;int bitwiseXor = a ^ b;printf("Addition: %d\\n", addition);printf("Subtraction: %d\\n", subtraction);printf("Multiplication: %d\\n", multiplication);printf("Division: %d\\n", division);printf("Bitwise AND: %d\\n", bitwiseAnd);printf("Bitwise OR: %d\\n", bitwiseOr);printf("Bitwise NOT: %d\\n", bitwiseNot);printf("Bitwise XOR: %d\\n", bitwiseXor);return0;}
This program adds on some bitwise operations and some basic arithmetic. Here is the corresponding assembly code for main by running it on gdb debugger:
The first three lines of assembly code that’s shown is the function prologue which is explained in the previous section. Note how this time it allocates 48 bytes (30 hex) for local variables which is more space that’s allocated than the previous code (which only had 1 variable):
Next, the assembly shows the declaration/initialization of the variables int a = 10; and int b = 5; in the source code. The first (also shown below) instruction 0x0000555555555141 <+8>: movl $0xa,-0x4(%rbp) is moving the hexadecimal value 0xa (which is 10 in decimal) to the memory location that is 4 bytes before the base pointer (%rbp). This corresponds to the variable a in the C code int a = 10;. The l in movl stands for 'long' and refers to a 32-bit value, which is the size of an int in C on most platforms. The 0x0000555555555148 <+15>: movl $0x5,-0x8(%rbp) instruction is moving the hexadecimal value 0x5 (which is 5 in decimal) to the memory location that is 8 bytes before the base pointer (%rbp). This corresponds to the variable b in the C code int b = 5;. Again, the l in movl stands for 'long' and refers to a 32-bit value. Here are the two lines of assembly code put together:
The next four lines correspond to the addition portion of the code int addition = a + b; which is shown below:
The first two assembly instructions are moving the variables b and a into the edx and the eax registers. After that we see the add instruction which adds the value in the edx register to the value in the eax register and stores the result in eax. This is the operation a + b. The last instruction mov %eax,-0xc(%rbp) is moving the value in eax (the result of the addition) into the memory location 12 bytes before the base pointer. This corresponds to storing the result of a + b in the variable addition in the original C code.
The next three lines of assembly instructions are from the subtraction portion of the code int subtraction = a - b;:
In the above code, it’s moving the value stored 4 bytes below the base pointer (variable a which equals 10) into the eax register. Then, the next instruction introduces the sub instruction which will subtract the value 8 bytes below the base pointer (in this case b), from the value in the eax register (being a) where the resulting value is then stored in the eax register. The last line of assembly moves the value of
The rest of the math, in the assembly up to <+94> follows the same pattern of three instructions being:
Setting the eax register to 10 (the variable a, this is because the resultant from the second step is stored in the eax register)
Doing the math operand which ends up storing the value into the eax register
Moving the result stored in the eax register to a few bytes below the base pointer
The following instructions that were not seen previously, were used:
imul - the integer multiply operand
idivl - the integer divide operand
and - the bitwise and operand
or - the bitwise or operand
not - the bitwise not operand
xor - the bitwise xor operand
cltd (this happened before the division) - covert long to double. This is to expand the register in the case that the number is negative (meaning the most significant bit is 1), the computer won’t think it’s a huge positive number instead
Following we see the same pattern involving the print statements, here is the first one as an example:
mov -0xc(%rbp),%eax - This is loading the value from 12 bytes before the base pointer %rbp (the addition result) into %eax.
mov %eax,%esi - The value from %eax (which we just loaded from memory) is moved into the %esi register. In the context of a function call on a Unix-like system, %esi is commonly used to hold the second argument. The function called is printf, and since the string for printf typically comes first, this value is likely a value to be printed.
lea 0xe5e(%rip),%rax - Load effective address (lea) calculates the address given by the sum of its operands. Here it's taking the address of the current instruction (%rip, the instruction pointer) and adding 0xe5e to it, storing the result in %rax. This is likely calculating the address of a string in memory, specifically a printf format string.
mov %rax,%rdi - The value from %rax (which is the address of the format string) is moved into the %rdi register. In the context of a function call on a Unix-like system, %rdi is commonly used to hold the first argument. So this sets up the first argument for printf.
mov $0x0,%eax - Zero is moved into %eax. In the context of a function call on a Unix-like system, %eax is used to indicate the number of floating point parameters passed in vector registers for a function call. In this case, it's setting that number to zero, indicating there are no such parameters.
call 0x555555555030 <printf@plt> - This instruction calls the printf function, which is located at the given address.