Ponto flutuante, endianness e bit-packing (verificando com Python)
TL;DR
- Você vai aprender como
floatedoublesão representados (IEEE-754 (Institute of Electrical and Electronics Engineers 754)) e como esses padrões de bits aparecem em assembly e em um hexdump. - Vai entender por que compiladores às vezes emitem uma constante
doublecomo dois valores.wordde 32 bits no RV32. - Vai aprender um método confiável de verificação: usar
structdo Python para pack/unpack e comparar com o que você vê no disassembly.
1. O básico de IEEE-754 que você realmente precisa
float (32-bit)
- 1 bit de sinal
- 8 bits de expoente
- 23 bits de fração
double (64-bit)
- 1 bit de sinal
- 11 bits de expoente
- 52 bits de fração
Fatos práticos importantes:
doubletem 8 bytes (64 bits).- Em RV32 little-endian, esses 8 bytes aparecem com o byte menos significativo no menor endereço.
2. Por que um double vira dois .word no RV32
RV32 tem registradores de propósito geral de 32 bits, e muitas toolchains representam constantes em blocos de 32 bits.
Então o compilador pode emitir:
| |
Isso é simplesmente 64 bits, divididos em 32 bits baixos e 32 bits altos (por causa do layout little-endian).
.dword ou .quad para constantes de 64 bits.3. Na prática: produzir uma constante double em assembly
Crie:
| |
Compile para assembly:
| |
Procure rótulos .LC* e os words emitidos.
4. Verifique os bits exatos com Python (struct)
Empacote um double em bytes
| |
'<d'significa little-endian (<) e double (d).
Divida em duas words de 32 bits (little-endian)
| |
Se o seu assembly mostra:
.word <lo>.word <hi>
…então bate exatamente.
< little, > big) e tamanhos (I = 32-bit unsigned, Q = 64-bit unsigned).5. O caminho inverso: reconstruir o double a partir das words
Se você só tem dois valores .word no assembly:
| |
Isso retorna o double exato representado por aqueles bits.
6. Sanity check rápido de endian
Se você empacotar com big-endian por engano:
| |
Você verá os bytes invertidos em relação à representação little-endian. Essa é uma fonte de confusão muito comum ao correlacionar hexdumps com valores numéricos.
7. E as instruções de ponto flutuante no RV32?
Ponto flutuante no RISC-V depende de extensões da ISA:
F(float precisão simples)D(double precisão dupla)
Se você compila com um ABI que espera registradores de float (como ilp32d), a convenção de chamada muda para parâmetros/retornos de ponto flutuante.
Em muitos casos embarcados você usará soft-float (operações de ponto flutuante em software), o que afeta desempenho e o formato do disassembly.
F/D, compilar com um ABI hard-float pode produzir binários que falham ou se comportam de forma incorreta.8. Casos práticos de engenharia reversa
- Identificar tabelas de lookup em
.rodata(seno, constantes de calibração) - Verificar campos de protocolo (codificações fixed-point)
- Confirmar se “constantes misteriosas” no firmware são floats/doubles
Uma heurística rápida:
- Se você vê padrões repetitivos que parecem aleatórios mas estáveis, e alinhamento de 4/8 bytes, suspeite de tabelas de float.
Exercícios
- Repita o experimento com um
doublenegativo (ex.:-0.125) e verifique o efeito do bit de sinal. - Pegue um par de constantes
.worddo seu próprio firmware/ELF e tente reconstruir odoublecom Python. - Crie uma constante
floate verifique sua representação de 32 bits comstruct.pack('<f', x)estruct.unpack('<I', ...). - Faça dump de
.rodatacomobjdump -s -j .rodatae tente decodificar os primeiros 3doubleque encontrar.
Como testar suas respostas
- As words derivadas no Python devem bater exatamente com os valores
.worddo assembler. - Floats/doubles reconstruídos devem bater com o valor impresso dentro do erro de arredondamento esperado.
Resumo
Você aprendeu como floats/doubles viram bytes, por que RV32 costuma emitir constantes de 64 bits como dois .word, e como verificar tudo com Python usando bit-packing.
A seguir: debug com QEMU + GDB, vamos combinar o que você aprendeu para inspecionar registradores, memória e fluxo de controle de forma disciplinada.