 |
Cheat Engine The Official Site of Cheat Engine
|
View previous topic :: View next topic |
Author |
Message |
ymiu Cheater
Reputation: 0
Joined: 16 Dec 2018 Posts: 41
|
Posted: Sun Mar 24, 2019 10:03 am Post subject: Problem trying to preserve FPU stack |
|
|
My process is a 32-bit mono game. I'm trying to save ST(0) from the FPU Registers so I can use it to do some float math and then restore it later. I created a label in my script to store the value until I'm done with the FPU.
However, it doesn't seem to be working correctly. When I debug, ST(0) is always 0.00, but when I fstp it to my placeholder, it writes FFC00000. When I fld from my placeholder later, of course ST(0) becomes NaN.
I don't actually know if I need to preserve the FPU stack like this, but whether or not it's essential now, I'm sure it will be one day, so I'd like to learn what's going wrong.
Script below. Note that I'm not actually doing any float math yet, so that's not a factor in the problem:
Code: |
usemono()
define(bytes, D9 1C 24 57 50 39 00)
[ENABLE]
assert(Stats:SetStat+ca, bytes)
globalalloc(statsmem,$1000,Stats:SetStat+ca)
label(return)
label(statsptrinj)
label(dictionaryptr)
label(valueslotsptr)
label(maxhealth)
label(maxhunger)
label(maxstamina)
label(fputemp)
registersymbol(statsptrinj)
registersymbol(dictionaryptr)
registersymbol(valueslotsptr)
registersymbol(maxhealth)
registersymbol(maxhunger)
registersymbol(maxstamina)
registersymbol(fputemp)
statsmem:
fstp dword ptr [esp] // oc
push edi // oc
push eax // oc
cmp [eax],eax // oc
pushfd // save flags register (32 bit)
push ecx // save ecx
mov [dictionaryptr],eax
lea eax,[eax+14]
mov eax,[eax]
mov [valueslotsptr],eax
lea eax,[eax+10]
mov eax,[eax]
mov [maxhealth],eax
mov eax,[valueslotsptr]
lea eax,[eax+1C]
mov eax,[eax]
mov [maxhunger],eax
mov eax,[valueslotsptr]
lea eax,[eax+20]
mov eax,[eax]
mov [maxstamina],eax
fstp dword ptr [fputemp] // pop & store the top of the FPU Stack, since I'd like to use it
// fld dword ptr [maxhealth] // push maxhealth onto FPU Stack
// fstp dword ptr [eax] // pop maxhealth into health
mov eax,[valueslotsptr]
lea eax,[eax+14] // eax = current health addr
mov ecx,[maxhealth]
mov [eax],ecx
// fld dword ptr [maxhunger] // push maxhunger onto FPU Stack
// fstp dword ptr [eax] // pop maxhunger into hunger
mov eax,[valueslotsptr]
lea eax,[eax+18] // eax = current hunger addr
mov ecx,(float)500
// mov ecx,[maxhunger] // max hunger doesn't let you eat food
mov [eax],ecx
// fld dword ptr [maxstamina] // push maxstamina onto FPU Stack
// fstp dword ptr [eax] // pop maxstamina into stamina
mov eax,[valueslotsptr]
lea eax,[eax+24] // eax = current stamina addr
mov ecx,[maxstamina]
mov [eax],ecx
fld dword ptr [fputemp] // push fputemp, restoring the FPU Stack to its original state
mov eax,[dictionaryptr] // restore eax to original state
pop ecx // restore ecx
popfd // restore flags register (32 bit)
jmp return
dictionaryptr:
dd 0
valueslotsptr:
dd 0
maxhealth:
dd 0
maxhunger:
dd 0
maxstamina:
dd 0
fputemp:
dd 0
Stats:SetStat+ca:
statsptrinj:
jmp statsmem // 5 bytes
nop // 1 byte
nop // 1 byte
return:
[DISABLE]
statsptrinj:
db bytes
unregistersymbol(fputemp)
unregistersymbol(maxstamina)
unregistersymbol(maxhunger)
unregistersymbol(maxhealth)
unregistersymbol(valueslotsptr)
unregistersymbol(dictionaryptr)
unregistersymbol(statsptrinj)
dealloc(statsmem)
|
Here are the values I get from the FPU registers when I debug:
1) Prior to fstp dword ptr [fputemp]
[fputemp] is 00000000
FPU Stack:
0.00
4.00
0.00
0.00
1.93
0.00
498.91
498.90
2) After fstp dword ptr [fputemp]
[fputemp] changes to FFC00000
FPU Stack:
4.00
0.00
0.00
1.93
0.00
498.91
498.90
0.00
3) After fld dword ptr [fputemp]
FPU Stack:
Nan
4.00
0.00
0.00
1.93
0.00
498.91
498.90
|
|
Back to top |
|
 |
