Chapter 25: Callee Register Application
We can use the callee-saved registers to keep data intact thru various function calls. A good way to showcase this is with some File Management. We will do the following..
We'll look at the whole Program then dissect it.
.section .text .globl main main: #Prologue; save r3 registers stwu sp, -0x0020 (sp) mflr r0 stmw r29, 0x8 (sp) stw r0, 0x0024 (sp) #Open the file lis r3, filename_ptr@ha la r3, filename_ptr@l (r3) lis r4, perms_ptr@ha la r4, perms_ptr@l (r4) bl fopen #Save File Stream Pointer in a non-vol register while checking against null pointer mr. r31, r3 beq- print_error #Seek to end of file li r4, 0 li r5, 2 bl fseek #Get size of file mr r3, r31 bl ftell #Save size in a non-vol register mr r30, r3 #Rewind back to start of file mr r3, r31 bl rewind #Allocate Memory mr r3, r30 bl malloc #Verify Malloc's return value while saving Allocated Memory Ptr mr. r29, r3 beq- print_error #Read file into the Allocated Memory; r3 arg already set li r4, 1 mr r5, r30 mr r6, r31 bl fread #Verify that fread worked cmpw r3, r30 bne- print_error #Close file mr r3, r31 bl fclose #Everything worked, print Success lis r3, success_msg@ha la r3, success_msg@l (r3) call_puts: bl puts #Exit Program (0). Epilogue; restore 3 registers lwz r0, 0x0024 (sp) lmw r29, 0x8 (sp) mtlr r0 addi sp, sp, 0x0020 blr #Portion of Program to handle errors; print Error print_error: lis r3, error_msg@ha la r3, error_msg@l (r3) b call_puts #Directives filename_ptr: .asciz "random_file.txt" perms_ptr: .asciz "rb" success_msg: .asciz "Success!" error_msg: .asciz "Error!"
Alright a lot to unpack. First, take a look of how we use r29, r30, r31 to keep data for later use by other functions. You CANNOT do this with the caller-saved registers (r3 thru 10) or r11/12. Due to the design of the PowerPC calling convention, those registers are not guaranteed to remain intact after a function call.
We ended up having to use 3 callee-saved registers. This is why we created a Stack Frame that had the size to contain 3 registers.
Take a look at the code that checks the return values of fopen, and malloc. With these two functions, it's proper procedure to check their return values. We also needed to place the return values in callee-saved registers. So by using the Record feature that comes with the mr instruction, we can do those two tasks at the same time. Recall back in Chapter 13 that the Record feature is a free use of "cmpwi rX, 0". Example....
Bad Code:
cmpwi r3, 0 beq- error mr r31, r3
Good Code:
mr. r31, r3 beq- error
mr. r31, r3 = mr r31, r3 then do cmpwi r3, 0
Fseek:
By default when you read/write files, the very beginning of the file is the starting point. With fseek, you can change this. In our program we seek to the last byte of file + 1. fseek has two args..
Most programs do not check the return value of fseek as it never fails natively.
Ftell:
This will give you the byte amount of where the fseek's "cursor" is located in the file.
Start of the file would be byte 0. So a 0 would be returned. When calling ftell after seeking to the end+1 of a file, this will give you back the file's exact byte size.
Rewind:
Since we were at the end+1 of the file, we need to go back to the start. Yeah you could use fseek with r4 being 0, but its much faster to use Rewind.
Most programs don't check the return value for this function as it never fails natively.
Malloc:
This will allocated memory for us. There will be times when allocated buffer/memory space in a Stack Frame won't be large enough to get the job done. For such cases, the C function of malloc needs to be used.
Question: Vega, are what exact size should I not use a Stack Frame for allocating memory and start using Malloc?
Answer: It really depends on the System's overall design. There's no clear cut answer to this. Rough shot, I would say 500 bytes. However whats really key here is that if you are dealing with an item that has a dynamic size (like a file), you should use Malloc. Files can vary in size, what if you thought a file was going to be small and end up being 10,000 bytes? For items that use a static size (predictable size that is always the same, and it's small), use the Stack Frame.
Fread:
Alrighty, this is what you use to copy-paste (dump) a file's contents to the Program's memory.
Args & Return Values:
When verifying fread, you always want to check r3 against what you used for the r5 arg. If not equal, a boo boo happened.
Puts:
If you are printing a string that you know will never have format descriptors and you also want to enter into a newline, then use Puts instead. It can only print basic strings and automatically does "/n" without needing to have it in the String. It's much faster than printf.
Save the source as Our_Program.S and assemble it to Our_Program.
powerpc-linux-gnu-gcc -mregnames -ggdb3 -o Our_Program -static Our_Program.S
Now here is a random_file.txt that contains a basic message.
Place random_file.txt in the program's directory. Be sure it can be read by a regular User within your Linux OS. Run the program.
chmod +x ./Our_Program
./Our_Program
You should see "Success!"
Now rename the random_file.txt to norandom_file.txt. Rerun the program. You should see "Error!". Obviously one the functions (fopen specifically) failed and therefore, the custom Error Message was printed. Now rename the file BACK to random_file.txt. We will need it again for the next Section.
.section .text .globl main main: #Prologue; save r3 registers stwu sp, -0x0020 (sp) mflr r0 stmw r29, 0x8 (sp) stw r0, 0x0024 (sp) #Open the file lis r3, filename_ptr@ha la r3, filename_ptr@l (r3) lis r4, perms_ptr@ha la r4, perms_ptr@l (r4) bl fopen #Save File Stream Pointer in a non-vol register while checking against null pointer mr. r31, r3 beq- print_error #Seek to end of file li r4, 0 li r5, 2 bl fseek #Get size of file mr r3, r31 bl ftell #Save size in a non-vol register mr r30, r3 #Rewind back to start of file mr r3, r31 bl rewind #Allocate Memory + 1 extra byte addi r3, r30, 1 bl malloc #Verify Malloc's return value while saving Allocated Memory Ptr mr. r29, r3 beq- print_error #Read file into the Allocated Memory; r3 arg already set li r4, 1 mr r5, r30 mr r6, r31 bl fread #Verify that fread worked cmpw r3, r30 bne- print_error #Close file mr r3, r31 bl fclose #Append the file contents with a null byte because we will be printing it as a string and ALL strings need to end in null or else we may print extra junk. li r3, 0 stbx r3, r30, r29 #Everything worked, print file contents mr r3, r29 call_puts: bl puts #Exit Program (0). Epilogue; restore 3 registers lwz r0, 0x0024 (sp) lmw r29, 0x8 (sp) mtlr r0 addi sp, sp, 0x0020 blr #Portion of Program to handle errors; print Error print_error: lis r3, error_msg@ha la r3, error_msg@l (r3) b call_puts #Directives filename_ptr: .asciz "random_file.txt" perms_ptr: .asciz "rb" error_msg: .asciz "Error!"
In this portion of the program...
#Allocate Memory + 1 extra byte addi r3, r30, 1 bl malloc
...we allocate one extra byte. Why? Because when printf/puts outputs a string to the console, it knows when to stop printing once it reads a null byte. Txt files aren't required to end in a null byte. Most don't. Therefore, after we read/dump random.txt to memory, we want to make sure a null byte is after the last byte of the file. So obviously, we need one more byte for our allocated memory. Next we need to write the null byte. This is why we have the following in the program..
#Append the file contents with a null byte because we will be printing it as a string, and ALL strings need to end in null or else we may print extra junk. li r3, 0 stbx r3, r30, r29
By using the file size with the pointer to the files contents, a stbx instruction will store a byte to EndofFile+1.
Assemble and run the program (as Our_Program2.S).
powerpc-linux-gnu-gcc -mregnames -ggdb3 -o Our_Program2 -static Our_Program2.S
chmod +x ./Our_Program2
./Our_Program2
You will see the contents of the file printed on the screen! Hooray. If you're familiar with C/C++ you may know that the %s format descriptor for printf could have been used to limit the output just in case a string doesn't end in a null byte. It wasn't used in the above Program because puts is faster, and you wouldn't be able to print accurately if the file is dynamic/changing size. Onto the next chapter!