Skip to content

Programming by Design

If you're not prepared to be wrong, you'll never come up with anything original. – Sir Ken Robinson

  • About
  • Java-PbD
  • C-PbD
  • ASM-PbD
  • Algorithms
  • Other

CISS-150 Project 4 – Assembly Language Basics

Posted on September 27, 2020October 13, 2024 By William Jojo
CISS-150-Project

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.

project4.sh
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.

lib.asm
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

project4.asm
%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.


STOP!
You are done with the project! However, if you’d like to experiment with some other math instructions, please read on!

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.

Post navigation

❮ Previous Post: Assembly Language With NASM
Next Post: CISS-150 Project 5 – Assembly Language Syscalls ❯

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Copyright © 2018 – 2025 Programming by Design.