Search code examples
assemblygdbx86-64nasmdwarf

gdb print and examine commands give incorrect results for a float variable


I'm going through a book on x64 assembly. The book described a little on how to use gdb I was able to examine integers however print wouldn't work. examine would work for integers if I included the size such as "db" by using examine like so, x/bu &bNum which would output 123 as expected.

for a float value though if I did not examine an integer before examining a float, I would get what I believe is an incorrect result. running x/qf &qNum3 would output the result 1.26443839e+11

if I used x/bu &bNum then ran x/qf &qNum3 it would give the expected result 3.1400000000000001 So why does examining a float give the correct output only after examining an integer?

using any of the previous commands without the ampersand '&' resulted in gdb responding with " 'qNum3' has unknown type; cast it to it's declared type " is this the expected response?

print just doesn't seem to ever give the right result.

I'm using the commands bellow, as the book instructed, in order to assemble and link the program nasm -f elf64 -g -F dwarf move.asm -l move.lst gcc -o move move.o -no-pie

This is an example program given by the book.

; move.asm
section .data
      bNum  db    123
      wNum  dw    12345
      dNum  dd    1234567890
      qNum1 dq    1234567890123456789
      qNum2 dq    123456
      qNum3 dq    3.14
section .bss
section .text
      global main
main:
push  rbp
mov   rbp,rsp
      mov rax, -1           ; fill rax with 1s
      mov al, byte [bNum]   ; does NOT clear upper bits of rax
      xor rax,rax           ; clear rax
      mov al, byte [bNum]   ; now rax has the correct value
      mov rax, -1           ; fill rax with 1s
      mov ax, word [wNum]   ; does NOT clear upper bits of rax
      xor rax,rax           ; clear rax
      mov ax, word [wNum]   ; now rax has the correct value
      mov rax, -1           ; fill rax with 1s
      mov eax, dword [dNum] ; does clear upper bits of rax
      mov rax, -1           ; fill rax with 1s
      mov rax, qword [qNum1] ; does clear upper bits of rax
      mov qword [qNum2], rax ; one operand always a register
      mov rax, 123456       ; source operand an immediate value
      movq xmm0, [qNum3]    ; instruction for floating point
mov rsp,rbp
pop rbp
ret

x/qf &qNum3 expected something close to 3.14 x/qf &qNum3 would output the result "<0x404027> 1.26443839e+11"

x/qt &qNum3 expected something close to 01000000 01001000 11110101 11000011 x/qt &qNum3 would output the result 01010001 11101011 10000101 00011111

x/qx &qNum3 expected something close to 0x4048F5C3 x/qx &qNum3 would output the result 0x51eb851f

print /f &qNum3expected something close to 3.14 print /f &qNum3 would output "$2 = 2.0803755547161745e-317"

If the command x/bu &bNum was run first the previous examine commands would now output results different than before.

the command x/qf &qNum3 would output the expected result 3.14

x/qt &qNum3 would now output the result 00011111

x/qx &qNum3 would now output the result 0x1f


Solution

  • /q isn't a size modifier for GDB. According to h x, 8-byte size is /g (Giant).

    x /gf &qNum3 correctly eXamines memory as 8-byte doubles,
    as does p (double)qNum3. print /f &qNum3 interprets the address as a float bit-pattern, same result as print (double)&qNum3, a tiny subnormal float (only some bits set near the bottom of the mantissa).

    GDB casts use C syntax but they're like std::bit_cast to tell it how to interpret the bits, not to produce a new value in a different type that represents the same number. (At least for variables, not for constants like print (double)1234.)


    I don't know what /q does as a modifier for /x, but with the size still being the default 32-bit, you're interpreting the low 4 bytes of the mantissa as a float (IEEE binary32), the same number you get from p (float)qNum3

    (x remembers the last size you used, so if you had used an 8-byte-chunk x previously, x /f would work, too. And that's why x /b (Byte) changes results from later x commands that don't include a size.)

    You can also do p (double[3])qNum1 to interpret all 3 qwords as IEEE binary64 bit-patterns. (The first two are integers in the NASM source whose bit-patterns represent tiny doubles.)