┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Inspect: Read the Bits
~ CuB3y0nd
─── Before we start ───────────────────────────────────────────────────────────

A hex constant in a write-up. A signed comparison that went the wrong way.
Four bytes from a debugger. Usually that means opening another tab or a REPL.

Try selecting -1 here, then open [inspect]. The decimal value is still -1.
The hex is 0xffffffff. Change BITS to 16: now it is 0xffff. Same idea, less
window shuffling.

The examples below are meant to be used. Select the input itself, without the
answer beside it. For a comparison, select the whole expression. A result
stays put until you select something else, scroll away, or press Esc.

[ Contents ]

  01    Bases & permissions          06    Floating point
  02    Signed meets unsigned        07    Numbers versus bits
  03    A pocket C evaluator         08    Bytes & byte order
  04    Widths & decompiler types    09    Reading a dump
  05    Masks & partial values       10    Text hiding in bytes

C expressions use Linux x86-64: signed char, 16-bit short, 32-bit int,
64-bit long and long long. Signed integer casts follow GCC. There is no
process attached; all operands have to be in the selected text.

─── 1. Bases & permissions ────────────────────────────────────────────────────

The four base rows describe one bit pattern. HEX, OCT and BIN use the selected
width, so a negative integer has a two's-complement representation.

  Input                         DEC                  HEX
  ────────────────────────────  ───────────────────  ──────────────────
  4096                          4096                 0x1000
  0X1000                        4096                 0x1000
  0b1001                        9                    0x9
  0o755                         493                  0x1ed
  0755                          493                  0x1ed
  755                           755                  0x2f3
  9007199254740993              9007199254740993     0x20000000000001

That last row is already past JavaScript's safe integer range. It stays exact.

/// mode bits /////////////////////////////////////////////////////////////////

Bare 755 is decimal. C's 0755 and the explicit 0o755 are octal. A chmod mode
is also octal, even without the leading zero. Select the command and its mode:

  chmod 755   rwxr-xr-x
  chmod 4755  rwsr-xr-x
  mode 2644   rw-r-Sr--
  01777       rwxrwxrwt
  07000       --S--S--T

Switch VIEW to Permissions to split owner, group and other. The uppercase
S or T means the special bit is set but the corresponding execute bit is not.
This view describes permission bits; it does not run chmod.

─── 2. Signed meets unsigned ──────────────────────────────────────────────────

Here is a table, with each row written as a complete expression.
Every comparison below is true. The interesting part is the COMPARE row:
it shows the type used for the operands, followed by their converted values.

  Expression                                Operands compared as
  ────────────────────────────────────────  ─────────────────────
  0 == 0U                                   unsigned int / 32
  -1 < 0                                    int / 32
  -1 > 0U                                   unsigned int / 32
  2147483647 > -2147483647-1                int / 32
  2147483647U < -2147483647-1               unsigned int / 32
  -1 > -2                                   int / 32
  (unsigned)-1 > -2                         unsigned int / 32
  2147483647 < 2147483648U                  unsigned int / 32
  2147483647 > (int)2147483648U             int / 32

Now try these two together:

  -1 < 0U   false  LEFT becomes 4294967295
  -1L < 0U  true   a signed long can hold every unsigned int

"Unsigned always wins" misses that second case. Width and rank both matter.
The comparison result itself is an int: 0 or 1.

Literal spelling matters too:

  2147483648        long / 64
  0x80000000        unsigned int / 32
  (int)2147483648U  -2147483648
  -2147483647-1     int / 32
  -2147483648       long / 64

The minus sign is an operator. In that last row, 2147483648 gets its type
before it is negated.

─── 3. A pocket C evaluator ───────────────────────────────────────────────────

Offsets, alignment, a flag check: small expressions save the most trips.

  Input                                  Result
  ─────────────────────────────────────  ────────────────────────
  0x400123 - 0x400000                    0x123
  (0x1234 + 0xfff) & ~0xfff              0x2000
  2 + 3 * 4                              14
  (2 + 3) * 4                            20
  -7 / 3                                 -2
  -7 % 3                                 -1
  0xff ^ 0x0f                            0xf0
  0x80 | 0x11                            0x91
  (0x91 & 0x80) != 0                     true
  1U << 31                               0x80000000
  -8 >> 2                                -2
  !(0 | 0)                               1
  3 >= 2 && 3 <= 4                       1
  0 && 1/0                               0
  1 || 1/0                               1
  1 ? 3 : 1/0                            3
  0 ? 0U : -1                            4294967295

