Tag: Programming

  • chastecmp 64-bit

    This post is the source of the 64-bit edition of chastecmp, my file comparison tool. It behaves exactly the same as the 32-bit edition. However, I am slowly translating my best programs to use the 64-bit calling convention for the eventual 64-bit edition of my book. This will be a few years away but there is not too much work that needs to be done. Mostly I just have to use different registers and different numbers along with the syscall instruction instead of interrupt 0x80. The calls are standard and part of the Linux kernel. All I am doing is translating it to be more compatible with how 64-bit Linux does things.

    main.asm

    ;Linux 64-bit Assembly Source for chastecmp
    format ELF64 executable
    entry main
    
    include 'chastelib64.asm'
    
    main:
    
    ;radix will be 16 because this whole program is about hexadecimal
    mov [radix],16 ; can choose radix for integer input/output!
    mov [int_width],1
    
    pop rax
    mov [argc],rax ;save the argument count for later
    
    ;first arg is the name of the program. we skip past it
    pop rax
    dec [argc]
    mov rax,[argc]
    
    cmp rax,2
    jb help
    mov [file_offset],0 ;assume the offset is 0,beginning of file
    jmp arg_open_file_1
    
    help:
    mov rax,help_message
    call putstring
    jmp main_end
    
    arg_open_file_1:
    pop rax
    mov [filename1],rax ; save the name of the file we will open to read
    
    call putstring ;print the name of the file we will try opening
    
    mov rsi,0   ;open file in read mode 
    mov rdi,rax ;filename should be in rax before this function was called
    mov rax,2   ;invoke SYS_OPEN (kernel opcode 2 on 64 bit systems)
    syscall     ;call the kernel
    
    cmp rax,0
    js file_error_display ;end program if the file can't be opened
    mov [filedesc1],rax ; save the file descriptor number for later use
    mov rax,file_open
    call putstr_and_line
    
    arg_open_file_2:
    pop rax
    mov [filename2],rax ; save the name of the file we will open to read
    
    call putstring ;print the name of the file we will try opening
    
    mov rsi,0   ;open file in read mode 
    mov rdi,rax ;filename should be in rax before this function was called
    mov rax,2   ;invoke SYS_OPEN (kernel opcode 2 on 64 bit systems)
    syscall     ;call the kernel
    
    cmp rax,0
    js file_error_display ;end program if the file can't be opened
    mov [filedesc2],rax ; save the file descriptor number for later use
    mov rax,file_open
    call putstr_and_line
    
    files_compare:
    
    file_1_read_one_byte:
    mov rdx,1            ;number of bytes to read
    mov rsi,byte1        ;address to store the bytes
    mov rdi,[filedesc1]  ;move the opened file descriptor into rdi
    mov rax,0            ;invoke SYS_READ (kernel opcode 0 on 64 bit Intel)
    syscall              ;call the kernel
    
    ;rax will have the number of bytes read after system call
    mov [file_1_bytes_read],rax ;we save the number of bytes read for later
    cmp rax,0
    jnz file_2_read_one_byte ;unless zero bytes were read, proceed to read from next file
    
    mov rax,[filename1]
    call putstring
    mov rax,end_of_file_string
    call putstr_and_line
    
    ;Even if we have reached the end of the first file,
    ;we still proceed to read a byte from the second file
    ;to see if it also ends at the same address
    
    file_2_read_one_byte:
    mov rdx,1            ;number of bytes to read
    mov rsi,byte2        ;address to store the bytes
    mov rdi,[filedesc2]  ;move the opened file descriptor into rdi
    mov rax,0            ;invoke SYS_READ (kernel opcode 0 on 64 bit Intel)
    syscall              ;call the kernel
    
    ;rax will have the number of bytes read after system call
    mov [file_2_bytes_read],rax ;we save the number of bytes read for later
    cmp rax,0
    jnz check_both_bytes ;unless zero bytes were read, proceed to compare bytes from both files
    
    mov rax,[filename2]
    call putstring
    mov rax,end_of_file_string
    call putstr_and_line
    
    jmp main_end ;we have reach end of one file and should end program
    
    check_both_bytes:
    
    ;we add the number of bytes read from both files
    mov rax,[file_1_bytes_read]
    add rax,[file_2_bytes_read]
    cmp rax,2
    jnz main_end
    
    compare_bytes:
    
    mov al,[byte1]
    mov bl,[byte2]
    
    ;compare the two bytes and skip printing them if they are the same
    cmp al,bl
    jz bytes_are_same
    
    ;print the address and the bytes at that address
    mov rax,[file_offset]
    mov [int_width],8
    call putint_and_space
    mov [int_width],2
    mov rax,0
    mov al,[byte1]
    call putint_and_space
    mov al,[byte2]
    call putint_and_line
    
    bytes_are_same:
    
    inc [file_offset]
    
    jmp files_compare
    
    file_error_display:
    
    mov rax,file_error
    call putstr_and_line
    
    main_end:
    
    ;this is the end of the program
    ;we close the open files and then use the exit call
    
    mov rdi,[filedesc1] ;file number to close
    mov rax,3           ;invoke SYS_CLOSE (kernel opcode 3 for 64 bit Intel)
    syscall             ;call the kernel
    
    mov rdi,[filedesc2] ;file number to close
    mov rax,3           ;invoke SYS_CLOSE (kernel opcode 3 for 64 bit Intel)
    syscall             ;call the kernel
    
    mov rax, 0x3C ; invoke SYS_EXIT (kernel opcode 0x3C (60 decimal) on 64 bit systems)
    mov rdi,0   ; return 0 status on exit - 'No Errors'
    syscall
    
    ;variables for displaying information
    
    help_message db 'chastecmp by Chastity White Rose',0Ah,0Ah
    db 9,'chastecmp file1 file2',0Ah,0Ah
    db 'Differing bytes are shown in hexadecimal',0Ah
    db 'until the EOF has been reached.',0Ah,0
    
    file_open db ' opened',0
    file_error db ' error',0
    end_of_file_string db ' EOF',0
    
    db 48 dup 0 ;fill with extra space to match 1280 executable size
    
    ;variables for managing arguments and files
    argc dq ?
    filename1 dq ? ; name of the file to be opened
    filename2 dq ? ; name of the file to be opened
    filedesc1 dq ? ; file descriptor
    filedesc2 dq ? ; file descriptor
    byte1 db ?
    byte2 db ?
    file_1_bytes_read dq ?
    file_2_bytes_read dq ?
    file_offset dq ?
    

    chastelib64.asm

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

    I have created a small prototype of a calculator in the C programming language. It is a stack based calculator similar to the forth programming language. It is written in C as a testing ground but uses methods designed to be translatable to Assembly language. It will be a featured program in chapter ten of my Linux Assembly book.

    main.c

    #include <stdio.h>
    #include <string.h>
    #include "chastelib.h"
    
    #define stack_length 0x10
    int stack[stack_length]; /*stack array of size stack_length*/
    
    /*
    variables named after registers
    
    esp is declared as a pointer because its only purpose in Assembly is managing the stack
    ebp is declared as a pointer to keep track of the original stack pointer address
    
    all other registers are used as normal integers
    */
    int eax,ebx,ecx,edx,esi,edi,*ebp,*esp;
    
    void push(i)
    {
     esp--;
     *esp=i;
    }
    
    int pop()
    {
     int i=*esp;
     *esp=0; /*set the value at [esp] to 0 to delete it*/
     esp++;
     return i;
    }
    
    
    int main(int argc, char **argv)
    {
     int x=1;
    
     /*set the radix used for integer display*/
     radix=10;
     int_width=1;
    
     /*set the stack pointer to where it should start*/
      esp=stack+stack_length;
      ebp=esp; /*backup address of esp to ebp*/
    
     /*
     Now the fun begins. Each argument is processed as a number or command
     */
    
     while(x!=argc)
     {
      /*
      putstr(argv[x]);
      putstr("\n");
      */
      
      /*first, we check for commands before we check for integers*/
      
      if(!strcmp(argv[x],"add"))
      {
       /*putstr("The add command adds using the top two numbers on the stack.\n");*/
       ebx=pop();
       eax=pop();
       eax+=ebx;
       push(eax);
      }
      
      else if(!strcmp(argv[x],"mul"))
      {
       /*putstr("The mul command multiplies using the top two numbers on the stack.\n");*/
       ebx=pop();
       eax=pop();
       eax*=ebx;
       push(eax);
      }
    
      else if(!strcmp(argv[x],"sub"))
      {
       /*putstr("The sub command subtracts using the top two numbers on the stack.\n");*/
       ebx=pop();
       eax=pop();
       eax-=ebx;
       push(eax);
      }
    
      else if(!strcmp(argv[x],"div"))
      {
       /*putstr("The div command divides using the top two numbers on the stack.\n");*/
       ebx=pop();
       eax=pop();
       eax/=ebx;
       push(eax);
      }
    
      else /*try to get a number and push it to the stack*/
      {
       
      eax=strint(argv[x]); /*get a number from the string*/
      if(strint_errors)
      {
       putstr("Last argument was not a number, but it could be a command!\n");
      }
      else
      {
       /*
       putstr("number returned by strint(argv[x]) is: ");
       putint(eax);
       putstr("\n");
       putstr("It will be pushed to the stack.");
       */
       push(eax);
      }
      
      }
      
      x++;
     }
     
     while(esp<ebp)
     {
      putint(*esp);
      putstr("\n");
      esp++;
     }
     
     return 0;
    }
    
  • AAA-Linux: Chapter 8: User Input

    This post is a preview of the Linux version of Assembly Arithmetic Algorithms. It is planned to be bigger than the DOS book was and so far I am up to chapter 8 and trying to explain everything I can for Linux users who want to write assembly for modern computers at a speed higher than what was possible using a DOS emulator.

    The first seven chapters have been about teaching the basics of Assembly and getting output of strings and numbers to the screen. All those steps were required for learning Assembly. However, at some point, when you have a program that is meant to do something, you need to have a way for other people, especially those who are not programmers, to be able to give input to direct what the program does.

    There are two main ways of doing this in a console program. The first way is have the program ask for the user to type something from the keyboard and then wait until they write something and press enter. The next program will achieve this. Copy this and try it out and then I will explain after the code how it works.

    FASM Keyboard Input

    format ELF executable
    
    main:
    
    mov dword [radix],10
    mov dword [int_width],1
    
    loop_input:
    
    mov eax,string0
    call putstring
    
    call getstring
    
    mov esi,eax     ;mov the string address in eax to esi
    mov edi,string3 ;mov the "exit" string address to edi
    call strcmp     ;call the function to compare the strings and return eax
    cmp eax,0       ;if eax is 0, the strings are the same
    jz the_end      ;go to the_end if the user typed "exit"
    
    mov eax,string1
    call putstring
    
    mov eax,buf
    call putstring
    call putline
    
    mov eax,string2
    call putstring
    
    mov eax,[count]
    call putint
    call putline
    
    jmp loop_input
    
    the_end:
    mov eax,1
    mov ebx,0
    int 80h
    
    string0 db 'Enter a string from the keyboard: ',0
    string1 db 'string: ',0
    string2 db 'length: ',0
    string3 db 'exit',0
    
    buf db 0x100 dup '?'
    count dd 0
    
    getstring:
    
    mov [count],0 ;set count of characters read during this function to zero
    mov edx,1     ;number of bytes to read
    mov ecx,buf   ;address to store the bytes
    
    getstring_chars:
    
    mov ebx,0     ;read from stdin
    mov eax,3     ;invoke SYS_READ (kernel opcode 3)
    int 80h       ;call the kernel
    
    cmp eax,1     ;was 1 character read?
    jnz getstring_end ; if not, then end this loop
    
    mov al,[ecx]  ;mov last character read into al register
    
    ;check if this character is in the proper range to be part of the string
    
    cmp al,0x20      ;compare with 0x20 (space)
    jb getstring_end ;jump if below to getstring_end label
    cmp al,0x7E      ;compare with 0x7E (tilde)
    ja getstring_end ;jump if above to getstring_end label
    
    ;if neither jump happened, keep the character and
    
    inc [count]   ;increment how many characters we have read
    inc ecx       ;increment address where next byte is read from
    jmp getstring_chars ;jump back to start of loop and keep reading
    
    getstring_end:
    
    mov byte[ecx],0 ;terminate this string with a zero
    
    mov eax,buf ;mov the buffer address to eax for returning the string
    
    ret
    
    ;strcmp compares the string at esi to the one at edi
    ;eax returns 0 if the strings are the same and 1 if different
    ;the algorithm is simple but I will explain it for those who are confused
    
    ;eax is initialized to zero
    ;a byte from each string is loaded into the al and bl registers
    ;the bytes are compared. if they are different, then we jump to the end
    ;However, if they are the same, then we check if one of them is zero
    ;for this purpose it doesn't matter whether we compare al or bl with zero
    ;because it is known that they are the same if the jnz did not take place
    ;if it is zero, this also jumps to the end of the function
    ;If neither jump took place, then we jump to the start of the loop
    ;but when the function finally ends bl will be subtracted from al
    ;this ensures that the function returns zero if the final characters are the same
    
    strcmp:
    
    mov eax,0
    
    strcmp_start:
    
    ;read a byte from each string
    mov al,[edi]
    mov bl,[esi]
    cmp al,bl
    jnz strcmp_end
    
    cmp al,0
    jz strcmp_end
    
    inc edi
    inc esi
    
    jmp strcmp_start
    
    strcmp_end:
    sub al,bl
    
    ret
    
    include 'chastelib32.asm'
    

    The getstring function uses a read system call to read from file descriptor 0 which represents standard input or the keyboard. It reads one character each time with a loop and starts at an address labeled “buf” which was declared as a global variable of 256 bytes which were initialized with question marks. I also defined a variable named count which was used to automatically count how many bytes were read.

    buf db 0x100 dup '?'
    count dd 0
    

    But I feel that the part of this function that needs the most explaining is this section:

    cmp al,0x20      ;compare with 0x20 (space)
    jb getstring_end ;jump if below to getstring_end label
    cmp al,0x7E      ;compare with 0x7E (tilde)
    ja getstring_end ;jump if above to getstring_end label
    

    Because this range of characters from space to tilde is what I have identified as the acceptable range of characters. There is no standard way that makes sense for all strings. For example, someone may want to make a getstring function that only accepts capital letters or that only accepts numbers 0 to 9. I can’t say that there is one way that is the best.

    The program listed above will keep running the loop until the user types “exit” as the string. Each time after it gets the string, it compares the what the user entered to the “exit” string. If the strcmp function returns 0, it means the two strings are the same.

    This particular variant of strcmp is based off of the C function of the same name. You may also remember that I wrote a strlen function for the first example in chapter 7 when I had a string that I wanted to write to a new file.

    I believe that using conventional names of C functions is a good idea because C programmers who read my books will already be familiar with that function and what it does in the C programming language.

    In any case, “exit” was the perfect name for a command to “exit” the program. It is also how you log out of a Linux terminal and is the official name for the system call that exits every program in this book!

    Although using the keyboard for input during a running program is a great interactive way of doing things, there is one way that I enjoy even more. The next program is one that I wrote long before I started writing this book and has been referred to as “chastearg” on my blog and the Flat Assembler Forum. It prints the command line arguments when you add them after the name of the program.

    FASM Command Line Arguments

    format ELF executable
    entry main
    
    include 'chastelib32.asm'
    
    main:
    
    pop eax              ;pop the number of arguments from the stack
    mov [argc],eax       ;save the argument count for later
    
    pop eax              ;pop argument 0 (name of the program)
    dec [argc]           ;subtract 1 from argument count
    
    putarg:
    
    cmp [argc],0         ;check for remaining arguments
    jz putarg_end        ;if none, end the loop and stop printing
    pop eax              ;pop the next argument off the stack
    call putstring       ;print the string and a new line
    call putline
    dec [argc]           ;subtract 1 from argument count
    jmp putarg           ;jump to the beginning of the loop
    
    putarg_end:
    
    mov eax, 1           ; invoke SYS_EXIT (kernel opcode 1)
    mov ebx, 0           ; return 0 status on exit - 'No Errors'
    int 0x80
    
    argc dd 0
    

    What is a Command Line Argument?

    People who come from a Windows environment may not even know what a command line argument is because they are used to pointing and clicking with a mouse. You can’t enter an argument this way. For clarification on this topic, here is some terminal text to clarify what arguments are.

    fasm main.asm
    flat assembler  version 1.73.30  (16384 kilobytes memory)
    2 passes, 481 bytes.
    chmod +x main
    ./main this program has command line arguments
    this
    program
    has
    command
    line
    arguments
    

    When we run fasm and give it the name of the Assembly file we want to assemble, the file is an argument or an option we provide to it. In the above example, main.asm is the file I provide to fasm as an argument.

    After the file is assembled, I run the chmod command with the arguments “+x” and “main” which adds the execution permission to the main executable that was just created.

    Finally, running “./main” followed by more words on the same line causes Linux to interpret them as arguments. They are pushed onto the stack.

    When a program begins on Linux, you can access the number of how many arguments were passed to the program by getting the first number you pop off the stack. In the chastearg program, there is a loop that keeps track of how many arguments are left. While there are some remaining, it keeps popping them into the eax register and calling putstring until there are none left.

    Arguments vs Keyboard Input

    The primary difference between input from the keyboard during a program and passing arguments is that the arguments do not stop the execution of a program and wait for anything. If you have an install script which is meant to compile and install a large program, it is better not to pause it for any reason unless an error happens. Arguments are best in this case so that someone can pass information to it that they want the program to know.

    Keyboard input does have a benefit though. For example, suppose that you ask the user to input a number and then they accidentally input a string that is not recognizable as a number. With keyboard input, you can tell them they made a mistake and ask them to try again. With arguments, you cannot edit them during the program because they are only pushed at the start when the program is run from the terminal.

    Only you can decide which of these methods your program needs, but I hope that my explanation and my strcmp function is helpful for you when you try to write a program that needs input to do different things conditionally.

    Later in this book, I will present a calculator written in Assembly language that builds from this chapter’s keyboard input loop. However, we are not ready for that until I teach you how to separate regular strings from numbers. That will be the subject of the next chapter and I can promise you it is simultaneously the hardest task but also the most useful feature you will need for writing any program that has to read numbers.

  • update for chastext on DOS

    I used my new getarg function I wrote to improve the DOS version of chastext. Nothing about the behavior of the program has changed but the code is a lot smaller and more readable. This chastext program was the original reason I wrote the chastearg program. I needed to get the command line arguments just write.

    But more importantly, people may not see the value of the chastext program and why it is useful to transform text. For that reason, I took this screenshot of a demo batch file that shows just how much I can modify a text file in stages.

    Besides changing a funny tongue twister about seashells into other things (which made a really great example), I also used the Linux version of chastext to make it possible to assemble my DOS programs with either FASM or NASM. The two assemblers are mostly compatible with each other when doing DOS programming due to the lack of headers in ‘.com’ files. Simply replacing “include” with “%include” is enough to transform my FASM source into NASM source because the % is required for the NASM include directive.

    FASM is my main assembler but making sure it assembles my code in NASM will also make a difference for those following my book, Assembly Arithmetic Algorithms for DOS. The book is complete and available on leanpub but there are possible corrections to the code if necessary.

    And for now, I am also trying to work on the Linux version of the book which will be more work because I have a new angle where I want to compare the Assembly to the C code of the same program. Since Linux developers are more familiar with C, it will help those with C language experience to learn the nature of Assembly language and how it is specifically very useful for Linux systems even more than it is for DOS or Windows.

    Also, the source code of the chastext program is available in its own repository.

    https://github.com/chastitywhiterose/chastext

    C and assembly versions are available which means that it can be either compiled or assembled and run on any operating system that I know about. Almost every platform has a C compiler and my custom assembly programs perform even higher on DOS and Linux than the C version did.

    Between chastehex, chastecmp, and now chastext, I have a small set of development tools that I can use for verifying when my programs are producing the output I want. Each tool was made for a specific need I had in mind.

  • chastarg for DOS

    The chastearg program, which is shortened to chastarg to respect DOS 8.3 filename limits, is a tool for separating command line arguments into multiple lines except preserving those that are quoted and therefore counting as one argument. Quoted strings will print on the same line.

    A key aspect of how this works is the new “getarg” function that I wrote. If you take a look at this small program that uses it, it is very simple.

    main.asm

    org 100h     ;DOS programs start at this address
    
    ;this loop will get all the command line arguments and print them on separate lines
    
    call getarg ;this first call will get the command string
    
    arg_loop:
    call getarg
    cmp ax,0 ;did the getarg function return 0?
    jz arg_loop_end ;if ax was zero, there are no args
    call putstring
    call putline
    jmp arg_loop
    arg_loop_end:
    
    ending:
    mov ax,4C00h ; Exit program
    int 21h
    
    include 'getarg.asm'
    include 'chastelib16.asm'
    
    db 0x48 dup 0 ;add extra bytes to make it 512 bytes exactly
    

    But the getarg function itself is a little bit complicated. I tried my best to comment it so that hopefully other DOS programmers can benefit from this useful function.

    getarg.asm

    ;The getarg function was something I badly needed in order to make my assembly code for DOS easier to read.
    ;It will automatically process the command line arguments if they are available.
    ;
    ;The first time it is run, it returns the whole command string or zero if no args are given
    ;DOS does not allow the program name to be part of the arguments
    ;
    ;Each time after that, it will give you the next argument which is a subtring of the original.
    ;When no more arguments are available, it will always return zero
    ;The program calling this is expected to check for this error and then terminate
    ;or print a message depending on the goals of that program
    
    ;A word of warning though, this function has multiple return statements and is long
    ;However, it is fully featured in that it can recognize quoted strings as being the same argument
    ;This brings full compatibility between my DOS and Linux programs which expect consistent behavior
    
    getarg:
    
    mov bx,[arguments_start] ;get the address of start of arguments
    cmp bx,0 ;is this address zero? (meaning this function was not called before)
    jz get_arg_data ;if it was zero, then get the argument data for the first execution of this function
    
    ;if the start was not zero, then clearly arguments exist and addresses have been saved
    cmp bx,[arguments_end]  ;is the address of the start and end the same?
    jnz find_next_string  ;if they are not the same, find the next sub string
    mov ax,0 ;otherwise, return ax as zero and check this in the main program
    
    ret
    
    find_next_string:
    
    mov bx,[arguments_start] ;get address of current arg
    
    skip_spaces:
    
    cmp byte[bx],' ' ;is this byte a space?
    jnz skip_spaces_end ;if it is not a space, we can end this loop
    inc bx ;otherwise, go to next byte
    jmp skip_spaces ;and keep looping till we find non-space
    skip_spaces_end:
    mov ax,bx ;copy this non-space address to ax register
    
    ;we have found a non-space which is the start of a printable string
    ;but we still have to find the next space and terminate it with a zero!
    
    ;however, there is a special case where we want a string to contain spaces. In this case, I have another routine!
    
    ;check for quoted strings
    cmp byte[bx],0x22 ;is this a double quote -> "
    jz scan_quoted_string
    cmp byte[bx],0x27 ;is this a single quote -> '
    jz scan_quoted_string
    
    find_space:
    cmp byte [bx],' ' ;is this a space?
    jz found_space ;if this was a space, end the loop and terminate with zero
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    cmp byte[bx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc bx
    jmp find_space ; this char was not space, go to the next char
    found_space:
    mov byte[bx],0 ;terminate this string
    
    inc bx ;but go to the next byte
    mov [arguments_start],bx ;and set the new start address for the next call
    
    ret ;We can return ax safely knowing the string ends in a zero
    
    scan_quoted_string:
    
    mov cl,byte[bx] ;mov this quote type to cl
    inc bx ;go to next byte
    mov ax,bx ;set ax to this address which is assumed to be the start of a quoted string
    
    find_end_quote:
    cmp byte[bx],cl ;is this the same quote we started with?
    jz found_end_quote ;if it is, end this loop
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    ;this avoids a crash if I forgot to add the second quotation mark in the arguments
    cmp byte[bx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc bx
    jmp find_end_quote
    found_end_quote:
    mov byte[bx],0 ;terminate this string
    
    inc bx ;but go to the next byte
    mov [arguments_start],bx ;and set the new start address for the next call
    
    ret
    
    no_more_args:
    
    mov [arguments_start],bx ;mov the start to where the string ended
    
    ;now that the start and end addresses are the same
    ;this function will always return zero
    ret
    
    ;this will happen first time this function is called to get the argument data
    get_arg_data:
    mov ax,0      ;zero ax (upper half of ax)
    mov al,[80h] ;load length of the command string from this address
    cmp ax,0
    jz getarg_end
    
    mov bx,0x81  ;mov into bx the address of the start of the argument string
    mov [arguments_start],bx ;save the start of the arguments to this variable
    add bx,ax    ;add the length of the command string to this address
    mov byte[bx],0 ;terminate this with a zero to avoid segfaults when printed with putstring
    mov [arguments_end],bx ;save the end of the arguments to this variable
    mov ax,[arguments_start] ;copy the address of the arguments start to ax
    
    getarg_end:
    ret
    
    ;start and end default to address of zero, which means we have not tested the arguments yet
    arguments_start dw 0
    arguments_end dw 0