Tracking Values Written to the Stack

Started by live2play, April 13, 2010, 04:21:33 AM

Previous topic - Next topic

live2play

I set an execute breakpoint (BP) at stfs f0,292(r1) and determined that the address at which f0 is being written is 809884B4.  However, if I put a read BP at 809884B4, the BP is constantly triggered.  If I then put an additional BP criteria like "break only if f0=40C00000", the game freezes.  So, my question is how do I find out where the value that has just been pushed to the stack is being read?  I have even stepped through the code and have not seen a reference to 809884B4 being read, yet the contents of that address have changed.  Any help is greatly appreciated.

In addition, I would like to know why the value of f0 is 40C00000 at the blue instruction below, but it is then 4B400000 on the very first instruction referenced by the bctrl after I use Step to proceed from an execute BP on the bctrl.  How is f0 getting changed?

80255750:  C004004C   lfs   f0,76(r4)
80255754:  D0010124   stfs   f0,292(r1)
80255758:  819F0000   lwz   r12,0(r31)
8025575C:  818C0170   lwz   r12,368(r12)
80255760:  7D8903A6   mtctr   r12
80255764:  4E800421   bctrl  -> branches to 8015D2CC   

8015D2CC:  8063015C   lwz   r3,348(r3)
8015D2D0:  80630000   lwz   r3,0(r3)
8015D2D4:  4E800020   blr   
8015D2D8:  8063015C   lwz   r3,348(r3)
8015D2DC:  D0230048   stfs   f1,72(r3)
8015D2E0:  4E800020   blr   
8015D2E4:  8063015C   lwz   r3,348(r3)
8015D2E8:  D023004C   stfs   f1,76(r3)
8015D2EC:  4E800020   blr   
8015D2F0:  54A0103A   rlwinm   r0,r5,2,0,29
8015D2F4:  7C630214   add   r3,r3,r0
8015D2F8:  90830188   stw   r4,392(r3)
8015D2FC:  4E800020   blr   
8015D300:  4E800020   blr   
8015D304:  4E800020   blr   
8015D308:  81840000   lwz   r12,0(r4)


dcx2

QuoteI set an execute breakpoint (BP) at stfs f0,292(r1) and determined that the address at which f0 is being written is 809884B4.  However, if I put a read BP at 809884B4, the BP is constantly triggered.

The stack is where temporary, locally-scoped variables are kept.  As a function is called, room is made on the stack.  As the function returns, that room is freed.  After the current function finished, it freed that memory, and your other breakpoints are probably the next set of functions which are using that part of the stack.

QuoteSo, my question is how do I find out where the value that has just been pushed to the stack is being read?

Get to your stfs when it has the correct value in it.  Then, after that stfs hits, set only one read breakpoint on the 292(r1) address (r1 may or may not change each time you hit the stfs).  You should go straight to the code which reads that value off the stack, skipping everything in between.  I would save a copy of this new address, because if you set another read breakpoint you will lose it.

QuoteIf I then put an additional BP criteria like "break only if f0=40C00000", the game freezes.
Weird...I know the float registers are really finicky.  But the extra breakpoint condition shouldn't cause freezing...

QuoteIn addition, I would like to know why the value of f0 is 40C00000 at the blue instruction below, but it is then 4B400000 on the very first instruction referenced by the bctrl after I use Step

Regarding those finicky floats, I noticed that the float registers change value randomly all the time.  I'm not sure why...that's why I hate floats.

live2play

I was able to find where the value pushed to the stack was being read.  The trick was not selecting Run after the BP at stfs 292(r1) triggered, but, like you said, go ahead and enter the address referenced by 292(r1) in the BP field, select read and then Set Breakpoint.  Unfortunately, I'm still not able to determine why the other levels freeze, but at least I learned something new.  Thanks!

dcx2

Are you sure you're using safe registers to do your work?  r3 is quite often used for arguments and return values...try using this routine, it pushes/pops the used registers so it should always be safe.


stfs f0,292(r1) # original instruction
stwu r1,-16(r1) # make room for a stack frame
stw r3,8(r1) # push r3
stw r5,12(r1) # push r5
lwz   r3,76(r4) # get 32-bit value
lis   r5,16576 # load r5 with the value to compare r3 against
ori   r5,r5,0
cmpw   r3,r5
bne- 0x10 # if r5 != r3, skip over this
lis   r5,0x40C0 # load r5 with the value we want
ori   r5,r5,0x0000
stw r5,308(r1) # overwrite what the original instruction did (note the adjusted r1 offset)
lwz r3,8(r1) # bne jumps here; pop r3
lwz r5,12(r1) # pop r5
addi r1,r1,16 # clear the stack frame


Did you try moving your C2 address from 80255750 (near your stfs) to the lfs that was reading your float off the stack?  Note that if you move it, the "original instruction" as well as the "overwrite instruction" may need to be modified.

This process can sometimes be frustrating, especially with floats.  But this general technique is very powerful, and avoids the complications associated with pointer searches and dynamic memory.  Eventually you sort of learn how to become one with the Matrix and reading ASM becomes easy.

By the way, have you written any codes?  Did you post them to the forum anywhere?  You seem more than competent enough to get Hacker status.

live2play

#4
Thanks for the ASM example.  I'll give that a shot.

I have made codes, but I'm not quite the hacker you are yet.  :)

live2play

Well, I implemented your code with the push/pop, but the game still freezes in all but two levels.  The other levels must be using the value pushed to the stack for other purposes that the "good" levels are not.  Is it possible for WiiRD to show the address on which a game freezes/crashes?

