Function Calls, Arguments, and Walking the Stack - by dcx2
This will be an academic discussion of how to “walk the stack”. When writing a code that modifies the game's assembly code, you need to be careful about when the modified code is being called. For example, I made a Super Mario Galaxy code that increased star bits by 1 when you shot them. However, it had the nasty side effect of subtracting 1 from your star bits when you picked them up. Worse, if you started with 0, you couldn't shoot any, and getting star bits subtracted 1, so you become screwed!
In programming, we have functions which are called with arguments. When a function has to do some work, it needs to free up some registers, so it makes some room on the stack and pushes the registers it will use onto the stack. When the function is done computing, it can return the registers to normal by popping the registers off the stack before returning to the caller.
So
you could say that the AddStarBits() function was being called when
when I collected [ AddStarBits(1); ] or shot [ AddStarBits(-1); ] star
bits. Because this code is called from multiple places, changing
it could have many wide-ranging and unintended side effects. It
would be much better to see who is calling me and change the argument
in the “shot star bits” code.
DCX2, why would we want to do that? Just write the max to the RAM value all the time! For your general h4x that's true, but this is an academic discussion so we're using something a bit more contrived in order to demonstrate this process.
Go
to Good Egg Galaxy (or any world that's not the observatory, because my
example requires you to be in a level). Get some Star Bits (I got
20), do some Code Search, find the memory location.
Too many results, so I shoot one
Ah, that's better. Right click on the address, then poke (to 0x14) and verify that it is the star bit count. Then right click on the address again, then breakpoint. Switch tabs and set a Write breakpoint and shoot a star bit. This will provide a clue as to who is changing our star bits.
[NOTE: Sometimes when using data breakpoints you will want Exact match, but this road is occasionally fraught with perils ]
This is our friend who is writing the new star bit count from r3 (was 0x14 but is now 0x13). We could nop this out if we wanted to, but remember, this is academic (and noping would break the adding star bits as well). So let's explore the dissassembly surrounding this instruction.
Note the highlighted add instruction. It sets r3, and a few instructions later, stores r3 into the star bit count. So what's the state of the registers when this is going on? Right click the add and breakpoint. Shoot another bit
[ NOTE: WiiRdGUI automatically makes it an execute breakpoint when you use right-click in the disassembly ]
r4 has 0xFFFFFFFF, which is two's complement for -1. So we're adding -1 to the current star bit count in r0 and putting the result in r3 before storing it back to RAM. So let's try making the add a sub instead!
This succeeds. Shall we call it a day? We shouldn't...let's run around and make sure nothing odd happens. Go grab a star bit and suddenly our star bit count is decreasing! Set the breakpoint on that instruction again and grab another star bit
r4 now has a 1! That's going to subtract from our current star bit count, making it go down. So where does r4 come from? If you search through the disassembly, you will find nothing that sets r4. That's because r4 is an argument. So when we get a star bit, it passes 1 in for an argument, and when we shoot one, -1 in for an argument. We must walk the stack and find the original caller and change the -1. But how do we find where the caller was from?
When
you call a function, you need some way to get back. This
mechanism is provided by the Link Register, LR. When you use the
Branch and Link instruction, BL, it places the address of the next
instruction into the Link Register as a way of “linking” program
execution back to where we left off. Once a function is done, it
will Branch to Link Register, BLR, and program execution continues
after the call.
We can't just look at the LR immediately,
though. Functions can call functions can call functions can call
functions...Any BL between the start of the function and the current
instruction will have modified the LR. So if a function is going
to modify the LR, it will need somewhere to put the current LR, so that
it can get back to the function that called it.
To store the LR,
we will use the stack. The stack is just a large portion of
continuous memory used for to hold register values so that a function
can use those registers without worrying about destroying any of the
caller's data. The stack pointer, r1, points to the top of
it. When a function needs to modify any registers (LR or
otherwise) without losing the original value, it will subtract the
required space from r1 to make room on the stack and then push the
register's value into the blank spot.
When making room on the
stack, you will always see stwu r1,[space required](r1). In the
case below, we're clearing out 16 bytes for storing registers on the
stack. The next instruction, Move From Link Register mflr, puts
the LR into r0. This is necessary because there's no instruction
for writing the LR directly onto the stack.
The next two
highlighted stw's are pushing registers r0 (the “return address” from
the LR, so that we can bl safely) and r31 (room required for a local
variable) onto the stack.
At the end of the function, the highlighted lwz's are popping the registers' original values off the stack now that we're done with them. Then we Move To Link Register mtlr to put the return address back to normal, add 16 to the stack pointer to release the memory we requested, and finally BLR back to our caller!
This
provides two opportunities to find our caller – before the function
pushes the LR and after it pops the LR. I usually choose to break
on stwu because there could potentially be multiple blr's in a single
function.
Right click and set a breakpoint on the stwu and shoot
another star bit. When we break, the link register will hold the
address of the instruction after the bl that called us.
Copy and paste the value in the LR into the disassembler. Scroll up several instructions, to see the calling bl and whatever comes before it.
[ NOTE: bl stores the address of the NEXT instruction in the
LR, so you won't be staring at the caller when you enter the address
into the disassembly, but the instruction AFTER the caller ]
The highlighted bl is one before the value in the LR. That's the instruction that calls the function that's updating the star bit count. As a sanity check, double click the bl and the disassembler will jump to that instruction.
It doesn't point to the stwu! That's okay because there's a branch that DOES go to the stwu.
Back to the disassembly. There's a bunch of bl's before the one we set the first breakpoint inside, and any one of them *could* load r4, or it could be another argument from whoever called us, so let's break at the start of this new function and see if we can see the argument.
Got a star bit here...
Shot a star bit there...
Gee, r4 looks like a really good candidate for the argument. After staring at the Matrix for a while, you'll start to notice that r3 and r4 are often used to pass things into/out of functions. So let's walk the stack again, by copying/pasting LR into the disassembly tab, and scroll back a few lines. (remember that the LR holds the address of the NEXT instruction AFTER the bl, which is why you end up staring at the lwz if you don't scroll up)
Notice that this time the bl points straight at the stwu; this is the typical case. So the argument r4 came from r31. Where did r31 come from?
So once again, someone passed something in, this time with r3. This was then cached in r31. So let's break on the start of this new function! (a journey of a thousand functions begins with a single call...)
Copy and paste the LR into the disassembly, scroll up, look around...
HMMM li r3,-1 right before the bl? Gee, I wonder what that does. Let's try changing it to li r3,0 and see what happens. Whoo, star bit count stays unchanged when shooting!
We started with rudely
swapping the add for a sub, which lead to unintended consequences when
getting star bits legitimately. After walking the stack a few
times, we found the one line of code that only gets run when we're
shooting star bits, and now our hack is sleek.
I hope that now you understand function calls, arguments, and stack management a little better, and that it might come in handy when writing more interesting hacks.