The unevaluated side of &&, || or ?: stays unevaluated. Both branches of ?:
still contribute to the result type. Try the last two rows to see the split.

Supported operators:

  unary        + - ~ !
  arithmetic   + - * / %
  shifts       << >>
  bitwise      & ^ |
  comparison   == != < <= > >=
  logical      && ||
  conditional  ? :

/// wrapping is not always an answer //////////////////////////////////////////

  0xffffffffU + 1       0  unsigned arithmetic wraps
  2147483647 + 1        UNDEFINED: signed overflow
  1 << 31               UNDEFINED: signed shift overflow
  1U << 32              UNDEFINED: count reaches the width
  -1 << 1               UNDEFINED: negative signed operand
  1 / 0                 UNDEFINED: integer division by zero
  (-2147483647-1) % -1  UNDEFINED: signed division overflow
  (int)1e40             UNDEFINED: conversion out of range

The inspector shows the reason for these cases instead of inventing a result.

─── 4. Widths & decompiler types ──────────────────────────────────────────────

BITS changes the displayed interpretation. A cast changes the expression.
This distinction matters when the next operator performs integer promotion.

  Input                         Result
  ────────────────────────────  ───────────────────────────────────
  (uint8_t)0x1234               0x34
  (int8_t)0xff                  -1
  (int16_t)0x8000               -32768
  (uint32_t)-1                  4294967295
  (int64_t)(int8_t)0x80         -128 / 0xffffffffffffff80
  (uint64_t)(uint8_t)0x80       128 / 0x80
  ~ (uint8_t)0                  -1, because uint8_t promotes to int
  (uint8_t)~0                   255
  (bool)256                     1
  (int)-3.9                     -3

Select 511 and change BITS to 8. The low byte is ff; its signed view is -1.
The note keeps the original value visible. Widen (int8_t)0x80 to 64 bits for
sign extension, then do the same with (uint8_t)0x80 for zero extension.

Signed and unsigned decimal alternatives appear when they differ. To read
both interpretations of raw memory, choose Integer in a byte selection.

/// spellings from C, asm and IDA /////////////////////////////////////////////

  42U  42UL  42ULL  42L    42LL
  18h  0FFh  0LL    42i64  42ui64

U, L and LL suffixes work in either case and in either legal order. IDA's
i64/ui64 suffixes name 64-bit values. An h suffix marks an assembler hex
constant; 18h is 24, not eighteen.

  (_BYTE)0x1234             0x34
  (_WORD)0x123456           0x3456
  (_DWORD)-1                0xffffffff
  (_QWORD)-1                0xffffffffffffffff
  (__int64)-1               -1
  (unsigned __int64)-1      18446744073709551615
  (size_t)-1                18446744073709551615
  (ptrdiff_t)0x100 - 0x120  -32
  'A'                       65
  '\n'                      10
  '\x41'                    65
  '\101'                    65

C type names include signed/unsigned char, short, int, long and long long;
float, double and _Bool; int8_t through uint64_t; size_t, ssize_t, intptr_t,
uintptr_t and ptrdiff_t. IDA's __int8/16/32/64 and _BYTE/_WORD/_DWORD/_QWORD
aliases have their stated widths. Character constants cover single ASCII
bytes and the usual C escapes.

/// larger than a machine word ////////////////////////////////////////////////

A standalone integer can be inspected up to 256 bits:

  0xffffffffffffffffffffffffffffffff
  0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0

These get an exact-integer view, with 128 or 256 available under BITS.
They are not silently introduced as extra types in C expressions.

─── 5. Masks & partial values ─────────────────────────────────────────────────

Select 0x91, choose Bits, then set BITS to 8.

         bit   7   6   5   4   3   2   1   0
               │   │   │   │   │   │   │   │
               1───0───0───1───0───0───0───1
               │           │               │
               └───────────┴───────────────┘  three set bits

SET BITS counts from zero at the least significant end. POPCNT is 3.
LOW 4 is 0x1; HIGH 4 is 0x9. Wider selections split into wider halves.

