Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usage of x87 fstp instruction - popped value showing up in ST7?

I have a question: let's say my stack of the Floating-point unit is as follows:

ST0 = +1.5000000000000000e+0001   
ST1 = +5.0000000000000000e+0000  
ST2 = +2.5000000000000000e+0001   
ST3 = +0.0000000000000000e+0000  
ST4 = +0.0000000000000000e+0000   
ST5 = +0.0000000000000000e+0000   
ST6 = +0.0000000000000000e+0000   
ST7 = +0.0000000000000000e+0000  

After doing this instruction:

fstp st(1) 

My prediction it that the unit would send the st(0) to st(1) (the situation on the stack would be 1.5; 1.5; 2.5; 0 etc.) and then pop the top of the stack so the situation would be 1.5; 2.5; 0 etc.

Instead, I see the following:

ST0 = +1.5000000000000000e+0001   
ST1 = +2.5000000000000000e+0001   
ST2 = +0.0000000000000000e+0000   
ST3 = +0.0000000000000000e+0000   
ST4 = +0.0000000000000000e+0000   
ST5 = +0.0000000000000000e+0000   
ST6 = +0.0000000000000000e+0000   
ST7 = +1.5000000000000000e+0001  

My question is, why has st(7) changed and how can I achieve my expected results?

like image 925
Simon Avatar asked Nov 15 '25 13:11

Simon


1 Answers

The value shown in st7 is what's there in the underlying register after a pop rotates the stack, but it's not actually readable as an operand for an instruction.

As https://masm32.com/masmcode/rayfil/tutorial/fpuchap1.htm says,

Some debuggers may still show the old value in the popped register but that should only be considered as residual "gun powder"

Ray makes an analogy to a revolver barrel for the x87 register stack, hence the "gun powder".
As his nice tutorial explains, the x87 stack registers actually map onto a fixed set of 8 registers, using a 3-bit top-of-stack index in the x87 status word.


After a pop, the register that was st0 is now st7, and instructions like fsave which dump the whole x87 state can still see its value. This is what your debugger is doing; GDB does the same.

But there's also metadata (in the x87 tag word) for each underlying register which marks it as in-use vs. free. This is how fld detects overflow: when the new top-of-stack is already in use. It also stops you from reading a "free" register (by making it read as NaN).

Pop operations (like fstp or faddp) mark the popped register as not-in-use as well as rotating the register stack. There's an ffree instruction to mark a register as free without rotating the stack, but it's not normally used. There isn't an instruction to mark a single register as in-use without pushing.

For example, using NASM syntax:

fldpi
fld1              ; st0 = 1.0  ; st1 = 3.14...
fstp st1          ; st0 = 1.0  ;   ...      st7(free) = 1.0
fadd st0, st7     ; st0 = -nan ;   ...      st7(free) = 1.0

mov eax, 231
syscall          ; x86-64 Linux  exit_group(edi)

I tested this by assembling + linking with nasm -felf64 x87.asm && ld -o x87 x87.o and running gdb ./x87. (This makes a static executable with the entry point at the top of the .text section, thanks to toolchain defaults when the linker doesn't see a _start symbol.)

In GDB, layout reg ; layout next (to get registers and disassembly since I didn't bother making source-debug metadata), and tui reg float to show the x87 registers.
Then starti and si (aka stepi) to single-step through the program.

GDB reproduces your output; you'd have to look at ftag = 0x3fff to see that only R7 (st0) was in-use before fadd st0, st7. Unfortunately it doesn't indicate the free / in-use status next to what it shows for the register contents for either the TUI layout or info reg float.

like image 91
Peter Cordes Avatar answered Nov 17 '25 08:11

Peter Cordes



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!