# PURPOSE: This program converts an input text file to an output text file with the base64 encoding. # # PROCESSING: 1) Open the input file # 2) Open the output file # 4) While we're not at the end of the input file # a) read part of the file into our piece of memory # b) go through each byte of memory # convert to base64 # c) write the piece of memory to the output file .section .data #Data storage base64: #Lookup table .ascii "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\0" #######CONSTANTS######## # System call numbers .equ SYS_OPEN, 2 .equ SYS_READ, 0 .equ SYS_WRITE, 1 .equ SYS_CLOSE, 3 .equ SYS_EXIT, 60 # Options for open (look at /usr/include/asm/fcntl.h for # various values. You can combine them # by adding them) .equ O_RDONLY, 0 # Open file options - read-only .equ O_CREAT_WRONLY_TRUNC, 03101 # Open file options - these options are: # CREAT - create file if it doesn't exist # WRONLY - we will only write to this file # TRUNC - destroy current file contents, if any exist .equ O_PERMS, 0666 # Read & Write permissions for everyone # End-of-file result status .equ END_OF_FILE, 0 # This is the return value of read() which # means we've hit the end of the file #######BUFFERS######### .section .bss # This is where the data is loaded into from # the data file and written from into the output file. # It should never exceed 16,000 for various reasons. .equ BUFFER_SIZE_INPUT, 600 .lcomm BUFFER_DATA_INPUT, BUFFER_SIZE_INPUT .equ BUFFER_SIZE_OUTPUT, 1000 .lcomm BUFFER_DATA_OUTPUT, BUFFER_SIZE_OUTPUT #######PROGRAM CODE### .section .text # STACK POSITIONS .equ ST_SIZE_RESERVE, 16 # Space for local variables # Note: Offsets are RBP-based, which is set immediately at program start .equ ST_FD_IN, -16 # Local variable for input file descriptor .equ ST_FD_OUT, -8 # Local variable for output file descriptor .equ ST_ARGC, 0 # Number of arguments .equ ST_ARGV_0, 8 # Name of program .equ ST_ARGV_1, 16 # Input file name .equ ST_ARGV_2, 24 # Output file name .globl _start _start: ###INITIALIZE PROGRAM### movq %rsp, %rbp subq $ST_SIZE_RESERVE, %rsp # Allocate space for our file descriptors on the stack ###CHECK PARAMETER COUNT### cmpq $3, ST_ARGC(%rbp) je open_files movq $-1, %rdi # Our return value for parameter problems movq $SYS_EXIT, %rax syscall open_files: open_fd_in: ###OPEN INPUT FILE### movq ST_ARGV_1(%rbp), %rdi # Input filename into %rdi movq $O_RDONLY, %rsi # Read-only flag movq $O_PERMS, %rdx # This doesn't really matter for reading movq $SYS_OPEN, %rax # Specify "open" syscall # Call Linux cmpq $0, %rax # Check success jl exit # In case of error simply terminate store_fd_in: movq %rax, ST_FD_IN(%rbp) # Save the returned file descriptor open_fd_out: ###OPEN OUTPUT FILE### movq ST_ARGV_2(%rbp), %rdi # Output filename into %rdi movq $O_CREAT_WRONLY_TRUNC, %rsi # Flags for writing to the file movq $O_PERMS, %rdx # Permission set for new file (if it's created) movq $SYS_OPEN, %rax # Open the file syscall # Call Linux cmpq $0, %rax # Check success jl close_input # In case of error close input file (already open!) store_fd_out: movq %rax, ST_FD_OUT(%rbp) # Store the file descriptor ###BEGIN MAIN LOOP### read_loop_begin: ###READ IN A BLOCK FROM THE INPUT FILE### movq ST_FD_IN(%rbp), %rdi # Get the input file descriptor movq $BUFFER_DATA_INPUT, %rsi # The location to read into movq $BUFFER_SIZE_INPUT, %rdx # The size of the buffer movq $SYS_READ, %rax syscall # Size of buffer read is returned in %eax ###EXIT IF WE'VE REACHED THE END### cmpq $END_OF_FILE, %rax # Check for end of file marker jle end_loop # If found (or error), go to the end movq $0, %rcx # Registry for counting data continue_read_loop: ###CONVERT TO BASE64### addq $3, %rcx #adds 3 to rcx to check data bounds cmpq %rcx, %rax #compares counter to actual data to check bounds jge end_loop #jumps to end if counter exceeds data subq $3, %rcx #subtracts rcx to keep counter accurate movq $BUFFER_DATA_INPUT, %rdx # moves input data address to rdx #move byte 1 movb 0(%rdx,%rcx,1), %dil # moves actual data to dil incq %rcx # increment pointer shlq $8, %rdi # moves rdi by one pos so data doesn't get overwritten #move byte 2 movb 0(%rdx,%rcx,1), %dil # moves actual data to dil incq %rcx # increment pointer shlq $8, %rdi # moves rdi by one pos so data doesn't get overwritten #move byte 3 movb 0(%rdx,%rcx,1), %dil # moves actual data to dil incq %rcx # increment pointer pushq $-1 # Dummy value for stack alignment pushq %rax # Store number of bytes read for write check call convert_to_upper # Note: RAX may (will) be destroyed (caller-safe!), # but will be returned identically as return value ###WRITE THE BLOCK OUT TO THE OUTPUT FILE### movq ST_FD_OUT(%rbp), %rdi # File to use movq $BUFFER_DATA_OUTPUT, %rsi # Location of buffer movq %rax, %rdx # Size of buffer (=number of bytes actually read/converted) movq $SYS_WRITE, %rax syscall ###CHECK WRITE SUCCESS### popq %rbx # Retrieve number of bytes read addq $8, %rsp # Remove stack alignment space cmpq %rax, %rbx # Compare number read to written jne end_loop # If not the same, terminate program ###CONTINUE THE LOOP### jmp read_loop_begin end_loop: ###CLOSE THE FILES### # NOTE - we don't need to do error checking on these, because # error conditions don't signify anything special here # and there is nothing for us to do anyway movq ST_FD_OUT(%rbp), %rdi movq $SYS_CLOSE, %rax syscall close_input: movq ST_FD_IN(%rbp), %rdi movq $SYS_CLOSE, %rax syscall exit: ###EXIT### movq $0, %rdi # Standard return value for all cases movq $SYS_EXIT, %rax syscall #####FUNCTION convert_to_upper # #PURPOSE: This function actually does the conversion to upper case for a block # #INPUT: The first parameter (rdi) is the location of the block of memory to convert # The second parameter (rsi) is the length of that buffer # #OUTPUT: This function overwrites the current buffer with the upper-casified # version. # Return value: The number of bytes converted (=content of RBX), as # nothing in here can happen to stop/interrupt this. # #VARIABLES: # %rax - beginning of buffer # %rbx - length of buffer (old value must be saved!) # %rdi - current buffer offset # %r10b - current byte being examined (%r10b is the first byte of %r10) # Note: This variable assignment is for exemplary purposes only and very suboptimal! # ###CONSTANTS## .equ LOWERCASE_A, 'a' # The lower boundary of our search .equ LOWERCASE_Z, 'z' # The upper boundary of our search .equ UPPER_CONVERSION, 'A' - 'a' # Conversion between upper and lower case convert_to_upper: pushq %rbp # Prepare stack movq %rsp, %rbp pushq %rbx # Save RBX ###SET UP VARIABLES### movq %rdi, %rax movq %rsi, %rbx movq $0, %rdi # If a buffer with zero length was given us, just leave cmpq $0, %rbx je end_convert_loop convert_loop: # Get the current byte movb (%rax,%rdi,1), %r10b # Go to the next byte unless it is between 'a' and 'z' cmpb $LOWERCASE_A, %r10b jl next_byte cmpb $LOWERCASE_Z, %r10b jg next_byte # Otherwise convert the byte to uppercase addb $UPPER_CONVERSION, %r10b # And store it back movb %r10b, (%rax,%rdi,1) next_byte: incq %rdi # Next byte cmpq %rdi, %rbx # Continue unless we've reached the end jne convert_loop end_convert_loop: movq %rdi, %rax # Store number of chars converted into RAX as return value popq %rbx movq %rbp, %rsp popq %rbp ret