IDA helpers are useful when the text already names the part you need:

  LOBYTE(0x12345678)              0x78
  HIBYTE(0x12345678)              0x12
  BYTE1(0x12345678)               0x56
  LOWORD(0x12345678)              0x5678
  HIWORD(0x12345678)              0x1234
  WORD1(0x12345678)               0x1234
  LODWORD(0x123456789abcdef0ULL)  0x9abcdef0
  HIDWORD(0x123456789abcdef0ULL)  0x12345678
  DWORD1(0x123456789abcdef0ULL)   0x12345678
  SLOBYTE(0xff)                   -1
  SHIBYTE((uint16_t)0x8000)       -128

The S-prefixed forms read a signed part. BYTE0..7, WORD0..3 and DWORD0..1
also work, provided the operand is wide enough. These helpers follow the
little-endian target. HIBYTE means its highest byte: 0x12 above. For the high
byte of a 16-bit word, make the word explicit: HIBYTE((uint16_t)0x1234).

  __PAIR16__(0x12, 0x34)              0x1234
  __PAIR32__(0x1234, 0x5678)          0x12345678
  __PAIR64__(0x12345678, 0x9abcdef0)  0x123456789abcdef0
  __ROL1__(0x81, 1)                   0x03
  __ROL2__(0x8001, 1)                 0x0003
  __ROR4__(0x12345678, 8)             0x78123456
  __ROR8__(1ULL, 1)                   0x8000000000000000

PAIR joins high and low halves. ROL/ROR rotate; their suffix is the operand
size in bytes: 1, 2, 4 or 8. Counts wrap around that width.

─── 6. Floating point ─────────────────────────────────────────────────────────

An f suffix selects float32. Without it, a floating literal is float64.
The VALUE row shows the stored number. HEX and the sign/exponent/fraction
rows show why it looks that way.

  Input                 Stored value                IEEE hex
  ────────────────────  ──────────────────────────  ──────────────────
  0.1f                  0.10000000149011612         0x3dcccccd
  11.28125f             11.28125                    0x41348000
  1.25e-3               0.00125                     0x3f547ae147ae147b
  0x1.8p+1              3                           0x4008000000000000
  -0.0f                 -0                          0x80000000
  1e-45f                1.401298464324817e-45       0x00000001
  0x1p-150f             0                           0x00000000

In a hex float, p scales by a power of two. The significand is still hex.
Here 0x1.8 is 1.5, multiplied by 2 to get 3.

Change VIEW from Float64 to Float32 on 0.1 to see the narrower rounding.
Change ORDER to see the same stored number laid out in LE or BE bytes.

  1.0f + 0.5                   1.5, with a float64 result
  0x1.000001p0f                halfway: rounds to 1, ties to even
  1.000000059604644775390626f  just above halfway: rounds upward

Decimal literals round directly to the requested format. That last example
would lose the distinction if it went through float64 before float32.

CLASS distinguishes normal, subnormal, zero, infinity and NaN. Try:

  INFINITY  -INFINITY  NAN  inf  nan
  1e400     1e-400  0.0 / 0.0

Uppercase NAN/INFINITY use float32, like the C macros. Lowercase spellings
use float64. Arithmetic uses IEEE round-to-nearest, ties-to-even; there is
no alternate rounding-mode or floating-exception environment here.

─── 7. Numbers versus bits ────────────────────────────────────────────────────

These two lines have very different jobs:

  (float)0x3f800000         1065353216
  COERCE_FLOAT(0x3f800000)  1

The cast converts the integer's value to float. COERCE reads the existing
bits as a float. You can get that second view by selecting 0x3f800000 and
choosing Float32 bits, without writing a helper.

  COERCE_DOUBLE(0x3ff0000000000000ULL)  1
  0x7f800001                            Float32 bits: NaN
  0x80000000                            Float32 bits: negative zero

The bit view keeps the original NaN payload in HEX. Integer inputs are
truncated or sign/zero extended to 32 or 64 bits, as stated in the note.
COERCE helpers require a source of that width; use a cast first if the
selected constant has a different C type.

─── 8. Bytes & byte order ─────────────────────────────────────────────────────

Select a whole byte run:

  00 02 00 00

