Category: Uncategorized

  • Chastity’s Source for ELF 64-bit executable creation

    I did it again. I decoded the 64 bit ELF header format for running an Assembly program on Intel CPUs running Linux. Although I don’t usually do 64 bit programming, I do have references which would allow me to translated my previous programs to use the 64-bit registers. Most of the time this is not needed for what they do.

    ;Chastity's Source for ELF 64-bit executable creation
    ;
    ;All data as defined in this file is based off of the specification of the ELF file format.
    ;I first looked at the type of file created by FASM's "format ELF executable" directive.
    ;It is great that FASM can create an executable file automatically. (Thanks Tomasz Grysztar, you are a true warrior!)
    ;However, I wanted to understand the format for theoretical use in other assemblers like NASM.
    
    ;The Github repository with the spec I used is here.
    ;<https://github.com/xinuos/gabi>
    ;And this is the wikipedia article which linked me to the specification document
    ;<https://en.wikipedia.org/wiki/Executable_and_Linkable_Format>
    
    ;This rest of this file contains a raw binary ELF64 header created using db,dw,dd,dq commands.
    ;After that, it proceeds to assemble a real "Hello World!" program
    
    ;Header for 64 bit ELF executable (with comments based on specification)
    
    db 0x7F,"ELF" ;ELFMAGIC: 4 bytes that identify this as an ELF file. The magic numbers you could say.
    db 2          ;EI_CLASS: 1=32-bit 2=64-bit
    db 1          ;EI_DATA: The endianness of the data. 1=ELFDATA2LSB 2=ELFDATA2MSB For Intel x86 this is always 1 as far as I know.
    db 1          ;EI_VERSION: 1=EV_CURRENT (ELF identity version 1) (which is current at time of specification Version 4.2 I was using)
    db 9 dup 0    ;padding zeros to bring us to address 0x10
    dw 2          ;e_type: 2=ET_EXEC (executable instead of object file)
    dw 0x3E       ;e_machine : 3=EM_386 (Intel 80386) 0x3E (AMD x86-64 architecture)
    dd 1          ;e_version: 1=EV_CURRENT (ELF object file version.)
    
    p_vaddr=0x400000
    e_entry=0x400078 ;we will be reusing this constant later 
    
    dq e_entry    ;e_entry: the virtual address at which the program starts
    dq 0x40       ;e_phoff: where in the file the program header offset is
    db 12 dup 0   ;e_shoff and e_flags are unused in this example,therefore all zeros
    dw 0x40       ;e_ehsize: size of the ELF header
    dw 0x38       ;e_phentsize: size of program header which happens after ELF header
    dw 1          ;e_phnum: How many program headers. Only 1 in this case
    dw 0x40       ;e_shentsize: Size of a section header
    dw 0          ;e_shnum number of section headers
    dw 0          ;e_shstrndx: section header string index (not used here)
    
    ;That is the end of the 0x40 byte (64 bytes decimal) ELF header. Sadly, this is not the end and a program header is also required (what drunk person made this format?)
    
    dd 1           ;p_type: 1=PT_LOAD
    dd 7           ;p_flags: permission flags: 7=4(Read)+2(Write)+1(Execute)
    dq 0           ;p_offset: Base address from file (zero)
    dq p_vaddr     ;p_vaddr: Virtual address in memory where the file will be.
    dq p_vaddr     ;p_paddr: Physical address. Same as previous
    
    image_size=0x1000 ;Chosen size for file and memory size. At minimum this must be as big as the actual binary file (code after header included)
                      ;By choosing a default size of 0x1000, I am assuming all assembly programs I write will be less than 4 kilobytes
    
    dq image_size  ;p_filesz: Size of file image of the segment. Must be equal to the file size or greater
    dq image_size  ;p_memsz: Size of memory image of the segment, which may be equal to or greater than file image.
    
    dq 0           ;p_align; Alignment (none)
    
    ;important FASM directives
    use64          ;tell assembler that 64 bit code is being used
    org e_entry    ;origin of new code begins at the entry point
    
    ;now, the actual hello world program
    mov rax,1   ; invoke SYS_WRITE (kernel opcode 1 on 64 bit systems)
    mov rdi,1   ; write to the STDOUT file
    mov rsi,msg ; pointer/address of string to write
    mov rdx,13  ; number of bytes to write
    syscall
    
    mov rax,0x3C ; invoke SYS_EXIT (kernel opcode 0x3C on 64 bit systems)
    mov rdi,0    ; return 0 status on exit - 'No Errors'
    syscall
    
    msg db 'Hello World!',0Ah
    
    ;This is the makefile I use when assembling and running this program
    
    ;main-fasm:
    ;	fasm ELF-64-hello.asm
    ;	chmod +x ELF-64-hello.bin
    ;	./ELF-64-hello.bin
    
  • How come Israel killing millions of Palestinians is not considered genocide?

    I sometimes answer questions on a website called Quora. Most of the time, I answer questions about Linux and computer programming. However, when I received this question in my email, I read the other answers and found them lacking in substance. Some people are picking on the use of the word “genocide,” and others are saying it was not “millions” but hundreds of thousands.

    My hope in sharing this is that other people will learn a little bit more about what this conflict between Israel and Palestine is about. I have included some links to books and videos to learn more, from people who know far more than I do.

    But I have a responsibility to say something because to be silent on this topic could be misinterpreted as approval of what is happening in the Middle East. I do not stand with Israel nor Hamas. I stand only with innocent people who have not wanted to fight but have been killed by the actions of violent groups.

    https://www.quora.com/How-come-Israel-killing-millions-of-Palestinians-is-not-considered-genocide/answer/Chastity-Rose-4

    Depending on who you ask, it is sometimes considered genocide, and other times, it is considered self-defense. I am no expert, but for what it is worth, here is my take based on what I was taught growing up and the books and documentaries I have read and watched in the past few years.

    1. Israel thinks the land of Palestine is the promised land, which was promised to them in the Old Testament by God. The Old Testament is also full of violent, bloody wars, sometimes assisted by miracles from God. The validity of these claims is entirely dependent on religious belief and belief in these texts. Also, some of the jews were killed and taken captive by Babylon in the Old Testament, while a few have remained in other parts of the land outside of Judah.

    2. The people who have lived in Palestine for thousands of years, including Christians, Jews, and Muslims, were pushed out when the modern state of Israel was founded in 1948. Palestinians and Jews have both killed each other in conflicts ever since. Palestinians see the Jews as invaders, much like the Native Americans saw the British settlers who came and founded the United States of America. The Palestinians want to remain on the land they have been on for generations. The Jews who came to Israel after World War 2 want to come back to what they think is their homeland, given to them by God in the Old Testament.

    3. There are many good Jews and Palestinians who would like to live in peace and not fight wars. However, the powers that be in the Israeli government and Palestinian groups like Hamas prefer to fight. Meanwhile, innocent civilians suffer. Innocent men, women, and children who have their homes, schools, churches, and hospitals blown up by bombs constantly.

    My opinion is this: I do not care about who is “right” in this never-ending war. I do not care who the land belongs to based on holy books or whatever other arguments are made. I would like the fighting to stop. I don’t want the innocent people to suffer and die slow, painful deaths by starvation or have their limbs blown off when bombs are dropped on them.

    And as an American, you might think my opinion doesn’t matter. However, when I discovered that the USA government is funding the Israeli military and has been sending billions of dollars to Israel, most of which was before October 7. According to a government document I found, they have received 174 billion dollars from the US.

    “Israel is the largest cumulative recipient of US foreign assistance since World War II. Successive Administrations, working with Congress, have provided Israel with assistance reflective of robust domestic US support for Israel and its security; shared strategic goals in the Middle East, and historical ties dating from US support for the creation of Israel in 1948. To date, the United States has provided Israel $174 billion (current, or non-inflation-adjusted, dollars) in bilateral assistance and missile defense funding.”

    https://www.congress.gov/crs_external_products/RL/PDF/RL33222/RL33222.51.pdf https://www.notmytaxdollars.org/

    Do I want the Palestinians killed? No!

    Do I want the Jews killed? No!

    But more importantly, do I want my tax dollars supporting wars and the military of a foreign country? HELL NO!

    I think maybe we should handle things the Old Testament way. If the God of the Old Testament really were on the side of Israel, we would see miracles on their behalf, just like in the book of Exodus. These divine acts, like plagues, parting the Red Sea, pillar of fire, food like manna, and water from rocks, all happened without modern technology or military weapons that we see in modern times. Our tax dollars should not be required and should instead be used to help Americans who have been paying them.

    The following are some resources that I recommend for more information on this conflict.

    Resources on Israel and Palestine

    Movies

    1948: Creation & Catastrophe (Full documentary)

    https://youtu.be/Bwy-Rf15UIs?si=nw3BSr1HDUEKH3rs

    Israelism: The awakening of young American Jews | Featured Documentary https://www.youtube.com/watch?v=Iq6J7Q6L0yw

    Books

    Genocide Bad: Notes on Palestine, Jewish History, and Collective Liberation by Sim Kern https://www.amazon.com/Genocide-Bad-Palestine-Collective-Liberation/dp/1623716365

    Palestine Peace Not Apartheid by Jimmy Carter https://www.amazon.com/Palestine-Peace-Apartheid-Jimmy-Carter-ebook/dp/B000MGAU4S/

    The Hundred Years’ War on Palestine: A History of Settler Colonialism and Resistance, 1917–2017 https://www.amazon.com/gp/product/B07HLYPNJN

    The Ethnic Cleansing of Palestine https://www.amazon.com/gp/product/B00KT5W342

  • Chastity’s Source for ELF 32-bit executable creation

    I wrote an example program using a custom made ELF-32 header using data declaration statements. I wrote many comments which reference the official specification. This was an exercise both in programming and also Technical Reading and Writing. I had to read enough of the specification PDF file to understand what I was doing. I then tried to write descriptive comments that at the very least I would understand when I need to remind myself how this format is created.

    ;Chastity's Source for ELF 32-bit executable creation
    ;
    ;All data as defined in this file is based off of the specification of the ELF file format.
    ;I first looked at the type of file created by FASM's "format ELF executable" directive.
    ;It is great that FASM can create an executable file automatically. (Thanks Tomasz Grysztar, you are a true warrior!)
    ;However, I wanted to understand the format for theoretical use in other assemblers like NASM.
    
    ;The Github repository with the spec I used is here.
    ;<https://github.com/xinuos/gabi>
    ;And this is the wikipedia article which linked me to the specification document
    ;<https://en.wikipedia.org/wiki/Executable_and_Linkable_Format>
    
    ;This file contains a raw binary ELF32 header created using db,dw,dd commands.
    ;After that, it proceeds to assemble a real "Hello World!" program
    
    ;Header for 32 bit ELF executable (with comments based on specification)
    
    db 0x7F,"ELF" ;ELFMAGIC: 4 bytes that identify this as an ELF file. The magic numbers you could say.
    db 1          ;EI_CLASS: 1=32-bit 2=64-bit
    db 1          ;EI_DATA: The endianness of the data. 1=ELFDATA2LSB 2=ELFDATA2MSB For Intel x86 this is always 1 as far as I know.
    db 1          ;EI_VERSION: 1=EV_CURRENT (ELF identity version 1) (which is current at time of specification Version 4.2 I was using)
    db 9 dup 0    ;padding zeros to bring us to address 0x10
    dw 2          ;e_type: 2=ET_EXEC (executable instead of object file)
    dw 3          ;e_machine : 3=EM_386 (Intel 80386)
    dd 1          ;e_version: 1=EV_CURRENT (ELF object file version.)
    
    p_vaddr=0x8048000
    e_entry=0x8048054 ;we will be reusing this constant later 
    
    dd e_entry    ;e_entry: the virtual address at which the program starts
    dd 0x34       ;e_phoff: where in the file the program header offset is
    db 8 dup 0    ;e_shoff and e_flags are unused in this example,therefore all zeros
    dw 0x34       ;e_ehsize: size of the ELF header
    dw 0x20       ;e_phentsize: size of program header which happens after ELF header
    dw 1          ;e_phnum: How many program headers. Only 1 in this case
    dw 0x28       ;e_shentsize: Size of a section header
    dw 0          ;e_shnum number of section headers
    dw 0          ;e_shstrndx: section header string index (not used here)
    
    ;That is the end of the 0x34 byte (52 bytes decimal) ELF header. Sadly, this is not the end and a program header is also required (what drunk person made this format?)
    
    dd 1          ;p_type: 1=PT_LOAD
    dd 0          ;p_offset: Base address from file (zero)
    dd p_vaddr    ;p_vaddr: Virtual address in memory where the file will be.
    dd p_vaddr    ;p_paddr: Physical address. Same as previous
    
    image_size=0x1000 ;Chosen size for file and memory size. At minimum this must be as big as the actual binary file (code after header included)
                      ;By choosing a default size of 0x1000, I am assuming all assembly programs I write will be less than 4 kilobytes
    
    dd image_size  ;p_filesz: Size of file image of the segment. Must be equal to the file size or greater
    dd image_size  ;p_memsz: Size of memory image of the segment, which may be equal to or greater than file image.
    
    dd 7           ;p_flags: permission flags: 7=4(Read)+2(Write)+1(Execute)
    dd 0           ;p_align; Alignment (none)
    
    ;important FASM directives
    use32          ;tell assembler that 32 bit code is being used
    org e_entry    ;origin of new code begins at the entry point
    
    ;now, the actual hello world program
    mov eax,4      ;invoke SYS_WRITE (kernel opcode 4 on 32 bit systems)
    mov ebx,1      ;write to the STDOUT file
    mov ecx,msg    ;pointer/address of string to write
    mov edx,13     ;number of bytes to write
    int 80h
    
    mov eax,1 ;function SYS_EXIT (kernel opcode 1 on 32 bit systems)
    mov ebx,0 ;return 0 status on exit - 'No Errors'
    int 80h   ;call Linux kernel with interrupt
    
    msg db 'Hello World!',0Ah
    
    ;This is the makefile I use when assembling and running this program
    
    ;main-fasm:
    ;	fasm ELF-32-hello.asm
    ;	chmod +x ELF-32-hello.bin
    ;	./ELF-32-hello.bin
    
    

    I made a repository for examples like this. Others may want to understand the header used on Linux systems.

    https://github.com/chastitywhiterose/ELF

  • Professional vs Open Source

    I totally spent 5 hours writing a program in Assembly Language. I realized that what I am doing is something that cannot be done for money in any way. The best that I can do is to learn the technical skills and then continue working on my books and API references I plan to write.

    But as far as my programs themselves, they don’t fit the model of how the world works. In a job, you are constantly pressured to do as much work in as short of a time as possible. Therefore, you are paid, hired, or fired based on how fast the program can be written for the client, regardless of whether it works correctly or has bugs or security flaws.

    But when I write computer software for myself, I am the only one to decide whether it meets my standards. I have said many times over the past 20 years that I would not want a job as a programmer. This is because I am only interested in the things I want to do. I find that I am at peace when the things I do are not attached to the love of money.

    I believe that money and the corporate world actually ruins top quality work. There are also things that the Open-Source Software movement has made possible that could never be done under a company with a proprietary system. Strangers who don’t even know each other offer improvements on programming forums to people out of the goodness of their heart with no financial incentive.

    I see something similar in the world of Chess. People who are playing for fun can enjoy the game at a higher level than those who are stressed out competing in tournaments to win money. I sometimes feel myself pulled in a direction I didn’t know existed. I will work to explore this feeling I get where I achieve inner peace for a moment when I am having pure fun and losing track of the time.

    I used to feel this way when playing video games as a kid. Now I get it from writing books, blog posts, and computer programs. I still enjoy games though. I plan to eventually getting back into my games but I have had a busy life lately.

  • Windows chastehex

    This is the source code of the Windows Assembly version of my program I invented called chastehex. I still need to record a video tutorial for how to use it, but it also displays a help message that is self-explanatory. However, most people are do not know how to assemble this from source. The main purpose of posting it on my blog is for my own backup. There is also an executable available on Github for those who want to try it out.

    https://github.com/chastitywhiterose/chastehex/tree/main/asm/windows

    It can arbitrarily read to or write from any address of a file with the correct arguments. It was designed to be part of a script to modify sections of files without requiring the installation of a hex editor. The best part is that it is free and open source because I wrote it and I say so. If you read the Github repository, you will also see that I released it under the GNU GENERAL PUBLIC LICENSE Version 3.

    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
    
    ;display the arg string to make sure it is working correctly
    ;mov eax,[arg_start]
    ;call putstring
    ;call putline
    
    ;print the length in bytes of the arg string
    ;mov eax,[arg_length]
    ;call putint
    
    ;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 putstring
    ;call putline
    
    ;get next arg (first one after name of program)
    call get_next_arg
    cmp eax,[arg_end]
    jz help
    
    mov [file_name],eax
    mov eax,file_open_message
    call putstring
    mov eax,[file_name]
    call putstring
    call putline
    
    jmp open_sesame
    
    help:
    
    mov eax,help_message
    call putstring
    
    jmp args_none
    
    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 args_none ;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
    
    mov [int_newline],0 ;disable automatic printing of newlines after putint
    ;we will be manually printing spaces or newlines depending on context
    
    ;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
    mov eax,file_seek_message
    call putstring
    mov eax,[file_offset]
    call putint
    call putline
    
    ;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
    call putspace
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint
    call putline
    
    ;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 args_none
    
    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 
    jb print_EOF ;if less than one bytes read, there is an error
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint
    call putline
    
    jmp args_none
    
    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 [bytes_read],1 
    jb print_EOF ;if less than one bytes read, there is an error
    
    call print_bytes_row
    
    jmp hexdump
    
    print_EOF:
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,end_of_file
    call putstring
    call putline
    
    jmp args_none
    
    
    
    
    ;this loop is very safe because it only prints arguments if they are valid
    ;if the end of the args are reached by comparison of eax with [arg_end]
    ;then it will jump to args_none and proceed from there
    args_list:
    call get_next_arg
    cmp eax,[arg_end]
    jz args_none
    call putstring
    call putline
    jmp args_list
    args_none:
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    arg_start  dd 0 ;start of arg string
    arg_end    dd 0 ;address of the end of the arg string
    arg_length dd 0 ;length of arg string
    arg_spaces dd 0 ;how many spaces exist in the arg command line
    
    ;variables for managing file IO.
    file_name dd 0
    bytes_read dd 0 ;how many bytes are read with ReadFile operation
    byte_array db 16 dup '?',0
    file_handle dd 0
    file_offset dd 0
    
    ;variables for displaying messages
    file_open_message db 'opening: ',0
    file_seek_message db 'seek: ',0
    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 'Welcome to chastehex! The tool for reading and writing bytes of a file!',0Ah,0Ah
    db 'To hexdump an entire file:',0Ah,0Ah,9,'chastehex file',0Ah,0Ah
    db 'To read a single byte at an address:',0Ah,0Ah,9,'chastehex file address',0Ah,0Ah
    db 'To write a single byte at an address:',0Ah,0Ah,9,'chastehex file address value',0Ah,0Ah
    db 'The file must exist before you launch the program.',0Ah
    db 'This design was to prevent accidentally opening a mistyped filename.',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 bytes
    ;each row is 16 bytes
    print_bytes_row:
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    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
    call putspace
    
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_byte
    
    call putline
    
    ret
    

    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
    
    stdout dd 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax ; copy eax to ebx 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 putint function
    int_string     db 32 dup '?' ;enough bytes to hold maximum size 32-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 0Ah,0
    
    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_newline-1 ;find address of lowest digit(just before the newline 0Ah)
    mov ecx,1
    
    digits_start:
    
    mov edx,0;
    div dword [radix]
    cmp edx,10
    jb decimal_digit
    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