OldCheatEngineUser Whateven rank
Reputation: 20
Joined: 01 Feb 2016 Posts: 1586
|
Posted: Sun Mar 24, 2019 11:37 pm Post subject: |
|
|
i did not read the full code nor the full post, but here are things you need to consider when working with fpu:
- the idea of loading / storing and popping fpu stack/registers is similar to stack pointer, popping a value from st(0) increments that pointer so it points to the next register at same time clears st(7) and sets it to nan value (ffc00000)
- always check status word when doing math
- always check control word especially when working with floating point values
- in order to display the correct value type you must check precision control value (stored in control word) and by default (at least in current processors) the precision control is set to extended precision
- usually when loaded value does not match the precision control field, it converts it internally
otherwise learn SSE and SSE2 and go for that.
_________________
About Me;
I Use CE Since Version 1.X, And Still Learning How To Use It Well!
Jul 26, 2020
STN wrote: | i am a sweetheart. |
|
|
Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4699
|
Posted: Mon Mar 25, 2019 10:59 am Post subject: |
|
|
You're not using the FPU at all. Even if you were, I doubt the game is using all 8 registers at the time of this code injection, so there's no need to do that. Why back it up in the first place?
The fstp probably underflowed the fpu stack. Basically, don't worry about it; the game probably isn't using empty fpu registers anyway, so do whatever you want with the fpu. Just keep it balanced so you don't overflow it.
For more information, look at Intel SDM volume 1 chapter 8 "Programming with the x87 FPU".
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
Back to top |
|
 |