The default view keeps the bytes in their original order. UINT LE is 512;
UINT BE is 131072. Neither number is more correct without the format around
it. Switch to Integer for signed and unsigned readings at a chosen width.

  ff 7f                    Integer / 16 / LE  32767
  ff 7f                    Integer / 16 / BE  -129
  91 00                    Bits / 8           set bits 0, 4, 7
  00 80 34 41              Float32 / LE       11.28125
  3f f0 00 00 00 00 00 00  Float64 / BE       1

The integer and float views read from the start of the selection. BYTES
shows exactly what was used; the note gives the byte count. To inspect a
later field, select that field in the article.

The following three selections contain the same bytes:

  41 42 00
  0x41 0x42 0x00
  \x41\x42\x00

Quoted escaped bytes work too: "\x41\x42\x00". Whitespace between plain or
prefixed pairs may include tabs and newlines. A lone 41 is an integer;
\x41 is unambiguously one byte.

Up to 32 bytes fit in a selection. This run exercises the full buffer:

  00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
  10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f

─── 9. Reading a dump ─────────────────────────────────────────────────────────

Select one of the complete rows below, including its address. The address
gets its own row in the inspector; the ASCII gutter is left out of the data.

/// xxd ///////////////////////////////////////////////////////////////////////

00000000: 7f45 4c46 0201 0100  .ELF....
00000008: 0000 0000 0000 0000  ........

Those two rows can also be selected together. Addresses must be contiguous;
a gap is not filled with guessed bytes.

/// hexdump -C ////////////////////////////////////////////////////////////////

00000000  7f 45 4c 46 02 01 01 00  |.ELF....|

/// GDB x/bx //////////////////////////////////////////////////////////////////

0x400000 <header>: 0x7f 0x45 0x4c 0x46

/// pwndbg hexdump ////////////////////////////////////////////////////////////

+0000 0x400000  7f 45 4c 46 02 01 01 00  │.ELF....│

A byte dump records byte order. A debugger word such as 0x41424344 is already
a number. Select that value on its own; the inspector does not pretend the
whole debugger row is a byte dump.

─── 10. Text hiding in bytes ──────────────────────────────────────────────────

ASCII is always available in the byte view. A dot stands in for a byte
outside printable ASCII. Choose Text when the data might be UTF-8 or UTF-16.

  Bytes                         View                  Result
  ────────────────────────────  ────────────────────  ─────────────────
  45 58 53 32 37                ASCII                 EXS27
  20 7e 7f 00                   ASCII                 space, ~, ., .
  c3 a9                         Text / UTF-8          U+00E9
  41 00 42 00                   Text / UTF-16 / LE    AB
  00 41 00 42                   Text / UTF-16 / BE    AB
  41 0a 42 00                   Text / UTF-8          "A\nB\u0000"
  c3 28                         Text / UTF-8          invalid encoding
  41 00 42                      Text / UTF-16         incomplete unit

Decoded text is quoted, with control characters escaped. An invalid sequence
is reported instead of being quietly replaced. ORDER applies to UTF-16;
UTF-8 has no byte-order switch.

─── Where it stops ────────────────────────────────────────────────────────────

  1.0L            unsupported: long double needs 80 bits
  (long double)1  same limit
  BYTE7(1)        unsupported: operand is only 32 bits

These are recognized, but need a representation the current view cannot give.
Other text simply does not open an inspector:

  $rsp + 8         needs a register value
  [rbp-18h]        needs a register and a memory read
  *(int*)0x400000  needs memory
  symbol + 4       needs a symbol table

Select 18h, an actual address, or a numeric difference from such a line.
The evaluator accepts constants, casts and the helpers listed above. It does
not execute arbitrary functions. Expressions are bounded to 512 characters
and 128 tokens, with at most 32 nested levels. Dump selections may use up to
2048 characters, but still at most 32 data bytes.

Click any result to select its value. Ctrl+C uses the browser's normal Copy;
Esc returns to the selected article text. That is the whole interaction.

─── Reading material ──────────────────────────────────────────────────────────

C11 draft, integer constants and arithmetic conversions:
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

GCC's implementation-defined integer behavior:
https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html

Hex-Rays on decompiler helpers:
https://hex-rays.com/blog/igors-tip-of-the-week-67-decompiler-helpers

ImHex's data inspector:
https://docs.werwolv.net/imhex/views/data-inspector

─────────────────────────────────────────────────────────────────── end / 0x0a