Blog

  • NASM Hello World Gists on Github

    Github has a feature called Gists which allow people to share short programs more conveniently than having an entire repository for them. Although of course these are part of my Chastity’s Code Cookbook repository, I like having these conveniently linked so that people who want a taste of Assembly programming can use my example code to get started with NASM.

    NASM 32-bit ELF Hello World with no linker
    https://gist.github.com/chastitywhiterose/4e429fd82f962907d1581307ed4e0ab7

    NASM 64-bit ELF Hello World with no linker
    https://gist.github.com/chastitywhiterose/b5acd7992fc8463a662ca4f86fff4a5e

    Obviously FASM can generate the ELF header without my math but I had to learn how the format works to get these working and also so I don’t depend on only one assembler. In my next book, I plan to cover using either FASM or NASM for Assembly language programming in Linux.

  • chastelib SDL API

    This repository began as an extension of chastelib, which was my own collection of routines for writing text and integers to standard output. The library was working well for terminal programs, but then I got a wild idea!

    What if instead of a Linux terminal, the functions output text to an SDL window using my favorite bitmap font?

    After a month of working on it on my off nights, I managed to create a working example program that exceeded my original vision. I had complete control over the color and scale of the text I drew.

    But there was a problem! Should I base it on SDL2, which I had the most experience with when I made chastetris? Or should I go with the modern SDL3?

    I couldn’t decide because I appreciate all libraries, new and old. Therefore, I made 3 separate versions of the same program, which operate in the exact same way, using the code matching that specific version of the SDL API.

    Although the names of the SDL functions differ between versions (the decision of the SDL library developers, not me), I was able to modify them so that I can create a window, load a font from a Portable Bitmap File, and then draw characters from that font using my own structures and functions.

    Because I worked on this code so much, I wanted to have a separate repository for it so that others can easily navigate it and use it for projects they have in mind.

    https://github.com/chastitywhiterose/chastelib-sdl-api

    The basic idea is that it will remain simple but allow making small text based games except compatible with modern graphical PCs and consoles (such as games made for Steam Deck). If you find it useful, please let me know!

  • chastehex 1280 byte edition for Linux

    The following source code is a major update to chastehex for 32-bit Assembly source code for Linux. The behavior of the program hasn’t changed. It is still the great command line hex editor. However, the executable is a lot smaller than it previously was. I found some optimizations to reduce function calls and also removed some of the text while still having the messages say the same basic idea. This may not mean much to the average person but this is the best hand written assembly I have ever achieved and I made some extensions to chastelib that will be helpful for future programs.

    main.asm

    ;Linux 32-bit Assembly Source for chastehex
    ;a special tool originally written in C
    format ELF executable
    entry main
    
    start:
    
    include 'chastelib32.asm'
    
    main:
    
    ;radix will be 16 because this whole program is about hexadecimal
    mov dword [radix],16 ; can choose radix for integer input/output!
    
    pop eax
    mov [argc],eax ;save the argument count for later
    
    ;first arg is the name of the program. we skip past it
    pop eax
    dec dword [argc]
    
    ;before we try to get the first argument as a filename, we must check if it exists
    cmp dword [argc],0
    jnz arg_open_file
    
    help:
    mov eax,help_message
    call putstring
    jmp main_end
    
    arg_open_file:
    
    pop eax
    dec dword [argc]
    mov [filename],eax ; save the name of the file we will open to read
    call putstr_and_line
    
    ;Linux system call to open a file
    
    mov ecx,2   ;open file in read and write mode 
    mov ebx,eax ;filename should be in eax before this function was called
    mov eax,5   ;invoke SYS_OPEN (kernel opcode 5)
    int 80h     ;call the kernel
    
    cmp eax,0
    jns file_open_no_errors ;if eax is not negative/signed there was no error
    
    ;Otherwise, if it was signed, then this code will display an error message.
    
    neg eax
    call putint_and_space
    mov eax,open_error_message
    call putstr_and_line
    
    jmp main_end ;end the program because we failed at opening the file
    
    file_open_no_errors:
    
    mov [filedesc],eax ; save the file descriptor number for later use
    mov dword [file_offset],0 ;assume the offset is 0,beginning of file
    
    ;check next arg
    cmp dword [argc],0 ;if there are no more args after filename, just hexdump it
    jnz next_arg_address ;but if there are more, jump to the next argument to process it as address
    
    hexdump:
    
    mov edx,0x10         ;number of bytes to read
    mov ecx,byte_array   ;address to store the bytes
    mov ebx,[filedesc]   ;move the opened file descriptor into EBX
    mov eax,3            ;invoke SYS_READ (kernel opcode 3)
    int 80h              ;call the kernel
    
    mov [bytes_read],eax
    
    cmp eax,0
    jnz file_success ;if more than zero bytes read, proceed to display
    
    ;display EOF to indicate we have reached the end of file
    
    mov eax,end_of_file_string
    call putstr_and_line
    
    jmp main_end
    
    ; this point is reached if file was read from successfully
    
    file_success:
    
    call print_bytes_row
    
    cmp dword [bytes_read],1 
    jl main_end ;if less than one bytes read, there is an error
    jmp hexdump
    
    ;address argument section
    next_arg_address:
    
    ;if there is at least one more arg
    pop eax ;pop the argument into eax and process it as a hex number
    dec dword [argc]
    call strint
    
    ;use the hex number as an address to seek to in the file
    mov edx,0          ;whence argument (SEEK_SET)
    mov ecx,eax        ;move the file cursor to this address
    mov ebx,[filedesc] ;move the opened file descriptor into EBX
    mov eax,19         ;invoke SYS_LSEEK (kernel opcode 19)
    int 80h            ;call the kernel
    
    mov [file_offset],eax ;move the new offset
    
    ;check the number of args still remaining
    cmp dword [argc],0
    jnz next_arg_write ; if there are still arguments, skip this read section and enter writing mode
    
    read_one_byte:
    mov edx,1          ;number of bytes to read
    mov ecx,byte_array ;address to store the bytes
    mov ebx,[filedesc] ;move the opened file descriptor into EBX
    mov eax,3          ;invoke SYS_READ (kernel opcode 3)
    int 80h            ;call the kernel
    
    ;eax will have the number of bytes read after system call
    cmp eax,1
    jz print_byte_read ;if exactly 1 byte was read, proceed to print info
    
    call show_eof
    
    jmp main_end ;go to end of program
    
    ;print the address and the byte at that address
    print_byte_read:
    call print_byte_info
    
    ;this section interprets the rest of the args as bytes to write
    next_arg_write:
    cmp dword [argc],0
    jz main_end
    
    pop eax
    dec dword [argc]
    call strint ;try to convert string to a hex number
    
    ;write that number as a byte value to the file
    
    mov [byte_array],al
    
    mov eax,4          ;invoke SYS_WRITE (kernel opcode 4 on 32 bit systems)
    mov ebx,[filedesc] ;write to the file (not STDOUT)
    mov ecx,byte_array ;pointer to temporary byte address
    mov edx,1          ;write 1 byte
    int 80h            ;system call to write the message
    
    call print_byte_info
    inc dword [file_offset]
    
    jmp next_arg_write
    
    main_end:
    
    ;this is the end of the program
    ;we close the open file and then use the exit call
    
    ;Linux system call to close a file
    
    mov ebx,[filedesc] ;file number to close
    mov eax,6          ;invoke SYS_CLOSE (kernel opcode 6)
    int 80h            ;call the kernel
    
    mov eax, 1  ; invoke SYS_EXIT (kernel opcode 1)
    mov ebx, 0  ; return 0 status on exit - 'No Errors'
    int 80h
    
    
    ;this function prints a row of hex bytes
    ;each row is 16 bytes
    print_bytes_row:
    mov eax,[file_offset]
    mov dword [int_width],8
    call putint_and_space
    
    mov ebx,byte_array
    mov ecx,[bytes_read]
    add [file_offset],ecx
    next_byte:
    mov eax,0
    mov al,[ebx]
    mov dword [int_width],2
    call putint_and_space
    
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_byte
    
    mov ecx,[bytes_read]
    pad_spaces:
    cmp ecx,0x10
    jz pad_spaces_end
    mov eax,space_three
    call putstring
    inc ecx
    jmp pad_spaces
    pad_spaces_end:
    
    ;optionally, print chars after hex bytes
    call print_bytes_row_text
    call putline
    
    ret
    
    space_three db '   ',0
    
    print_bytes_row_text:
    mov ebx,byte_array
    mov ecx,[bytes_read]
    next_char:
    mov eax,0
    mov al,[ebx]
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp al,0x20
    jb not_printable
    cmp al,0x7E
    ja not_printable
    
    printable:
    ;if char is in printable range,keep as is and proceed to next index
    jmp next_index
    
    not_printable:
    mov al,'.' ;otherwise replace with placeholder value
    
    next_index:
    mov [ebx],al
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_char
    mov [ebx],byte 0 ;make sure string is zero terminated
    
    mov eax,byte_array
    call putstring
    
    ret
    
    
    ;function to display EOF with address
    show_eof:
    
    mov eax,[file_offset]
    mov dword [int_width],8
    call putint_and_space
    mov eax,end_of_file_string
    call putstr_and_line
    
    ret
    
    ;print the address and the byte at that address
    print_byte_info:
    mov eax,[file_offset]
    mov dword [int_width],8
    call putint_and_space
    mov eax,0
    mov al,[byte_array]
    mov dword [int_width],2
    call putint_and_line
    
    ret
    
    end_of_file_string db 'EOF',0
    
    help_message db 'chastehex by Chastity White Rose',0Ah,0Ah
    db 'hexdump a file:',0Ah,0Ah,9,'chastehex file',0Ah,0Ah
    db 'read a byte:',0Ah,0Ah,9,'chastehex file address',0Ah,0Ah
    db 'write a byte:',0Ah,0Ah,9,'chastehex file address value',0Ah,0Ah
    db 'The file must exist',0Ah,0
    
    ;variables for managing arguments and files
    argc dd 0
    filename dd 0 ; name of the file to be opened
    filedesc dd 0 ; file descriptor
    bytes_read dd 0
    file_offset dd 0
    open_error_message db 'error while opening file',0
    
    ;where we will store data from the file
    byte_array db 17 dup '?'
    

    chastelib32.asm

    ; chastelib assembly header file for 32 bit Linux
    ; This file is where I keep the source of my most important Assembly functions
    ; These are my string and integer output and conversion routines.
    
    ; To simplify documentation. The Accumulator/Arithmetic register
    ; (ax,ebx,rax) depending on bit size shall be referred to as register A
    ; for the description of these core functions because the A register
    ; is treated special both by the Intel company and my code;
    
    ; putstring; Prints a zero terminated string from the address pointer to by A register.
    ; intstr;    Converts the number in A into a zero terminated string and points A to that address
    ; putint;    Prints the integer in A by calling intstr and then putstring.
    ; strint;    Converts the zero terminated string into an integer and sets A to that value
       
    ; Now, the source of the functions begins, with comments included for parts that I felt needed explanation.
    
    stdout dd 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax ; copy eax to ebx. ebx will be used as index to the string
    
    putstring_strlen_start: ; this loop finds the length of the string as part of the putstring function
    
    cmp [ebx],byte 0 ; compare byte at address ebx with 0
    jz putstring_strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp putstring_strlen_start
    
    putstring_strlen_end:
    sub ebx,eax ;subtract start pointer from current pointer to get length of string
    
    ;Write string using Linux Write system call.
    ;Reference for 32 bit x86 syscalls is below.
    ;https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/#x86-32-bit
    
    mov edx,ebx      ;number of bytes to write
    mov ecx,eax      ;pointer/address of string to write
    mov ebx,[stdout] ;write to the STDOUT file
    mov eax, 4       ;invoke SYS_WRITE (kernel opcode 4 on 32 bit systems)
    int 80h          ;system call to write the message
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret ; this is the end of the putstring function return to calling location
    
    ; This is the location in memory where digits are written to by the intstr function
    ; The string of bytes and settings such as the radix and width are global variables defined below.
    
    int_string db 32 dup '?' ;enough bytes to hold maximum size 32-bit binary integer
    
    int_string_end db 0 ;zero byte terminator for the integer string
    
    radix dd 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dd 8
    
    ;this function creates a string of the integer in eax
    ;it uses the above radix variable to determine base from 2 to 36
    ;it then loads eax with the address of the string
    ;this means that it can be used with the putstring function
    
    intstr:
    
    mov ebx,int_string_end-1 ;find address of lowest digit(just before the newline 0Ah)
    mov ecx,1
    
    digits_start:
    
    mov edx,0;
    div dword [radix]
    cmp edx,10
    jb decimal_digit
    jae hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add edx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub edx,10
    add edx,'A'
    
    save_digit:
    
    mov [ebx],dl
    cmp eax,0
    jz intstr_end
    dec ebx
    inc ecx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp ecx,[int_width]
    jnb end_zeros
    dec ebx
    mov [ebx],byte '0'
    inc ecx
    jmp prefix_zeros
    end_zeros:
    
    mov eax,ebx ; now that the digits have been written to the string, display it!
    
    ret
    
    ; function to print string form of whatever integer is in eax
    ; The radix determines which number base the string form takes.
    ; Anything from 2 to 36 is a valid radix
    ; in practice though, only bases 2,8,10,and 16 will make sense to other programmers
    ; this function does not process anything by itself but calls the combination of my other
    ; functions in the order I intended them to be used.
    
    putint: 
    
    push eax
    push ebx
    push ecx
    push edx
    
    call intstr
    
    call putstring
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    ;this function converts a string pointed to by eax into an integer returned in eax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    
    strint:
    
    mov ebx,eax ;copy string address from eax to ebx because eax will be replaced soon!
    mov eax,0
    
    read_strint:
    mov ecx,0 ; zero ecx so only lower 8 bits are used
    mov cl,[ebx]
    inc ebx
    cmp cl,0 ; compare byte at address edx with 0
    jz strint_end ; if comparison was zero, this is the end of string
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp cl,'0'
    jb not_digit
    cmp cl,'9'
    ja not_digit
    
    ;but if it is a digit, then correct and process the character
    is_digit:
    sub cl,'0'
    jmp process_char
    
    not_digit:
    ;it isn't a digit, but it could an alphabet character which is a digit in a higher base
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function
    jmp strint_end
    
    process_char:
    
    cmp ecx,[radix] ;compare char with radix
    jae strint_end ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov edx,0 ;zero edx because it is used in mul sometimes
    mul  dword [radix] ;mul eax with radix
    add eax,ecx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    
    ;The utility functions below simply print a space or a newline.
    ;these help me save code when printing lots of strings and integers.
    
    space db ' ',0
    line db 0Dh,0Ah,0
    
    putspace:
    push eax
    mov eax,space
    call putstring
    pop eax
    ret
    
    putline:
    push eax
    mov eax,line
    call putstring
    pop eax
    ret
    
    ;a function for printing a single character that is the value of al
    
    char: db 0,0
    
    putchar:
    push eax
    mov [char],al
    mov eax,char
    call putstring
    pop eax
    ret
    
    ;a small function just for the common operation
    ;printing an integer followed by a space
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    
    putint_and_space:
    call putint
    call putspace
    ret
    
    ;a small function just for the common operation
    ;printing an integer followed by a line feed
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    
    putint_and_line:
    call putint
    call putline
    ret
    
    ;a small function just for the common operation
    ;printing a string followed by a line feed
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    ;it also means we don't need to include a newline in every string!
    
    putstr_and_line:
    call putstring
    call putline
    ret
    

  • Chastity’s List of Completed Full Sail University Classes

    This list contains all the classes I completed as part of 3 different programs. My original enrollment was in Game Business and Esports Science, but then I switched to creative writing. After graduation, I took some computer programming classes as part of the ACE (Alumni Continuing Education) program.

    These varied interests I have all have a generic theme. Storytelling as it relates to video games includes two parts. There are the words of the story and how the games are communicated in English to humans. The other side of it is the math that computers understand as well as autistic math nerds like me.

    Joseph Sheckels
    Systems Programming – Online
    SDV3111-O • Term C202603 • Section 02

    Garrett Girod
    Programming II – Online
    COP2334-O • Term C202602 • Section 04

    Davide Bisso
    Career Readiness – Online
    CRR4000-O • Term C202601 • Section 17ACE

    Douglas Arley
    Programming I – Online
    COP1334-O • Term C202601 • Section 08

    Joshua Collier
    Project and Portfolio III: Creative Writing – Online
    CWB338-O • Term C202506 • Section 02

    Matthew Peters
    Publishing and Distribution – Online
    ECW2953-O • Term C202505 • Section 02

    Convert Instructor
    Graduation Launch
    GRAD4000-O • Term C202506 • Section 05

    Joshua Begley
    Literary Genre I: Comedy and Tragedy – Online
    ECW3111-O • Term C202504 • Section 03

    John King
    Project and Portfolio II: Creative Writing – Online
    CWB228-O • Term C202503 • Section 02

    Cory Helms
    Television Writing – Online
    ECW3702-O • Term C202502 • Section 02

    Elise McKenna
    Developing New Worlds: Environment and Historical Research – Online
    ECW2841-O • Term C202501 • Section 02

    Genevieve Tyrrell
    Project and Portfolio I: Creative Writing – Online
    CWB119-O • Term C202412 • Section 01

    Cory Helms
    Writing Workshop I: Film – Online
    ECW4101-O • Term C202411 • Section 02

    Beth Strudgeon
    Introduction to Media Communications and Technologies – Online
    MCM1002-O • Term C202410 • Section 03

    Christopher Ramsey
    Multimedia Storytelling – Online
    ECW1409-O • Term C202410 • Section 01

    Brett Pribble
    Creative Skills Development – Online
    ECW1225-O • Term C202409 • Section 03

    Cory Helms
    Literary Techniques and Story Development – Online
    ECW2123-O • Term C202408 • Section 04

    Becca Godsey
    Professional Development Seminar II: Game Business and Esports – Online
    GBE3222-O • Term C202407 • Section 02

    Aaron Conner
    Project and Portfolio II: Game Business and Esports – Online
    GBE229-O • Term C202407 • Section 01

    Ren Vickers
    Game Business Models – Online
    GBE2501-O • Term C202406 • Section 01

    Shane Marcus
    Physical Science – Online
    PHY3020-O • Term C202405 • Section 05

    Ashley Jones
    Professional Development Seminar I: Game Business and Esports – Online
    GBE1111-O • Term C202404 • Section 01

    Becca Godsey
    Project and Portfolio I: Game Business and Esports – Online
    GBE119-O • Term C202404 • Section 01

    Terry Clark
    College Mathematics – Online
    MGF1213-O • Term C202403 • Section 04

    Otavio Moulin Lessa
    Gaming Culture and Engagement – Online
    GBE2001-O • Term C202402 • Section 01

    Philip Lacinak
    Digital Video and Audio Production – Online
    MCM2416-O • Term C202401 • Section 02

    David Sussman
    English Composition I – Online
    ENC1101-O • Term C202312 • Section 37

    Mike Dunn
    New Media Tools – Online
    MCM1203-O • Term C202311 • Section 09

    Ricky Sellers
    Video-Sharing Platforms – Online
    VID1555-O • Term C202310 • Section 01

    Katherine Coulthart
    Storytelling for Marketing – Online
    MKT163-O • Term C202309 • Section 02

    Alexia Brehm
    Introduction to Marketing – Online
    MKT210-O • Term C202308 • Section 01

    Brandon Morris
    Introduction to Esports Production – Online
    GBE1021-O • Term C202307 • Section 02

    Charles Cardwell
    Introduction to the Gaming Industry – Online
    GBE1001-O • Term C202306 • Section 02

    Jennifer Salzberg
    Psychology of Play – Online
    DEP1013-O • Term C202305 • Section 17

    Eddie Tapia
    Creative Presentation – Online
    GEN1011-O • Term C202304 • Section 26

    Orientation Team
    Full Sail Online Orientation (FSLE) – Online
    GEN2000-O • Term C202304 • Section 06

  • chastelib test suite for 32-bit Linux GNU Assembly Language

    Ever since I figured out the technique for translating NASM source files into GNU Assembler source files, I have been quite excited at the possibilities. I translated the whole chastelib library into this syntax. This means that I could even learn to use these as inline assembly in C programs on Linux. I am not sure what the benefit is unless of course I enter some kind of obfuscated code contest!

    main.s

    # Using Linux System calls for 32-bit
    # Tested with GNU Assembler on Debian 12 (bookworm)
    # It uses Chastity's putstring function for output
    
    .global _start
    
    _start:
    
    
    mov $main_string,%eax # move address of string into eax register
    call   putstring      # call the putstring function Chastity wrote
    
    movl $0x10,radix # set the radix to sixteen or hexadecimal
    movl $1,int_width # set the minimum integer width
    
    mov $input_string,%eax
    call strint
    
    mov %eax,%ebx # copy eax to ebx before we use it in a loop1
    
    movl $0,%eax # zero eax
    
    loop1:
    
    movl $2,radix # set radix to binary
    movl $8,int_width # width of 8 bits
    call putint
    call putspace
    
    movl $0x10,radix # set radix to hexadecimal
    movl $1,int_width # width of 2 hex digits
    call putint
    call putspace
    
    movl $10,radix # set radix to decimal (what humans read)
    movl $3,int_width # width of 3 decimal digits
    call putint
    call putspace
    
    cmp    $0x20,%al
    jb     not_char
    cmp    $0x7e,%al
    ja     not_char
    
    call putspace
    call putchar  # print the character if it is in the range 0x20 to 0x7E
    
    not_char:
    
    call putline
    inc    %eax
    cmp    %ebx,%eax
    jne    loop1
    
    mov    $0x1,%eax      # system call 1 is exit
    mov    $0x0,%ebx      # we want to return code 0
    int    $0x80          # end program with system call
    
    main_string:
    .string	"This program is the official test suite for the Linux Assembly version of chastelib.\n"
    
    input_string:
    .string	"100"
    
    putstring:            # the start of the putstring function
    push   %eax
    push   %ebx
    push   %ecx
    push   %edx
    mov    %eax,%ebx
    
    putstring_strlen_start:
    cmpb   $0x0,(%ebx)
    je     putstring_strlen_end
    inc    %ebx
    jmp    putstring_strlen_start
    
    putstring_strlen_end:
    sub    %eax,%ebx # subtract eax from ebx for number of bytes to write
    mov    %ebx,%edx # copy number of bytes from ebx to edx
    mov    %eax,%ecx # address of string to output
    mov    $0x1,%ebx # file handler 1 is stdout
    mov    $0x4,%eax # system call 4 is write
    int    $0x80
    
    pop    %edx
    pop    %ecx
    pop    %ebx
    pop    %eax
    ret
    
    .data        # must declare data section for mutable memory
    
    int_string:  # storage for the bytes of an integer
    .skip 32,'?' # unknown bytes represented with question marks
    .byte 0      # terminating zero of this string
    
    int_newline: # optional newline to print after a number
    .byte 0xA,0
    
    radix: .long 2
    int_width: .long 8
    
    intstr: # start of the intstr function
    mov    $int_string+31,%ebx
    mov    $0x1,%ecx
    
    digits_start:
    mov    $0x0,%edx
    divl   radix            # divide by number at address radix
    cmp    $0xa,%edx
    jb     decimal_digit
    jae    hexadecimal_digit
    
    decimal_digit:
    add    $0x30,%edx
    jmp    save_digit
    
    hexadecimal_digit:
    sub    $0xa,%edx
    add    $0x41,%edx
    
    save_digit:
    mov    %dl,(%ebx)
    cmp    $0x0,%eax
    je     intstr_end
    dec    %ebx
    inc    %ecx
    jmp    digits_start
    
    intstr_end:
    cmp    int_width,%ecx # see if ecx is above or equal to the integer width we want
    jae    end_zeros
    dec    %ebx
    movb   $0x30,(%ebx)
    inc    %ecx
    jmp    intstr_end
    
    end_zeros:
    mov    %ebx,%eax
    ret
    
    putint: # the putint function calls intstr and then putstring to display any number
    
    push   %eax
    push   %ebx
    push   %ecx
    push   %edx
    call   intstr
    call   putstring
    pop    %edx
    pop    %ecx
    pop    %ebx
    pop    %eax
    ret
    
    # the strint function is arguably the most complicated assembly function I have ever written
    # it can convert any string into a number based on the current radix
    # as soon as it finds a zero byte or a charact that is not a valid digit in that radix
    # it will return the value in the eax register so it can be printed or used elsewhere
    
    strint:
    mov    %eax,%ebx # copy string address from eax to ebx because eax will be replaced soon!
    mov    $0x0,%eax # eax set to zero before digits multiplied in
    
    
    read_strint:
    mov    $0x0,%ecx
    mov    (%ebx),%cl
    inc    %ebx
    cmp    $0x0,%cl
    je     strint_end
    cmp    $0x30,%cl
    jb     not_digit
    cmp    $0x39,%cl
    ja     not_digit
    
    is_digit:
    sub    $0x30,%cl
    jmp    process_char
    
    not_digit:
    cmp    $0x41,%cl
    jb     not_upper
    cmp    $0x5a,%cl
    ja     not_upper
    
    is_upper:
    sub    $0x41,%cl
    add    $0xa,%cl
    jmp    process_char
    
    not_upper:
    cmp    $0x61,%cl
    jb     not_lower
    cmp    $0x7a,%cl
    ja     not_lower
    
    is_lower:
    sub    $0x61,%cl
    add    $0xa,%cl
    jmp    process_char
    
    not_lower:
    jmp    strint_end
    
    process_char:
    cmp    radix,%ecx
    jae    strint_end
    mov    $0x0,%edx
    mull   radix
    add    %ecx,%eax
    jmp    read_strint
    
    strint_end:
    ret
    
    space: .byte ' ',0
    line: .byte 0x0A,0
    
    putspace:
    push   %eax
    mov    $space,%eax
    call   putstring
    pop    %eax
    ret
    
    putline:
    push   %eax
    mov    $line,%eax
    call   putstring
    pop    %eax
    ret
    
    char: .byte 0,0 # where char data is temporarily stored by the putchar function
    
    putchar:
    push   %eax
    mov    %al,char
    mov    $char,%eax
    call   putstring
    pop    %eax
    ret
    
    # This Assembly source file has been formatted for the GNU assembler.
    # The following makefile rule has commands to assemble, link, and run the program
    #
    #main-gas:
    #	gcc -nostdlib -nostartfiles -nodefaultlibs -static main.s -o main -m32
    #	strip main
    #	./main