Author: Chastity White Rose

  • AAA Windows Chapter 11: Command Line Arguments

    This post is another chapter of my book: Assembly Arithmetic Algorithms for Windows.

    In chapter 10, I presented a program that prints a hex dump of a file named “byte.bin”. However, what if we want a hex dump tool that allows the user to provide a filename that they want a hex dump from?

    To do this, we have two main ways. The first would be to call the getstring function, as seen in chapter 6, at the beginning of the program and ask the user for which file they want to open. However, there is a serious problem in this because then every time the file was modified, the user has to retype the filename.

    The Windows API has a function for this and it is called GetCommandLineA.

    https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getcommandlinea

    Using the GetCommandLineA is not complicated by any means because it takes no arguments. It returns a pointer to a null terminated string which contains the program name and all the arguments on the line. In 32-bit mode, the eax register will have the pointer. In 64-bit mode, the rax register will have it, because it is really just the 64-bit extension of eax.

    Because my putstring function already prints the string pointed to by eax or rax, a short program can be constructed to print its own arguments. Below are the minimal examples in both modes to get the command line string, print it, and exit the program with no errors.

    GetCommandLineA example 32-bit

    format PE console
    entry main
    
    include 'win32a.inc'
    include 'chastelib-w32.asm'
    
    main:
    
    call [GetCommandLineA]
    call putstring
    
    push 0
    call [ExitProcess]
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    GetCommandLineA example 64-bit

    format PE64 console
    entry main
    
    include 'win64a.inc'
    include 'chastelib-w64.asm'
    
    main:
    
    call [GetCommandLineA]
    call putstring
    
    sub rsp,40
    mov rcx,0
    call [ExitProcess]
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    Shortening the Windows Prompt

    When using command line arguments, the long prompt of the Windows command line can be annoying because it displays the drive letter and full path to the current directory. Fortunately there is a fix for this.

    For example, I first shortened my command prompt to illustrate what the program does on my machine.

    prompt -$g
    

    Gives me only “->” instead of showing me the full path of which directory I am in. This allows me to conveniently run commands and show you exactly how this program works.

    ->fasm main.asm
    flat assembler  version 1.73.35  (1734819 kilobytes memory)
    3 passes, 1536 bytes.
    
    ->main What is a man?
    main  What is a man?
    ->main A miserable little pile of secrets.
    main  A miserable little pile of secrets.
    

    Notice that every time we are assembling a program with fasm, the name of the source file is an argument to fasm. In a similar way, the second two command examples of me running the main program shows that it prints the entire command line, including the name of the program, which just happens to be “main” by the convention I use while developing.

    However, what if we don’t want the entire command string but only parts of it? That is exactly where it starts getting a little bit complicated. Fortunately, I have written a function called “getarg” which solves the problem completely.

    I must warn you though, the function is about 150 lines of Assembly code and is meant to handle things by many different branches depending on conditions. Here are some odd cases that need to be considered when dealing with command line arguments.

    Design of the getarg function

    The getarg function which you are going to see in the source code later, is arguably the hardest content to understand in this whole book so far. However, with a few notes on the reasoning behind it, it might start to make sense.

    The program name

    The name of the program is not usually relevant in the context of what a program is meant to do. The user should be free to rename the executable to whatever they want and it behave the same. For this reason, my getarg function skips past the program name, main or otherwise.

    The return value

    The convention I use in the getarg function is that it will return 0 if there are no more arguments. Otherwise, it will return a pointer to the string of the next argument, which will have been terminated by zero to prepare it for printing with my putstring function.

    However, there is an exception. The first time getarg is called, it has to call the Windows API function “GetCommandLineA”. At the beginning of the program the “arguments_start” variable is defined to be 0. This variable is replaced with something else after the first call so that it never needs to call the Windows API again after the first time you call getarg.

    It also stores a pointer to the “arguments_end” variable which is the address of the terminating zero in the string returned by GetCommandLineA. These two variables are compared each time you call getarg again. If they are equal, it means there are no more argument strings. Therefore, the function jumps to a label which sets eax or rax to 0.

    Quoted strings

    One of the reasons the getarg function is so long is that it has to account for quoted strings. The default behavior of always delimiting strings by spaces doesn’t work if you are trying to pass a filename which contains spaces. Because we want our hex dump program to be able to handle opening any file, including those with spaces in their name, we need to have the loop which processes strings which are enclosed in single or double quotes. It will process these by going to the next byte after the opening quote and then search until it finds the second quote. When it finds the second quote, it is replaced with 0 for the null termination of that particular argument.

    Purpose

    The purpose of writing the getarg function was to make it possible to port over all of my Linux Assembly programs. You see, in Linux, there is no API function to get the arguments, but they are placed on the stack by the Linux kernel. Processing command line arguments in Linux is easier because even quoted strings are automatically handled for me and the contents of them count as only one argument that is zero terminated like all the rest.

    Although it was a lot of work, the results are worth it because now all of my best programs have been ported to Windows and will be included in future chapters of this book. The first and most basic of these is called chastearg (short for Chastity’s Arguments). The program prints each argument on a separate line. As usual, both 32-bit and 64-bit examples have been provided.

    chastearg 32-bit main.asm

    ;chastearg Assembly source code for FASM
    ;This program prints all the arguments on a separate line
    ;Quoted strings count as one argument and will be on the same line.
    format PE console
    entry main
    
    include 'win32a.inc'
    include 'chastelib-w32.asm'
    include 'getarg-w32.asm'
    
    main:
    
    call getarg ;this first call will get the command string
    cmp eax,0   ;did the getarg function return 0?
    jz main_end ;if eax was zero, there are no args so we end program
    
    ;uncomment these lines to see the whole command string
    ;call putstring
    ;call putline
    
    arg_loop:
    call getarg     ;get the next argument
    cmp eax,0       ;did the getarg function return 0?
    jz arg_loop_end ;if eax was zero, there are no args
    call putstring
    call putline
    jmp arg_loop
    arg_loop_end:
    
    main_end:
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    getarg-w32.asm

    ;This is the Windows 32-bit source of the getarg function 
    
    ;The getarg function makes my assembly code for DOS and Windows easier to read
    ;by writing one function to handle all possible cases I will need for my tools.
    ;
    ;The first time it is run, it returns the whole command string or zero if no args are given
    ;Each time after that, it will give you the next argument, which is a substring of the original.
    ;When no more arguments are available, it will always return zero
    ;The program calling this is expected to check for this error and then terminate
    ;or print a message depending on the goals of that program
    
    ;A word of warning, though, this function has multiple return statements and is long
    ;However, it is fully featured in that it can recognize quoted strings as being the same argument
    ;This brings full compatibility for my DOS and Windows programs that use command line arguments.
    
    ;Because all of my programs are ported to DOS and Windows from Linux
    ;I found there to be conflicts in the way these operating systems did not
    ;handle the quoted strings.
    ;
    ;This same function is available in a separate file for DOS Assembly.
    
    getarg:
    
    mov ebx,[arguments_start] ;get the address of start of arguments
    cmp ebx,0 ;is this address zero? (meaning this function was not called before)
    jz get_arg_data ;if it was zero, then get the argument data for the first execution of this function
    
    ;if the start was not zero, then clearly arguments exist and addresses have been saved
    cmp ebx,[arguments_end]  ;is the address of the start and end the same?
    jnz find_next_string  ;if they are not the same, find the next sub string
    mov eax,0 ;otherwise, return eax as zero and check this in the main program
    
    ret
    
    find_next_string:
    
    mov ebx,[arguments_start] ;get address of current arg
    
    skip_spaces:
    
    cmp byte[ebx],' '   ;is this byte a space?
    jnz skip_spaces_end ;if it is not a space, we can end this loop
    inc ebx ;otherwise, go to next byte
    jmp skip_spaces ;and keep looping till we find non-space
    skip_spaces_end:
    mov eax,ebx ;copy this non-space address to eax register
    
    ;we have found a non-space which is the start of a printable string
    ;but we still have to find the next space and terminate it with a zero!
    
    ;however, there is a special case where we want a string to contain spaces. In this case, I have another routine!
    
    ;check for quoted strings
    cmp byte[ebx],0x22 ;is this a double quote -> "
    jz scan_quoted_string
    cmp byte[ebx],0x27 ;is this a single quote -> '
    jz scan_quoted_string
    
    find_space:
    cmp byte [ebx],' ' ;is this a space?
    jz found_space ;if this was a space, end the loop and terminate with zero
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    cmp byte[ebx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc ebx
    jmp find_space ; this char was not space, go to the next char
    found_space:
    mov byte[ebx],0 ;terminate this string
    
    inc ebx ;but go to the next byte
    mov [arguments_start],ebx ;and set the new start address for the next call
    
    ret ;We can return eax safely knowing the string ends in a zero
    
    scan_quoted_string:
    
    mov cl,byte[ebx] ;mov this quote type to cl
    inc ebx ;go to next byte
    mov eax,ebx ;set eax to this address which is assumed to be the start of a quoted string
    
    find_end_quote:
    cmp byte[ebx],cl ;is this the same quote we started with?
    jz found_end_quote ;if it is, end this loop
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    ;this avoids a crash if I forgot to add the second quotation mark in the arguments
    cmp byte[ebx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc ebx
    jmp find_end_quote
    found_end_quote:
    mov byte[ebx],0 ;terminate this string
    
    inc ebx ;but go to the next byte
    mov [arguments_start],ebx ;and set the new start address for the next call
    
    ret
    
    no_more_args:
    
    mov [arguments_start],ebx ;mov the start to where the string ended
    
    ;now that the start and end addresses are the same
    ;this function will always return zero
    ret
    
    ;this will happen first time this function is called to get the argument data
    get_arg_data:
    
    ;get command line argument string with this Windows API call
    ;it returns a pointer in the eax register
    
    call [GetCommandLineA]
    mov [arguments_start],eax ;but copy it to memory too
    
    
    ;the following loop is a modified strlen loop except that we don't
    ;care about how long the string is. We only need the pointer for where it ends
    
    mov ebx,eax  ;copy eax to ebx to be used in loop to find ending zero
    argend_find: ; this loop finds the zero at the end of the argument string
    cmp [ebx],byte 0 ; compare byte at address ebx with 0
    jz argend_found ; if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp argend_find
    argend_found:
    mov [arguments_end],ebx ;save ebx address containing the zero byte
    
    mov ebx,eax ;mov the start of the arg string again before the next loop
    
    skip_prog_name:
    cmp byte[ebx],0   ;check for zero first (very important)
    jz no_args        ;if we found a zero before a space, there are no arguments after program name!
    cmp byte[ebx],' ' ;is this byte a space?
    jz skip_prog_name_end ;if it is a space, we can end this loop
    mov byte[ebx],' ' ;turn this byte into a space and therefore delete it
    inc ebx ; go to next byte
    jmp skip_prog_name ;and keep looping till we find space
    skip_prog_name_end:
    mov eax,ebx ;copy this non-space address to eax register
    ;return eax pointing to an address of string with arguments after program name
    ret
    
    no_args:
    mov eax,0
    ;in this case, we return zero because there are no args
    
    ret
    
    ;start and end default to address of zero, which means we have not tested the arguments yet
    arguments_start dd 0
    arguments_end dd 0
    

    chastearg 64-bit main.asm

    ;chastearg Assembly source code for FASM
    ;This program prints all the arguments on a separate line
    ;Quoted strings count as one argument and will be on the same line.
    format PE64 console
    entry main
    
    include 'win64ax.inc'
    include 'chastelib-w64.asm'
    include 'getarg-w64.asm'
    
    main:
    
    call getarg ;this first call will get the command string
    cmp rax,0   ;did the getarg function return 0?
    jz main_end ;if rax was zero, there are no args so we end program
    
    ;uncomment these lines to see the whole command string
    ;call putstring
    ;call putline
    
    arg_loop:
    call getarg     ;get the next argument
    cmp rax,0       ;did the getarg function return 0?
    jz arg_loop_end ;if rax was zero, there are no args
    call putstring
    call putline
    jmp arg_loop
    arg_loop_end:
    
    main_end:
    
    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
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    getarg-w64.asm

    ;This is the Windows 64-bit source of the getarg function 
    
    ;The getarg function makes my assembly code for DOS and Windows easier to read
    ;by writing one function to handle all possible cases I will need for my tools.
    ;
    ;The first time it is run, it returns the whole command string or zero if no args are given
    ;Each time after that, it will give you the next argument, which is a substring of the original.
    ;When no more arguments are available, it will always return zero
    ;The program calling this is expected to check for this error and then terminate
    ;or print a message depending on the goals of that program
    
    ;A word of warning, though, this function has multiple return statements and is long
    ;However, it is fully featured in that it can recognize quoted strings as being the same argument
    ;This brings full compatibility for my DOS and Windows programs that use command line arguments.
    
    ;Because all of my programs are ported to DOS and Windows from Linux
    ;I found there to be conflicts in the way these operating systems did not
    ;handle the quoted strings.
    ;
    ;This same function is available in a separate file for DOS Assembly.
    
    getarg:
    
    mov rbx,[arguments_start] ;get the address of start of arguments
    cmp rbx,0 ;is this address zero? (meaning this function was not called before)
    jz get_arg_data ;if it was zero, then get the argument data for the first execution of this function
    
    ;if the start was not zero, then clearly arguments exist and addresses have been saved
    cmp rbx,[arguments_end]  ;is the address of the start and end the same?
    jnz find_next_string  ;if they are not the same, find the next sub string
    mov rax,0 ;otherwise, return rax as zero and check this in the main program
    
    ret
    
    find_next_string:
    
    mov rbx,[arguments_start] ;get address of current arg
    
    skip_spaces:
    
    cmp byte[rbx],' '   ;is this byte a space?
    jnz skip_spaces_end ;if it is not a space, we can end this loop
    inc rbx ;otherwise, go to next byte
    jmp skip_spaces ;and keep looping till we find non-space
    skip_spaces_end:
    mov rax,rbx ;copy this non-space address to rax register
    
    ;we have found a non-space which is the start of a printable string
    ;but we still have to find the next space and terminate it with a zero!
    
    ;however, there is a special case where we want a string to contain spaces. In this case, I have another routine!
    
    ;check for quoted strings
    cmp byte[rbx],0x22 ;is this a double quote -> "
    jz scan_quoted_string
    cmp byte[rbx],0x27 ;is this a single quote -> '
    jz scan_quoted_string
    
    find_space:
    cmp byte [rbx],' ' ;is this a space?
    jz found_space ;if this was a space, end the loop and terminate with zero
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    cmp byte[rbx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc rbx
    jmp find_space ; this char was not space, go to the next char
    found_space:
    mov byte[rbx],0 ;terminate this string
    
    inc rbx ;but go to the next byte
    mov [arguments_start],rbx ;and set the new start address for the next call
    
    ret ;We can return rax safely knowing the string ends in a zero
    
    scan_quoted_string:
    
    mov cl,byte[rbx] ;mov this quote type to cl
    inc rbx ;go to next byte
    mov rax,rbx ;set rax to this address which is assumed to be the start of a quoted string
    
    find_end_quote:
    cmp byte[rbx],cl ;is this the same quote we started with?
    jz found_end_quote ;if it is, end this loop
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    ;this avoids a crash if I forgot to add the second quotation mark in the arguments
    cmp byte[rbx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc rbx
    jmp find_end_quote
    found_end_quote:
    mov byte[rbx],0 ;terminate this string
    
    inc rbx ;but go to the next byte
    mov [arguments_start],rbx ;and set the new start address for the next call
    
    ret
    
    no_more_args:
    
    mov [arguments_start],rbx ;mov the start to where the string ended
    
    ;now that the start and end addresses are the same
    ;this function will always return zero
    ret
    
    ;this will happen first time this function is called to get the argument data
    get_arg_data:
    
    ;get command line argument string with this Windows API call
    ;it returns a pointer in the rax register
    sub rsp,40  ;align stack before Win API functions(required in windows 64-bit)
    call [GetCommandLineA]
    mov [arguments_start],rax ;but copy it to memory too
    add rsp,40  ;restore stack now that WinAPI calls are done
    
    ;the following loop is a modified strlen loop except that we don't
    ;care about how long the string is. We only need the pointer for where it ends
    
    mov rbx,rax  ;copy rax to rbx to be used in loop to find ending zero
    argend_find: ; this loop finds the zero at the end of the argument string
    cmp [rbx],byte 0 ; compare byte at address rbx with 0
    jz argend_found ; if comparison was zero, jump to loop end because we have found the length
    inc rbx
    jmp argend_find
    argend_found:
    mov [arguments_end],rbx ;save rbx address containing the zero byte
    
    mov rbx,rax ;mov the start of the arg string again before the next loop
    
    skip_prog_name:
    cmp byte[rbx],0   ;check for zero first (very important)
    jz no_args        ;if we found a zero before a space, there are no arguments after program name!
    cmp byte[rbx],' ' ;is this byte a space?
    jz skip_prog_name_end ;if it is a space, we can end this loop
    mov byte[rbx],' ' ;turn this byte into a space and therefore delete it
    inc rbx ; go to next byte
    jmp skip_prog_name ;and keep looping till we find space
    skip_prog_name_end:
    mov rax,rbx ;copy this non-space address to rax register
    ;return rax pointing to an address of string with arguments after program name
    ret
    
    no_args:
    mov rax,0
    ;in this case, we return zero because there are no args
    
    ret
    
    ;start and end default to address of zero, which means we have not tested the arguments yet
    arguments_start dq 0
    arguments_end dq 0
    

    Relevance of Arguments

    To conclude this chapter, I will give you my best “argument” as to why command line arguments are usually the best way to provide input to a program.

    Reading from standard input pauses the program and requires the user to enter every detail. Command line arguments provide the input before the program is even started. This means they can be included as batch scripts on Windows so that the same arguments don’t have to be typed by the user.

    If you have a really long script, you need it to be able to run automatically and not ask the user for any choices unless something went seriously wrong. The FASM (the Flat Assembler) takes a filename as an argument and this is also what compilers for other languages do.

    Therefore, despite how difficult it is to explain my code briefly, it is heavily commented and I ask you to come back and review it as your knowledge of Assembly language improves. But beyond that, whether or not you fully understand the getarg function, you will see that it does work because it will be required for the program in the next chapter.

  • Book Review: Aphantasia An Essay About Nothing

    This book surprised me because I was made aware that other people don’t have my ability to generate pictures and movies in their head.

    When asked to imagine a ball rolling off a table, not only can I tell you that the table was a checkerboard of light and dark oak wood but the ball was made of the light oak and I can also hear it roll off the table. I can even pick the ball up off the floor and put it back on the table.

    The example where the author told his wife he didn’t have the ability to play Chess in his head like Beth Harmon was what caught my interest even more. It made me question how much of an unfair advantage I have in Chess just because I can plan ahead and move pieces in my mind while my opponent is choosing their move.

    And much to my surprise, the author was able to enjoy computer programming with his “highly abstract, pictureless brain”. I would have guessed this to be impossible without mental images of bits in a memory grid that I use when doing C or Assembly programming.

    And the larger questions reading this book brought to my mind are how common it is for people to not have the abilities I rely on. How might teachers of all subjects benefit from understanding people like Peter Armstrong with Aphantasia and others like me with Hyperphantasia. Clearly different styles for learning are required for people to reach their full power in a skill.

    In the second half of the book, Peter also talks about the lack of an inner monologue. I also have an inner monologue and can have entire conversations in my head that are loud enough to drown out people around me. There might be a connection between mental images and mental sounds.

    I appreciate Peter for explaining this neurodivergence in an easy to understand way. The examples were chosen very well despite being done without imagination!

  • SDL2 Prime Sieve

    This was a video made with a program I wrote using a font drawing library in SDL. It prints all the prime numbers less than 65536. Whenever it runs out of space, it clears the screen and starts on the next page.

  • FreeBASIC Graphics Counting and ASCII Character Program

    This is a picture that I made with a program I wrote in FreeBASIC. One of the cool things about FreeBASIC is that it has built in graphics as part of its standard library. This means it takes less work to get some text and pictures onto the screen compared to doing it with C and SDL.

    The full source code is included in this post. It is quite large but still smaller than what I usually do with C. Mostly, it serves as a reminder of how I achieved this to inspire me to make a game in FreeBASIC.

    main.bas

    #include "chastelib.bi"
    
    'define screen size variables before the graphics header
    dim shared as integer screen_width=1280,screen_height=720
    #include "chastelib-graphics.bi"
    
    ' Set the screen mode to size I want and 32 bits true color
    ScreenRes 1280, 720, 32
    
    'optionally change font
    
    'width screen_width/8,screen_height\8  'use 8x8 font (default)
    width screen_width\8,screen_height\16 'use 8x16 font
    
    chaste_checker
    
    color &h000000,&hFFFFFF 'set foreground and background text color using hex RGB codes
    
    dim as integer cursor_x=3,cursor_y=2,y_top=7
    
    locate cursor_y,cursor_x
    print "FreeBASIC Graphics Counting and ASCII Character Program"
    
    cursor_y+=2
    locate cursor_y,cursor_x
    print "Written by Chastity White Rose, the Pure Princess of Pixels, Polygons, and Ponies"
    
    
    cursor_x=2
    cursor_y=y_top
    locate cursor_y,cursor_x
    
    ' Keep the window open until the user presses a key
    
    dim as integer a,b,c
    
    radix=16
    
    a=0
    b=strint("100")
    c=32
    
    while a<b
    
    locate cursor_y,cursor_x
    
    radix=2
    int_width=8
    print intstr(a);" ";
    
    radix=16
    int_width=2
    print intstr(a);" ";
    
    radix=10
    int_width=3
    print intstr(a);
    
    'if(a>=32) and (a<=126) then
    print " "+chr(a);
    'endif
    
    'print
    
    a+=1
    cursor_y+=1:
    
    'cursor math to display to the right of last column of numbers
    if (a mod c)=0 then
    cursor_x+=20
    cursor_y=y_top
    end if
    
    wend
    
    sleep
    
    /'
     This is a FreeBASIC program.
    
     compile and run as:
    
     fbc main.bas && ./main
    '/
    

    chastelib.bi

    /'
     global variables to define radix and formatting
     for the intstr function
    '/
    dim shared as integer radix=2
    dim shared as integer int_width=1
    
    /'
     translation of intstr function for FreeBASIC
     by original C programmer Chastity White Rose
    '/
    function intstr(i as uinteger) as string
     dim as string s=""
     dim as integer w=0
     dim as byte c
    
     while i<>0 or w<int_width 
    
      c=i mod radix                  
      i\=radix                     
    
      if c<10 then 
      c+=48
      else
      c+=55
      end if
    
      s=chr(c)+s
    
      w+=1                     
     wend
    
    return s
    end function
    
    /'
     global variable for error detection in strint function
     this variable will be zero if last string was a number
    '/
    dim shared as integer strint_errors=0
    
    /'
     translation of strint function for FreeBASIC
     by original C programmer Chastity White Rose
    '/
    function strint(s as string) as uinteger
    dim as uinteger i=0
    dim as integer x=0,y=len(s)
    dim as byte c
    
    strint_errors = 0 /' clear errors '/
    
    while x<y
    
     /' read digit from string '/
     c=s[x]
    
     /' 0 to 9 '/
     if c >= 48 and c <= 57 then
     c-=48
     /' A to Z '/
     elseif c >= 65 and c <= 90 then
     c-=65
     c+=10
     /' a to z '/
     elseif c >= 97 and c <= 122 then
     c-=97
     c+=10
     /' whitespace '/
     elseif c >= 0 and c <= 32 then
      exit while /' exit correctly at string end '/
     else
      strint_errors+=1
      print "Error: ";chr(s[x]);" is not an alphanumeric character!"
      exit while /' exit at invalid character '/
     end if
    
     if c>=radix then
      strint_errors+=1
      print "Error: ";chr(s[x]);" is not a valid character for radix ";radix
      exit while /' exit at digit wrong for radix '/
     end if
    
     /'multiply by radix then add digit'/
     i*=radix
     i+=c
    
    x+=1
    wend
    
    return i
    end function
    
    

    chastelib-graphics.bi

    dim shared as integer rect_size=8
    
    /'
     this function draws a checkerboard. it is highly optimized because it does not switch colors during the function. it only draws half of the checkerboard squares and leaves the remaining areas the same as the background
    '/
    sub chaste_checker()
     dim as integer x,y,index,index1
     dim as integer rect_x,rect_y,rect_w,rect_h
    
     'draw filled rectangle with the line function
     'fill the whole screen
      line (0,0)-(screen_width,screen_height), &HFF000000,bf
     
     index=0
     rect_x=0
     rect_y=0
     rect_w=rect_size
     rect_h=rect_size
    
     y=0
     while(y<screen_height)
      index1=index
      x=0
      while(x<screen_width)
      if(index=1) then
    
       'draw filled rectangle with the line function
       line (x,y)-(x+rect_w,y+rect_h), &HFFFFFF, bf
    
       end if
       index=index xor 1
       x+=rect_size
      wend
      index=index1 xor 1
      y+=rect_size
      wend
    
    end sub
    
    
    
    ' Notes
    
    'The line function actually can draw rectangles, despite its name
    'https://www.freebasic.net/wiki/KeyPgLinegraphics
    
  • Learning Pascal for fun

    I am experimenting with old programming languages from the 20th century. My main focus is on teaching beginners to program with languages easier than Assembly. BASIC and Pascal are both older than C and so it is my duty to learn about them so I can make recommendations.

    The following program is something I wrote as a basic outline of how variables in Pascal are defined and how a loop can be used. As I learn more I intend to translate some of my C programs into Pascal so I can share with others who appreciate this old language that is good but not as well known as most others.

    program life;
    
    var
     name:string;
     year:integer;
     age:integer;
    
    begin
     name:='Chastity';
     year:=1987;
     age:=0;
    
     while year<=2026 do
     begin
      WriteLn('name=',name,' age=',age,' year=',year);
      year:=year+1;
      age:=age+1;
     end;
    
    end.
    
    (*
     fpc main.pas && ./main
    *)
    
  • AAA Linux Chapter 18

    Chapter 18: Bitwise Operations for Advanced Nerds

    This chapter contains information which will assist you in understanding more about how computers work, but that in general is not required for MOST programming unless you are trying to operate on individual bits.

    To start out, I will describe 5 essential bitwise operations independently of any specific programming language. This is because these operations exist in every programming language I know of, including Assembly and C.

    After I have explained what the bitwise operations do, I will give examples of how this can be used in Assembly language to substitute for addition and subtraction! You might wonder why you would do this. The fact is that you don’t need to but it is a fun trick that only advanced nerds like me do for a special challenge.

    The Bitwise Operations

    This chapter explains 5 bitwise operations which operate on the bits of data in a computer. For the purpose of demonstration, it doesn’t matter which number the bits represent at the moment. This is because the bits don’t have to represent numbers at all but can represent anything described in two states. Bits are commonly used to represent statements that are true or false. For the purposes of this section, the words AND, OR, XOR are in capital letters because their meaning is only loosely related to the English words they get their name from.

    Bitwise AND Operation

    0 AND 0 == 0
    0 AND 1 == 0	
    1 AND 0 == 0
    1 AND 1 == 1
    

    Think of the bitwise AND operation as multiplication of single bits. 1 times 1 is always 1 but 0 times anything is always 0. That’s how I personally think of it. I guess you could say that something is true only if two conditions are true. For example, if I go to Walmart AND do my job then it is true that I get paid.

    I like to think of the AND operation as the “prefer 0” operation. It will always choose a 0 if either of the two bits is a 0, otherwise, if no 0 is available, it will choose 1.

    Bitwise OR Operation

    0 OR 0 == 0
    0 OR 1 == 1	
    1 OR 0 == 1
    1 OR 1 == 1
    

    The bitwise OR operation can be thought of as something that is true if one or two conditions are true. For example, it is true that playing in the street will result in you dying because you got run over by a car. It is also true that if you live long enough, something else will kill you. Therefore, the bit of your impending death is always 1.

    I like to think of the OR operation as the “prefer 1” operation. It will always choose a 1 if one of the two bits is a 1, otherwise, if no 1 is available, it will choose 0.

    Bitwise XOR Operation

    0 XOR 0 == 0
    0 XOR 1 == 1	
    1 XOR 0 == 1
    1 XOR 1 == 0
    

    The bitwise XOR operation is different because it isn’t really used much for evaluating true or false. Instead, this operation returns 1 if the bits compared are different or 0 if they are the same. This means that any bit, or group of bits, XORed with itself, will always result in 0.

    If you look at my XOR chart above, you will see that using XOR of any bit with a 1 causes the result to be the opposite of the original bit.

    The XOR operation is the quickest way to achieve this bit inversion. If you have a setting that you want to switch on or off, you can toggle it by XORing that bit with 1.

    While the AND, OR, XOR operations can work in the context of individual bits, or groups of them, the next operations, the bit shifts, only make sense in the context of a group of bits. At minimum, you will be operating on 8 bits at a time because a byte is the lowest addressable size of memory.

    Bitwise Left and Right Shift Operations

    Consider the case of the following 8 bit binary value:

    00001000

    This would of course represent the number 8 because a 1 is in the 8’s place value. We can left shift or right shift.

    00001000 ==  8 : is the original byte
    
    00010000 == 16 : original left shift 1
    00000100 ==  4 : original right shift 1
    

    That is really all there is to shifts. They can be used to multiply or divide by a power of two. In some cases, this can be faster than using the mul and div instructions described in chapter 6.

    Example 00: Fake Add

    The following example shows how it is possible to write an addition routine using a combination of the AND,XOR,SHL operations. In this case, the numbers are shown in decimal to be easier for most people to see that the addition is correct.

    format ELF executable
    
    main:
    
    mov dword [radix],10
    mov dword [int_width],1
    
    mov edi,1987
    mov esi,39
    
    mov eax,edi
    call putint
    call putline
    mov eax,esi
    call putint
    call putline
    
    fake_add:
    mov eax,edi
    xor edi,esi
    and esi,eax
    shl esi,1
    jnz fake_add
    
    mov eax,edi
    call putint
    call putline
    mov eax,esi
    call putint
    call putline
    
    mov eax,1
    mov ebx,0
    int 0x80
    
    include 'chastelib32.asm'
    

    If you run it, you will see that the correct result of 2026 which is 1987+39. These are the values we set the edi and esi registers to before simulating addition with these fancy bitwise operations that make even seasoned programmers run scared.

    But how does this monstrosity of a program work? You see the AND operation keeps track of whether both bits in each place value are 1 or not. If they both are, this means that we have to “carry” those bits as we would do in an ordinary binary addition. We store the carry in the esi register and then left shift it once each time in the loop. The loop continues until esi equals zero and there are no more bits to invert with XOR.

    The fact that it works is easy to work out in my head but I don’t blame you if you can’t visualize it. However, this shows the power of what bit operations can do, even though you will probably never need to do this.

    Example 01: Fake Sub

    In case the fake addition example above wasn’t enough for you, here is a slightly modified example that does a fake subtraction operation using the same operations. Try it out and you will see that it subtracts 38 from 2025 and gets the original 1987.

    format ELF executable
    
    main:
    
    mov dword [radix],10
    mov dword [int_width],1
    
    mov edi,2026
    mov esi,39
    
    mov eax,edi
    call putint
    call putline
    mov eax,esi
    call putint
    call putline
    
    fake_sub:
    xor edi,esi
    and esi,edi
    shl esi,1
    jnz fake_sub
    
    mov eax,edi
    call putint
    call putline
    mov eax,esi
    call putint
    call putline
    
    mov eax,1
    mov ebx,0
    int 0x80
    
    include 'chastelib32.asm'
    

    I will try to explain how this works. You see, we first XOR the edi register with the esi register. Then, we AND esi with the new value of edi. This means that the bits in the current place value will only both be 1 if those bits were 0 in edi and then were inverted to 1 by the XOR with esi. This means that at the start of the loop, destination bit=0 and source bit=1. 0 minus 1 means that we need to “borrow” (I hate that term because it is really stealing because we never give it back). We left shift esi as usual and then we keep XORing the new borrow in esi until it is zero.

    Also, you may have noticed that I never used the “cmp” instruction to compare si with zero in this examples. This is because the zero flag is automatically updated with most operations. In fact there are places in my standard library of functions (chastelib) where it wasn’t strictly required to compare with “cmp” but I added it for clarity so I could read my code and more easily remember what I was doing.

    But let’s face it, the examples in this chapter are purely for showing off how advanced my knowledge of the binary numeral system and manipulating bits in ways no reasonable person should ever attempt. I must admit, it would be great for an obfuscated code contest to make a program with code that is unreadable to most humans.

    And while I am on the subject of making unreadable code, why stop at simulating addition and subtraction? I can do multiplication and division too!

    Example 10 Fake Mul

    format ELF executable
    
    main:
    
    mov dword [radix],10
    mov dword [int_width],1
    
    mov edi,6
    mov esi,7
    
    mov eax,edi
    call putint
    call putline
    mov eax,esi
    call putint
    call putline
    
    fake_mul:
    mov eax,0
    fake_mul_add_loop:
    cmp esi,0
    jz fake_mul_add_loop_end
    test esi,1
    jz skip_add
    add eax,edi
    skip_add:
    shl edi,1
    shr esi,1
    jmp fake_mul_add_loop
    fake_mul_add_loop_end:
    mov edi,eax
    
    mov eax,edi
    call putint
    call putline
    
    mov eax,1
    mov ebx,0
    int 0x80
    
    include 'chastelib32.asm'
    

    This fake multiplication uses eax as a temporary variable to store the sum of repeated addition. Each time through the loop, we test the low bit of esi (source register) and add edi (destination register). Each time we double edi by left shifting it once while right shifting esi once.

    To test the low bit of esi, this example uses an instruction that has not been presented in this book. However, this is one of those times when it should be used because it is the easiest way to test for a bit.

    test esi,1
    

    This tests the lowest bit of the esi register. If the low bit (ones place) is 0, then the jump if zero command will skip past the part where we add edi to eax.

    The purpose of the bit shifts is to reduce the number of times we have to add edi to eax. Because 6 and 7 are the values we use, the loop will execute and exactly these results will happen.

    eax starts as 0 but at the end of each cycle, eax, edi, and esi will be these values.

    cycle eax edi esi
    0 6 6 7
    1 18 12 3
    2 42 24 1

    In short, because esi was 7 and was represented as 111 in binary, because it is the sum of 4+2+1, then the result of 42 is the sum of:

    (4*6)+(2*6)+(1*6)

    Because of this optimized algorithm, it took only 3 loop cycles instead of the usual 7 cycles if we had just added 6 to eax 7 times. The speed won’t be much for such a small number as 42 but for larger numbers, this is the kind of optimization I would want.

    Example 11 Fake Div

    For the final example, I have prepared the long division algorithm. I always wondered why they called it long division, but now that I wrote this 70 line long program just to divide 256 by 10, I finally understand.

    Despite its complexity, it follows the same method that humans use for doing long division. We extract the bits from high to low positions from the dividend (edi) and shift them into the low bits of a temporary number (ebx). If this number is large enough (not below divisor) then we subtract the divisor. Each time, we save the resulting bit in the quotient (eax). 0 means the number was too small to subtract the divisor and 1 means it was big enough to subtract the divisor.

    Because this is a 32 bit system, the “fake_div_sub_loop” needs to execute exactly 32 times. For this purpose, ecx was chosen as a counter. By initializing it to 1 and left shifting it each time, it will loop 32 times until ecx will be 0 when an overflow happens and the bit is lost.

    The eax,ebx,and ecx registers are pushed and popped in this example, despite not being part of a function call. This is to signal that their purpose was temporary and because of the complexity, I thought it would be a good convention to follow for clarity that they are not the final return values.

    format ELF executable
    
    main:
    
    mov dword [radix],10
    mov dword [int_width],1
    
    mov edi,256
    mov esi,10
    
    mov eax,edi
    call putint
    call putline
    mov eax,esi
    call putint
    call putline
    
    fake_div:
    ;save registers used in the long division algorithm
    push eax
    push ebx
    push ecx
    mov eax,0
    mov ebx,0
    mov ecx,1
    cmp esi,0
    jz fake_div_sub_loop_end ;div by 0 invalid
    fake_div_sub_loop:
    cmp ecx,0
    jz fake_div_sub_loop_end
    shl eax,1
    shl ebx,1
    test edi,edi ;test edi with itself to check sign bit
    jns skip_or  ;skip copy of sign bit if it was 0
    or ebx,1     ;store a 1 in low bit of ebx based on sign
    skip_or:
    shl edi,1
    
    ;skip subtraction if ebx is below esi
    cmp ebx,esi
    jb skip_sub 
    sub ebx,esi
    or eax,1
    skip_sub:
    
    shl ecx,1
    jmp fake_div_sub_loop
    fake_div_sub_loop_end:
    
    ;send results to correct registers and clean up
    mov edi,eax ;copy quotient to edi
    mov esi,ebx ;copy remainder to esi
    ;restore registers to their original values
    pop ecx
    pop ebx
    pop eax
    
    mov eax,edi
    call putint
    call putline
    
    mov eax,esi
    call putint
    call putline
    
    mov eax,1
    mov ebx,0
    int 0x80
    
    include 'chastelib32.asm'
    

    The results of the fake division program are here:

    256
    10
    25
    6
    

    The program correctly divides 256 by 10 which is 25 but with a remainder of 6. Although this program is probably the most difficult to understand, it gives the correct results.

    Which can only mean that the long division algorithm I have in my autistic head works because I used to perform these operations on paper a lot and I know the process so well that I created this example.

    Will I need to know these Algorithms?

    Believe it or not, there may be times when you will need to use these algorithms. There are some older processors which don’t have multiplication and division instructions. For Intel processors, you can already use the real mul and div instructions.

    But I have a feeling that somewhere in the hardware of those instructions on the Intel CPUs, they probably work a lot like the algorithms I wrote for this chapter.

    When I promised you Assembly Arithmetic Algorithms, I was not joking. I have spent 26 years learning computer programming and I always liked to learn everything whether other people considered it practical or profitable.

  • AAA Linux News: Chapter 17 added

    The 3 integer sequence programs from the DOS Assembly Arithmetic Algorithms have now been added to the Linux version. What was Chapter 5 for the DOS book is now Chapter 17 for the Linux book.

    I am not sure whether readers prefer this content sooner or later in the book but I felt that I had a lot more ground to cover in the Linux book and so I spent 16 chapters explaining the system calls and why they were used in the programs I included. The Linux book has chastack,chastext,chastecmp, and chastehex.

    These programs use all of them most important system calls and therefore, since I have succeeded in writing these, there is no terminal program for Linux that is out of reach. Those who read this book will probably learn to do everything I can in Assembly for Linux and probably more if they have true courage!

    https://leanpub.com/assemblyarithmeticalgorithms-Linux

  • chastext 64-bit

    I have converted my chastext program to 64 bit Assembly for Linux. Next to chastehex, this is the program I am most proud of because it can find and replace exact strings of text. It isn’t quite the same as the Linux “sed” tool, but it is faster, smaller, and I wrote it myself and can do whatever I want with it.

    So of course what I did was write shell script to show what it is capable of!

    main.asm

    ;Linux 64-bit Assembly Source for chastext
    ;a basic text search and replace program
    format ELF64 executable
    entry main
    
    include 'chastelib64.asm'
    
    main:
    
    pop rax
    mov [argc],rax ;save the argument count for later
    
    cmp qword [argc],1
    ja help_skip ;if more than 1 argument is given, skip the help message and process the other arguments
    
    help:
    mov rax,help_message
    call putstring
    jmp main_end
    help_skip:
    
    pop rax ;pop the next arg which is the name of the program we are running
    
    get_filename:
    pop rax ;pop the next arg which is the name of the file we will open
    
    mov [filename],rax ; save the name of the file we will open to read
    
    arg_open_file:
    
    ;Linux system call to open a file
    
    mov rsi,0   ;open file in read only mode
    mov rdi,rax ;filename should be in rax before this function was called
    mov rax,2   ;invoke SYS_OPEN (kernel opcode 2 on 64 bit systems)
    syscall     ;call the kernel
    
    cmp rax,0
    jns file_open_no_errors ;if rax is not negative/signed there was no error
    
    ;Otherwise, if it was signed, then this code will display an error message.
    
    mov rax,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],rax ; save the file descriptor number for later use
    
    ;before we just textdump or "cat" the file, we need to check for the existence of more arguments which will modify the output
    
    cmp qword[argc],3
    jb search_skip
    
    pop rax ;pop the next arg which is the string we are searching for
    mov [string_search],rax
    
    search_skip:
    
    cmp qword[argc],4
    jb replace_skip
    
    pop rax ;pop the next arg which is the string we are searching for
    mov [string_replace],rax
    
    replace_skip:
    
    ;now we begin displaying the file but also searching for the search string if it exists. We will check for these based on the number of arguments like we did earlier
    
    textdump:
    
    ;if only there are only 2 arguments (name of program plus input file)
    ;then we do a loop that ignores searching and replacing
    ;this loop will read one character from the file and then send it to stdout
    ;until there are no more bytes to display
    ;but if there are above 2 arguments, we skip this loop and go to search mode
    
    cmp qword[argc],2 ;test arguments 2=only filename given
    ja search_mode    ;but if above 2, then go to search mode because a search string was given
    
    ;This loop is the same as the Linux 'cat' command
    ;or the DOS 'type' command for a single file
    ;it will read one byte and echo it to standard output until EOF
    
    cat:
    
    mov rdx,1            ;number of bytes to read
    mov rsi,byte_array   ;address to store the bytes
    mov rdi,[filedesc]   ;move the opened file descriptor into rdi
    mov rax,0            ;invoke SYS_READ (kernel opcode 0 on 64 bit Intel)
    syscall              ;call the kernel
    
    mov [bytes_read],rax
    
    cmp rax,0
    jnz file_success ;if more than zero bytes read, proceed to display
    
    jmp main_end ;otherwise, end the program
    
    ; this point is reached if file was read from successfully
    
    file_success:
    
    ;print the last read character to stdout by switching to write call
    mov rdi,1            ;write to the STDOUT file
    mov rax,1          ;invoke SYS_WRITE (kernel opcode 1 on 64 bit systems)
    syscall            ;system call to write the message
    
    jmp cat
    
    search_mode:
    
    ;this is the beginning of search mode
    ;it handles the file by seeking and reading to search every position for the search string
    
    ;first, seek to the file_address we initialized to zero
    ;this variable will be added to depending on actions taken
    
    mov rdx,0              ;whence argument (SEEK_SET)
    mov rsi,[file_address] ;move the file cursor to this address
    mov rdi,[filedesc]     ;move the opened file descriptor into rbx
    mov rax,8              ;invoke SYS_LSEEK (kernel opcode 8 on 64 bit Intel)
    syscall                ;call the kernel
    
    ;obtain the length of the search string using my strlen function
    mov rax,[string_search]
    call strlen ;get the length of the search string
    
    ;use the length of the string we are searching for as the number of bytes to read at this location
    
    mov rdx,rax            ;number of bytes to read
    mov rsi,byte_array     ;address to store the bytes
    mov rdi,[filedesc]     ;move the opened file descriptor into rbx
    mov rax,0              ;invoke SYS_READ (kernel opcode 0 on 64 bit Intel)
    syscall                ;call the kernel
    
    mov [bytes_read],rax   ;store how many bytes were read with that last read operation
    
    mov rbx,byte_array     ;move the address of bytes read into rbx
    add rbx,rax            ;add number of bytes read (return value of read function in rax)
    mov byte[rbx],0        ;terminate the string with zero
    
    cmp rax,rdx ;if the number of bytes is not what we expected to read, end this loop
    jnz textdump_end
    
    ;move our two strings into the rsi and rdi registers for comparison
    ;with my custom written strcmp function
    
    mov rsi,[string_search]
    mov rdi,byte_array
    call strcmp ;compare these two strings
    
    cmp rax,0 ;test if they are the same (if rax returned zero)
    jnz not_match ;if they are not a match go to that section for printing a character
    
    ;but if they are a match, then we either quote them
    ;or replace them if a replacement string is available
    
    ;but regardless of which action we do, since a match was found, let us add this count to the file address
    ;so that we read from beyond this point next time the textdump loop starts
    mov rax,[bytes_read]
    add [file_address],rax
    
    cmp qword[argc],4 ;if less than 4 args, no replacement exist, so we quote the strings
    jb print_quotes
    
    ;otherwise, we will print the replacement string instead of the original!
    
    mov rax,[string_replace]
    call putstring ;print the string
    
    jmp textdump ;restart the main loop
    
    print_quotes:
    ;print quotes around matched string
    mov al,'"'
    call putchar
    
    mov rax,byte_array
    call putstring ;print the string
    
    mov al,'"'
    call putchar
    
    jmp textdump ;restart the main loop
    
    not_match: 
    
    ;Instead of calling the putchar function in the case of no match,
    ;I do a system call to print 1 byte to standard output
    ;This is simple and also compatible with binary files we want to replace text in.
    ;But it only works if the search and replace strings are of the same length
    
    mov rdx,1            ;number of bytes to write == 1
    mov rsi,byte_array   ;pointer/address of string to write
    mov rdi,1            ;write to the STDOUT file
    mov rax,1            ;invoke SYS_WRITE (kernel opcode 1 on 64 bit systems)
    syscall              ;system call to write the message
    
    add [file_address],1 ;add 1 to the file address so we don't read this same position again
    
    jmp textdump
    
    textdump_end:
    
    ;print the remaining bytes, if any, left after the main loop ended
    ;mov rax,byte_array
    ;call putstring
    
    mov rdx,[bytes_read] ;number of bytes to write == last read call result
    mov rsi,byte_array   ;pointer/address of string to write
    mov rdi,1            ;write to the STDOUT file
    mov rax,1            ;invoke SYS_WRITE (kernel opcode 1 on 64 bit systems)
    syscall              ;system call to write the message
    
    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 rdi,[filedesc] ;file number to close
    mov rax,3          ;invoke SYS_CLOSE (kernel opcode 3 for 64 bit Intel)
    syscall            ;call the kernel
    
    mov rax, 0x3C ; invoke SYS_EXIT (kernel opcode 0x3C (60 decimal) on 64 bit systems)
    mov rdi,0   ; return 0 status on exit - 'No Errors'
    syscall
    
    ;the strlen and strcmp are named after the equivalent C functions
    ;but are written from scratch by me based on their expected behavior
    
    ;The strlen function gets the length of string in rax and returns it in rax
    ;This is the same algorithm used in my putstring function
    
    strlen:
    
    push rbx
    mov rbx,rax ; copy rax to rbx. rbx will be used as index to the string
    
    strlen_start: ; this loop finds the length of the string
    
    cmp [rbx],byte 0 ; compare byte at address rbx with 0
    jz strlen_end ; if comparison was zero, jump to loop end
    inc rbx
    jmp strlen_start
    
    strlen_end:
    sub rbx,rax ;subtract start pointer from current pointer to get length of string
    mov rax,rbx ;copy the string length back to rax
    pop rbx
    
    ret
    
    ;strcmp compares the string at rsi to the one at rdi
    ;rax returns 0 if the strings are the same and 1 if different
    ;the algorithm is simple but I will explain it for those who are confused
    
    ;rax is initialized to zero
    ;a byte from each string is loaded into the al and bl 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
    ;for this purpose it doesn't matter whether we compare al or bl with zero
    ;because it is known that they are the same if the jnz did not take place
    ;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 bl will be subtracted from al
    ;this ensures that the function returns zero if the final characters are the same
    ;rbx,rsi,and rdi are preserved but rax is the return value
    ;also, the sub instruction at the end of the function also updates the flags
    ;so you can "jz" or "jnz" to a label after calling this function based on results
    
    strcmp:
    
    push rbx
    push rsi
    push rdi
    
    mov rax,0
    
    strcmp_start:
    
    ;read a byte from each string
    mov al,[rdi]
    mov bl,[rsi]
    cmp al,bl
    jnz strcmp_end
    
    cmp al,0
    jz strcmp_end
    
    inc rdi
    inc rsi
    
    jmp strcmp_start
    
    strcmp_end:
    sub al,bl
    
    pop rdi
    pop rsi
    pop rbx
    
    ret
    
    help_message db 'chastext by Chastity White Rose',0Ah,0Ah
    db '"cat" a file:',0Ah,0Ah,9,'chastext file',0Ah,0Ah
    db 'search for a string:',0Ah,0Ah,9,'chastext file search',0Ah,0Ah
    db 'replace string:',0Ah,0Ah,9,'chastext file search replace',0Ah,0Ah
    db 'Find or replace any string!',0Ah,0
    
    open_error_message db 'error while opening file',0
    
    file_address dq 0 ;file address defaults to zero AKA beginning of file
    
    ;variables for managing arguments and files
    argc rq 1
    filename rq 1 ; name of the file to be opened
    filedesc rq 1 ; file descriptor
    bytes_read rq 1
    
    string_search rq 1 ; place to hold the search string pointer
    string_replace rq 1 ; place to hold the replacement string pointer
    
    ;where we will store data from the file
    byte_array db 0xA4 dup 0
    

    chastelib64.asm

    ; chastelib assembly header file for 64 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,eax,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.
    
    putstring:
    
    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 rbx 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
    
    ;Write string using Linux Write system call.
    ;Reference for 64 bit x86 syscalls is below.
    ;https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/#x86_64-64-bit
    
    mov rdx,rbx      ;number of bytes to write
    mov rsi,rax      ;pointer/address of string to write
    mov rdi,1        ;write to the STDOUT file
    mov rax,1        ;write (kernel opcode 1 on 64 bit systems)
    syscall          ;system call for 64-bit Linux kernel
    
    pop rdx
    pop rcx
    pop rbx
    pop rax
    
    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 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(just before the newline 0Ah)
    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
    
    ;this function converts a string pointed to by rax into an integer returned in rax 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
    ;This function is improved with error checking and uses the new strint_error variable
    ;The program can check this value after the call and see how many errors happened.
    
    strint_error db 0 ;declare a byte variable that keeps track of errors
    
    strint:
    
    mov rbx,rax ;copy string address from rax to rbx because rax will be replaced soon!
    mov rax,0
    mov [strint_error],0 ;set errors to 0 at the start of this function
    
    read_strint:
    mov rcx,0 ; zero rcx so only lower 8 bits are used
    mov cl,[rbx]
    inc rbx
    cmp cl,0 ; compare byte at address rdx 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 with error
    jmp strint_end_error
    
    process_char:
    
    cmp rcx,[radix] ;compare char with radix
    jnb strint_end_error ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov rdx,0 ;zero rdx because it is used in mul sometimes
    mul qword [radix] ;mul rax with radix
    add rax,rcx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end_error: ;we jump here if there was an error with one of the chars
    inc [strint_error] ;increment error counter because char invalid
    
    strint_end: ;we jump here when no errors happened
    
    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 0Ah,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
    
    ;a function for printing a single character that is the value of al
    
    char: db 0,0
    
    putchar:
    push rax
    mov [char],al
    mov rax,char
    call putstring
    pop rax
    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
    

  • Assembly Magic

    A programmer is not a magician
    They just know how to use addition
    Those with skill and high ambition
    Don’t hesitate to break tradition

    Reverse addition is subtraction
    There is no need for fancy abstraction
    Do not fall for hype and distraction
    Don’t hesitate to learn, take action

    Repeated addition is called multiplication
    Despite its badly taught reputation
    Teaching math is my obligation
    With my books I will teach the nation

    Subtraction loops can form division
    Conditional jumps make each decision
    Divide by the radix for integer vision
    But a zero divisor can cause a collision

    Programming languages are all the same
    When arithmetic is your favorite game
    It is fun to choose each variable name
    But when my code fails, I take the blame

    But of every language I have used
    I love writing Assembly the most
    And I wrote the chastehex program
    Of which I sometimes like to boast

    I like Assembly language because
    It gives me the complete control
    And brings back the satisfaction
    That the evil tech companies stole

    Anyone can learn to write code
    That is what some people say
    And I agree with this statement
    When they learn in the right way

    People hear that something is hard
    And so they never even try to start
    But if they did they would soon see
    That building software is an art

    I wrote a book to teach my favorite
    Assembly Arithmetic Algorithms
    I am a bit too obsessed with math
    And others suffer from my autism

    I write my books and comment my functions
    So that other people have a chance to read
    And learn what makes computers work
    And do all the tasks that humans need

    And to those who don’t yet understand
    They think my math is some kind of magic
    I rarely meet those who take the time to learn
    And my lonely pursuit is kind of tragic

    But the special way I write my programs
    Is yet another form of Creative Writing
    And because of evil tech companies
    People like me cannot stop fighting

    They fire people and replace them with AI
    But that will only work for a short while
    Because the code is of no use at all
    Unless it can make a human soul smile

  • chastext for Linux

    This is the source of my chastext program in Linux Intel Assembly language. It is actually very impressive that I managed to fix the many bugs it had. It is a simple find a replace program that I may use in future development of small assembly programs. Each run of the program can only change one kind of string to another, but since the commands can be chained together, transformations are possible beyond what I can explain right now. I would have to write a script just to show what it can do.

    main.asm

    ;Linux 32-bit Assembly Source for chastext ;a basic text search and replace program format ELF executable entry main

    ;a reduced form of chastelib without functions this program doesn’t use include ‘chastext-chastelib32.asm’

    main:

    pop eax mov [argc],eax ;save the argument count for later

    cmp dword [argc],1 ja help_skip ;if more than 1 argument is given, skip the help message and process the other arguments

    help: mov eax,help_message call putstring jmp main_end help_skip:

    pop eax ;pop the next arg which is the name of the program we are running

    get_filename: pop eax ;pop the next arg which is the name of the file we will open

    mov [filename],eax ; save the name of the file we will open to read

    arg_open_file:

    ;Linux system call to open a file

    mov ecx,0 ;open file in read only 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.

    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

    ;before we just textdump or “cat” the file, we need to check for the existence of more arguments which will modify the output

    cmp dword[argc],3 jb search_skip

    pop eax ;pop the next arg which is the string we are searching for mov [string_search],eax

    search_skip:

    cmp dword[argc],4 jb replace_skip

    pop eax ;pop the next arg which is the string we are searching for mov [string_replace],eax

    replace_skip:

    ;now we begin displaying the file but also searching for the search string if it exists. We will check for these based on the number of arguments like we did earlier

    textdump:

    ;if only there are only 2 arguments (name of program plus input file) ;then we do a loop that ignores searching and replacing ;this loop will read one character from the file and then send it to stdout ;until there are no more bytes to display

    cmp dword[argc],2 jnz putchar_skip

    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

    mov [bytes_read],eax

    cmp eax,0 jnz file_success ;if more than zero bytes read, proceed to display

    jmp main_end ;otherwise, end the program

    ; this point is reached if file was read from successfully

    file_success:

    ;normally, we will print the last read character mov al,[byte_array] call putchar

    putchar_skip:

    cmp dword[argc],3 ;if not enough arguments, skip the search string section jb textdump

    ;this is the beginning of search mode ;it handles the file by seeking and reading to search every position for the search string

    ;first, seek to the file_address we initialized to zero ;this variable will be added to depending on actions taken

    mov edx,0 ;whence argument (SEEK_SET) mov ecx,[file_address] ;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

    ;obtain the length of the search string using my strlen function mov eax,[string_search] call strlen ;get the length of the search string

    ;use the length of the string we are searching for as the number of bytes to read at this location

    mov edx,eax ;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 ebx,byte_array ;move the address of bytes read into ebx add ebx,eax ;add number of bytes read (return value of read function in eax) mov byte[ebx],0 ;terminate the string with zero

    mov [bytes_read],eax ;store how many bytes were read with that last read operation

    cmp eax,edx ;if the number of bytes is not what we expected to read, end this loop jnz textdump_end

    ;move our two strings into the esi and edi registers for comparison ;with my custom written strcmp function

    mov esi,[string_search] mov edi,byte_array call strcmp ;compare these two strings

    cmp eax,0 ;test if they are the same (if eax returned zero) jnz not_match ;if they are not a match go to that section for printing a character

    ;but if they are a match, then we either quote them ;or replace them if a replacement string is available

    ;but regardless of which action we do, since a match was found, let us add this count to the file address ;so that we read from beyond this point next time the textdump loop starts mov eax,[bytes_read] add [file_address],eax

    cmp dword[argc],4 ;if less than 4 args, no replacement exist, so we quote the strings jb print_quotes

    ;otherwise, we will print the replacement string instead of the original!

    mov eax,[string_replace] call putstring ;print the string

    jmp textdump ;restart the main loop

    print_quotes: ;print quotes around matched string mov al,‘"’ call putchar

    mov eax,byte_array call putstring ;print the string

    mov al,‘"’ call putchar

    jmp textdump ;restart the main loop

    not_match:

    mov al,[byte_array] call putchar add [file_address],1 ;add 1 to the file address so we don’t read this same position again

    jmp textdump

    textdump_end:

    ;print the remaining bytes, if any, left after the main loop ended mov eax,byte_array call putstring

    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

    ;the strlen and strcmp are named after the equivalent C functions ;but are written from scratch by me based on their expected behavior

    ;a function to get the length of string in eax and return the integer in eax

    strlen:

    mov ebx,eax ; copy eax to ebx. ebx will be used as index to the string

    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 strlen_end ; if comparison was zero, jump to loop end because we have found the length inc ebx jmp strlen_start

    strlen_end: sub ebx,eax ;subtract start pointer from current pointer to get length of string

    mov eax,ebx ;copy the string length back to eax

    ret

    ;compare the string at esi to the one at edi

    strcmp:

    mov eax,0 ;this will be stay zero unless the strings are different

    strcmp_start: mov bl,[edi] cmp bl,0 jz strcmp_end mov bh,[esi] cmp bh,0 jz strcmp_end

    inc edi inc esi

    cmp bl,bh jz strcmp_start ;if they are the same, continue to next character

    inc eax ;if they were different, eax will be incremented and the function ends

    strcmp_end: ret

    help_message db ‘chastext by Chastity White Rose’,0Ah,0Ah db ‘“cat” a file:’,0Ah,0Ah,9,‘chastext file’,0Ah,0Ah db ‘search for a string:’,0Ah,0Ah,9,‘chastext file search’,0Ah,0Ah db ‘replace string:’,0Ah,0Ah,9,‘chastext file search replace’,0Ah,0Ah db ‘Find or replace any string!’,0Ah,0

    open_error_message db ‘error while opening file’,0

    file_address dd 0 ;file address defaults to zero AKA beginning of file

    ;variables for managing arguments and files argc rd 1 filename rd 1 ; name of the file to be opened filedesc rd 1 ; file descriptor bytes_read rd 1

    string_search rd 1 ; place to hold the search string pointer string_replace rd 1 ; place to hold the replacement string pointer

    ;where we will store data from the file byte_array db 0xBD dup 0