Tag: computers

  • 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

  • chastext for DOS

    I wrote a DOS version of the chastext program for simple search and replace. It does have some limitations because command line arguments are handled very different in DOS than they are in Linux. I can’t simple put quotes around two words to have them count as one argument like I can in Linux.

    Aside from that, it seems to work. I can replace individual words in a text file with a different word. I will have a demo video up soon but see the post about the Linux version in the Linux forum to get the basic idea of what it should do.

    I am not trying to recreate sed or awk but a simple find/replace is a worthwhile project for learning something new after I have mastered my chastehex and chastecmp programs. I can manipulate binary files flawlessly because they are predictable so now I am testing my limits on text based processing.

    main.asm

    org 100h     ;DOS programs start at this address
    
    mov word [radix],16 ; can choose radix for integer output!
    
    mov ch,0     ;zero ch (upper half of cx)
    mov cl,[80h] ;load length in bytes of the command string
    cmp cx,0
    jnz args_exist
    
    mov ax,help    ;if no arguments were given, show a help message
    call putstring
    jmp ending     ;and end the program because there is nothing to do
    
    args_exist:
    
    ;Point bx to the beginning of arg string
    ;however, this always contains a space
    mov bx,81h
    
    skip_start_spaces:
    cmp byte [bx],' ' ;is this byte a space?
    jnz skip_start_spaces_end ;if not, we are done skipping spaces
    inc bx ;otherwise, go to next char
    dec cx ;but subtract 1 from character count
    jmp skip_start_spaces
    skip_start_spaces_end:
    
    mov [arg_string_index],bx ; save the location of the first non space in the arg string
    
    ;find the end of the string based on length
    mov ax,bx
    add ax,cx
    mov [arg_string_end],ax ;now we know where the string ends.
    
    ;now bx points to the first non space character in the arguments passed to the DOS program
    ;and we know that [arg_string_end] is where it ends
    
    ;the next step is to filter the arguments into separate zero terminated strings
    ;each space will be changed to a zero (normally)
    ;but we also need to account for spaces inside quotes that are considered part of the string
    ;Linux handles this normally but DOS needs me to write the code to mimic this behavior
    ;because the program needs to function identically for DOS or Linux
    
    mov cl,' ' ;set the default filter character (argument terminator) to a space
    mov ch,0   ;are we currently checking spaces 0 or quote characters 1 as terminators?
    
    ;this loop is the new and improved argument filter
    ;it keeps track of whether we are inside or outside a quote
    ;and also which type of quote started the quote
    ;the actual quote marks are not part of the string unless they
    ;are the opposite quote type than what started the string
    ;The important thing is that spaces can exist inside of quoted strings
    ;as one argument rather than each new word being a new argument
    ;could be important for filenames containing spaces, etc.
    
    argument_filter:
    
    cmp bx,[arg_string_end] ;are we at the end of the arg string?
    jz argument_filter_end       ;if yes, stop the filter and terminate with zero
    
    cmp ch,1       ;are we inside a quoted string?
    jz quote_check ;if yes, don't do anything to the spaces
    
    cmp byte[bx],cl ;compare the byte at address bx to the string terminator
    jnz ignore_char ;if it is not the same, we ignore it
    mov byte[bx],0  ;but if it matches, change it to a zero
    ignore_char:
    
    cmp byte [bx],0x22 ;is this a double quote -> "
    jz start_quote
    cmp byte [bx],0x27 ;is this a single quote -> '
    jz start_quote
    jmp quote_no ;it was not a quote
    
    start_quote:
    
    mov ch,1    ;set ch to 1 to set that we are inside a quote now
    mov cl,[bx] ;save this quote type as the new terminator
    mov byte[bx],0 ;but delete the first quote with zero
    
    ;check for single or double quotes
    quote_check:
    
    cmp [bx],cl ;is this character the same type of quote that started this sub string?
    jnz quote_no ;if it is not, then skip to quote_no section
    
    ;but if it was matching, change this byte to zero
    ;and change cl back to a space
    mov cl,' ' ;cl is now a space
    mov ch,0   ;ch is 0 because now we have ended the quoted string
    mov byte[bx],0 ;delete the end quote with zero
    
    quote_no:
    
    inc bx ;go to the next character
    jmp argument_filter   ;jump back to the beginning of argument filter
    
    argument_filter_end:
    mov byte [bx],0 ;terminate the ending with a zero for safety
    
    ;special case!!!
    ;If the first argument passed began with a quoted string
    ;it would have been changed to a 0 instead. This requires us to add one to the
    ;starting argument string index
    mov bx,[arg_string_index]
    cmp byte[bx],0
    jnz first_argument_was_not_quote
    inc word[arg_string_index] ;add 1 so it points to the next byte before we process arguments
    first_argument_was_not_quote:
    
    
    
    ;now that the argument string is prepared, we will try to use the first argument as a filename to open
    
    mov ah,3Dh                ;call number for DOS open existing file
    mov al,0                  ;file access: 0=read,1=write,2=read+write
    mov dx,[arg_string_index] ;string address to interpret as filename
    int 21h                   ;DOS call to finalize open function
    
    mov [file_handle],ax ;save the file handle
    
    jc file_error ;if carry flag is set, we have an error, otherwise, file is open
    
    file_opened:
    
    mov ax,dx
    ;call putstring
    ;call putline
    jmp use_file ;skip past error message and start using the file
    
    ;this section prints error message and then ends the program if file error found
    
    file_error: ;prints error code2=file not found
    mov ax,dx
    call putstr_and_line
    mov ax,file_error_message
    call putstring
    mov ax,[file_handle]
    call putint
    jmp ending
    
    ;how we use the file depends on the number of arguments given
    ;if no arguments other than the filename exist, we do a regular hex dump
    ;otherwise we look for two more arguments: the search and replace strings
    
    use_file:
    
    call get_next_arg ;get address of next arg and return into ax register
    cmp ax,[arg_string_end] ;this time, if ax equals end of string, we hex dump and then end the program later
    jz textdump ;jump to hexdump section
    
    ;otherwise, we save the address at ax to our search string
    mov [string_search],ax
    ;call putstr_and_line
    
    
    call get_next_arg ;get address of next arg and return into ax register
    cmp ax,[arg_string_end] ;this time, if ax equals end of string, we hex dump and then end the program later
    jz textdump ;jump to hexdump section
    
    ;otherwise, we save the address at ax to our replacement string
    mov [string_replace],ax
    ;call putstr_and_line
    
    ;all other arguments that may exist after this are irrelevant
    
    textdump:
    
    ;we start the loop with a call to read exactly 1 byte
    
    mov ah,3Fh           ;call number for read function
    mov bx,[file_handle] ;store file handle to read from in bx
    mov cx,1             ;we are reading one byte
    mov dx,byte_array    ;store the bytes here
    int 21h
    
    ;call putint ;check the number of bytes read
    
    cmp ax,1        ;check to see if exactly 1 byte was read
    jz file_success ;if true, proceed to display
    ;mov ax,end_of_file
    ;call putstring
    jmp file_close ;otherwise close the file and end program after failure
    
    ; this point is reached if 1 byte was read from the file successfully
    file_success:
    
    ;first, check to see if there is a search string
    ;if there is a search string, skip the normal putchar
    cmp word[string_search],0 
    jnz putchar_skip
    
    ;but if there is not a search string
    ;we will print the last read character
    ;and then jump to the beginning of the textdump loop to print them until EOF
    mov al,[byte_array]
    call putchar
    jmp textdump
    
    putchar_skip:
    
    ;if search string doesn't exist, just jump and repeat the loop
    ;otherwise we continue into the section that compares the input with the search string
    
    mov bx,[string_search]
    
    mov al,[bx]
    mov ah,[byte_array]
    cmp al,ah ;compare the first character of search string with the byte read already
    jz search_start ; if they are equal, skip putchar and begin searching for the string
    
    ;otherwise, if they are not equal, just putchar the last byte read and repeat the loop
    mov al,[byte_array]
    call putchar
    jmp textdump
    
    search_start:
    mov ax,[string_search]
    call strlen ;get the length of the search string
    ;call putint_and_line ; print length of search string only for debugging
    
    ;attempt to read the length-1 bytes because the first one is already read into the byte array
    
    dec ax               ;subtract 1 from ax which holds our length of string
    
    mov dx,byte_array+1  ;store the bytes here
    mov cx,ax            ;we are reading this many bytes to have a string to compare
    mov bx,[file_handle] ;store file handle to read from in bx
    mov ah,3Fh           ;call number for read function
    int 21h
    
    ;do some math to calculate where the string should end
    
    mov bx,dx ;mov into bx the address of second byte in the string
    add bx,ax ;add ax (the return value of the number of characters read)
    mov byte [bx],0 ;terminate the string with zero
    
    mov si,[string_search]
    mov di,byte_array
    
    call strcmp ;compare these two strings
    
    cmp ax,0 ;test if they are the same (if ax returned zero)
    jnz normal_print ;if they are not a match print them unmodified and unquoted
    
    ;but if they are a match, then we either quote them
    ;or replace them if a replacement string is available
    
    cmp word[string_replace],0 ;check to see if a replacement string is available
    jz print_quotes ;if not, skip to the part where we just quote the strings that match
    
    ;otherwise, we will print the replacement string instead of the original!
    
    mov ax,[string_replace]
    call putstring ;print the string
    
    jmp normal_print_skip
    
    print_quotes:
    ;print quotes around matched string
    mov al,'"'
    call putchar
    
    mov ax,byte_array
    call putstring ;print the string
    
    mov al,'"'
    call putchar
    
    jmp normal_print_skip
    
    normal_print: ;print normal / unquoted because it doesn't match
    
    mov ax,byte_array
    call putstring ;print the string
    
    normal_print_skip:
    
    jmp textdump
    
    file_close:
    ;close the file if it is open
    mov ah,3Eh
    mov bx,[file_handle]
    int 21h
    
    ;debugging section I use just to test values
    ;call putline
    ;mov ax,[string_search]
    ;call putstr_and_line
    ;mov ax,[string_replace]
    ;call putstr_and_line
    
    
    ending:
    mov ax,4C00h ; Exit program
    int 21h
    
    ;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 ax and return the integer in ax
    
    strlen:
    
    mov bx,ax ; copy ax to bx. bx 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 [bx],byte 0 ; compare byte at address bx with 0
    jz strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc bx
    jmp strlen_start
    
    strlen_end:
    sub bx,ax ;subtract start pointer from current pointer to get length of string
    
    mov ax,bx ;copy the string length back to eax
    
    ret
    
    ;compare the string at si to the one at di
    
    strcmp:
    
    mov ax,0 ;this will be stay zero unless the strings are different
    
    strcmp_start:
    mov bl,[di]
    cmp bl,0
    jz strcmp_end
    mov bh,[si]
    cmp bh,0
    jz strcmp_end
    
    inc di
    inc si
    
    cmp bl,bh
    jz strcmp_start ;if they are the same, continue to next character
    
    inc ax ;if they were different, eax will be incremented and the function ends
    
    strcmp_end:
    ret
    
    ;function to move ahead to the next argument
    ;only works after the filter has been applied to turn all spaces into zeroes
    
    get_next_arg:
    mov bx,[arg_string_index] ;get address of current arg
    find_zero:
    cmp byte [bx],0
    jz found_zero
    inc bx
    jmp find_zero ; this char is not zero, go to the next char
    found_zero:
    
    ;once we have found a zero, check to make sure we are not at the end
    
    find_non_zero:
    cmp bx,[arg_string_end]
    jz arg_finish ;if bx is already at end, nothing left to find
    cmp byte [bx],0
    jnz arg_finish ;if this char is not zero we have found the next string!
    inc bx
    jmp find_non_zero ;otherwise, keep looking
    
    arg_finish:
    mov [arg_string_index],bx ; save this index to the variable
    mov ax,bx ;but also save it to ax register for use in printing or something else
    ret
    
    help db 'chastext by Chastity White Rose',0Dh,0Ah
    db '"cat" or "type" a file without changing it:',0Dh,0Ah,9,'chastext file',0Dh,0Ah
    db 'search for a string and quote it:',0Dh,0Ah,9,'chastext file search',0Dh,0Ah
    db 'replace string:',0Dh,0Ah,9,'chastext file search replace',0Dh,0Ah
    db 'Find or replace any string!',0Dh,0Ah,0
    
    ; About the chastelib variant
    
    ;instead of including chastelib16.asm as a header file
    ;I copy pasted it except that I excluded functions that were not used.
    ;Notably, the strint function is excluded because strint_32 is used instead
    
    ;start of chastelib
    
    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    ;this is my best putstring function for DOS because it uses call 40h of interrupt 21h
    ;this means that it works in a similar way to my Linux Assembly code
    ;the plan is to make both my DOS and Linux functions identical except for the size of registers involved
    
    putstring:
    
    push ax
    push bx
    push cx
    push dx
    
    mov bx,ax                  ;copy ax to bx for use as index register
    
    putstring_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx], byte 0           ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,1                   ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    ;this is the location in memory where digits are written to by the intstr function
    int_string db 16 dup '?' ;enough bytes to hold maximum size 16-bit binary integer
    int_string_end db 0 ;zero byte terminator for the integer string
    
    radix dw 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dw 8
    
    intstr:
    
    mov bx,int_string_end-1 ;find address of lowest digit(just before the newline 0Ah)
    mov cx,1
    
    digits_start:
    
    mov dx,0;
    div word [radix]
    cmp dx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add dx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub dx,10
    add dx,'A'
    
    save_digit:
    
    mov [bx],dl
    cmp ax,0
    jz intstr_end
    dec bx
    inc cx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp cx,[int_width]
    jnb end_zeros
    dec bx
    mov [bx],byte '0'
    inc cx
    jmp prefix_zeros
    end_zeros:
    
    mov ax,bx ; store string in ax for display later
    
    ret
    
    ;function to print string form of whatever integer is in ax
    ;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 ax
    push bx
    push cx
    push dx
    
    call intstr
    call putstring
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    ;the next utility functions simply print a space or a newline
    ;these help me save code when printing lots of things for debugging
    
    space db ' ',0
    line db 0Dh,0Ah,0
    
    putspace:
    push ax
    mov ax,space
    call putstring
    pop ax
    ret
    
    putline:
    push ax
    mov ax,line
    call putstring
    pop ax
    ret
    
    ;a function for printing a single character that is the value of al
    
    char: db 0,0
    
    putchar:
    push ax
    mov [char],al
    mov ax,char
    call putstring
    pop ax
    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
    
    putint_and_space:
    call putint
    call putspace
    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
    
    putint_and_line:
    call putint
    call putline
    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
    
    putstr_and_space:
    call putstring
    call putspace
    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
    
    putstr_and_line:
    call putstring
    call putline
    ret
    
    ;end of chastelib
    
    arg_string_index dw 0
    arg_string_end dw 0
    
    file_error_message db 'Could not open the file! Error number: ',0
    file_handle dw 0
    end_of_file db 'EOF',0
    
    ;where we will store data from the file
    bytes_read dw 0
    
    string_search dw 0 ; place to hold the search string pointer
    string_replace dw 0 ; place to hold the replacement string pointer
    
    byte_array db 0x80 dup 0
    
  • chastehex for Windows update

    I made an update to the chastehex program for Windows. I made it consistent with the behavior of the Linux assembly and C version of the same program. Now it will print the name of the file being opened, display text according to the current mode you are using, and then display EOF to indicate that the end of the file was reached.

    chastehex is a rather complex program because of the fact that it can read or write bytes at specific addresses if you give it the right arguments. If you give it only a filename as an argument, it will hex dump the entire file.

    This update doesn’t change the size of the Windows executable despite the fact that I removed a lot of code from the source than was no longer used. It still pads it to the nearest multiple of 512 bytes. The total size of the executable is 2560 bytes or 2 and a half kilobytes. Although it is bigger than the Linux version, it is still smaller than the compiled C version that behaves the same.

    Although I have said it before, I will mention that this program did not need to be written because the C version is the same. However, writing assembly code and optimizing it is very fun. I don’t usually write things for Windows but because Windows is the most popular desktop operating system and it always runs on an Intel CPU, this program will always work for the majority of computers in the world.

    I was able to translate my Linux version updates into the Windows version of the program because my chastelib library provides a layer that works the same on any OS. It does exactly what I wrote it to do. The programs chastehex and chastecmp are the first two tools and I am still planning what the next tool will be. I hope to make something else that assists me in the act of programming directly rather than just modifying and comparing the binary code I generate.

    main.asm

    format PE console
    include 'win32ax.inc'
    include 'chastelibw32.asm'
    
    main:
    
    mov [radix],16 ; Choose radix for integer output.
    mov [int_width],1
    
    ;get command line argument string
    call [GetCommandLineA]
    
    mov [arg_start],eax ;store start of arg string
    
    ;short routine to find the length of the string
    ;and whether arguments are present
    mov ebx,eax
    find_arg_length:
    cmp [ebx], byte 0
    jz found_arg_length
    inc ebx
    jmp find_arg_length
    found_arg_length:
    ;at this point, ebx has the address of last byte in string which contains a zero
    ;we will subtract to get and store the length of the string
    mov [arg_end],ebx
    sub ebx,eax
    mov eax,ebx
    mov [arg_length],eax
    
    ;this loop will filter the string, replacing all spaces with zero
    mov ebx,[arg_start]
    arg_filter:
    cmp byte [ebx],' '
    ja notspace ; if char is above space, leave it alone
    mov byte [ebx],0 ;otherwise it counts as a space, change it to a zero
    notspace:
    inc ebx
    cmp ebx,[arg_end]
    jnz arg_filter
    
    arg_filter_end:
    
    ;optionally print first arg (name of program)
    ;mov eax,[arg_start]
    ;call putstr_and_line
    
    ;get next arg (first one after name of program)
    call get_next_arg
    cmp eax,[arg_end]
    jz help
    
    mov [file_name],eax
    call putstr_and_line
    
    jmp open_sesame
    
    help:
    
    mov eax,help_message
    call putstring
    
    jmp main_end
    
    open_sesame:
    
    ;open a file with the CreateFileA function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
    
    push 0           ;NULL: We are not using a template file
    push 0x80        ;FILE_ATTRIBUTE_NORMAL
    push 3           ;OPEN_EXISTING
    push 0           ;NULL: No security attributes
    push 0           ;NULL: Share mode irrelevant. Only this program reads the file.
    push 0x10000000  ;GENERIC_ALL access mode (Read+Write)
    push [file_name] ;
    call [CreateFileA]
    
    ;check eax for file handle or error code
    ;call putint
    cmp eax,-1
    jnz file_ok
    
    mov eax,file_error_message
    call putstring
    call [GetLastError]
    call putint
    jmp main_end ;end program if the file was not opened
    
    ;this label is jumped to when the file is opened correctly
    file_ok:
    
    mov [file_handle],eax
    
    ;before we proceed, we also check for more arguments.
    
    ;get next arg (first one after name of program)
    call get_next_arg
    cmp eax,[arg_end]
    jz hexdump ;proceed to normal hex dump if no more args
    
    ;otherwise interpret the arg as a hex address to seek to
    
    call strint
    mov [file_offset],eax
    
    ;seek to address of file with SetFilePointer function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointer
    push 0             ;seek from beginning of file (SEEK_SET)
    push 0             ;NULL: We are not using a 64 bit address
    push [file_offset] ;where we are seeking to
    push [file_handle] ;seek within this file
    call [SetFilePointer]
    
    ;check for more args
    call get_next_arg
    cmp eax,[arg_end]
    jz read_one_byte ;proceed to read one byte mode
    
    ;otherwise, write the rest of the arguments as bytes to the file!
    write_bytes:
    call strint
    mov [byte_array],al
    
    ;write only 1 byte using Win32 WriteFile system call.
    push 0              ;Optional Overlapped Structure 
    push 0              ;Optionally Store Number of Bytes Written
    push 1              ;Number of bytes to write
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [WriteFile]
    
    mov eax,[file_offset]
    inc [file_offset]
    mov [int_width],8
    call putint_and_space
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint_and_line
    
    ;check for more args
    call get_next_arg
    cmp eax,[arg_end]
    jnz write_bytes
    ;continue write if the args still exist
    ;otherwise end program
    jmp main_end
    
    read_one_byte:
    
    ;read only 1 byte using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push 1              ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [ReadFile]
    
    cmp [bytes_read],1 
    jz print_byte ;if less than one bytes read, there is an error
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint_and_space
    mov eax,end_of_file
    call putstr_and_line
    
    jmp main_end
    
    print_byte:
    mov eax,[file_offset]
    mov [int_width],8
    call putint_and_space
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint_and_line
    
    jmp main_end
    
    hexdump:
    
    ;read bytes using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push 16             ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [ReadFile]     ;all the data is in place, do the write thing!
    
    mov eax,[bytes_read]
    ;call putint
    ;mov eax,byte_array
    ;call putstring
    
    cmp eax,0
    jnz read_ok ;if more than zero bytes read, proceed to display
    
    jmp eof_end
    
    read_ok:
    call print_bytes_row
    
    jmp hexdump
    
    print_EOF:
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint_and_space
    
    mov eax,end_of_file
    call putstr_and_line
    
    jmp main_end
    
    
    eof_end:
    ;before we end the program, let the user know End Of File was reached
    mov eax,end_of_file
    call putstr_and_line
    
    main_end:
    
    ;close the file
    push [file_handle]
    call [CloseHandle]
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    
    
    ;variables for displaying messages
    file_error_message db 'error: ',0
    end_of_file db 'EOF',0
    read_error_message db 'Failure during reading of file. Error number: ',0
    
    help_message db 'chastehex by Chastity White Rose',0Ah,0Ah
    db 'hexdump a file:',0Ah,0Ah,9,'chastehex file',0Ah,0Ah
    db 'read a byte:',0Ah,0Ah,9,'chastehex file address',0Ah,0Ah
    db 'write a byte:',0Ah,0Ah,9,'chastehex file address value',0Ah,0Ah
    db 'The file must exist',0Ah,0
    
    ;function to move ahead to the next art
    ;only works after the filter has been applied to turn all spaces into zeroes
    get_next_arg:
    mov ebx,[arg_start]
    find_zero:
    cmp byte [ebx],0
    jz found_zero
    inc ebx
    jmp find_zero ; this char is not zero, go to the next char
    found_zero:
    
    find_non_zero:
    cmp ebx,[arg_end]
    jz arg_finish ;if ebx is already at end, nothing left to find
    cmp byte [ebx],0
    jnz arg_finish ;if this char is not zero we have found the next string!
    inc ebx
    jmp find_non_zero ;otherwise, keep looking
    
    arg_finish:
    mov [arg_start],ebx ; save this index to variable
    mov eax,ebx ;but also save it to ax register for use
    ret
    ;we can know that there are no more arguments when
    ;the either [arg_start] or eax are equal to [arg_end]
    
    
    
    ;this function prints a row of hex bytes
    ;each row is 16 bytes
    print_bytes_row:
    mov eax,[file_offset]
    mov [int_width],8
    call putint_and_space
    
    mov ebx,byte_array
    mov ecx,[bytes_read]
    add [file_offset],ecx
    next_byte:
    mov eax,0
    mov al,[ebx]
    mov [int_width],2
    call putint_and_space
    
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_byte
    
    mov ecx,[bytes_read]
    pad_spaces:
    cmp ecx,0x10
    jz pad_spaces_end
    mov eax,space_three
    call putstring
    inc ecx
    jmp pad_spaces
    pad_spaces_end:
    
    ;optionally, print chars after hex bytes
    call print_bytes_row_text
    call putline
    
    ret
    
    space_three db '   ',0
    
    print_bytes_row_text:
    mov ebx,byte_array
    mov ecx,[bytes_read]
    next_char:
    mov eax,0
    mov al,[ebx]
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp al,0x20
    jb not_printable
    cmp al,0x7E
    ja not_printable
    
    printable:
    ;if char is in printable range,copy as is and proceed to next index
    jmp next_index
    
    not_printable:
    mov al,'.' ;otherwise replace with placeholder value
    
    next_index:
    mov [ebx],al
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_char
    mov [ebx],byte 0 ;make sure string is zero terminated
    
    mov eax,byte_array
    call putstring
    
    ret
    
    
    
    
    ;variables for managing arguments
    arg_start  dd ? ;start of arg string
    arg_end    dd ? ;address of the end of the arg string
    arg_length dd ? ;length of arg string
    arg_spaces dd ? ;how many spaces exist in the arg command line
    
    ;variables for managing file IO.
    file_name dd ?
    bytes_read dd ? ;how many bytes are read with ReadFile operation
    byte_array db 16 dup ?,0
    file_handle dd ?
    file_offset dd ?
    

    chastelibw32.asm

    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    ; function to print zero terminated string pointed to by register eax
    
    putstring:
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax ; copy eax to ebx as well. Now both registers have the address of the main_string
    
    putstring_strlen_start: ; this loop finds the lenge 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 ;ebx will now have correct number of bytes
    
    ;Write String using Win32 WriteFile system call.
    push 0              ;Optional Overlapped Structure 
    push 0              ;Optionally Store Number of Bytes Written
    push ebx            ;Number of bytes to write
    push eax            ;address of string to print
    push -11            ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle] ;use the above handle
    push eax            ;eax is return value of previous function
    call [WriteFile]    ;all the data is in place, do the write thing!
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret ; this is the end of the putstring function return to calling location
    
    ; This is the location in memory where digits are written to by the intstr function
    ; The string of bytes and settings such as the radix and width are global variables defined below.
    
    int_string db 32 dup '?' ;enough bytes to hold maximum size 32-bit binary integer
    
    int_string_end db 0 ;zero byte terminator for the integer string
    
    radix dd 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dd 8
    
    ;this function creates a string of the integer in eax
    ;it uses the above radix variable to determine base from 2 to 36
    ;it then loads eax with the address of the string
    ;this means that it can be used with the putstring function
    
    intstr:
    
    mov ebx,int_string_end-1 ;find address of lowest digit
    mov ecx,1
    
    digits_start:
    
    mov edx,0;
    div dword [radix]
    cmp edx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add edx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub edx,10
    add edx,'A'
    
    save_digit:
    
    mov [ebx],dl
    cmp eax,0
    jz intstr_end
    dec ebx
    inc ecx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp ecx,[int_width]
    jnb end_zeros
    dec ebx
    mov [ebx],byte '0'
    inc ecx
    jmp prefix_zeros
    end_zeros:
    
    mov eax,ebx ; now that the digits have been written to the string, display it!
    
    ret
    
    
    ; function to print string form of whatever integer is in eax
    ; The radix determines which number base the string form takes.
    ; Anything from 2 to 36 is a valid radix
    ; in practice though, only bases 2,8,10,and 16 will make sense to other programmers
    ; this function does not process anything by itself but calls the combination of my other
    ; functions in the order I intended them to be used.
    
    putint: 
    
    push eax
    push ebx
    push ecx
    push edx
    
    call intstr
    
    call putstring
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    ;this function converts a string pointed to by eax into an integer returned in eax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    
    strint:
    
    mov ebx,eax ;copy string address from eax to ebx because eax will be replaced soon!
    mov eax,0
    
    read_strint:
    mov ecx,0 ; zero ecx so only lower 8 bits are used
    mov cl,[ebx]
    inc ebx
    cmp cl,0 ; compare byte at address edx with 0
    jz strint_end ; if comparison was zero, this is the end of string
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp cl,'0'
    jb not_digit
    cmp cl,'9'
    ja not_digit
    
    ;but if it is a digit, then correct and process the character
    is_digit:
    sub cl,'0'
    jmp process_char
    
    not_digit:
    ;it isn't a digit, but it could be perhaps and alphabet character
    ;which is a digit in a higher base
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function
    jmp strint_end
    
    process_char:
    
    cmp ecx,[radix] ;compare char with radix
    jae strint_end ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov edx,0 ;zero edx because it is used in mul sometimes
    mul [radix]    ;mul eax with radix
    add eax,ecx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    
    
    
    ;the next utility functions simply print a space or a newline
    ;these help me save code when printing lots of things for debugging
    
    space db ' ',0
    line db 0Dh,0Ah,0
    
    putspace:
    push eax
    mov eax,space
    call putstring
    pop eax
    ret
    
    putline:
    push eax
    mov eax,line
    call putstring
    pop eax
    ret
    
    ;a function for printing a single character that is the value of al
    
    char: db 0,0
    
    putchar:
    push eax
    mov [char],al
    mov eax,char
    call putstring
    pop eax
    ret
    
    ;a small function just for the common operation
    ;printing an integer followed by a space
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    
    putint_and_space:
    call putint
    call putspace
    ret
    
    ;a small function just for the common operation
    ;printing an integer followed by a line feed
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    
    putint_and_line:
    call putint
    call putline
    ret
    
    ;a small function just for the common operation
    ;printing a string followed by a line feed
    ;this saves a few bytes in the assembled code
    ;by reducing the number of function calls in the main program
    ;it also means we don't need to include a newline in every string!
    
    putstr_and_line:
    call putstring
    call putline
    ret
    
    
  • Programming Updates

    This is my Chess blog and I want you to know I still play Chess every day but until I have inspiration to write about Chess, I have more programming news to share. I have been doing a TON of Assembly language programming as part of the new book I am writing on DOS programming. Chapter 8 includes some Linux examples to show people the similarity between writing assembly for Linux and how similar it is to DOS.

    When the book is finished, I will probably make a version of the book specifically for Linux because I have a lot more details to share.

    The following text is what happens when I “git pull” on my repository on my Windows PC. This shows all the files that were updated on my Linux computer over the past few weeks. I do all my coding on Linux but the power of git allows me to upload everything to github from Linux and then download it to Windows as a backup. I really do everything I can not to lose this code because it is my math soul at full power!

    Microsoft Windows [Version 10.0.26200.8037]
    (c) Microsoft Corporation. All rights reserved.

    C:\Users\chand\Documents\git\Chastity-Code-Cookbook>git pull
    remote: Enumerating objects: 252, done.
    remote: Counting objects: 100% (250/250), done.
    remote: Compressing objects: 100% (136/136), done.
    remote: Total 201 (delta 126), reused 137 (delta 63), pack-reused 0 (from 0)
    Receiving objects: 100% (201/201), 49.53 KiB | 551.00 KiB/s, done.
    Resolving deltas: 100% (126/126), completed with 36 local objects.
    From https://github.com/chastitywhiterose/Chastity-Code-Cookbook
    f5e931c..c46c7c9 main -> origin/main
    Updating f5e931c..c46c7c9
    Fast-forward
    …/chapter 7 examples/chastelib-C/chastelib.h | 119 ++++++—-
    …/chapter 7 examples/chastelib-C/main.c | 22 +-
    …/chapter 7 examples/chastelib-C/readme.md | 51 ++++
    …/chapter 7 examples/chastelib-DOS/chastelib.h | 143 ++++++++++++
    …/chapter 7 examples/chastelib-DOS/main.asm | 20 +-
    …/chapter 7 examples/chastelib-DOS/main.c | 48 ++++
    …/chapter 7 examples/chastelib-DOS/main.com | Bin 0 -> 443 bytes
    …/fasm_32-bit_putstring/main | Bin 0 -> 172 bytes
    …/fasm_32-bit_putstring/main.asm | 56 +++++
    …/fasm_32-bit_putstring}/makefile | 0
    …/fasm_64-bit_putstring/main | Bin 0 -> 224 bytes
    …/fasm_64-bit_putstring/main.asm | 58 +++++
    …/fasm_64-bit_putstring/makefile | 4 +
    …/gasm_64-bit_putstring/main | Bin 0 -> 4488 bytes
    …/gasm_64-bit_putstring/main.s | 52 +++++
    …/gasm_64-bit_putstring/makefile | 5 +
    …/nasm_32-bit_putstring/main | Bin 0 -> 4324 bytes
    …/nasm_32-bit_putstring/main.asm | 55 +++++
    …/nasm_32-bit_putstring/main.o | Bin 0 -> 688 bytes
    …/nasm_32-bit_putstring/makefile | 5 +
    …/nasm_64-bit_putstring/main | Bin 0 -> 4888 bytes
    …/nasm_64-bit_putstring/main.asm | 55 +++++
    …/nasm_64-bit_putstring/main.o | Bin 0 -> 928 bytes
    …/nasm_64-bit_putstring/makefile | 8 +
    code/asm/fasm/dos/chastelib-DOS/main-output.txt | 257 ———————
    code/asm/fasm/dos/chastelib-DOS/main.asm | 20 +-
    code/asm/fasm/linux-64/chaste-lib64/main | Bin 737 -> 0 bytes
    code/asm/fasm/linux-64/chaste-lib64/main.asm | 52 —–
    code/asm/fasm/linux-64/chastehex64/chastelib64.asm | 50 +++-
    code/asm/fasm/linux-64/chastehex64/main | Bin 1634 -> 1657 bytes
    …/{chaste-lib64 => chastelib64}/chasteio64.asm | 0
    …/{chaste-lib64 => chastelib64}/chastelib64.asm | 50 +++-
    code/asm/fasm/linux-64/chastelib64/main | Bin 0 -> 824 bytes
    code/asm/fasm/linux-64/chastelib64/main.asm | 64 +++++
    code/asm/fasm/linux-64/chastelib64/makefile | 4 +
    code/asm/fasm/linux/chastecmp/chastelib32.asm | 37 ++-
    code/asm/fasm/linux/chastecmp/main | Bin 0 -> 1024 bytes
    code/asm/fasm/linux/chastehex/chastelib32.asm | 25 +-
    code/asm/fasm/linux/chastelib/chastelib32.asm | 25 +-
    code/asm/fasm/linux/chastelib/main.asm | 16 +-
    code/asm/fasm/linux/fasm_32-bit_putstring/main | Bin 0 -> 172 bytes
    code/asm/fasm/linux/fasm_32-bit_putstring/main.asm | 56 +++++
    code/asm/fasm/linux/fasm_32-bit_putstring/makefile | 4 +
    code/asm/fasm/linux/fasm_64-bit_putstring/main | Bin 0 -> 224 bytes
    code/asm/fasm/linux/fasm_64-bit_putstring/main.asm | 58 +++++
    code/asm/fasm/linux/fasm_64-bit_putstring/makefile | 4 +
    …/chastelib-diff-32vs64 (2026)/chastelib32.asm | 252 ++++++++++++++++++++
    …/chastelib-diff-32vs64 (2026)/chastelib64.asm | 254 ++++++++++++++++++++
    …/mixed-bits/chastelib-diff-32vs64 (2026)/main32 | Bin 0 -> 672 bytes
    …/chastelib-diff-32vs64 (2026)/main32.asm | 64 +++++
    …/mixed-bits/chastelib-diff-32vs64 (2026)/main64 | Bin 0 -> 824 bytes
    …/chastelib-diff-32vs64 (2026)/main64.asm | 64 +++++
    …/chastelib-diff-32vs64 (2026)/makefile | 18 ++
    …/chastelib32-test | Bin
    …/chastelib32-test.asm | 0
    …/chastelib32.asm | 0
    …/chastelib64-test | Bin
    …/chastelib64-test.asm | 0
    …/chastelib64.asm | 0
    …/makefile | 0
    code/asm/gas/gasm_32-bit_putstring/main | Bin 0 -> 4380 bytes
    code/asm/gas/gasm_32-bit_putstring/main.s | 53 +++++
    code/asm/gas/gasm_32-bit_putstring/makefile | 5 +
    code/asm/gas/gasm_64-bit_putstring/main | Bin 0 -> 4488 bytes
    code/asm/gas/gasm_64-bit_putstring/main.s | 53 +++++
    code/asm/gas/gasm_64-bit_putstring/makefile | 5 +
    code/asm/gas/hello/hello | Bin 0 -> 4448 bytes
    code/asm/gas/hello/hello.s | 29 +++
    code/asm/gas/hello/makefile | 5 +
    code/asm/nasm/nasm_32-bit_putstring/main | Bin 0 -> 4324 bytes
    code/asm/nasm/nasm_32-bit_putstring/main.asm | 55 +++++
    code/asm/nasm/nasm_32-bit_putstring/main.o | Bin 0 -> 688 bytes
    code/asm/nasm/nasm_32-bit_putstring/makefile | 5 +
    code/asm/nasm/nasm_64-bit_putstring/main | Bin 0 -> 4888 bytes
    code/asm/nasm/nasm_64-bit_putstring/main.asm | 55 +++++
    code/asm/nasm/nasm_64-bit_putstring/main.o | Bin 0 -> 928 bytes
    code/asm/nasm/nasm_64-bit_putstring/makefile | 8 +
    code/c/ncurses/ncurses_chastelib/chastelib.h | 143 ++++++++++++
    …/ncurses/ncurses_chastelib/chastelib_ncurses.h | 109 ———
    code/c/ncurses/ncurses_chastelib/main | Bin 16936 -> 17104 bytes
    code/c/ncurses/ncurses_chastelib/main.c | 28 +–
    code/c/std/chastelib_core/chastelib.h | 56 +++–
    code/c/std/chastelib_core/main | Bin 16416 -> 0 bytes
    code/c/std/chastelib_core/main.c | 12 +-
    code/c/std/chastelib_extensions/float/chastelib.h | 87 ++++—
    code/c/std/chastelib_extensions/float/main | Bin 16432 -> 16512 bytes
    code/c/std/chastelib_extensions/format/chastelib.h | 87 ++++—
    code/c/std/chastelib_extensions/format/main | Bin 16400 -> 16480 bytes
    code/c/std/chastelib_extensions/input/chastelib.h | 87 ++++—
    …/chastelib_extensions/input/chastelib_input.h | 4 +-
    code/c/std/chastelib_extensions/input/main | Bin 16536 -> 16616 bytes
    …/c/std/chastelib_extensions/ncurses/chastelib.h | 143 ++++++++++++
    …/ncurses/chastelib_ncurses.h | 109 ———
    code/c/std/chastelib_extensions/ncurses/main | Bin 16936 -> 17104 bytes
    code/c/std/chastelib_extensions/ncurses/main.c | 28 +–
    code/cpp/chastelib_pp_core/chastelib.hpp | 49 ++–
    code/cpp/chastelib_pp_core/main | Bin 16360 -> 16440 bytes
    code/cpp/chastelib_pp_core/main.cpp | 22 +-
    ebook.epub | Bin 85989 -> 85986 bytes
    99 files changed, 2494 insertions(+), 818 deletions(-)
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-C/readme.md
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-DOS/chastelib.h
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-DOS/main.c
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-DOS/main.com
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_32-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_32-bit_putstring/main.asm
    rename code/asm/fasm/{linux-64/chaste-lib64 => dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_32-bit_putstring}/makefile (100%)
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_64-bit_putstring/makefile
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/gasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/gasm_64-bit_putstring/main.s
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/gasm_64-bit_putstring/makefile
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/main.o
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/makefile
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/main.o
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/makefile
    delete mode 100644 code/asm/fasm/dos/chastelib-DOS/main-output.txt
    delete mode 100755 code/asm/fasm/linux-64/chaste-lib64/main
    delete mode 100644 code/asm/fasm/linux-64/chaste-lib64/main.asm
    rename code/asm/fasm/linux-64/{chaste-lib64 => chastelib64}/chasteio64.asm (100%)
    rename code/asm/fasm/linux-64/{chaste-lib64 => chastelib64}/chastelib64.asm (76%)
    create mode 100755 code/asm/fasm/linux-64/chastelib64/main
    create mode 100644 code/asm/fasm/linux-64/chastelib64/main.asm
    create mode 100644 code/asm/fasm/linux-64/chastelib64/makefile
    create mode 100755 code/asm/fasm/linux/chastecmp/main
    create mode 100755 code/asm/fasm/linux/fasm_32-bit_putstring/main
    create mode 100644 code/asm/fasm/linux/fasm_32-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/linux/fasm_32-bit_putstring/makefile
    create mode 100755 code/asm/fasm/linux/fasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/linux/fasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/linux/fasm_64-bit_putstring/makefile
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/chastelib32.asm
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/chastelib64.asm
    create mode 100755 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main32
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main32.asm
    create mode 100755 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main64
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main64.asm
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/makefile
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib32-test (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib32-test.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib32.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib64-test (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib64-test.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib64.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/makefile (100%)
    create mode 100755 code/asm/gas/gasm_32-bit_putstring/main
    create mode 100644 code/asm/gas/gasm_32-bit_putstring/main.s
    create mode 100644 code/asm/gas/gasm_32-bit_putstring/makefile
    create mode 100755 code/asm/gas/gasm_64-bit_putstring/main
    create mode 100644 code/asm/gas/gasm_64-bit_putstring/main.s
    create mode 100644 code/asm/gas/gasm_64-bit_putstring/makefile
    create mode 100755 code/asm/gas/hello/hello
    create mode 100644 code/asm/gas/hello/hello.s
    create mode 100644 code/asm/gas/hello/makefile
    create mode 100755 code/asm/nasm/nasm_32-bit_putstring/main
    create mode 100644 code/asm/nasm/nasm_32-bit_putstring/main.asm
    create mode 100644 code/asm/nasm/nasm_32-bit_putstring/main.o
    create mode 100644 code/asm/nasm/nasm_32-bit_putstring/makefile
    create mode 100755 code/asm/nasm/nasm_64-bit_putstring/main
    create mode 100644 code/asm/nasm/nasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/nasm/nasm_64-bit_putstring/main.o
    create mode 100644 code/asm/nasm/nasm_64-bit_putstring/makefile
    create mode 100644 code/c/ncurses/ncurses_chastelib/chastelib.h
    delete mode 100644 code/c/ncurses/ncurses_chastelib/chastelib_ncurses.h
    delete mode 100755 code/c/std/chastelib_core/main
    create mode 100644 code/c/std/chastelib_extensions/ncurses/chastelib.h
    delete mode 100644 code/c/std/chastelib_extensions/ncurses/chastelib_ncurses.h

  • AAA DOS Chapter 7: Translating Assembly to Other Programming Languages

    This post is a newly written chapter for my recently published book “Assembly Arithmetic Algorithms:16-bit DOS Edition“. I published it on Leanpub which is a place you can publish books even before they are completely finished. I already had a couple of sales of this book and I was surprised because DOS is pretty much a dead platform except for hobbyists who need an easy environment to learn assembly language with. I still have a few more chapters I plan to add to it but this post is one of the best of them. A lot of my best documented code is here.

    Chapter 7: Translating Assembly to Other Programming Languages

    This chapter is going to be a weird one, because most people don’t start with assembly language before moving to higher level languages. In fact most people would probably recommend against Assembly as a first programming language.

    But for the purpose of this chapter alone, I will be assuming that you have been following the first 6 chapters of this Assembly book and want to know how this knowledge can be used to translate the Assembly into other languages like C and C++. This is actually very easy to do because the other languages are easier and have built in functions for you to use.

    So what I did is write a test suite program. It makes use of the core 4 of my chastelib functions (putstring,putint,intstr,strint) as well as some other utility functions just for displaying single characters, lines, and spaces.

    In this chapter, I will be including the entire source code of the main program “main.asm” as well as “chastelib16.asm” which is the included file containing all the useful output functions. Snippets of these have been included throughout the book but by including them all in this chapter, you can be sure that you have the most updated and commented version of the source code. This will become more important later when I show you the C equivalent program.

    main.asm

    org 100h
    
    main:
    
    mov eax,main_string
    call putstring
    
    mov word[radix],16           ; can choose radix for integer output!
    mov word[int_width],1
    mov byte[int_newline],0
    
    mov ax,input_string_int  ;address of input string to convert to integer using current radix
    call strint              ;call strint to return the string in eax register
    mov bx,ax                ;bx=ax (copy the converted value returned in ax to bx)
    
    mov ax,0
    loop1:
    
    mov word[radix],2            ;set radix to binary
    mov word[int_width],8        ;width of 8 for maximum 8 bits
    call putint
    call putspace
    mov word[radix],16           ;set radix to hexadecimal
    mov word[int_width],2        ;width of 8 for maximum 8 bits
    call putint
    call putspace
    mov word[radix],10           ;set radix to decimal (what humans read)
    mov word[int_width],3        ;width of 8 for maximum 8 bits
    call putint
    
    cmp al,0x20
    jb not_char
    cmp al,0x7E
    ja not_char
    
    call putspace
    call putchar
    
    not_char:                ;jump here if character is outside range to print
    
    call putline
    
    inc ax
    cmp ax,bx;
    jnz loop1
    
    mov ax,4C00h ;DOS system call number ah=0x4C to exit program with ah=0x00 as return value
    int 21h      ;DOS interrupt to exit the program with numbers on previous line
    
    ;A string to test if output works
    main_string db 'This program is the official test suite for the DOS Assembly version of chastelib.',0Ah,0
    
    ;test string of integer for input
    input_string_int db '100',0
    
    include 'chastelib16.asm' ; use %include if assembling with NASM instead of FASM.
    
    ; This 16 bit DOS Assembly source has been formatted for the FASM assembler.
    ; In order to run it, you will need the DOSBOX emulator or something similar.
    ; First, assemble it into a binary file. FASM will automatically add
    ; the .com extension because of the "org 100h" command.
    ;
    ;	fasm main.asm
    ;
    ; Then you will need to open DOSBOX and mount the folder that it is in.
    ; For example:
    ;
    ;	mount c ~/.dos
    ;	c:
    ;
    ;	Then, you will be able to just run the main.com.
    ;
    ;	main
    

    chastelib16.asm

    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    ;this is my best putstring function for DOS because it uses call 40h of interrupt 21h
    ;this means that it works in a similar way to my Linux Assembly code
    ;the plan is to make both my DOS and Linux functions identical except for the size of registers involved
    
    stdout dw 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push ax
    push bx
    push cx
    push dx
    
    mov bx,ax                  ;copy ax to bx for use as index register
    
    putstring_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp byte[bx],0             ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,[stdout]            ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    
    
    ;this is the location in memory where digits are written to by the intstr function
    
    int_string db 16 dup '?' ;enough bytes to hold maximum size 16-bit binary integer
    
    ;this is the end of the integer string optional line feed and terminating zero
    ;clever use of this label can change the ending to be a different character when needed 
    
    int_newline db 0Dh,0Ah,0 ;the proper way to end a line in DOS/Windows
    
    radix dw 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dw 8
    
    intstr:
    
    mov bx,int_newline-1 ;find address of lowest digit(just before the newline 0Ah)
    mov cx,1
    
    digits_start:
    
    mov dx,0;
    div word [radix]
    cmp dx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add dx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub dx,10
    add dx,'A'
    
    save_digit:
    
    mov [bx],dl
    cmp ax,0
    jz intstr_end
    dec bx
    inc cx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp cx,[int_width]
    jnb end_zeros
    dec bx
    mov byte[bx], '0'
    inc cx
    jmp prefix_zeros
    end_zeros:
    
    mov ax,bx ; store string in ax for display later
    
    ret
    
    
    
    ;function to print string form of whatever integer is in ax
    ;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 ax
    push bx
    push cx
    push dx
    
    call intstr
    call putstring
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    
    
    
    
    
    
    
    ;this function converts a string pointed to by ax into an integer returned in ax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    
    strint:
    
    mov bx,ax ;copy string address from ax to bx because ax will be replaced soon!
    mov ax,0
    
    read_strint:
    mov cx,0 ; zero cx so only lower 8 bits are used
    mov cl,[bx] ;copy byte/character at address bx to cl register (lowest part of cx)
    inc bx ;increment bx to be ready for next character
    cmp cl,0 ; compare this byte 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 decimal digit, but it could be perhaps an alphabet character
    ;which could be a digit in a higher base like hexadecimal
    ;we will check for that possibility next
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function
    jmp strint_end
    
    process_char:
    
    cmp cx,[radix] ;compare char with radix
    jae strint_end ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov dx,0 ;zero dx because it is used in mul sometimes
    mul word [radix]    ;mul ax with radix
    add ax,cx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    
    
    
    ;returns in al register a character from the keyboard
    getchr:
    
    mov ah,1
    int 21h
    
    ret
    
    ;the next utility functions simply print a space or a newline
    ;these help me save code when printing lots of things for debugging
    
    space db ' ',0
    line db 0Dh,0Ah,0
    
    putspace:
    push ax
    mov ax,space
    call putstring
    pop ax
    ret
    
    putline:
    push ax
    mov ax,line
    call putstring
    pop ax
    ret
    
    ;a function for printing a single character that is the value of al
    
    char: db 0,0
    
    putchar:
    push ax
    mov [char],al
    mov ax,char
    call putstring
    pop ax
    ret
    

    Now that you have the full source code. You can either copy and paste it from the PDF or epub edition (if you purchased the Leanpub edition) or you can download it directly from the github repository I have linked to at least twice in this book already.

    But you don’t even have to assembly and run it to see what it does because I am going to show you the entire output that it generates!

    Assembly Test Suite Output

    This program is the official test suite for the DOS Assembly version of chastelib.
    00000000 00 000
    00000001 01 001
    00000010 02 002
    00000011 03 003
    00000100 04 004
    00000101 05 005
    00000110 06 006
    00000111 07 007
    00001000 08 008
    00001001 09 009
    00001010 0A 010
    00001011 0B 011
    00001100 0C 012
    00001101 0D 013
    00001110 0E 014
    00001111 0F 015
    00010000 10 016
    00010001 11 017
    00010010 12 018
    00010011 13 019
    00010100 14 020
    00010101 15 021
    00010110 16 022
    00010111 17 023
    00011000 18 024
    00011001 19 025
    00011010 1A 026
    00011011 1B 027
    00011100 1C 028
    00011101 1D 029
    00011110 1E 030
    00011111 1F 031
    00100000 20 032  
    00100001 21 033 !
    00100010 22 034 "
    00100011 23 035 #
    00100100 24 036 $
    00100101 25 037 %
    00100110 26 038 &
    00100111 27 039 '
    00101000 28 040 (
    00101001 29 041 )
    00101010 2A 042 *
    00101011 2B 043 +
    00101100 2C 044 ,
    00101101 2D 045 -
    00101110 2E 046 .
    00101111 2F 047 /
    00110000 30 048 0
    00110001 31 049 1
    00110010 32 050 2
    00110011 33 051 3
    00110100 34 052 4
    00110101 35 053 5
    00110110 36 054 6
    00110111 37 055 7
    00111000 38 056 8
    00111001 39 057 9
    00111010 3A 058 :
    00111011 3B 059 ;
    00111100 3C 060 <
    00111101 3D 061 =
    00111110 3E 062 >
    00111111 3F 063 ?
    01000000 40 064 @
    01000001 41 065 A
    01000010 42 066 B
    01000011 43 067 C
    01000100 44 068 D
    01000101 45 069 E
    01000110 46 070 F
    01000111 47 071 G
    01001000 48 072 H
    01001001 49 073 I
    01001010 4A 074 J
    01001011 4B 075 K
    01001100 4C 076 L
    01001101 4D 077 M
    01001110 4E 078 N
    01001111 4F 079 O
    01010000 50 080 P
    01010001 51 081 Q
    01010010 52 082 R
    01010011 53 083 S
    01010100 54 084 T
    01010101 55 085 U
    01010110 56 086 V
    01010111 57 087 W
    01011000 58 088 X
    01011001 59 089 Y
    01011010 5A 090 Z
    01011011 5B 091 [
    01011100 5C 092 \
    01011101 5D 093 ]
    01011110 5E 094 ^
    01011111 5F 095 _
    01100000 60 096 `
    01100001 61 097 a
    01100010 62 098 b
    01100011 63 099 c
    01100100 64 100 d
    01100101 65 101 e
    01100110 66 102 f
    01100111 67 103 g
    01101000 68 104 h
    01101001 69 105 i
    01101010 6A 106 j
    01101011 6B 107 k
    01101100 6C 108 l
    01101101 6D 109 m
    01101110 6E 110 n
    01101111 6F 111 o
    01110000 70 112 p
    01110001 71 113 q
    01110010 72 114 r
    01110011 73 115 s
    01110100 74 116 t
    01110101 75 117 u
    01110110 76 118 v
    01110111 77 119 w
    01111000 78 120 x
    01111001 79 121 y
    01111010 7A 122 z
    01111011 7B 123 {
    01111100 7C 124 |
    01111101 7D 125 }
    01111110 7E 126 ~
    01111111 7F 127
    10000000 80 128
    10000001 81 129
    10000010 82 130
    10000011 83 131
    10000100 84 132
    10000101 85 133
    10000110 86 134
    10000111 87 135
    10001000 88 136
    10001001 89 137
    10001010 8A 138
    10001011 8B 139
    10001100 8C 140
    10001101 8D 141
    10001110 8E 142
    10001111 8F 143
    10010000 90 144
    10010001 91 145
    10010010 92 146
    10010011 93 147
    10010100 94 148
    10010101 95 149
    10010110 96 150
    10010111 97 151
    10011000 98 152
    10011001 99 153
    10011010 9A 154
    10011011 9B 155
    10011100 9C 156
    10011101 9D 157
    10011110 9E 158
    10011111 9F 159
    10100000 A0 160
    10100001 A1 161
    10100010 A2 162
    10100011 A3 163
    10100100 A4 164
    10100101 A5 165
    10100110 A6 166
    10100111 A7 167
    10101000 A8 168
    10101001 A9 169
    10101010 AA 170
    10101011 AB 171
    10101100 AC 172
    10101101 AD 173
    10101110 AE 174
    10101111 AF 175
    10110000 B0 176
    10110001 B1 177
    10110010 B2 178
    10110011 B3 179
    10110100 B4 180
    10110101 B5 181
    10110110 B6 182
    10110111 B7 183
    10111000 B8 184
    10111001 B9 185
    10111010 BA 186
    10111011 BB 187
    10111100 BC 188
    10111101 BD 189
    10111110 BE 190
    10111111 BF 191
    11000000 C0 192
    11000001 C1 193
    11000010 C2 194
    11000011 C3 195
    11000100 C4 196
    11000101 C5 197
    11000110 C6 198
    11000111 C7 199
    11001000 C8 200
    11001001 C9 201
    11001010 CA 202
    11001011 CB 203
    11001100 CC 204
    11001101 CD 205
    11001110 CE 206
    11001111 CF 207
    11010000 D0 208
    11010001 D1 209
    11010010 D2 210
    11010011 D3 211
    11010100 D4 212
    11010101 D5 213
    11010110 D6 214
    11010111 D7 215
    11011000 D8 216
    11011001 D9 217
    11011010 DA 218
    11011011 DB 219
    11011100 DC 220
    11011101 DD 221
    11011110 DE 222
    11011111 DF 223
    11100000 E0 224
    11100001 E1 225
    11100010 E2 226
    11100011 E3 227
    11100100 E4 228
    11100101 E5 229
    11100110 E6 230
    11100111 E7 231
    11101000 E8 232
    11101001 E9 233
    11101010 EA 234
    11101011 EB 235
    11101100 EC 236
    11101101 ED 237
    11101110 EE 238
    11101111 EF 239
    11110000 F0 240
    11110001 F1 241
    11110010 F2 242
    11110011 F3 243
    11110100 F4 244
    11110101 F5 245
    11110110 F6 246
    11110111 F7 247
    11111000 F8 248
    11111001 F9 249
    11111010 FA 250
    11111011 FB 251
    11111100 FC 252
    11111101 FD 253
    11111110 FE 254
    11111111 FF 255
    

    main.c (The C Test Suite)

    #include <stdio.h>
    #include <stdlib.h>
    #include "chastelib.h"
    
    int main(int argc, char *argv[])
    {
     int a=0,b;
    
     radix=16;
     int_width=1;
    
     putstring("This program is the official test suite for the C version of chastelib.\n");
    
     b=strint("100");
     while(a<b)
     {
      radix=2;
      int_width=8;
      putint(a);
      putstring(" ");
      radix=16;
      int_width=2;
      putint(a);
      putstring(" ");
      radix=10;
      int_width=3;
      putint(a);
    
      if(a>=0x20 && a<=0x7E)
      {
       putstring(" ");
       putchar(a);
      }
    
      putstring("\n");
      a+=1;
     }
      
     return 0;
    }
    

    The C version above does the exact same steps as the assembly version, but is calling functions that do very much the same steps as the assembly version. I will show you the contents of the included file “chastelib.h” next. Be prepared for a slightly less painful mess of code! However, much like the Assembly version it is heavily commented to help with understanding it.

    chastelib.h (The C chastelib library)

    /*
     This file is a library of functions written by Chastity White Rose. The functions are for converting strings into integers and integers into strings.
     I did it partly for future programming plans and also because it helped me learn a lot in the process about how pointers work
     as well as which features the standard library provides, and which things I need to write my own functions for.
    
     As it turns out, the integer output routines for C are too limited for my tastes. This library corrects this problem.
     Using the global variables and functions in this file, integers can be output in bases/radixes 2 to 36
    */
    
    /*
     These two lines define a static array with a size big enough to store the digits of an integer, including padding it with extra zeroes.
     The integer conversion function always references a pointer to this global string, and this allows other standard library functions
     such as printf to display the integers to standard output or even possibly to files.
    */
    
    #define usl 32 /*usl stands for Unsigned String Length*/
    char int_string[usl+1]; /*global string which will be used to store string of integers. Size is usl+1 for terminating zero*/
    
     /*radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal*/
    int radix=2;
    /*default minimum digits for printing integers*/
    int int_width=1;
    
    /*
    This function is one that I wrote because the standard library can display integers as decimal, octal, or hexadecimal, but not any other bases(including binary, which is my favorite).
    My function corrects this, and in my opinion, such a function should have been part of the standard library, but I'm not complaining because now I have my own, which I can use forever!
    More importantly, it can be adapted for any programming language in the world if I learn the basics of that language.
    */
    
    char *intstr(unsigned int i)
    {
     int width=0;
     char *s=int_string+usl;
     *s=0;
     while(i!=0 || width<int_width)
     {
      s--;
      *s=i%radix;
      i/=radix;
      if(*s<10){*s+='0';}
      else{*s=*s+'A'-10;}
      width++;
     }
     return s;
    }
    
    /*
     This function prints a string using fwrite.
     This algorithm is the best C representation of how my Assembly programs also work.
     Its true purpose is to be used in the putint function for conveniently printing integers, 
     but it can print any valid string.
    */
    
    void putstring(const char *s)
    {
     int c=0;
     const char *p=s;
     while(*p++){c++;} 
     fwrite(s,1,c,stdout);
    }
    
    /*
     This function uses both intstr and putstring to print an integer in the currently selected radix and width.
    */
    
    void putint(unsigned int i)
    {
     putstring(intstr(i));
    }
    
    /*
     This function is my own replacement for the strtol function from the C standard library.
     I didn't technically need to make this function because the functions from stdlib.h can already convert strings from bases 2 to 36 into integers.
     However, my function is simpler because it only requires 2 arguments instead of three, and it also does not handle negative numbers.
    I have never needed negative integers, but if I ever do, I can use the standard functions or write my own in the future.
    */
    
    int strint(const char *s)
    {
     int i=0;
     char c;
     if( radix<2 || radix>36 ){printf("Error: radix %i is out of range!\n",radix);}
     while( *s == ' ' || *s == '\n' || *s == '\t' ){s++;} /*skip whitespace at beginning*/
     while(*s!=0)
     {
      c=*s;
      if( c >= '0' && c <= '9' ){c-='0';}
      else if( c >= 'A' && c <= 'Z' ){c-='A';c+=10;}
      else if( c >= 'a' && c <= 'z' ){c-='a';c+=10;}
      else if( c == ' ' || c == '\n' || c == '\t' ){break;}
      else{printf("Error: %c is not an alphanumeric character!\n",c);break;}
      if(c>=radix){printf("Error: %c is not a valid character for radix %i\n",*s,radix);break;}
      i*=radix;
      i+=c;
      s++;
     }
     return i;
    }
    
    /*
     Those four functions above are the core of chastelib.
     While there may be extensions written for specific programs, these functions are essential for absolutely every program I write.
     
     The only reason you would not need them is if you only output numbers in decimal or hexadecimal, because printf in C can do all that just fine.
     However, the reason my core functions are superior to printf is that printf and its family of functions require the user to memorize all the arcane symbols for format specifiers.
     
     The core functions are primarily concerned with standard output and the conversion of strings and integers. They do not deal with input from the keyboard or files. A separate extension will be written for my programs that need these features.
    */
    

    Now that you have witnessed the largest dump of code to ever be included in a chapter of a book, I want you to look it over carefully and you will notice that there is almost direct equivalence between the Assembly version and the C version.

    Here is a detailed breakdown of how both versions operate despite the language syntax looking completel different.

    • putstring finds the terminating zero to calculate string length and prints that length of bytes. It achieves this by using the fwrite function which is part of the standard library. Just like the “ah=40h” DOS call, it must be given the arguments to say “Start at this address and write exactly this number of bytes to standard output!”. It also uses tons of pointer arithmetic in both the C and assembly versions. In this case, I would argue that the Assembly version might be easier to read than the C version. C lets a person create some weird looking code with the syntax used for pointers

    • intstr converts an integer into a string at a specific predetermined address and then returns a pointer to this address from the function. In both the C and Assembly version, the process of repeated division by the radix (also known as number base) while the integer is above zero or the string has not reached the minimum width or length I want the string to have. It will prefix the string with extra zeros just so it lines up perfectly as you say in the output earlier in this chapter.

    • putint is merely a convenience function the calls intstr and then putstr to convert and print an integer in one step. Functions are designed to repeat frequent operations to reduce code size and save programmer time. Though to be honest, if your time was valuable to you, you probably wouldn’t be reading a DOS assembly language book. Thanks for reading my book anyway!

    • strint does the opposite of intstr as you might guess from its name. It converts a string into an integer. Its usefulness is not fully obvious here because the example program reads a predefined string. Ordinarily, you would get user input from either the keyboard during the program or from command line arguments passed to the program before it starts. One small piece of advice though, if you want to accept user input, C is a better language than Assembly because I haven’t been successful in getting anyone to assembly and run my DOS assembly programs anyway!

    You probably noticed other functions like putchar, putline, and putspace. I made these convenience functions in assembly because there are times when you need to print a character to separate numbers. Usually spaces and newlines are the most important. The putchar function exists in the C standard library since at least 1989 and probably much earlier.

    Portable Assembly Language

    C has been called a portable assembly language because C compilers translate C code into assembly language and then link it with the precompiled functions in other libraries. In short, they do the opposite process of what I have done in this chapter. I wrote these string and integer output routines because they didn’t exist in assembly language by default.

    C already has printf,putchar, and fwrite. These are more than enough to handle outputting text including numbers without having to use the functions I have written. But in any case, I provided them in this chapter for helping people understand how these operations are done.

    But when you are trying to write programs for DOS, assembly is still better because there are not enough easy to find C compilers that still work in 16 bit DOS mode. There was one made by the company Borland known as Turbo C. If you are lucky enough to find it on the internet and get it running, you might enjoy it.

    C++ is a programming language that came after C and includes everything C has plus more. However, this book is about assembly and this chapter was but a brief introduction to the idea of translating assembly language into C for the purpose of having something portable to all platforms.

    My other book, Chastity’s Code Cookbook uses mostly C code as an introduction to programming. If you liked this chapter, consider reading it for more nerdy programming content.

  • Chastity Windows Reverse Engineering Notes

    The Windows version of FASM includes header files for the Windows API. It also includes some examples, but not a single one of them were a simple “Hello World” console program.

    Fortunately, I was able to find one that actually assembled and ran on the FASM forum. Below is the source code.

    format PE console
    include 'win32ax.inc'
    .code
    start:
    invoke  WriteConsole, <invoke GetStdHandle,STD_OUTPUT_HANDLE>,"Hello World!",12,0
    invoke  ExitProcess,0
    .end start
    

    To get it working required me to keep the include files in a location I remembered and set the include environment variable. I found this out from the FASM Windows documentation.

    set include=C:\fasm\INCLUDE

    However, there was another problem. The example that I found online and got working uses macros, most specifically, one called “invoke”. While this works if you include the headers, it hides the details of what is actually happening. Therefore, I decided to reverse engineer the process by using NOP instructions to sandwich the bytes of machine code.

    90 hex is the byte for NOP (No OPeration). So to extract the macro call that exits the program, I use this.

    db 10h dup 90h
    invoke  ExitProcess,0
    db 10h dup 90h
    

    Then I disassemble the executable and find the actual instructions given.

    ndisasm main.exe -b 32 > disasm.txt

    As simple as this method is, it actually works. For example, this output is given as part of the output.

    0000022D  90                nop
    0000022E  90                nop
    0000022F  90                nop
    00000230  90                nop
    00000231  90                nop
    00000232  90                nop
    00000233  90                nop
    00000234  90                nop
    00000235  90                nop
    00000236  90                nop
    00000237  90                nop
    00000238  90                nop
    00000239  90                nop
    0000023A  90                nop
    0000023B  90                nop
    0000023C  90                nop
    0000023D  6A00              push dword 0x0
    0000023F  FF1548204000      call dword near [0x402048]
    00000245  90                nop
    00000246  90                nop
    00000247  90                nop
    00000248  90                nop
    00000249  90                nop
    0000024A  90                nop
    0000024B  90                nop
    0000024C  90                nop
    0000024D  90                nop
    0000024E  90                nop
    0000024F  90                nop
    00000250  90                nop
    00000251  90                nop
    00000252  90                nop
    00000253  90                nop
    00000254  90                nop
    

    There can be no mistake that it is that location between the NOPs where the relevant code is. Therefore, I replaced the macro that exits the program with this.

    ;Exit the process with code 0
     push 0
     call [ExitProcess]
    

    What I learned

    As I repeated the same process for the other macros, I found that the way system calls in Windows work is that the numbers are pushed onto the stack in the reverse order they are needed. I was able to decode the macros and get a working program without the use of “invoke”. Here is the full source!

    format PE console
    include 'win32ax.inc'
    
    main:
    
    ;Write 13 bytes from a string to standard output
    push 0              ;this must be zero. I have no idea why!  
    push 13             ;number of bytes to write
    push main_string    ;address of string to print
    push -11            ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle] ;use the above handle
    push eax            ;eax is return value of previous function
    call [WriteConsole] ;all the data is in place, do the write thing!
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    main_string db 'Hello World!',0Ah
    

    I don’t know much about the Windows API, but I did discover some helpful information when I searched the names of these functions that were part of the original macros.

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

    Why I did this

    You might wonder why I even bothered to get a working Windows API program in Assembly Language. After all, I am a Linux user to the extreme. However, since Windows is the most used operating system for the average person, I figured that if I write any useful programs in Assembly for 32-bit Linux, I can probably port them over to Windows by changing just a few things.

    Since my toy programs are designed to write text to the console anyway and I don’t do GUI stuff unless I am programming a game in C with SDL, I now have enough information from this small Hello World example to theoretically write anything to the console that I might want to in an official Windows executable.

    Obviously I need to learn a lot more for bigger programs but this is the first Assembly program I have ever gotten working for Windows, despite my great success with DOS and Linux, which are easier because they are better documented and ARE TAUGHT BETTER by others. People programming Assembly in Windows have been ruined by macros which hide the actual instructions being used. As I learn how these things work, I will be sure to pass on the information to others!

  • DOS Assembly putstring function

    This is a small program which uses the putstring function I wrote. This function is one of 4 ultimate functions I have created which make up “chastelib”. DOS programming is simpler than Windows and is not that different from Linux in that system calls are done with an interrupt.

    org 100h
    
    main:
    
    mov ax,text
    call putstring
    
    mov ax,4C00h
    int 21h
    
    text db 'Hello World!',0Dh,0Ah,0
    
    ;This section is for the putstring function I wrote.
    ;It will print any zero terminated string that register ax points to
    
    stdout dw 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push ax
    push bx
    push cx
    push dx
    
    mov bx,ax                  ;copy ax to bx for use as index register
    
    putstring_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx], byte 0           ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,[stdout]            ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    

    Anyone can assemble and run this source code, but you will need a DOS emulator like DOSBox in order for it to work. In fact, I have a video showing me assembling and running it inside of DOSBox.

    Lately I have been having a programming phase and am working on a book about programming in DOS. There is no money involved in this because nobody except nerds like me care about DOS. Speaking of nerds, if you follow my blog, don’t forget that this site was set up for teaching Chess. Leave me a comment if you play Chess online or live in Lee’s Summit. I am still playing Chess every day although some of my time has been taken up with programming in Assembly language because it is so much fun.

    If you like this post, you may be interested in my much longer post/book that is all about Assembly programming in DOS.

  • Assembly Chaste Lib

    I have created two functions in x86 Assembly Language that allow me to output any zero terminated string or output any integer in any base from 2 to 36. I will show the full source so people can play with it. This uses system calls on Linux so it won’t work on Windows.

    first, the header file “chaste-lib.asm”

    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    putstring: ; function to print zero terminated string pointed to by register eax
    
    mov edx,eax ; copy eax to edx as well. Now both registers have the address of the main_string
    
    strlen_start: ; this loop finds the lenge of the string as part of the putstring function
    
    cmp [edx],byte 0 ; compare byte at address edx with 0
    jz strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc edx
    jmp strlen_start
    
    strlen_end:
    sub edx,eax ; edx will now have correct number of bytes when we use it for the system write call
    
    mov ecx,eax ; copy eax to ecx which must contain address of string to write
    mov eax, 4  ; invoke SYS_WRITE (kernel opcode 4)
    ;mov ebx, 1  ; ebx=1 means write to the STDOUT file
    int 80h     ; system call to write the message
    
    ret ; this is the end of the putstring function return to calling location
    
    
    
    
    
    
    radix dd 16 ;radix or base for integer output. 2=binary, 16=hexadecimal, 10=decimal
    
    putint: ; function to output decimal form of whatever integer is in eax
    
    push eax ;save eax on the stack to restore later
    
    mov ebp,int_string+31 ;address of start digits
    
    digits_start:
    
    mov edx,0;
    mov esi,[radix] ;radix is from memory location just before this function
    div esi
    cmp edx,10
    jb decimal_digit
    jge 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 [ebp],dl
    cmp eax,0
    jz digits_end
    dec ebp
    jmp digits_start
    
    digits_end:
    
    mov eax,ebp ; now that the digits have been written to the string, display it!
    call putstring
    
    pop eax  ;load eax from the stack so it will be as it was before this function was called
    ret
    

    next, the main source that includes the header: “main.asm”

    format ELF executable
    entry main
    
    include 'chaste-lib.asm'
    
    main: ; the main function of our assembly function, just as if I were writing C.
    
    ; I can load any string address into eax and print it!
    
    mov ebx, 1 ;ebx must be 1 to write to standard output
    
    mov eax,msg
    call putstring
    mov eax,main_string ; move the address of main_string into eax register
    call putstring
    
    mov [radix],2 ; can choose radix for integer output!
    
    mov eax,0
    loop1:
    call putint
    inc eax
    cmp eax,100h;
    jnz loop1
    
    mov eax, 1  ; invoke SYS_EXIT (kernel opcode 1)
    mov ebx, 0  ; return 0 status on exit - 'No Errors'
    int 80h
    
    ; this is where I keep my string variables
    
    msg db 'Hello World!', 0Ah,0     ; assign msg variable with your message string
    main_string db "This is Chastity's Assembly Language counting program!",0Ah,0
    int_string db 32 dup '?',0Ah,0
    
    
    
    ; This Assembly source file has been formatted for the FASM assembler.
    ; The following 3 commands assemble, give executable permissions, and run the program
    ;
    ;	fasm main.asm
    ;	chmod +x main
    ;	./main
    
    

    If you have the fasm compiler, you can assemble this source on any Linux distribution running on an intel CPU. Assembly is platform specific but it runs really fast and the process of programming with it is enjoyable for me.