CISS-150 Project 4 (10 points)
(Updated October 13, 2024)
Overview
In this project, you become somewhat familiar with assembly language, the CPU registers, and the primitive level of operation that occurs at the CPU.
Learning Outcomes
- Understanding basic assembly instructions.
- Understanding CPU registers.
- Understanding the basics of calling subroutines.
- Creating a build-environment.
Getting Started
Begin by reviewing the assigned reading and Assembly Language with NASM document. This is an introductory deep dive into the world of assembly language and will get you prepared for this project and the next.
What you will be learning here is the complexity of harnessing the CPU in its most primitive form. This exercise uses the x86-64 call model to move data around. There is a great deal to learn about operating at this level – it is impossible at this stage to learn it all. The goal here is simply exposure to assembly language and how some languages, like C, get translated to this code level.
The Environment
You will be performing this work in Ubuntu. All the code provided will execute in your Ubuntu VM with a small amount of setup. You can use any editor, but the gedit
editor may be easier. You will also work in a subdirectory where the code will be stored. Rather than download the files from a site, you will create each using the editor. You are creating a build environment.
Be sure to install the assembler before you begin.
student@student-vm:~/asm$ sudo apt update
student@student-vm:~/asm$ sudo apt install nasm
The Code
Start by opening a terminal window and making a directory to hold the code.
student@student-vm:~$ mkdir asm
student@student-vm:~$ cd asm
Once complete, you can use gedit
to create the first file.
student@student-vm:~/asm$ gedit project4.sh
Now put the following code into that file.
nasm -felf64 -g project4.asm && {
ld project4.o -o project4
chmod +x project4
./project4
}
This script will build your program, link the pieces, and execute it. After creating that file, you need to change the permissions on it.
student@student-vm:~/asm$ chmod +x project4.sh
The next file is a library of routines written in assembly language. These power your program to perform primitive operations like printing numbers and text. This is a lengthy yet straightforward piece of code that is easily understood. It takes time to grasp the pieces, so take your time if you seek a deeper understanding. The library uses system calls, which are discussed more in Project 5.
section .data
LL_digits db "0123456789-",0
LL_cr db 0x0d
LL_lf db 0x0a
section .text
print_digit:
mov rsi, rdi ; rdi contains digit
add rsi, LL_digits ;
mov rdx, 1 ; len
mov rax, 1 ; write
mov rdi, 1 ; stdout
syscall
xor rax, rax ; return code 0
ret
print_lf:
mov rdx, 1 ; len
mov rsi, LL_lf ;
mov rax, 1 ; write
mov rdi, 1 ; stdout
syscall
xor rax, rax ; return code 0
ret
print_crlf:
mov rdx, 2
mov rsi, LL_cr
mov rax, 1 ; write
mov rdi, 1 ; stdout
syscall
xor rax, rax ; return code 0
ret
;
; print null-terminated string
;
print_str:
; string already in rdi
push rdi
call strlen2
pop rdi
mov rdx, rax ; len in rax
mov rsi, rdi
mov rax, 1 ; write
mov rdi, 1 ; stdout
syscall
xor rax, rax ; return code 0
ret
; end print_str
;
; calculate length of null-terminated string
;
strlen1:
mov rax, rdi ; rax points to string
con:
cmp byte [rax], 0 ; check for null
jz done ; found it!
inc rax ; move ahead
jmp con
done:
sub rax, rdi ; subtract addresses for length
ret
; end strlen
;
; calculate length of null-terminated string
; String in rdi
;
strlen2:
xor rcx, rcx ; rcx = 0
not rcx ; rcx = -1
cld ; forward direction
xor al, al ; AL = 0
repnz scasb ; search for null
not rcx ; ABS(rcx) - 1
dec rcx ; length
mov rax, rcx
ret
; end strlen
;
; print number in rdi
;
print_num:
xor r10, r10 ; zero digits
mov rax, rdi ; move arg to rax
cmp rax, 0 ;
jge pos
neg rax ; number is <0
push rax ; preserve for call!
mov rdi, 10 ; print hyphen
call print_digit
pop rax
pos:
cmp rax, 10
jl prt_digits
do_digits:
cqo ; sign extend into rdx
mov r11, 10
idiv r11 ; divide by 10
push rdx ; remainder to stack
inc r10 ; new digit
cmp rax, 10
jge do_digits
prt_digits:
push rax ; final digit in rax
inc r10
next:
pop rdi ; get next digit
call print_digit
dec r10
jnz next
end:
ret
; end print_num
The final piece of the build environment is the code that will serve as your main program. The code below
%include "lib.asm"
section .text
global _start
_start:
;
; YOUR CODE HERE!
;
; SAMPLE
mov rdi, 65536 ; going to print 65536
call print_num ; print the number
call print_lf ; move to next line
exit:
mov rax, 60 ; exit
mov rdi, 0 ; return code
syscall
What you need to do next
In the project4.asm
file, you are to create at least 3 arithmetic calculations with at least 3 operands each using the following instructions:
mov add sub
Then, after each expression is calculated, you will print the result using:
mov rdi, XXX
call print_num
call print_lf
The value of `XXX` will depend on which register your final result landed.
Do this at least 3 times using only the registers rax
, rbx
, rcx
, and rdx
.
For example, if you wanted to print the result of 32 + 5 - 10
, you could do something like:
mov rax, 32
add rax, 5
sub rax, 10
mov rdi, rax
call print_num
call print_lf
Remember that for the print_num
subroutine to print your number, you must move the result into rdi
.
The idea is to have fun with the assignment and experiment!
There is a very good instruction reference manual located here: https://www.felixcloutier.com/x86/index.html
Submit a note in the Learning Management System when the project is completed.
If you want to branch out and try multiplication and division, read up on how additional registers are utilized. Some example code is shown below.
%include "lib.asm"
section .data
result dq 0 ; 64-bit result
x dq 478682376 ;
y dq 8793845 ; 64-bit values
r dq 0 ;
x32 dd 8793845 ; 32-bit value of x
section .text
global _start
_start:
;-----------------------
; This version uses an alternate 32-bit model
; IMUL r/m32 (EDX:EAX <- EAX * r/m32)
; This is EDX:EAX <- EAX * r/m32.
;
mov eax, 65536
mov ebx, -16384
imul ebx
mov [result], eax ; move lower portion into result
mov [result+4], edx ; move higher portion into result+4
mov rdi, [result] ; move 64-bit
call print_num ; -1073741824
call print_lf
;-----------------------
; Multiply with 64-bit registers only (easiest!)
; IMUL r64, r/m64, imm32
; This is rdx <- rax * imm32
;
mov rax, 1024
imul rdx, rax, -1024 ; rdx <- rax * -1024
; move rdx to rdi for printing
mov rdi, rdx
call print_num ; -1048576
call print_lf
;-----------------------
; Multiply with 64-bit memory (Variables)
; IMUL r64, r/m64
; This is rdx <- rax * imm32
;
mov rdx, [x] ; x value in rdx
imul rdx, [y] ; rdx <- rdx * y (x * y)
mov [r], rdx ; r = x * y
; move rdx to rdi for printing
mov rdi, rdx
call print_num ; 4209458618775720
call print_lf
; Multiply with 64-bit memory (Variables)
; IMUL r/m64
; This is rdx:rax <- rax * r/imm64
;
mov rax, [x] ; x value in rax
mov rbx, [x] ; x value in rbx
neg rbx ; -x
imul rbx ; rdx:rax <- rax * rbx
mov [r], rax ; r = x * x
; we are basically ignoring rdx
; move rax to rdi for printing
mov rdi, rax
call print_num ; -229136817093005376
call print_lf
;-----------------------
; Divide with 64-bit memory (Variable & reg)
; IDIV r/m64
; This is divide rdx:rax by rcx.
; rax <- quotient, rdx <- remainder
;
mov rax, [r] ; rax gets -229136817093005376
cqo ; sign extend rax to rdx
mov rcx, 100 ; dividing by 100
idiv rcx
; move rax to rdi for printing
push rdx ; save the remainder
mov rdi, rax ; print the quotient (-2291368170930053)
call print_num
call print_lf
pop rdx ; print the remainder (-76)
mov rdi, rdx
call print_num
call print_lf
;-----------------------
; Divide with 32-bit memory (Variable & reg)
; IDIV r/m32
; This is divide edx:eax by ecx.
; eax <- quotient, edx <- remainder
;
mov eax, [x32] ; rax gets 8793845
cdq ; sign extend eax to edx
mov ecx, 100 ; dividing by 100
idiv ecx
; move eax to rdi for printing
movsxd rcx, edx ; save the...
push rcx ; ...remainder
movsxd rdi, eax ; print the quotient (87938)
call print_num
call print_lf
pop rdx ; print the remainder (45)
mov rdi, rdx
call print_num
call print_lf
exit:
mov rax, 60 ; exit
mov rdi, 0 ; return code
syscall
Glossary of Assembly Language
call - invoke named subroutine. dd - Reserve declared double-byte space. dq - Reserve declared quad byte space.