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 &qNum3
expected 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
/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.)