Blog

  • RISC-V Assembly Progress

    After having some minor success with Windows Intel Assembly, I took a break and went back to learning RISC-V Assembly using the RARS simulator and Robert Winkler’s book as a reference. I have managed to convert most of my Intel functions, including the Standard Input functions into RISC-V Assembly Language.

    The benefit of this will become useful for both simulation and teaching but also eventually on real hardware if I ever have the time and money to play with such things. In the mean time, I am appreciating how easy RISC-V is compared to Intel Assembly.

    # chastelib test suite for RISC-V Assembly in RARS simulator
    
    # this program tests the stdin extension of chastelib
    
    # The same library of functions I commonly use in my Intel Assembly code
    # have now been translated to RISC-V.
    # All assembly code seen here is for the RARS simulator written in Java.
    
    .data
    
    ##################################################################
    # chastelib core specific variables                              #
    #                                                                #
    # These variables are used by the intstr function to convert an  #
    # integer to a string and what radix and widthshould be used     #
    # width means how many minimum digits including leading zeros    #
    ##################################################################
    
    int_string: .space 32 #reserve space for 32 bytes for up to 32 bits if printed in binary
    int_end: .byte 0 #the terminating zero of the integer string
    radix: .byte 2   #the radix the number will be shown in
    int_width: .byte 1 #by default
    
    # These variables are for outputting special strings
    # such as a newline, space, or a single character based on s0
    
    space: .byte 0x20, 0
    line:  .byte 0x0A, 0
    char:  .byte 0, 0 
    
    ##################################################################
    # chastdin specific variables                                    #
    #                                                                #
    # these variables are used as the default controllers            #
    # for the getstring and getline functions                        #
    # buf stores keyboard input during those functions               #
    # count stores how many bytes were read during system read calls #
    # last_char stores the last character read                       #
    # usually this will be a space, tab, or newline                  #
    ##################################################################
    
    buf: .space 0x100
    count: .word 0
    last_char: .byte 0
    
    # program specific variables
    # These variables are for outputting specific messages
    # or to simulate user input as integers in the strint function
    
    string0: .ascii "chastelib test suite for RISC-V Assembly\n"
    string1: .asciz "stdin (STanDard INput) extension\n"
    
    string_exit: .asciz "exit"
    
    .text
    
    la s0, string0
    jal putstr
    
    # change radix for this program
    li t0, 16    #load t0 register with the new radix
    la t1, radix #load t1 register with the address the radix will go to
    sb t0, 0(t1) #save t0 register (byte) to address t1
    
    main_loop:
    
    jal getstr  # read the string from standard input
    jal putline # print extra line for readability
    
    jal putstr # echo it to standard output
    jal putline
    
    #s0 already contains string that was input and printed
    #s1 will be loaded with address of exit string
    la s1, string_exit
    jal strcmp
    
    # end program if the string entered is equal to string_exit
    beq t0, zero, exit
    
    #method 0: loading the length of string just entered from (count)
    #la t1, count       #load address of count into t2
    #lw s0, 0(t1)       #store number of chars read at (count) address
    
    #method 1: calculate the length with strlen function
    jal strlen
    
    # regardless of method used, display the length of last string
    jal putint
    jal putline
    
    j main_loop # keep restarting until exit string is entered
    
    exit:
    li a0, 0  #status
    li a7, 93 #exit
    ecall     #environment call
    
    #################################################################################
    # The following functions are independent of a specific RISC-V Operating System #
    #                                                                               #
    # intstr = convert integer into a string ready for printing                     #
    # putint = prints integer using intstr and the OS specific putstr function      #
    # strint = convert string into an integer                                       #
    #                                                                               #
    # The s0 register is used for pass data in or out of these functions            #
    # See comments above those specific functions for full details                  #
    #################################################################################
    
    # The intstr function does several things at once and is the foundation for all integer output.
    # It uses the global radix variable to know which radix or number base to use when turning the integer to a string
    # It also uses the global int_width variable to determine how many leading zeros should be used for the string
    # The purpose of this is to make numbers look good when lined up when they are printed in a list.
    # radices 2 to 36 are supported. Digits higher than 9 will be capital letters
    
    intstr:
    
    la t1, radix     #load address of radix into t1
    lb t2, 0(t1)     #load value of radix into t2
    la t1, int_width #load address of width into t1
    lb t4, 0(t1)     #load value of int_width into t4
    li t3, 1         #load current number of digits, always 1
    
    la t1, int_end   #t1=address of terminating zero in string
    addi t1, t1, -1  #t1-- to go to lowest digit
    
    digits_start:
    
    remu t0, s0, t2  #t0=remainder of the previous division
    divu s0, s0, t2  #s0=s0/t2 (divide s0 by the radix value in t2)
    
    li t5, 10        #load t5 with 10 because RISC-V does not allow constants for branches
    
    blt t0, t5, decimal_digit
    bge t0, t5, hexadecimal_digit
    
    decimal_digit:   #we go here if it is only a digit 0 to 9
    
    addi t0, t0, 0x30
    
    j save_digit
    
    hexadecimal_digit:
    addi t0, t0, -10
    addi t0, t0, 0x41
    
    save_digit:
    sb t0, 0(t1)     #store byte from t0 at address t1
    beq s0, zero, intstr_end
    addi t1, t1, -1
    addi t3, t3, 1
    j digits_start
    
    intstr_end:
    
    li t0, 0x30
    prefix_zeros:
    bge t3, t4, end_zeros
    addi t1, t1, -1
    sb t0, 0(t1) # store byte from t0 at address t1
    addi t3, t3, 1
    j prefix_zeros
    end_zeros:
    
    mv s0, t1
    
    ret
    
    # this function calls intstr to convert the s0 register into a string
    # then it uses the system specific putstr call to print the string
    # it also uses the stack to save the value of s0 and ra (return address)
    # this way, s0 is restored to the value it had before this function
    # restoring ra is required because it is modified during calls to other functions
    
    putint:
    
    addi sp, sp, -8
    sw ra, 0(sp)
    sw s0, 4(sp)
    
    jal intstr
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    addi sp, sp, 8
    
    ret
    
    # RISC-V does not allow constants for branches
    # Because of this fact, the RISC-V version of strint
    # requires a lot more code than the MIPS version
    # Whatever value I wanted to compare in the branch statement
    # was placed in the t5 register on the line before the conditional branch
    # Even though it is completely stupid, it has proven to work
    
    strint:
    
    la t1, radix     #load address of radix into t1
    lb t2, 0(t1)     #load value of radix into t2
    
    mv t1, s0        #copy string address from s0 to t1
    li s0, 0
    
    read_strint:
    lb t0, 0(t1)
    addi t1, t1, 1
    beq t0, zero, strint_end
    
    #if char is below '0' or above '9', it is outside the range of these and is not a digit
    li t5, 0x30
    blt t0, t5, not_digit
    li t5, 0x39
    blt t5, t0, not_digit
    
    #but if it is a digit, then correct and process the character
    is_digit:
    andi t0, t0, 0xF
    j process_char
    
    not_digit:
    #it isn't a digit, but it could be an alphabet character
    #which counts as 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
    li t5, 0x41
    blt t0, t5, not_upper
    li t5, 0x5A
    blt t5, t0, not_upper
    
    is_upper:
    li t5, 0x41
    sub t0, t0, t5
    addi t0, t0, 10
    j process_char
    
    not_upper:
    
    # if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    li t5, 0x61
    blt t0, t5, not_lower
    li t5, 0x7A
    blt t5, t0, not_lower
    
    is_lower:
    li t5, 0x61
    sub t0, t0, t5
    addi t0, t0, 10
    j process_char
    
    not_lower:
    
    # if we have reached this point, result invalid and end function
    # this is only reached if the byte was not a valid digit or alphabet character
    j strint_end
    
    process_char:
    
    blt t2, t0 strint_end #;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mul s0, s0, t2 # multiply s0 by the radix
    add s0, s0, t0 # add the correct value of this digit
    
    j read_strint # jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    
    ###############################################################################
    # This putstr function is my most portable function for RISC-V simulators     #
    # It calculates the length of a zero terminated string before printing it     #
    # This is the same way used in my Intel Assembly programs for DOS and Linux   #
    # This function was written to operate the same in both RARS and riscemu      #
    ###############################################################################
    
    putstr:
    
    mv t1, s0                       # t1 will be used as an index register
    
    putstr_strlen_start:
    lb t0, 0(t1)                    # load byte into t0 from address of t1
    beq t0, zero, putstr_strlen_end # if t0==0, then we jump to the end of the loop.
    addi t1, t1, 1                  # go to next byte
    j putstr_strlen_start           # jump to start of the loop
    putstr_strlen_end:              
    
    li a0, 1                        # STDOUT file number
    mv a1, s0                       # address of string 
    sub a2, t1, s0                  # length of string
    li a7, 64                       # write call number
    ecall                           # environment call
    
    ret
    
    #############################################################################
    # The next four 3 functions print things to standard output                 #
    # All of them use the putstr function above to achieve the output           #
    # They use the stack to preserve the values of the s0 and t1 registers used #
    # They also use global variables in the data section                        #
    #############################################################################
    
    #the putchar function, which is named after the C language function of the same name
    #prints the lowest byte of the s0 register as a byte or character to standard output
    
    putchar:
    
    addi sp, sp, -12
    sw ra, 0(sp)
    sw s0, 4(sp)
    sw t1, 8(sp)
    
    la t1, char
    sb s0, 0(t1)
    la s0, char
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    lw t1, 8(sp)
    addi sp, sp, 12
    
    ret
    
    # the putspace function prints a space to standard output
    
    putspace:
    
    addi sp, sp, -8
    sw ra, 0(sp)
    sw s0, 4(sp)
    
    la s0, space
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    addi sp, sp, 8
    
    ret
    
    # the putline function prints a newline to standard output
    
    putline:
    
    addi sp, sp, -8
    sw ra, 0(sp)
    sw s0, 4(sp)
    
    la s0, line
    jal putstr
    
    lw ra, 0(sp)
    lw s0, 4(sp)
    addi sp, sp, 8
    
    ret
    
    ##########################################################################
    # chastdin extension functions                                           #
    #                                                                        #
    # all functions that deal with getting strings and characters from stdin #
    ##########################################################################
    
    # the getstr function will read a string into a buffer and return it
    # in the s0 register for printing with the putstr function
    # the (count) variable will also return the number of characters
    
    getstr:
    
    li t0, 0                        # use t0 register to track chars read
    la a1, buf                      # load address of buffer for read string
    li a2, 1                        # read only 1 byte for each env call
    
    getstring_chars:
    
    li a0, 0                        # STDIN file number
    li a7, 63                       # read call number
    ecall                           # environment call
    
    # Branch to label getstring_end if a0 is less than a2
    # a0 is the return value of this environment read call
    # as will be -1 on error or 1 if successful
    # because we read 1 character at a time
    
    blt a0, a2, getstring_end
    
    # if no error, test range of the last byte
    
    lb t1, 0(a1)      #load byte at address (a1) into t1 register
    
    # if t1 is less than 0x21
    # of t1 is more than 0x7E
    # branch to function end because it is outside of print range
    
    li t2, 0x21
    blt t1, t2, getstring_end
    li t2, 0x7E
    blt t2, t1, getstring_end
    
    # otherwise, proceed to read more characters
    add t0, t0, a0    # add to read counter
    addi a1, a1, 1    # add 1 to buffer pointer register a1
    j getstring_chars # unconditional jump to getstring_chars
    
    getstring_end:
    
    la t2, count       #load address of count into t2
    sw t0, 0(t2)       #store number of chars read at (count) address
    la t2, last_char   #load address of last_char into t2
    sb t1, 0(t2)       #store last byte at (last_char) address
    sb zero, 0(a1)     #store byte zero to terminate string
    la s0, buf         #return address of buf in s0 register
    
    ret
    
    
    # Short Description of strlen:
    # The strlen function gets the length of string in s0 and returns it in s0
    # This is the same algorithm used in my putstr function but is independent of an operating system.
    
    strlen:
    
    mv t1, s0                       # t1 will be used as an index register
    
    strlen_start:
    lb t0, 0(t1)                    # load byte into t0 from address of t1
    beq t0, zero, strlen_end        # if t0==0, then we jump to the end of the loop.
    addi t1, t1, 1                  # go to next byte
    j strlen_start                  # jump to start of the loop
    strlen_end:              
    
    sub s0, t1, s0                  # return length of string in s0
    
    ret
    
    
    # Short Description of strcmp:
    # strcmp compares the string at s0 to the one at s1
    # t0 returns 0 if the strings are the same and non zero if different
    # the algorithm is simple but I will explain it for those who are confused
    
    # Long Description of strcmp:
    # each byte from each string is loaded into the t0 and t1 registers
    # the bytes are compared. if they are different, then we jump to the end
    # However, if they are the same, then we check if one of them is zero
    # if it is zero, this also jumps to the end of the function
    # If neither jump took place, then we jump to the start of the loop
    # but when the function finally ends t1 will be subtracted from t0
    # this ensures that the t0 register returns zero if the final characters are the same
    # a zero result in t0 also guarantees that both strings are equal
    
    strcmp:
    
    mv a0,s0 # move pointer s0 to t0
    mv a1,s1 # move pointer s0 to t0
    
    strcmp_start:
    
    #read a byte from each string
    lb t0, 0(a0) 
    lb t1, 0(a1) 
    #if the two bytes are not equal end comparison
    bne t0, t1, strcmp_end
    
    #but if they are equal, test for zero
    #if one of them is zero, also end the loop
    beq t0, zero, strcmp_end
    
    addi a0, a0, 1                  # go to next byte
    addi a1, a1, 1                  # go to next byte
    
    j strcmp_start
    
    strcmp_end:
    
    #subtract t1 from t0
    #if t0 is still zero after the function returns
    #it means that the strings are equal
    sub t0, t0, t1
    
    ret
    

    To use this example, it is required to run the RARS Java archive and have a Java runtime installed on whatever operating system you have.

    java -jar ~/rars.jar main.s

    However complicated the RISC-V code looks to a beginner, to me it makes more sense than Java did in my experience. I have invested significant time into learning it though because I enjoyed it right from the start.

    Anyway, what the program does is keep reading input from the keyboard until the user enters “exit” as a string.

  • Chapter 14: Word of Man versus God

    After Stacy had slapped Pastor Mark, Chad was concerned, not about Mark but about the kids who had to watch this unfortunate conflict. Nonetheless, he asked his friend John to read the final Bible verses from the book of John.

    “We have only one more section from the Bible to read today, and then I will explain the entire purpose of all these verses we have read from Matthew, Luke, and John. John, I’d like you to proceed with John chapter 1, verses 1 to 18,” said Chad.

    John 1:1-18 from NET Bible


    1 In the beginning was the Word, and the Word was with God, and the Word was fully God. 2 The Word was with God in the beginning. 3 All things were created by him, and apart from him not one thing was created that has been created. 4 In him was life, and the life was the light of mankind. 5 And the light shines on in the darkness, but the darkness has not mastered it.

    6 A man came, sent from God, whose name was John. 7 He came as a witness to testify about the light, so that everyone might believe through him. 8 He himself was not the light, but he came to testify about the light. 9 The true light, who gives light to everyone, was coming into the world. 10 He was in the world, and the world was created by him, but the world did not recognize him. 11 He came to what was his own, but his own people did not receive him. 12 But to all who have received him—those who believe in his name—he has given the right to become God’s children— 13 children not born by human parents or by human desire or a husband’s decision, but by God.

    14 Now the Word became flesh and took up residence among us. We saw his glory—the glory of the one and only, full of grace and truth, who came from the Father. 15 John testified about him and shouted out, “This one was the one about whom I said, ‘He who comes after me is greater than I am, because he existed before me.’” 16 For we have all received from his fullness one gracious gift after another. 17 For the law was given through Moses, but grace and truth came about through Jesus Christ. 18 No one has ever seen God. The only one, himself God, who is in closest fellowship with the Father, has made God known.


    After John had finished reading, Chad explained the reason he chose these verses from the gospels and how they are all connected.

    "It may surprise you, but I noticed something for the first time in my life as I was reading through the four gospels. Although they start at different places in the story of Jesus, they all have a theme about being born differently. Matthew and Luke cover the unusual story of how Jesus was born of the virgin named Mary. The specific issue of Jesus not having a biological father because God was his father instead is most unusual.

    Another unusual thing is that John the Baptist’s birth was unusual because Zechariah and Elizabeth were old and could not have children for many years. The books of Mark and John don’t talk about the birth of Jesus or John but start with what they did as adults. In any case, it is clear that John knew who Jesus was and told everyone to be ready for him. John also knew that Jesus existed before him, even though he was conceived by Elizabeth six months prior to the angel visiting Mary and telling her she was going to be the mother of Jesus.

    Someone has to read all four gospels even to get a coherent picture of everything that happened because they start in different places and tell different details. But most importantly, I realized that the Bible is not the Word of God, but that Jesus is the Word who was with God and was also fully God. The Word became flesh, which means Jesus took a human form when he was conceived by his mother Mary after the visit from the angel.

    I can see that humans were all trying to write down the story of Jesus from their perspectives. It also appears they made an error when including the genealogy of Jesus because he was not created by human parents but, just as John said, he existed before John the Baptist did and was with God in the beginning of all things before anything was created.

    But the most amazing thing to me is that the book of John explains in verses 12 and 13 that we can become children of God by the decision of God, and it doesn’t matter who your biological parents were or when they decided to have you.

    And I wanted everyone to know that I think it is important for people to read the Bible and understand these stories, not because the Bible is God’s Word, but because it covers the life and teachings of Jesus, who is the actual Word of God. I live my life always pondering the question: “What would Jesus do?” and I thought understanding this could be the most important thing I tell you since I was asked to be part of this ministry," said Chad.

  • Windows: Assembly Arithmetic Algorithms

    Assembly Arithmetic Algorithms

    32 and 64 bit Windows Edition

    Preface

    This book is the Windows edition of Assembly Arithmetic Algorithms. The first book was for 16-bit DOS programming using Assembly. The second book was for 32-bit Linux programming using the same assembly language for Intel machines. But this book is very different than those because it is for Windows users who don’t know anything about DOS or Linux.

    I suspect most people fall into this category because Windows comes preinstalled on almost any PC you would buy in a store. Although I am old enough to have experienced DOS, and autistic enough use Linux for everything since my teenage days, I am aware that most people will never both trying other operating systems.

    Although I use Linux for most things, I had to buy a laptop with Windows on it to use specific software required by Full Sail University when I was an online student. Since I have it, I decided I might as well try out some assembly language on it and learn how it works so I can pass the knowledge on to other people who are not ready to leave Windows but ARE ready to try learning assembly language.

    At the time of this writing, most Windows systems use the x86-64 Intel architecture which can run 32-bit or 64-bit code. Because of this, I have decided to include code samples for both modes and explain the differences between them.

    I highly suspect people don’t even know what it means for something to be 32 or 64 bits. Understanding this requires knowing that a bit is a BInary digiT and explaining the binary numeral system.

    If you are someone who likes to learn the math behind how computers work, but still cannot or don’t want to switch to Linux, this book will act as a bridge to test the waters of Assembly language and the control it offers you as a programmer. Programming in Assembly language is not a task for complete computer programming beginners. I do recommend having some C or C++ experience before jumping into this book, but I have tried my best not to assume knowledge of any prior languages when writing my explanations.

    Introduction

    In this short book, I plan to teach you the basics of Assembly language for Intel Central Processing Units and you will learn how to make small programs that run on the Windows operating system. Theoretically, these programs should be compatible with Windows version 7, 8, 10, and 11. My only OS to test with is Windows 11 which is on the laptop I am writing this on.

    There is one myth that I need to break before I can teach you how to get started programming on Windows. This book will not use an IDE (Interactive Development Environment). I consider IDEs to be evil because they hide the details of how things work. You WILL be entering commands at a terminal which is called the “Command Prompt” or the executable file at:

    "C:\WINDOWS\system32\cmd.exe"
    

    This program is the modern descendant of the original command.com from DOS. Windows may no longer be compatible with DOS but MS-DOS was a Microsoft product and Windows originally started as a program that can in DOS. Therefore, common commands such as “dir”, "mkdir, “copy”, “del”, “rename”, “type” and “exit” still work the same as they did on DOS.

    Because the Assembler I will be using is FASM, which includes an IDE, you don’t technically have to use the command line the way I will teach you, but you are cheating yourself if you don’t become comfortable with basic commands in a terminal/console.

    There is a common lie that Windows is point and click whereas Linux requires running commands at a terminal. Technically neither of these are true. The actual truth is that a PROGRAMMER must know how to use the command line on ANY operating system to achieve full power in controlling their own operating system or the building of their own programs.

    But don’t worry, you don’t need to have been born in 1987 or grow up reading MS-DOS manuals to learn these commands. I will give you all the commands you need and you will still be pointing and clicking your way through the Windows file explorer a lot when going to your specific folder or directory (these two words mean the exact same thing in this context).

    The best part is that you can use any text editor you like. However, I recommend either the default Notepad so you don’t have to install an extra tool, or perhaps installing Notepad++ to benefit from syntax highlighting.

    Chapter 1: The First Program

    Before you can write Windows programs in Assembly language, you will need the FASM Assembler. Be sure to download the Windows version from here:

    https://flatassembler.net/

    The file will probably be named something similar to “fasmw17335.zip”

    You will need to extract the files in the zip archive and place them somewhere convenient for you. I placed them in my root C drive directory.

    C:\fasm
    

    Here is an easy way to test and see if the files are correctly located.

    Using the command “dir c:\fasm” should return the results of the following files:

     Volume in drive C is Windows-SSD
     Volume Serial Number is D43F-B788
    
     Directory of c:\fasm
    
    08/20/2026  04:00 AM    <DIR>          .
    08/20/2026  04:00 AM    <DIR>          EXAMPLES
    08/20/2026  04:00 AM           118,272 FASM.EXE
    08/20/2026  04:00 AM           529,038 FASM.PDF
    08/20/2026  04:00 AM           161,280 FASMW.EXE
    08/20/2026  04:00 AM    <DIR>          INCLUDE
    08/20/2026  04:00 AM             1,820 LICENSE.TXT
    08/20/2026  04:00 AM    <DIR>          SOURCE
    08/20/2026  04:00 AM    <DIR>          TOOLS
    08/20/2026  04:00 AM            17,640 WHATSNEW.TXT
                   5 File(s)        828,050 bytes
                   5 Dir(s)   5,106,724,864 bytes free
    

    For this book, we will mostly be concerned with FASM.EXE and the INCLUDE directory. I also recommend reading the FASM.PDF file because it is where I learned how to use the FASM Assembler.

    The next step is to (temporarily) set your path variables so that you can assemble your source files no matter which folder/directory you happen to be in. Once you have chosen you location to begin coding, you will want to run two commands to set the “path” and “include” variables. I usually place them in a short batch file named fasmpath.bat for convenience.

    fasmpath.bat

    set path=C:\fasm
    set include=C:\fasm\INCLUDE
    

    Whether you type those two commands or just place them in a batch file and enter “fasmpath” to execute the script, either way, your paths will be set until you close your console/terminal window. Then all changes will revert to whatever your system defaults were.

    There is a GUI setting to permanently change the variables but I DO NOT recommend this because making a mistake can make your system completely unusable. I will explain more about this later.

    Anyway, once you have a source file of a valid program, you can assemble it like this.

    fasm main.asm
    

    The file does not have to specifically be named “main.asm”. It could just as well be “fartbutt.asm” or even “count-dracula.txt”. You can choose whatever seems like a good name to you and adjust the commands accordingly.

    But in this example, a file named “main.exe” will be created and so you just type:

    main
    

    To run it like you would any other Windows program.

    To get started, I will provide the first example program that can be assembled and run under the Windows operating system. This was tested on my laptop with Windows 11 but should theoretically work on older versions as well as long as you followed my instructions so far.

    Behold,the “Hello World” source file for a Windows console program.

    Hello World for 32-bit Windows

    format PE console
    entry main
    
    include 'win32a.inc' ;include Windows 32-bit macros
    
    main:
    
    mov eax,main_string
    call putstring
    
    
    push 0             ;exit code for operating system
    call [ExitProcess] ;Exit the process with code 0
    
    main_string db 'Hello World',0x0D,0x0A,0
    
    putstring:         ;print string pointed to by eax register
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax             ;copy eax to ebx to 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
    
    ;Windows 32-bit WriteFile system call
    
    push 0               ;lpOverlapped = NULL
    push 0               ;lpNumberOfBytesWritten = NULL
    push ebx             ;nNumberOfBytesToWrite = ebx
    push eax             ;lpBuffer = address of string to write
    push -11             ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle]  ;Get Standard Handle for -11
    push eax             ;hFile = eax (returned from GetStdHandle)
    call [WriteFile]
    
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    section '.idata' import data readable writeable
    
    library kernel32, 'KERNEL32.DLL'
    
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess'
    

    You might wonder why it took nearly 70 lines to print a simple message. That is because unlike in C, Pascal, or BASIC, there are no printf, write, or print statements. The included putstring function is one I had to write and is not normally available unless someone like me builds it.

    It does however make use of the WriteFile Windows API call. My function calculates the length of the string by finding where the zero is and then subtracting the address of the beginning from the end. Then once the length is known, the arguments to the function are pushed to the stack in the order that Microsoft wanted them to be before calling the WriteFile function.

    I have no idea where the source code for this API call is because it is proprietary information and Windows is not an Open Source operating system. However, using a Windows API call like this is an extremely fast operation and it is the start of everything else this book will cover.

    However, this is only the 32 bit version of the program. A 64 bit version looks more like the following.

    Hello World for 64-bit Windows

    format PE64 console
    entry main
    
    include 'win64a.inc' ;include Windows 64-bit macros
    
    main:
    
    mov rax,main_string
    call putstring
    
    sub rsp,40         ;align stack (required in windows 64-bit)
    mov rcx,0          ;exit code for operating system
    call [ExitProcess] ;Exit the process with code 0
    
    main_string db 'Hello World',0x0D,0x0A,0
    
    putstring:         ;print string pointed to by rax register
    
    push rax
    push rbx
    push rcx
    push rdx
    
    mov rbx,rax             ;copy eax to ebx to 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 [rbx],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 rbx
    jmp putstring_strlen_start
    
    putstring_strlen_end:
    sub rbx,rax ;subtract start pointer from current pointer to get length of string
    
    ;Windows 64-bit WriteFile system call
    sub rsp,40           ;align stack for Win64 API calls
    mov qword [rsp+32],0 ;lpOverlapped = NULL
    mov r9,0             ;lpNumberOfBytesWritten = NULL
    mov r8,rbx           ;nNumberOfBytesToWrite = rbx
    mov rdx,rax          ;lpBuffer = address of string to write
    mov rcx, -11         ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle]  ;Get Standard Handle for -11
    mov rcx,rax          ;hFile = rax (returned from GetStdHandle)
    call [WriteFile]
    add rsp,40           ;restore stack now that WinAPI calls are done
    
    pop rdx
    pop rcx
    pop rbx
    pop rax
    
    ret
    
    section '.idata' import data readable writeable
    
    library kernel32, 'KERNEL32.DLL'
    
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess'
    

    Because in both cases, the programs are identical, you might wonder which is better or the correct thing to use. Actually, they are exactly the same but using a different calling convention and register size.

    You may also notice that at the bottom of the source files there is an “idata” section which includes data from the Windows kernel which is KERNEL32.DLL. Regardless of whether your code using 32 or 64 bit registers, the exact same functions from the kernel are being dynamically linked and loaded so that your program can do basic tasks.

    First 3 Windows API calls

    These three functions are required for even a simple Hello World program like both of those above.

    • GetStdHandle
    • WriteFile
    • ExitProcess

    The documentation for these functions can be found on Microsoft’s website but it is not very helpful for Assembly because it is written for C and C++ programming. But don’t worry, I will teach you how to translate these C functions into something usable for Assembly programming. Therefore, I suggest you look at these links because they have been my primary sources.

    https://learn.microsoft.com/en-us/windows/console/getstdhandle

    https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile

    https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess

    Despite the fact that most of the web pages don’t tell us what we need for assembly, some of it is helpful. For example, the C function prototype the GetStdHandle is below.

    GetStdHandle Syntax

    HANDLE WINAPI GetStdHandle(
      _In_ DWORD nStdHandle
    );
    

    This tells us that the function has one parameter called “nStdHandle”. The values we need for it are in the table below.

    For example the following table for the 3 standard handles on the GetStdHandle page is copied below.

    GetStdHandle function table

    Value Meaning
    -10 STD_INPUT_HANDLE
    -11 STD_OUTPUT_HANDLE
    -12 STD_ERROR_HANDLE

    These three “handles” are just the Windows version of what would have been called a “file descriptor” in Linux. They actual handle numbers returned from the GetStdHandle function may be different from the numbers passed as the nStdHandle. However, in all programs, the standard input, standard output, and standard error handles are supposed to exist.

    For right now, we need to only consider the standard output handle because we want to display something on the screen. Because negative 11 is how the standard output handle is obtained, that is why the 32 bit putstring has these 3 lines

    push -11            ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle] ;use the above handle
    push eax            ;eax is return value of previous function
    

    and the 64 bit putstring has these lines

    mov rcx, -11        ; STD_OUTPUT_HANDLE
    call [GetStdHandle] ; Get Standard Output Handle
    mov rcx,rax         ; copy handle to ecx
    

    In both cases, the argument -11 is passed to the GetStdHandle function. In 32 bit mode, it is pushed to the stack before the call and in 64 bit mode is is loaded into the rcx register before the call.

    This may seem silly but it highlights the importance of a calling convention. If you read my DOS or Linux editions of Assembly Arithmetic Algorithms, you will see that they use a purely register based convention for all system calls.

    Windows is harder because it uses a hybrid approach of sometimes using registers for function arguments and other times using specific locations on the stack relative to the stack pointer.

    But you are probably asking at this point: “What is a stack?”, “What is a register?”, and “What is a bit?”.

    I will attempt to answer all these questions in the next chapter. For now, I still need to finish explaining the WriteFile and ExitProcess calls.

    WriteFile Syntax

    BOOL WriteFile(
      [in]                HANDLE       hFile,
      [in]                LPCVOID      lpBuffer,
      [in]                DWORD        nNumberOfBytesToWrite,
      [out, optional]     LPDWORD      lpNumberOfBytesWritten,
      [in, out, optional] LPOVERLAPPED lpOverlapped
    );
    

    As you can see above, the WriteFile function has 5 parameters. 2 of these are optional and have been marked as NULL in my Hello World examples above. This leaves us with only 3 variables as our parameters, which are sometimes called arguments.

    WriteFile parameters

    Variable Meaning
    hfile destination file or device
    lpBuffer address of byte string
    nNumberOfBytesToWrite write this many bytes

    The WriteFile function looks complicated mostly because of the optional arguments used in it. Because the Windows API expects all these arguments to be present on the stack (32-bit mode) or a combination of stack and registers (64-bit mode), extra code is wasted every time we make a call to WriteFile.

    It is precisely for this reason that the Hello World examples for this chapter called the WriteFile function inside a function named “putstring”. The idea behind this is to have to only call this function inside another function that automatically calculates how many bytes exist before the zero byte, then gets the standard output handle with GetStdHandle, and then writes exactly that many bytes from the address pointed to by the eax or rax register before the putstring function was called.

    ExitProcess Syntax

    VOID ExitProcess(
      [in] UINT uExitCode
    );
    

    The ExitProcess function is the easiest of all to use. It ends the program and therefore only needs to be called at the end. But there is a special trick it does. You pass the exit code to it that you want. This can literally be any number you like best, but the tradition is to pass 0 to say that there were zero problems in this program.

    In 32-bit mode you need to only push one 32-bit number onto the stack before you call it. In 64-bit mode, you load the rcx register with the number you prefer. The best part is that you can run the following command right after the program finishes to see the error code you used.

    echo %errorlevel%
    

    Those 3 Windows API calls are all you actually need to build most programs. There are more that will be covered later, but you will need to understand some terminology that I will cover in Chapter 2 before we can proceed to more advanced things like getting user input and printing numbers.

    Chapter 2: Assembly Terminology

    Register

    A variable with a fixed name that is always available to use. These come in different sizes such as “EAX” for 32-bit and “RAX” for 64-bit.

    The General Purpose Registers

    There are 8 general purpose registers that exist on 32-bit Intel machines. Their names are the same as those used in 16-bit Intel machines except with the letter ‘E’ prefixed. Their names are acronyms that mean the following.

    Register Meaning
    EAX Accumulator Register
    EBX Base Register
    ECX Count Register
    EDX Data Register
    ESI Source Index
    EDI Destination index
    EBP Base Pointer
    ESP Stack Pointer

    In 64 bit mode, all of these are prefixed with an ‘R’ and are 64 bits in size. However, the 32 bit versions above still exist as the lower half of the 64 bit registers.

    But in 64-bit mode, there are also 8 more registers which are named R8 to R15. This gives you plenty more registers to work with which in my opinion is the primary advantage of 64-bit Assembly programming. More registers is generally good because you might be doing something complicated and use them to store variables instead of saving them to memory. Because registers are faster to access than RAM, the faster programs are those that use the most registers and the least RAM.

    With all that being said, I only use the new registers R8 and R9 in this book because they are the third and fourth arguments in the 64-bit calling convention of the Windows API. Most of the time I prefer to stick with the Accumulator Register, Base Register, Count Register, and Data Register. For this reason, there is a convention of using them in a specific way in the DOS, Linux, and Windows versions of Assembly Arithmetic Algorithms. Since this is the Windows book, you will see a lot of use of the RCX, RDX, R8, and R9 registers for the 64-bit sample programs.

    Bit

    A bit is a BInary digiT. It is a number that can be 0 or 1. These are the only two numbers a bit can be but by combining multiple bits as a group, any number can be represented. Just as the decimal systems humans use only uses digits 0,1,2,3,4,5,6,7,8,9 but can represent any possible number, binary can also represent any number once you learn how it works. Explaining the Binary Numeral System will be a central feature of this book because no programmer can be successful without it.

    The Binary Numeral System is essential because all computers define their data types in terms of how many bits they are. A 32 bit number can access up to 4294967296 bytes (4 Gigabytes) of memory at a time. A 64 bit number can access far more memory than you will probably ever see in a computer.

    This math is based on powers of two. Two to the power of 64 is 18446744073709551616 because it is what happens if you keep multiplying two by itself 64 times. This number is so large that I highly doubt humanity will have need of machines processing larger than 64-bits at a time.

    Stack

    A stack can be many things. It can be a stack of plates, a stack of pancakes on top of plates that you are going to eat, or it can be a stack of numbers where we temporarily place numbers that are in registers and free them up to be used for other tasks. Assembly programming requires basic understanding of the stack, but Windows specifically requires using the stack in the way Microsoft wants you do. Admittedly this is less fun and more restrictive compared to DOS or Linux, but there are clever ways to break the convention.

    For example, the putstring function from chapter 1 is an example of a user written function that uses the Windows API so that I don’t have to manually call a Windows API function every time I need to print a string.

    It gets easier!

    This is the point where most people will give up. There are so many terms to learn and it takes a lot of information to even get a small program working to display a message like “Hello World”.

    But despite being difficult to get started, it gets easier as you proceed. It is like playing a new game which you don’t know the controls for or where your character is supposed to go next. Yes Assembly is hard, but not as hard as playing the Legend of Zelda: Ocarina of Time. Seriously, that game way more stressful than any programming language I have have used (except for Rust).

    A funny example I suppose, but programming really is like playing a game where you get to create your own rules. Perhaps Minecraft would be an even better example because you start with nothing and slowly create your own tools to progress faster.

    I can tell you one thing, when I started playing Minecraft, I knew nothing. Back in those early days, I had to look up the recipes in order to arrange my sticks and planks on a grid to make a sword, axe, pickaxe, or shovel. They didn’t have the recipes built into the interface like they do now.

    Assembly programming is actually a lot like Minecraft or Terraria because you start the game with nothing and have to slowly build your tools to make something useful. I started Assembly in 2024 and have already built a series of tools I personally use on both DOS and Linux operating systems. Through the course of this book, I will be slowly showing you how I can port everything in the Linux version of Assembly Arithmetic Algorithms to Windows.

    Chapter 3: Printing Integers

    In this chapter, I will be showing two identical programs much like I did in chapter 1 with the examples of using the putstring function. However, I will be introducing new functions that all depend on the use of putstring but are used as a system for printing integers.

    The first of of these new functions is intstr, which converts the number in the Accumulator Register into a string.

    The second is putstring which saves(pushes) several registers to the stack, calls intstr and then putstring to print the string just created. Finally, the registers are restored(popped) to their original state before putint was called.

    The basic idea is that we can print what a register contains without modifying it permanently and messing up the main loop in the program. Both of these programs contain a loop of a register starting as 1 and then adding itself to itself. Eventually this will reach an “overflow” and result in 0. This sounds strange but is a feature of fix-sized integers in computers.

    Read each program and the output that follows it. It is okay if you don’t understand them at first. The goal is to get something working and then explain why it works as it does later.

    putint for 32-bit Windows

    format PE console
    entry main
    
    include 'win32a.inc'    ;includes standard Windows 32-bit definitions and macros
    
    main:
    
    mov eax,1
    loop0:
    
    mov dword[radix],2      ;set radix to binary
    mov dword[int_width],32
    call putint
    call putspace
    mov dword[radix],10     ;set radix to decimal (what humans read)
    mov dword[int_width],10
    call putint
    call putline            ;print newline before the next loop
    
    add eax,eax
    cmp eax,0
    jnz loop0
    
    
    push 0             ;exit code for operating system
    call [ExitProcess] ;Exit the process with code 0
    
    putstring:         ;print string pointed to by eax register
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax             ;copy eax to ebx to 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
    
    ;Windows 32-bit WriteFile system call
    
    push 0               ;lpOverlapped = NULL
    push 0               ;lpNumberOfBytesWritten = NULL
    push ebx             ;nNumberOfBytesToWrite = ebx
    push eax             ;lpBuffer = address of string to write
    push -11             ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle]  ;Get Standard Handle for -11
    push eax             ;hFile = eax (returned from GetStdHandle)
    call [WriteFile]
    
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    ; 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 '?' ;reserve bytes for characters string for 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 ;default width of integers. Extra zeros prefixed if more than 1
    
    ;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
    mov ecx,1
    
    digits_start:
    
    mov edx,0;
    div dword [radix]
    cmp edx,10
    jb decimal_digit
    jnb 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 ;point eax register to this string for putstring
    
    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
    
    ;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 ;a string containing only a space
    
    putspace:
    push eax
    mov eax,space
    call putstring
    pop eax
    ret
    
    line db 0x0D,0x0A,0 ;a string containing only a newline
    
    ;the next function which pushes eax to the stack
    ;moves the address of the line string and prints it with putstring
    ;then it pops the original value of eax back from the stack before the function returns
    ;this allows me to print a newline anywhere in the code without a single register changing
    
    putline:
    push eax
    mov eax,line
    call putstring
    pop eax
    ret
    
    section '.idata' import data readable writeable
    
    library kernel32, 'KERNEL32.DLL'
    
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess'
    

    Output of 32-bit putint program

    00000000000000000000000000000001 0000000001
    00000000000000000000000000000010 0000000002
    00000000000000000000000000000100 0000000004
    00000000000000000000000000001000 0000000008
    00000000000000000000000000010000 0000000016
    00000000000000000000000000100000 0000000032
    00000000000000000000000001000000 0000000064
    00000000000000000000000010000000 0000000128
    00000000000000000000000100000000 0000000256
    00000000000000000000001000000000 0000000512
    00000000000000000000010000000000 0000001024
    00000000000000000000100000000000 0000002048
    00000000000000000001000000000000 0000004096
    00000000000000000010000000000000 0000008192
    00000000000000000100000000000000 0000016384
    00000000000000001000000000000000 0000032768
    00000000000000010000000000000000 0000065536
    00000000000000100000000000000000 0000131072
    00000000000001000000000000000000 0000262144
    00000000000010000000000000000000 0000524288
    00000000000100000000000000000000 0001048576
    00000000001000000000000000000000 0002097152
    00000000010000000000000000000000 0004194304
    00000000100000000000000000000000 0008388608
    00000001000000000000000000000000 0016777216
    00000010000000000000000000000000 0033554432
    00000100000000000000000000000000 0067108864
    00001000000000000000000000000000 0134217728
    00010000000000000000000000000000 0268435456
    00100000000000000000000000000000 0536870912
    01000000000000000000000000000000 1073741824
    10000000000000000000000000000000 2147483648
    

    putint for 64-bit Windows

    format PE64 console
    entry main
    
    include 'win64a.inc'    ;includes standard Windows 64-bit definitions and macros
    
    main:
    
    mov rax,1
    loop0:
    
    mov qword[radix],2      ;set radix to binary
    mov qword[int_width],64
    call putint
    call putspace
    mov qword[radix],10     ;set radix to decimal (what humans read)
    mov qword[int_width],19
    call putint
    call putline            ;print newline before the next loop
    
    add rax,rax
    cmp rax,0
    jnz loop0
    
    sub rsp,40         ;align stack (required in windows 64-bit)
    mov rcx,0          ;exit code for operating system
    call [ExitProcess] ;Exit the process with code 0
    
    putstring:         ;print string pointed to by rax register
    
    push rax
    push rbx
    push rcx
    push rdx
    
    mov rbx,rax             ;copy eax to ebx to 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 [rbx],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 rbx
    jmp putstring_strlen_start
    
    putstring_strlen_end:
    sub rbx,rax ;subtract start pointer from current pointer to get length of string
    
    ;Windows 64-bit WriteFile system call
    sub rsp,40           ;align stack for Win64 API calls
    mov qword [rsp+32],0 ;lpOverlapped = NULL
    mov r9,0             ;lpNumberOfBytesWritten = NULL
    mov r8,rbx           ;nNumberOfBytesToWrite = rbx
    mov rdx,rax          ;lpBuffer = address of string to write
    mov rcx, -11         ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle]  ;Get Standard Handle for -11
    mov rcx,rax          ;hFile = rax (returned from GetStdHandle)
    call [WriteFile]
    add rsp,40           ;restore stack now that WinAPI calls are done
    
    pop rdx
    pop rcx
    pop rbx
    pop rax
    
    ret
    
    ; 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 64 dup '?' ;reserve bytes for characters string for 64-bit binary integer
    
    int_string_end db 0 ;zero byte terminator for the integer string
    
    radix dq 2     ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dq 8 ;default width of integers. Extra zeros prefixed if more than 1
    
    ;this function creates a string of the integer in rax
    ;it uses the above radix variable to determine base from 2 to 36
    ;it then loads rax with the address of the string
    ;this means that it can be used with the putstring function
    
    intstr:
    
    mov rbx,int_string_end-1 ;find address of lowest digit
    mov rcx,1
    
    digits_start:
    
    mov rdx,0;
    div qword [radix]
    cmp rdx,10
    jb decimal_digit
    jnb hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add rdx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub rdx,10
    add rdx,'A'
    
    save_digit:
    
    mov [rbx],dl
    cmp rax,0
    jz intstr_end
    dec rbx
    inc rcx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp rcx,[int_width]
    jnb end_zeros
    dec rbx
    mov [rbx],byte '0'
    inc rcx
    jmp prefix_zeros
    end_zeros:
    
    mov rax,rbx ;point eax register to this string for putstring
    
    ret
    
    ;function to print string form of whatever integer is in rax
    ;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 rax
    push rbx
    push rcx
    push rdx
    
    call intstr
    call putstring
    
    pop rdx
    pop rcx
    pop rbx
    pop rax
    
    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 ;a string containing only a space
    
    putspace:
    push rax
    mov rax,space
    call putstring
    pop rax
    ret
    
    line db 0x0D,0x0A,0 ;a string containing only a newline
    
    ;the next function which pushes rax to the stack
    ;moves the address of the line string and prints it with putstring
    ;then it pops the original value of rax back from the stack before the function returns
    ;this allows me to print a newline anywhere in the code without a single register changing
    
    putline:
    push rax
    mov rax,line
    call putstring
    pop rax
    ret
    
    section '.idata' import data readable writeable
    
    library kernel32, 'KERNEL32.DLL'
    
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess'
    

    Output of 64-bit putint program

    0000000000000000000000000000000000000000000000000000000000000001 0000000000000000001
    0000000000000000000000000000000000000000000000000000000000000010 0000000000000000002
    0000000000000000000000000000000000000000000000000000000000000100 0000000000000000004
    0000000000000000000000000000000000000000000000000000000000001000 0000000000000000008
    0000000000000000000000000000000000000000000000000000000000010000 0000000000000000016
    0000000000000000000000000000000000000000000000000000000000100000 0000000000000000032
    0000000000000000000000000000000000000000000000000000000001000000 0000000000000000064
    0000000000000000000000000000000000000000000000000000000010000000 0000000000000000128
    0000000000000000000000000000000000000000000000000000000100000000 0000000000000000256
    0000000000000000000000000000000000000000000000000000001000000000 0000000000000000512
    0000000000000000000000000000000000000000000000000000010000000000 0000000000000001024
    0000000000000000000000000000000000000000000000000000100000000000 0000000000000002048
    0000000000000000000000000000000000000000000000000001000000000000 0000000000000004096
    0000000000000000000000000000000000000000000000000010000000000000 0000000000000008192
    0000000000000000000000000000000000000000000000000100000000000000 0000000000000016384
    0000000000000000000000000000000000000000000000001000000000000000 0000000000000032768
    0000000000000000000000000000000000000000000000010000000000000000 0000000000000065536
    0000000000000000000000000000000000000000000000100000000000000000 0000000000000131072
    0000000000000000000000000000000000000000000001000000000000000000 0000000000000262144
    0000000000000000000000000000000000000000000010000000000000000000 0000000000000524288
    0000000000000000000000000000000000000000000100000000000000000000 0000000000001048576
    0000000000000000000000000000000000000000001000000000000000000000 0000000000002097152
    0000000000000000000000000000000000000000010000000000000000000000 0000000000004194304
    0000000000000000000000000000000000000000100000000000000000000000 0000000000008388608
    0000000000000000000000000000000000000001000000000000000000000000 0000000000016777216
    0000000000000000000000000000000000000010000000000000000000000000 0000000000033554432
    0000000000000000000000000000000000000100000000000000000000000000 0000000000067108864
    0000000000000000000000000000000000001000000000000000000000000000 0000000000134217728
    0000000000000000000000000000000000010000000000000000000000000000 0000000000268435456
    0000000000000000000000000000000000100000000000000000000000000000 0000000000536870912
    0000000000000000000000000000000001000000000000000000000000000000 0000000001073741824
    0000000000000000000000000000000010000000000000000000000000000000 0000000002147483648
    0000000000000000000000000000000100000000000000000000000000000000 0000000004294967296
    0000000000000000000000000000001000000000000000000000000000000000 0000000008589934592
    0000000000000000000000000000010000000000000000000000000000000000 0000000017179869184
    0000000000000000000000000000100000000000000000000000000000000000 0000000034359738368
    0000000000000000000000000001000000000000000000000000000000000000 0000000068719476736
    0000000000000000000000000010000000000000000000000000000000000000 0000000137438953472
    0000000000000000000000000100000000000000000000000000000000000000 0000000274877906944
    0000000000000000000000001000000000000000000000000000000000000000 0000000549755813888
    0000000000000000000000010000000000000000000000000000000000000000 0000001099511627776
    0000000000000000000000100000000000000000000000000000000000000000 0000002199023255552
    0000000000000000000001000000000000000000000000000000000000000000 0000004398046511104
    0000000000000000000010000000000000000000000000000000000000000000 0000008796093022208
    0000000000000000000100000000000000000000000000000000000000000000 0000017592186044416
    0000000000000000001000000000000000000000000000000000000000000000 0000035184372088832
    0000000000000000010000000000000000000000000000000000000000000000 0000070368744177664
    0000000000000000100000000000000000000000000000000000000000000000 0000140737488355328
    0000000000000001000000000000000000000000000000000000000000000000 0000281474976710656
    0000000000000010000000000000000000000000000000000000000000000000 0000562949953421312
    0000000000000100000000000000000000000000000000000000000000000000 0001125899906842624
    0000000000001000000000000000000000000000000000000000000000000000 0002251799813685248
    0000000000010000000000000000000000000000000000000000000000000000 0004503599627370496
    0000000000100000000000000000000000000000000000000000000000000000 0009007199254740992
    0000000001000000000000000000000000000000000000000000000000000000 0018014398509481984
    0000000010000000000000000000000000000000000000000000000000000000 0036028797018963968
    0000000100000000000000000000000000000000000000000000000000000000 0072057594037927936
    0000001000000000000000000000000000000000000000000000000000000000 0144115188075855872
    0000010000000000000000000000000000000000000000000000000000000000 0288230376151711744
    0000100000000000000000000000000000000000000000000000000000000000 0576460752303423488
    0001000000000000000000000000000000000000000000000000000000000000 1152921504606846976
    0010000000000000000000000000000000000000000000000000000000000000 2305843009213693952
    0100000000000000000000000000000000000000000000000000000000000000 4611686018427387904
    1000000000000000000000000000000000000000000000000000000000000000 9223372036854775808
    

    You may have noticed in the source that I included putspace and putline functions. These operations are so common when printing lists of numbers that they deserved special functions so that the eax or rax register did not need to be pushed and popped during the main function of the program.

    The output of the program in both cases is the same number printed twice in two different bases. The first base is binary (radix two) which is how computers see numbers (0 or 1). The second base is decimal (radix ten) which is the number system humans teach children in school.

    Perhaps the hardest barrier to entry when learning computer programming is that you have to unlearn the trash that your school teachers taught you when it comes to math. Computers work only in binary for representing numbers.

    The intstr function I wrote is an algorithm to generate a string that can use any radix from 2 to 36. It is designed so that humans can read something they recognize but still get an idea of how the numbers look to a computer.

    The program prints all the bits in binary followed by a space, the decimal version of the same thing, and then a newline. Keep in mind that it may not look perfect in the book you are reading right now (due to different formatting of ebook settings and paperback sizes), but if you assemble and run the program on your computer, it will look as intended for sure.

    Dependency Chain

    Although this is still relatively early in the book, we already have a dependency chain of functions.

    putstring depends on the Windows API WriteFile function and WriteFile depends on GetStdHandle to grab the standard output handle for displaying things to the screen.

    intstr does not directly require anything but the string it produces is designed to be used with putstring. putint calls both intstr and putstring and therefore won’t work if either of these functions are missing.

    Sometimes in software development, you can run into what is called a “Dependency Hell” because sometimes the maker of one library will change the number of parameters in a function or change the order of them. Although this is a real danger in larger projects, you can take comfort in knowing that problems rarely happen in console programs because we are using the Windows kernel which has these functions standardized.

    If even one function in the Windows kernel was changed by Microsoft, then all things on the operating system would stop working. Although theoretically it could happen, this is unlikely because Microsoft would lose even more business if everything stopped working entirely.

    But regardless of what may happen, all operating systems are guaranteed to have some functions that don’t change for some time because it is bad for business if all the software breaks.

    There will probably be a day when Windows stops existing, but even if it does, don’t worry because there is always Linux to switch to as a superior alternative! I also already wrote an Assembly book for Linux by the way.

    Chapter 4: To Be Written

  • 64-bit Windows API test suite for chastelib

    I published a new gist on github of my working test suite for my chastelib set of functions using the 64-bit API. The putstring function finally works now that I fixed the stack alignment issue I was having because I am new to 64-bit Windows programming. The other functions like intstr and strint were copied from the Linux version because they are independent of which operating system is being used as long as it is a 64-bit capable Intel machine.

    test suite for 64 bit Windows Assembly version of chastelib

    The original 32-bit Windows test suite is available as well. It behaves in the exact same way as the 64-bit but uses the 32 bit registers and stack based calling convention for Windows. This was easier because it doesn’t required the weird stack alignment that 64-bit does.

    test suite for 32 bit Windows Assembly version of chastelib

    There is still a lot I don’t understand about Windows API programming but I am keeping it simple and sticking only to making console based programs. There may eventually be a book that I write on this subject.

  • Chad Chapter 13: Spirit is Thicker than Blood

    After the children and their parents ate watermelon, Chad wanted to explain the purpose behind the Bible verses he and his friends were sharing.

    “In the first chapter of Luke, we discovered that the mother of Jesus was a virgin named Mary. In case anyone is still wondering about the virgin part. It is clear that Mary became pregnant by the power of the holy Spirit instead of through the regular way all of you were created by your parents.”

    “But how did our parents create us?” asked Simon.

    “I wish I could tell you, but if I were to try, your parents would probably get angry at me for telling you something they should have told you themselves already. For now, just keep in mind that you are biologically related to your parents and look like a combination of them because you received half your DNA from your mother and father,” said Chad.

    “What is a DNA?” asked Sally.

    "Honestly, Sally, I ain’t got a clue. It is one of those fancy things biologists talk about, but I don’t know how it works. But more importantly, I would like Matthew to begin reading from the first chapter of the book of Matthew so we can compare it to the story in Luke.

    Matt then began to read from the book of Matthew:

    Matthew Chapter 1 from NET Bible


    Matthew 1:1 This is the record of the genealogy of Jesus Christ, the son of David, the son of Abraham.

    2 Abraham was the father of Isaac, Isaac the father of Jacob, Jacob the father of Judah and his brothers, 3 Judah the father of Perez and Zerah (by Tamar), Perez the father of Hezron, Hezron the father of Ram, 4 Ram the father of Amminadab, Amminadab the father of Nahshon, Nahshon the father of Salmon, 5 Salmon the father of Boaz (by Rahab), Boaz the father of Obed (by Ruth), Obed the father of Jesse, 6 and Jesse the father of David the king.

    David was the father of Solomon (by the wife of Uriah), 7 Solomon the father of Rehoboam, Rehoboam the father of Abijah, Abijah the father of Asa, 8 Asa the father of Jehoshaphat, Jehoshaphat the father of Joram, Joram the father of Uzziah, 9 Uzziah the father of Jotham, Jotham the father of Ahaz, Ahaz the father of Hezekiah, 10 Hezekiah the father of Manasseh, Manasseh the father of Amon, Amon the father of Josiah, 11 and Josiah the father of Jeconiah and his brothers, at the time of the deportation to Babylon.

    12 After the deportation to Babylon, Jeconiah became the father of Shealtiel, Shealtiel the father of Zerubbabel, 13 Zerubbabel the father of Abiud, Abiud the father of Eliakim, Eliakim the father of Azor, 14 Azor the father of Zadok, Zadok the father of Achim, Achim the father of Eliud, 15 Eliud the father of Eleazar, Eleazar the father of Matthan, Matthan the father of Jacob, 16 and Jacob the father of Joseph, the husband of Mary, by whom Jesus was born, who is called Christ.

    17 So all the generations from Abraham to David are fourteen generations, and from David to the deportation to Babylon, fourteen generations, and from the deportation to Babylon to Christ, fourteen generations.

    18 Now the birth of Jesus Christ happened this way. While his mother Mary was engaged to Joseph, but before they came together, she was found to be pregnant through the Holy Spirit. 19 Because Joseph, her husband to be, was a righteous man, and because he did not want to disgrace her, he intended to divorce her privately. 20 When he had contemplated this, an angel of the Lord appeared to him in a dream and said, “Joseph, son of David, do not be afraid to take Mary as your wife because the child conceived in her is from the Holy Spirit. 21 She will give birth to a son and you will name him Jesus because he will save his people from their sins.” 22 This all happened so that what was spoken by the Lord through the prophet would be fulfilled: 23 “Look! The virgin will conceive and give birth to a son, and they will name him Emmanuel,” which means “God with us.” 24 When Joseph awoke from sleep he did what the angel of the Lord told him. He took his wife, 25 but did not have marital relations with her until she gave birth to a son, whom he named Jesus.


    Before Matt had finished reading from chapter 1 of the book of Matthew, some children were so bored they fell asleep.

    “This doesn’t make any sense! Why should we care who was the father of some guy who was the father of another dude who was the father of some other brother from another mother?” asked Trevor.

    "Glad you asked, Trevor! I don’t care any more than you do. I don’t know the names of my ancestors from thousands of years ago, and I don’t care. I am who I am regardless of how I came to this world. Similarly, I don’t care about the people mentioned in these verses because who Jesus was had nothing to do with these people. Also, the genealogy was clearly an error because it was the ancestors of Joseph, who was not the biological father of Jesus. God was the father of Jesus, and Mary was his mother. Jesus was conceived by the holy Spirit and not through the bloodline of the 42 generations of ancestors mentioned in the book of Matthew.

    “Why were these people included in the book if they don’t matter?” asked Trevor.

    Whoever was writing these books died thousands of years ago, and so we can’t ask them, but if I had to guess, they saw Jesus as being the legal son of Joseph because Mary became his wife, and in those days, women were seen more as property than people. You may have noticed that the genealogy lists mostly men and only a few women, despite the fact that every one of them had a mother. The writer probably didn’t like women or perhaps excluded them so he didn’t have to write so many of those long names," said Chad.

    “Come on, Chad, that is just guessing. You have no clue what you are talking about,” said Luke.

    “Yes, I did say I was guessing, but it doesn’t miss the larger point that the Holy Spirit was the reason that Mary became pregnant with Jesus without needing a man. I guess you could say that the Spirit is thicker than blood in this case,” said Chad.

    At this point, Stacy decided to speak, despite generally being shy in crowds.

    “Chad, sorry to interrupt, but I think I understand. The writers tried to frame everything in terms of their culture and ancestry because it was important to them, but it was not important to God. The conception and birth of Jesus completely contradict everything else in the society. Patriarchy and a man’s permission were ignored because God sent the angel directly to Mary before Joseph knew anything. It also opposed the genealogy because Jesus was not a biological descendant of Joseph’s bloodline,” said Stacy.

    “Thanks for your input, Stacy! Yes, I do think that the writers tried to include extra things that were not important to understanding the life of Jesus. It is one of the mistakes in the New Testament, but we can still learn a lot from the gospels about who Jesus is and what he taught,” said Chad.

    “Excuse me, Chad, but did you really say there are mistakes in the Bible?” said Pastor Mark.

    “Of course, Mark. All writings have mistakes due to human error and personal or political bias. Seeing which parts are true and which are just the opinions of humans is the hardest part,” said Chad.

    “But the Bible is the infallible word of God!” I can’t let you speak such heresy to these children," said Mark.

    “And I won’t let you lie to them and say that there are no mistakes in the Bible when clearly there are contradictions between the intentions of the writers and what God was intending to teach people through the life of Jesus,” said Chad.

    “Chad, you’re fired. No one talks back to me. I am the pastor of this church, and I say what happens here,” said Mark

    SLAP

    Stacy slapped her father hard in the face and interrupted him.

    “Shut up, Dad. You think you are the boss, but you are not God, and you are not a man of God either. You care only about your reputation and money rather than whether these kids hear the truth. I think Chad has a point here, and I want the kids to hear it,” said Stacy.

    "Thanks, Stacy. We are almost done here for today. Pastor, we will talk about this later, but for the record, you can’t fire me because I am a volunteer!

    Pastor Mark did not say anything else because his daughter, Stacy, might slap him again. She was in a mood today that he had never seen before.