In the Hello World sample
program we had used the instructions REPNZ
and SCASB
to calculate the length of the string being printed at runtime. In this program
we use NASM’s equ
directive to calculate the length during assembly time as
opposed to at runtime. The variable promptlen
gives an example.
In the below program, we print the prompt asking for the user to enter an
integer which is then read into the variable input defined in the .bss
section,
which is the uninitialized memory section, using the function read_int()
defined earlier in the file asm_io.asm.
To multiply two signed integers, we use the IMUL
instruction, which is what we
have used here. To divide two signed integers, we use the IDIV
instruction,
which places the quotient of the division in the RAX
register and the remainder
in the RDX
register. A very common error is to forget to
initialize the RDX
register, which we do here with instruction CQO
which sign extends RAX
to RDX
.
The negation of a number, i.e. multiplication by -1
is done using
the NEG
instruction which calculates the two’s complement of the value in the register
operand for the instruction.
%include "asm_io.inc"
%macro prologue 0
push rbp
mov rbp,rsp
push rbx
push r12
push r13
push r14
push r15
%endmacro
%macro epilogue 0
pop r15
pop r14
pop r13
pop r12
pop rbx
leave
ret
%endmacro
section .bss
input resd 1
section .rodata
prompt db "Enter a number: ",0
promptlen equ $-prompt
cube db "The cube of the number is: ",0
square db "The square of the number is: ",0
cube25 db "The value of cube of the number times 25 is: ",0
quotient db "The quotient of cube/100 is: ",0
remainder db "The remainder of cube/100 is: ",0
negation db "The negation of the remainder is: ",0
section .text
global main
main:
prologue
; print the prompt for the user
mov rdx, promptlen
mov rsi, dword prompt
push 0x1
pop rdi
mov rax,rdi
syscall
; read the integer
mov rdi, dword input
call read_int
; calculate its square and print the output
mov rdi, dword square
call print_string
mov rdi, [input]
imul rdi, rdi
mov rbx, rdi
call print_int
call print_nl
; calculate its cube and print the output
mov rdi,dword cube
call print_string
mov rdi, [input]
imul rdi,rdi
imul rdi,[input]
mov rbx, rdi
call print_int
call print_nl
; calculate the cube times 25 and print the output
mov rdi, dword cube25
call print_string
mov rdi, rbx
imul rdi,0x19
call print_int
call print_nl
; calculate cube/100 and print the output
mov rdi, dword quotient
call print_string
; initialize rax and sign extend it to rdx
mov rax,rbx
cqo
mov rcx,0x64 ; this is the hex representation of 100
idiv rcx
push rdx
mov rdi, rax
call print_int
call print_nl
; calculate the remainder of cube/100 and print the output
mov rdi, dword remainder
call print_string
pop rdx
mov rdi,rdx
push rdx
call print_int
call print_nl
; calculate the negation of the remainder and print the output
mov rdi, dword negation
call print_string
pop rdi
neg rdi
call print_int
call print_nl
epilogue
To compile the above program which we shall call math.asm
, we execute the following steps:
$ yasm -f elf64 asm_io.asm
$ yasm -f elf64 math.asm
$ ld -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o \
math.o asm_io.o /usr/lib/x86_64-linux-gnu/crtn.o -lc -o math.out
Since we are using C library functions like printf()
scanf()
and putchar()
, we
need to link in the C library during link time, hence the -lc
is used for that.
This however will generate a very large executable because all the C library
functions will become a part of the executable. We do not want this, so we link
in dynamically using the C runtime object files provided by the operating system
in /usr/lib/x86_64-linux-gnu/
or /usr/lib64/
(depending on which GNU/Linux you are on) directory as below:
It is advisable that the files should be linked in the correct order as shown above.
Download math.asm, asm_io.inc and asm_io.asm.