Write a simplified version of PrintF in ARM assembly language.
Requirements and Specifications
For this exercise, you are to write an ARM assembly program that implements a heavily simplifiedversion of the printf function found in the C standard library. Your program will take as input (inspecific registers, your program must use the registers listed for input), the address of a format string to print out (in R1), and the address of a sequence of values (in R2) to interpolate into it.
As with C’s printf(), the format string is composed of zero or more directives: ordinary characters (not ‘%’), which are printed out unchanged; and conversions specifications, which begin with a ‘%’ character. For each conversion specifier encountered in the string, rather than print out the conversion specifier (%s, %d, etc.), your program should instead printout a value of the type specified (string, integer, and so on). These values are specified as a sequence of 32-bit words in memory, with the address of the first value being given to your program in the register, R2.
Some hints for completing this exercise:
- The address of the format string is specified in R1, the address of the values is specified in R2 — if you use other registers, you program will fail in the pipeline. Likewise, you must not add in code before the label printf in the skeleton file, 01-printf.s, or your program will not execute correctly. You may however (and probably should) modify the format strings and sequence of values to ensure your program works correctly with different input.
- Your program will need to loop through every character of the format string, and print out thecharacters (using SWI 0), unless the character is a percent (‘%’) character.
- Your program should stop looping when it reaches a null character in the format string, (i.e. when the byte read from memory has the literal value of zero (not the ASCII code for the character representing the digit zero). You can find this by comparing with #0.
- The format string is made up of characters that are one byte long, you will need to use LDRB toaccess these. The values are all 32-bits (four bytes) long, you will need to use LDR to access these.
- If your program encounters a conversion specifier, (i.e. you read a ‘%’ character from the format string), then your program should fetch the next byte of the format string from memory. Then based on the value of this character, your program should:
- If the conversion specifier is %%, then your program should print out a single percent character. The value of R2 should not be updated.
- If the conversion specifier is %c, then your program should read the 32-bit value from the address currently specified in R2. Your program should treat the value read as an ASCII character and print it out using SWI 0. The value of R2 should be updated to contain the address of the next value in the sequence.
- If the conversion specifier is %d, then your program should read the 32-bit value from the address currently specified in R2. Your program should treat the value read as an unsignedinteger value and print it out using SWI 4. The value of R2 should be updated to contain the address of the next value in the sequence.
- If the conversion specifier is %s, then your program should read the 32-bit value from the address currently specified in R2. Your program should treat the value read as containing the address of a null-terminated string and print it out using SWI 3. The value of R2 should be updated to contain the address of the next value in the sequence.
- Invalid conversion specifiers should print nothing out.
- Bear in mind that each value in the sequence of values is 4 bytes long.
- It can be instructive to consider this program as being formed from two parts. One part of the program is stepping through each character of the format string deciding what needs to be printed (the character, or a value — and for each value, what type of value). The other part of the program is fetching the value from the sequence of values and printing it out based on the type specified in the string. I’d suggest working on the two parts of the program separately, first get the program stepping through the format string printing out dummy values, then when you are confident that part is working, move onto writing the code to read the actual values from memory.
Screenshots of output
B maintstfmt DEFB "%s%c%c\nNumbers: %d %d\n100%% Bad %zformats %ynot printed\n", 0tststr DEFB "This is a test string",0ALIGNseq DEFW tststr,'!','!',1,2main LDR R1, =tstfmt ; formatLDR R2, =seq ; sequenceprintf MOV R3, #0 ; current sequence indexprloop LDRB R0, [R1] ; load character from format stringCMP R0, #0 ; if end of stringBEQ prend ; end printCMP R0, #'%' ; if not a format specifierBNE prchar ; print the characterADD R1, R1, #1 ; else, advance to next character in format stringLDRB R0, [R1] ; load next character from format stringCMP R0, #0 ; if end of stringBEQ prend ; end printCMP R0, #'%' ; if double percentBEQ prchar ; print single percent charCMP R0, #'c' ; if %cBEQ charfmt ; print using char formatCMP R0, #'d' ; if %dBEQ intfmt ; print using int formatCMP R0, #'s' ; if %sBEQ strfmt ; print using string formatB prnext ; else, print nothingcharfmt LDR R0, [R2] ; load value from sequenceADD R2, R2, #4 ; advance to next position in sequenceB prchar ; print characterintfmt LDR R0, [R2] ; load value from sequenceADD R2, R2, #4 ; advance to next position in sequenceSWI 4 ; print integerB prnext ; advance to next char in formatstrfmt LDR R0, [R2] ; load value from sequenceADD R2, R2, #4 ; advance to next position in sequenceSWI 3 ; print stringB prnext ; advance to next char in formatprchar SWI 0 ; print a characterprnext ADD R1, R1, #1 ; advance to next character in format stringB prloop ; repeat loopprend SWI 2
B mainwidth DEFW 20 ; max line widthbuffer DEFS 100 ; word bufferALIGNmain LDR R1, =width ; load width in R1LDR R1, [R1]LDR R2, =buffer ; load buffer address in R2MOV R3, #0 ; current buffer lengthMOV R4, #0 ; current line lengthrdloop SWI 1 ; read characterCMP R0, #'#' ; if hashBEQ quit ; terminate programCMP R0, #' ' ; if spaceBEQ prbuff ; print bufferCMP R0, #10 ; if newlineBEQ prbuff ; print bufferSTRB R0, [R2, R3] ; else, save char in bufferADD R3, R3, #1 ; increment buffer lengthB rdloop ; read next charprbuff MOV R5, R0 ; save space or newline charCMP R3, #0 ; if there's no info in bufferBEQ prnline ; go to print newline if neededprnxt MOV R0, #0 ; save zero in bufferSTRB R0, [R2, R3] ; to mark end of stringADD R0, R3, R4 ; add length of line plus wordADD R0, R0, #1 ; add spaceCMP R0, R1 ; compare with max widthBLE prword ; if <= width, print the wordMOV R0, #10 ; else, print a newline before the wordSWI 0MOV R4, #0 ; clear length of lineprword CMP R4, #0 ; if it's the first wordBEQ prskip ; don't print starting spaceMOV R0, #' ' ; print a space before the wordSWI 0ADD R4, R4, #1 ; increment line lengthprskip LDR R0, =buffer ; print the bufferSWI 3ADD R4, R4, R3 ; add word length to line lengthMOV R3, #0 ; reset word lengthprnline CMP R5, #10 ; if input was not a newlineBNE rdloop ; read next charMOV R0, #10 ; else, print a newlineSWI 0MOV R4, #0 ; clear length of lineB rdloop ; read next charquit SWI 2