ymiu Cheater
Reputation: 0
Joined: 16 Dec 2018 Posts: 41
|
Posted: Mon Mar 25, 2019 11:00 am Post subject: |
|
|
@OldCheatEngineUser:
Doing my best to Google what all that means, but it feels like it's a bit over-the-top the more I read. I'm not trying to do any fancy math in the end, my above example was simplified by not doing any math at all (they're commented out), proving that the FPU math instructions are unrelated to my problem.
Eventually, all I wanted to do was decrement the max-stat values by 1 and then load the results into the current-stat values. After some research, it looks like FISUB was the easiest way to do this since I'm just decrementing by 1. I have this working, but it destroys st(7) if I can't find a way to save st(0) ahead of time.
But like I said, the example doesn't actually do any of this. I'm just concerned that pushing temporary values to the FPU Stack, thus clearing st(7) as you described, may create some unwanted behavior by whatever follows my code. I don't know if st(7) is important at this time. So I wanted to pop st(0) to a safe place, do my stuff, and then restore st(0) from my safe place. I thought this would look like:
Code: | label(fputemp)
...
fstp dword ptr [fputemp] // st(0) is currently 0.00, but this writes FFC00000 to [fputemp], which is (float)NaN
...
fld dword ptr [fputemp] // since [fputemp] is FFC00000 instead of 00000000, this makes st(0) NaN instead of 0.00
...
fputemp:
dd 0
... |
... and the comments describe where I'm seeing unexpected behavior.
As I'm researching all this, I'm finding some other commands that look like they might help me: FNSAVE and FRSTOR. Am I better off using these to save and restore the entire FPU Stack? Unfortunately, I can't find any practical examples of their use, and CE's error doesn't provide any guidance on what's wrong with my syntax.
@ParkourPenguin:
re: not using FPU: that was intentional to show that my math wasn't causing the problem. After I posted, I worked out my FPU instructions as:
Code: |
fld dword ptr [maxhealth] // push maxhealth onto FPU Stack
fisub dword ptr [sdec]
mov eax,[valueslotsptr]
lea eax,[eax+14] // eax = current health addr
fstp dword ptr [eax] // pop maxhealth into health
fld dword ptr [maxhunger] // push maxhunger onto FPU Stack
fidiv dword ptr [sdiv]
mov eax,[valueslotsptr]
lea eax,[eax+18] // eax = current hunger addr
fstp dword ptr [eax] // pop maxhunger into hunger
fld dword ptr [maxstamina] // push maxstamina onto FPU Stack
fisub dword ptr [sdec]
mov eax,[valueslotsptr]
lea eax,[eax+24] // eax = current stamina addr
fstp dword ptr [eax] // pop maxstamina into stamina |
I had a little trouble with fisub and fidiv since I wanted to use straight integers e.g. fisub (int)1 but that threw an error, and I couldn't find any examples proving it could work. So I just threw dd 1 into [sdec] and dd 2 into [sdiv], which worked.
And you're right, Without trying to backup st(0), everything seems to work fine. I know I'm my own worst enemy sometimes. I often find myself asking such questions out of curiosity rather than actually trying to solve an apparent problem. Maybe one day, I really will have to pop st(0) and restore it later. It doesn't sound like a hard thing to do from all the CEA I've mucked with over the years, so I'm very perplexed why it's proving so difficult, regardless if it's necessary. You proposed that it may be an underflow. I'm sure that giant manual you linked talks about it somewhere, but a text search didn't show any hits =) Wikipedia is more helpful with https://en.wikipedia.org/wiki/Arithmetic_underflow but I'm really more interested in the thoughts of this community that spends so much time in a relevant context to what I'm doing. Assembly reference material rarely feels truly relevant to what I encounter in my CE struggles.
Reflecting on all this, I believe my confusion stemmed from CE's debugger showing me that st(0) contained 0.00. If I'm understanding this underflow idea correctly, does that mean that 0.00 wasn't entirely accurate as to what was stored in st(0)?
|
|
Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4699
|
Posted: Mon Mar 25, 2019 12:18 pm Post subject: |
|
|
If you look at documentation for the fisub instruction, you'll see that there is no instruction that takes an immediate value as an operand- only a memory location containing an integer.
You shouldn't need to back up the fpu register(s) the same way as you do the general-purpose registers. I highly doubt you'll encounter a circumstance where the game is using all 8 fpu registers at the time of the code injection. If you do find a need to backup and restore an fpu register, then what you're doing will work. Just push it into some memory location and pop it back when you're done.
The reason why it's not working for you right now is because the game isn't using any of the fpu registers at the time of this code injection. Every fpu register is marked as empty. Any attempt to access them will cause a floating point stack underflow exception and will write the relevant indefinite value (i.e. NaN) to the destination.
This behaviour is documented in Intel SDM volume 1 section 8.5.1.1 as of the current revision (order number 253665-069US / January 2019):
Quote: | Stack underflow — An instruction references an empty x87 FPU register as a source operand, including attempting to write the contents of an empty register to memory. ...
The term stack overflow originates from the situation where the program has loaded (pushed) eight values from memory onto the x87 FPU register stack and the next value pushed on the stack causes a stack wraparound to a register that already contains a value.
The term stack underflow originates from the opposite situation. Here, a program has stored (popped) eight values from the x87 FPU register stack to memory and the next value popped from the stack causes stack wraparound to an empty register.
...
If the invalid-operation exception is masked, the x87 FPU returns the floating point, integer, or packed decimal integer indefinite value to the destination operand, depending on the instruction being executed. This value over-writes the destination register or memory location specified by the instruction. |
That Wikipedia article talks about something completely different.
ymiu wrote: | Assembly reference material rarely feels truly relevant to what I encounter in my CE struggles. | Well, this is relevant.
Formal reference material is often unnecessary to get an AA script working. However, you're asking questions that go beyond the common mentality of willful apathy. If you want to start writing good assembly code, you should learn how assembly works. Reading documentation is the most correct way of doing that. It's not often as pragmatic as winging it, but it is a good (and sometimes, the only) source of the information one needs to write better assembly code.
If you don't want to, then you should stop "asking such questions out of curiosity rather than actually trying to solve an apparent problem."
CE displays 0 because that's what's in the register. It doesn't say whether or not the register is marked as empty. There should probably be some feature to avoid this confusion (e.g. hide/show empty registers), but it's not a common problem to have.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
Back to top |
|
 |
OldCheatEngineUser Whateven rank
Reputation: 20
Joined: 01 Feb 2016 Posts: 1586
|
Posted: Mon Mar 25, 2019 2:39 pm Post subject: |
|
|
ehm just noticed a mistake in your code:
- fld ...
- fisub ...
i should have mentioned this earlier, the i is used for integer operations.
fld expects a floating point value, fisub subtracts integer 1.
so you should expect an undefined behaviour.
if you are working with integers, use fild instead of fld. (otherwise use fsub instead of fisub)
and if its integer then why using fpu at all? general purpose instructions will do the job, in case you just need a copy of the st(0) integer value use FIST.
saving fpu registers:
you can save all registers and then restore them, but remember this rule FOLI. (first out last in)
but why saving when fpu registers are not used by the software, using fpu have greater overhead so dont use fpu if you cant handle it properly. (not even for simple addition/subtraction)
and stuff i mentioned about status word and control word can be found in intel-sdm vol2, try sse and sse2 extensions.
_________________
About Me;
I Use CE Since Version 1.X, And Still Learning How To Use It Well!
Jul 26, 2020
STN wrote: | i am a sweetheart. |
|
|
Back to top |
|
 |
Dark Byte Site Admin
Reputation: 470
Joined: 09 May 2003 Posts: 25788 Location: The netherlands
|
Posted: Mon Mar 25, 2019 3:43 pm Post subject: |
|
|
Code: |
sub esp,#128
fnsave [esp]
do whatever you like with the fpu registers
frstor [esp]
add esp,#128
|
_________________
Do not ask me about online cheats. I don't know any and wont help finding them.
Like my help? Join me on Patreon so i can keep helping |
|
Back to top |
|
 |
ymiu Cheater
Reputation: 0
Joined: 16 Dec 2018 Posts: 41
|
Posted: Mon Mar 25, 2019 5:31 pm Post subject: |
|
|
ParkourPenguin wrote: | ...the game isn't using any of the fpu registers at the time of this code injection. Every fpu register is marked as empty. |
Are you inferring this from the behavior I described, or is there some place to explicitly see that they're marked as empty? This is the insight I was missing all along.
@Dark Byte: very helpful... thanks for the example!
@OldCheatEngineUser: I'm not sure I follow. The code acts exactly as expected. E.g. When [maxhealth] is (float)68 and [sdec] is dd 1, then I run:
Code: | fld dword ptr [maxhealth] // push maxhealth onto FPU Stack
fisub dword ptr [sdec]
mov eax,[valueslotsptr]
lea eax,[eax+14] // eax = current health addr
fstp dword ptr [eax] // pop maxhealth into health |
... and it correctly stores (float)67 into my health. fidiv dword ptr [sdiv] also correctly turns (float)1000 into (float)500 when [sdiv] is dd 2. It appears to me that the fi instructions are just meant to allow mixed-type arithmetic, similar to how I could execute x = y + z in higher level languages where x and y are defined as floats and z is defined as an integer. The reason I went with the fi instructions was the simplicity of my required arithmetic and the (perceived) benefit that it only uses one FPU register instead of two.
|
|
Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4699
|
Posted: Mon Mar 25, 2019 7:59 pm Post subject: |
|
|
Storing the value writes NaN instead of the value itself. That's the exact behaviour as is described in that section of the manual I referenced.
There's no obvious way to get that information in CE AFAIK. If you want it in AA, it should be in the structure fnsave writes to. Read the manual for more information.
I don't know what OldCheatEngineUser is talking about. fisub should work fine- it's documented that "The FISUB instructions convert an integer source operand to double extended-precision floating-point format before performing the subtraction."
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
Back to top |
|
 |
OldCheatEngineUser Whateven rank
Reputation: 20
Joined: 01 Feb 2016 Posts: 1586
|
Posted: Mon Mar 25, 2019 11:17 pm Post subject: |
|
|
i apologize about fisub.
_________________
About Me;
I Use CE Since Version 1.X, And Still Learning How To Use It Well!
Jul 26, 2020
STN wrote: | i am a sweetheart. |
|
|
Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You can download files in this forum
|
|