Bitfield member accesses
When accessing C bitfields, the compiler works on the storage unit that holds the field and extracts the bits with a mask and a shift. Take this structure, where kind occupies bits 3 to 5 of a 4-byte unit:
struct state
{
unsigned int ready : 1;
unsigned int busy : 1;
unsigned int error : 1;
unsigned int kind : 3;
unsigned int count : 26;
};A read and a write of kind used to decompile into unit-sized arithmetic:
if ( (*(_DWORD *)&s >> 3) & 7 )
*p = *p & 0xFFFFFFC7 | (v << 3) & 0x38;When the correct type definitions are available and applied, the decompiler recognizes those masks and shifts and prints the member that they access:
if ( s.kind )
p->kind = v;What it needs
The proper structure type has to be applied at the access, because the recognition is driven by the type: an expression is rewritten only when the structure declares a bitfield member whose bit offset, width and signedness match exactly what the code extracts. When no member matches, the raw mask and shift are kept, which is also what you see when the type is missing or wrong.
The recognition works the same way on every processor supported by the decompiler. The dedicated bitfield instructions (ARM64 UBFX/SBFX/BFI, PowerPC rlwinm/rlwimi, MIPS EXT/INS, RISC-V Zbs) are all lowered to plain shift and mask microcode before this point, so the recognition is not processor-specific.
What is recognized
reads, signed and unsigned, including a single-bit test and a field tested through the sign of its unit
reads that the compiler scaled by a power of two, folding the scaling into the extraction:
4 * fe->FuncLenrather than(*((_DWORD *)v1 + 1) >> 6) & 0xFFFFFCreads taken from a copy of the unit that the compiler loads once into a register and then extracts one or more fields from
writes and read-modify-write updates, including the compound forms:
p->cnt += n,++p->cnt,p->f ^= va store that packs several fields of one unit at once, which is split into one assignment per field
accesses narrower than the storage unit, such as a byte load covering a field at the edge of a 4-byte unit
fields reached through a union, when exactly one alternative declares a matching bitfield
a field copied from another structure, where the compiler emitted no mask because the store truncates the value anyway
When the raw form is kept
The rewrite is deliberately conservative. The mask and shift stay as they are when:
no declared member matches the extracted bit range, its width or its signedness
the mask is not exactly the field's mask, or it spans two members
the member is unnamed, or a zero-width bitfield
the access reaches outside the declared storage unit
more than one union alternative would match, or you have selected a union view at that address
the structure is packed and a narrow access does not identify its storage unit unambiguously
Working with the members
A recognized access is an ordinary member access, so renaming the member, listing its cross-references and jumping to its declaration all work on it, at bit granularity: two members sharing one storage unit are told apart.
Allocation order
A compiler allocates the members of a bitfield from one end of the storage unit or the other, and this is independent of the byte order. A structure follows the byte order of the database unless it says otherwise: least significant bit first on a little-endian database, most significant bit first on a big-endian one.
A structure that allocates from the other end carries the __invbf attribute and prints with it:
There is no database-wide setting: the decompiler takes the order from the structure that declares the bitfields, which is often an inner structure of the one being indexed.
When the types come from a PDB, the order is recovered from the debug information. When you write the declaration yourself, use either __invbf or the #pragma bitfield_order directive, which both parsers accept:
The directive takes lsb_to_msb, msb_to_lsb or default, and push/pop to nest. A structure declared under an order that differs from the database default comes out with __invbf set.
The attribute travels with the type, so a type library built from headers that set the order records it as well. The libraries that predate it do not, and neither do the databases populated from them: those structures follow the byte order until their types are re-imported.
Turning it off
The recognition is on by default. To switch it off, untick Decompile bitfield accesses in Edit → Plugins → Hex-Rays Decompiler Options → Options 2, or clear HO_BITFIELD_ACCESS in the HEXOPTIONS mask of hexrays.cfg.
Last updated
Was this helpful?