Romaap

Sometimes when the game freezes, I could just hit next in the breakpoint tab and WiiRD would show the address which caused the freeze.

dcx2

Short answer: It's possible to recover.  Usually.  I've done it before, a few times.  But I take lots of pictures

Long answer...

Aaah, the black art of debugging a game freeze...this might deserve its own thread.

Next works, but there's also a button labeled Get BP Data.  When you hit that button, look at the disassembly.  It's probably a load or a store (though, being technology, that is not always the case).  Find the value in the pointer argument register...it's probably not a valid Wii memory address, and the processor's memory manager stops execution as if it had hit a breakpoint.  Some folks would refer to this as "trapping".

Now, if you just so happen to know the right pointer (or, being technology, sometimes even something else's pointer will suffice)...you can overwrite the bad pointer value and recover (assuming you also went back through the disassembly and fixed whatever started this whole mess).

This is rendered a black art for several reasons.

1) Gotta know the right/good-enough address (I take lots of screenshots so that part is easy if I ever hit a breakpoint in that general area)
2) There's a certain amount of luck, because the borked pointer may have caused other branches to be taken/not-taken, the system's state could become corrupt and then you'd need to know everywhere that's been messed up, and it would certainly be quicker to simply restart
3) It might not be a load or store, but it could be an illegal opcode; if the disassembler looks confused you better know what instruction was supposed to go there (screenshots of a lot of disassembly...)

dcx2

Quote from: live2play on April 15, 2010, 06:14:25 AM
Well, I implemented your code with the push/pop, but the game still freezes in all but two levels.  The other levels must be using the value pushed to the stack for other purposes that the "good" levels are not.

I'll make this a separate post, because I'm so long-winded...

You should try following the code along from the Load, through it's processing.  Be mindful of branches during the processing, because they might be making important decisions. If you get to a Store, use the Read-Breakpoint to jump to the Load.  (be careful about Exact/Not-Exact!  Not Exact may hit things that you aren't interested in and Exact may miss things that you are interested in)

Remember the dependency-free register stuff?  Once something has moved from registers to memory, you can't follow it along in the assembly anymore...so that register is safe, until it gets Loaded (i.e. someone else's Read Breakpoint would have hit it, and then they would follow it along through processing to its associated Store).  Between a Load and Store: dependency.  Between a Store and Load: dependency-free (independency?  lol)

--

Set an Execute Breakpoint on your C2 address.  Once it hits, repeatedly set more breakpoints.  In the best case, you see one frame go by each time you press Set Breakpoint.  However, if you have to hit the button more than once to make one frame pass by, then someone else is running this same section of code.  You'll need to add some compares/branches to make your code only execute once per frame - when it's handling your guy's stuff.

NOTE: If you spam the Set Breakpoint button, you will piss WiiRD off.  Set Breakpoint is like a doorbell.  You wouldn't rapidly press someone's doorbell.  You press it once, and then wait for someone to come to the door (i.e. for WiiRD to display the register and disassembly)

This is where screenshots of the Breakpoints can come in handy.  You can use them to compare what values were to what they are, and you can usually find something interesting.  A lot of the time you might see an odd register somewhere that is counting up or down by 1...that's probably a loop that's iterating through a collection of objects and processing each one.  The player's character is probably first in that loop, enemies/other players might come after.  You might need to push/pop more registers, in which case we should probably use stwm instead...

live2play

Great information!  However, will take awhile to digest.  :)

I did some more tracking down of the freeze issue and believe I am getting closer to at least finding the instruction that's doing something "illegal".  If I don't care about having the hack in the levels that freeze, I did find an instruction that I can nop/un-nop to deactivate/activate the hack.  Kind of a pain especially if I forget to deactivate when I go into the "bad" levels.  :)

live2play

When you say
QuoteFind the value in the pointer argument register
, do you mean see what the value of r1 is?

dcx2

#11
Let's say your game looks like it crashed.  You go to the Breakpoint tab and hit Get BP Data.  You see that it's trapped on this instruction:

lwz   r12,0(r4)

You keep hitting Step but it stays on that instruction.

r4 is the "pointer argument register". EDIT: Argument means that it is given to something else - in this case, the pointer is given to the load instruction, which applies the offset 0, and then fetches that memory location and puts it into r12

r4 should hold a pointer (i.e. something in MEM1 or MEM2).  However, it probably doesn't contain a pointer if you're trapped on it.  If you put the right pointer in r4, the game will continue.

But how do you know what was in r4?  That's why you need to keep notes and screenshots and stuff.

live2play

Actually, wouldn't I be able to see what's in r4 if I do a GetBP Data?  Let's say that I'm able to put a valid pointer into r4 and the game continues without issues.  I guess a "messy" solution to my problem could be to C2 hook some ASM that looks for the bad pointer address and forces it to the "good" one, right?

dcx2

Well, by the time the game froze, it's too late and the value in r4 would have been lost.  You'd need to set an Execute breakpoint on that address when the game doesn't crash, so you can see what's supposed to be in it.

And Get BP Data is pressed for you any time you hit Next or a Breakpoint.

Rather than fixing r4, the solution to the problem is to figure out what you did that corrupted r4 and stop that.  Fixing frozen games is a black art that is used in development of codes, but no released code should ever rely on it.

live2play

I performed the action that caused the game to freeze and then selected Get BP Data.  WiiRD displayed Dumping file... in the title bar and never displayed any of the BP data.  Any ideas?