Skip to content

BufIter

Description

Iterator over an immutable byte buffer. Layout matches Iter(const u8) so the generic Iter macros work on it.

Usage example (Cross-references)

Usage examples (Cross-references)
        BufWriteU64LE(&b, 0xFEEDFACECAFEBEEFull);
    
        BufIter it = BufIterFromBuf(&b);
        u16     v16;
        u32     v32;
        BufWriteSLeb128(&b, -123456);
    
        BufIter it = BufIterFromBuf(&b);
        u64     uv;
        i64     sv;
        BufWriteCstr(&b, "world");
    
        BufIter     it = BufIterFromBuf(&b);
        const char *s1 = BufReadCstr(&it);
        const char *s2 = BufReadCstr(&it);
        BufAppendFmt(&b, "{<2r}{>4r}{<8r}", (u16)0x1234, (u32)0xDEADBEEF, (u64)0x0102030405060708ull);
    
        BufIter it = BufIterFromBuf(&b);
        u16     v16;
        u32     v32;
        BufAppendFmt(&b, "{<2r}", (u16)0xABCD); // only 2 bytes; reader wants 6
    
        BufIter it    = BufIterFromBuf(&b);
        size    entry = it.pos;
        u16     v16   = 0;
    bool test_byte_iter_from_memory(void) {
        const u8 bytes[] = {0xAA, 0xBB, 0xCC};
        BufIter  it      = BufIterFromMemory(bytes, 3);
        u8       v;
        bool     ok = BufReadU8(&it, &v) && v == 0xAA;
    #include "../Util/TestRunner.h"
    
    typedef Iter(const u8) BufIter;
    
    static BufIter from(const u8 *p, size n) {
    typedef Iter(const u8) BufIter;
    
    static BufIter from(const u8 *p, size n) {
        BufIter it = {.data = p, .length = n, .pos = 0, .alignment = 1, .dir = 1};
        return it;
    
    static BufIter from(const u8 *p, size n) {
        BufIter it = {.data = p, .length = n, .pos = 0, .alignment = 1, .dir = 1};
        return it;
    }
    bool deadend_must_read_eof(void) {
        const u8 buf[1] = {1};
        BufIter  it     = from(buf, 1);
        u8       v;
        IterRead(&it, &v); // consume sole element
    bool deadend_must_peek_out_of_range(void) {
        const u8 buf[1] = {1};
        BufIter  it     = from(buf, 1);
        u8       v;
        // pos=0, length=1: peek at +1 is out of range.
    bool deadend_must_move_overflow(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from(buf, 3);
        // length=3, dir=+1: move by 4 lands past end.
        IterMustMove(&it, 4);
    bool deadend_must_next_eof(void) {
        const u8 buf[1] = {0};
        BufIter  it     = from(buf, 1);
        u8       v;
        IterRead(&it, &v); // pos=1, exhausted
    bool deadend_must_prev_underflow(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from(buf, 3);
        // pos=0, dir=+1: prev would land at -1.
        IterMustPrev(&it);
    #include "../Util/TestRunner.h"
    
    typedef Iter(const u8) BufIter;
    
    static BufIter from(const u8 *p, size n) {
    typedef Iter(const u8) BufIter;
    
    static BufIter from(const u8 *p, size n) {
        BufIter it = {.data = p, .length = n, .pos = 0, .alignment = 1, .dir = 1};
        return it;
    
    static BufIter from(const u8 *p, size n) {
        BufIter it = {.data = p, .length = n, .pos = 0, .alignment = 1, .dir = 1};
        return it;
    }
    }
    
    static BufIter from_rev(const u8 *p, size n) {
        BufIter it = {.data = p, .length = n, .pos = n - 1, .alignment = 1, .dir = -1};
        return it;
    
    static BufIter from_rev(const u8 *p, size n) {
        BufIter it = {.data = p, .length = n, .pos = n - 1, .alignment = 1, .dir = -1};
        return it;
    }
    bool test_iter_remaining_forward(void) {
        const u8 buf[3] = {1, 2, 3};
        BufIter  it     = from(buf, 3);
        if (IterRemainingLength(&it) != 3) {
            return false;
    bool test_iter_remaining_reverse(void) {
        const u8 buf[3] = {1, 2, 3};
        BufIter  it     = from_rev(buf, 3);
        if (IterRemainingLength(&it) != 3) {
            return false;
    bool test_iter_read_forward(void) {
        const u8 buf[3] = {10, 20, 30};
        BufIter  it     = from(buf, 3);
        u8       v      = 0;
        if (!IterRead(&it, &v) || v != 10) {
    bool test_iter_read_reverse(void) {
        const u8 buf[3] = {10, 20, 30};
        BufIter  it     = from_rev(buf, 3);
        u8       v      = 0;
        if (!IterRead(&it, &v) || v != 30) {
    bool test_iter_read_eof_leaves_state(void) {
        const u8 buf[1] = {7};
        BufIter  it     = from(buf, 1);
        u8       v      = 0;
        IterRead(&it, &v); // consume sole element
    bool test_iter_peek_in_range(void) {
        const u8 buf[4] = {5, 6, 7, 8};
        BufIter  it     = from(buf, 4);
        IterMove(&it, 2); // pos == 2
        u8 v;
    bool test_iter_peek_out_of_range(void) {
        const u8 buf[2] = {1, 2};
        BufIter  it     = from(buf, 2);
        u8       v      = 0xAA;
        if (IterPeekAt(&it, 2, &v)) {
    bool test_iter_move_forward_basic(void) {
        const u8 buf[5] = {0};
        BufIter  it     = from(buf, 5);
        if (!IterMove(&it, 3) || it.pos != 3) {
            return false;
    bool test_iter_move_forward_to_exhausted(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from(buf, 3);
        if (!IterMove(&it, 3) || it.pos != 3) {
            return false;
    bool test_iter_move_forward_overflow(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from(buf, 3);
        size     before = it.pos;
        if (IterMove(&it, 4)) {
    bool test_iter_move_forward_underflow(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from(buf, 3);
        if (IterMove(&it, -1)) {
            return false;
    bool test_iter_move_reverse_basic(void) {
        const u8 buf[5] = {0};
        BufIter  it     = from_rev(buf, 5);
        // start at pos=4
        if (!IterMove(&it, 2) || it.pos != 2) {
    bool test_iter_move_reverse_to_past_start(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from_rev(buf, 3);
        // pos=2, dir=-1, move by 3 lands on sentinel pos=-1
        if (!IterMove(&it, 3) || it.pos != (size)-1) {
    bool test_iter_move_reverse_overflow(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from_rev(buf, 3);
        // pos=2, dir=-1, move by 4 would land at pos=-2 — invalid
        size before = it.pos;
    bool test_iter_next_prev(void) {
        const u8 buf[3] = {0};
        BufIter  it     = from(buf, 3);
        if (!IterNext(&it) || it.pos != 1) {
            return false;
    typedef struct PeContext {
        PeFile *out;
        BufIter file;      // bounds for the whole image
        u32     nt_offset; // offset of NT signature
        u16     num_sections;
        }
        u32     e_lfanew;
        BufIter c = BufIterFromMemory(ctx->out->data + DOS_E_LFANEW_OFFSET, ctx->out->data_size - DOS_E_LFANEW_OFFSET);
        if (!BufReadU32LE(&c, &e_lfanew))
            return false;
    // NT signature + File Header. Returns the offset of the Optional Header.
    static bool pe_decode_nt(PeContext *ctx, u64 *out_opt_offset) {
        BufIter c = BufIterFromMemory(ctx->out->data + ctx->nt_offset, ctx->out->data_size - ctx->nt_offset);
        u32     sig;
        if (!BufReadU32LE(&c, &sig) || sig != NT_SIGNATURE) {
            return false;
        }
        BufIter c = BufIterFromMemory(ctx->out->data + opt_offset, ctx->opt_hdr_size);
    
        u16 magic;
    static bool pe_decode_sections(PeContext *ctx, u64 opt_offset) {
        u64     sec_offset = opt_offset + ctx->opt_hdr_size;
        BufIter c          = BufIterFromMemory(ctx->out->data + sec_offset, ctx->out->data_size - sec_offset);
    
        for (u32 i = 0; i < ctx->num_sections; ++i) {
        for (u32 i = 0; i < num_entries; ++i) {
            u64     entry_off = dir_offset + (u64)i * DEBUG_ENTRY_SIZE;
            BufIter c         = BufIterFromMemory(ctx->out->data + entry_off, ctx->out->data_size - entry_off);
            u32     charac, ts, type, sz, raddr, rptr;
            u16     ver_maj, ver_min;
            if (sz < 4 + 16 + 4 + 1)
                continue;
            BufIter cv_cur = BufIterFromMemory(ctx->out->data + rptr, sz);
            u32     cv_sig;
            if (!BufReadU32LE(&cv_cur, &cv_sig))
    //
    // Returns false on cycles, oversized names, or out-of-bounds.
    static bool decode_name(BufIter *it, Str *out_name) {
        const u32 MAX_HOPS  = 64;
        u32       hops      = 0;
    
    // Decode one resource record. Advances the iter past the record.
    static bool decode_record(BufIter *it, DnsRecord *rec, Allocator *alloc) {
        rec->name   = StrInit(alloc);
        rec->target = StrInit(alloc);
                // follow compression pointers back into the whole message,
                // so we use a sub-iter cloned from the main one.
                BufIter sub = *it;
                if (!decode_name(&sub, &rec->target)) {
                    return false;
    // ---------------------------------------------------------------------------
    
    static bool decode_record_list(BufIter *it, u16 count, DnsRecords *out, Allocator *alloc) {
        for (u16 i = 0; i < count; ++i) {
            DnsRecord rec = {0};
            return false;
        }
        BufIter it = BufIterFromMemory(buf, len);
        u16     flags, qd, an, ns, ar;
        if (!BufReadFmt(&it, "{>2r}{>2r}{>2r}{>2r}{>2r}{>2r}", out->id, flags, qd, an, ns, ar)) {
            return false;
        }
        BufIter sb = BufIterFromMemory(self->data + 32, self->data_size - 32);
        u32     free_blk, num_blocks, unknown;
        if (!BufReadFmt(
            return false;
        }
        BufIter dir_iter = BufIterFromMemory(self->stream_dir, self->stream_dir_size);
        if (!BufReadU32LE(&dir_iter, &self->num_streams))
            return false;
        if (!stream_read(self, 1, 0, buf, sizeof(buf)))
            return false;
        BufIter bi = BufIterFromMemory(buf, sizeof(buf));
        if (!BufReadFmt(&bi, FMT_PDB_INFO_LE, self->info.version, self->info.signature, self->info.age)) {
            LOG_ERROR("PDB: info stream prefix truncated");
            return r;
    
        BufIter bi = BufIterFromMemory(hdr, DBI_HEADER_SIZE);
        u32 version_sig, version_hdr, age, mod_size, seccontrib, secmap, srcinfo, tsm, mfc_tsm_idx, optdbg_size, ec_size,
            padding;
        for (u32 i = 0; i < n; ++i) {
            // IMAGE_SECTION_HEADER: name[8] + VirtualSize(4) + VirtualAddress(4) + ...
            BufIter rec = BufIterFromMemory(buf + i * 40 + 8, 40 - 8);
            (void)BufReadU32LE(&rec, &out[i].virtual_size);
            (void)BufReadU32LE(&rec, &out[i].virtual_address);
                // Record body starts at cur + 4 (past len + kind). 10-byte
                // prefix (Flags/Offset/Segment) then NUL-terminated Name.
                BufIter body = BufIterFromMemory(buf + cur + 4, rec_len - 2);
                u32     flags, offset;
                u16     segment;
    // cursor sits at the start of the include_directories table; the
    // directory / file tables are walked separately by collect_cu_strings.
    static bool decode_line_program_header(BufIter *cur, LineProgHeader *out) {
        MemSet(out, 0, sizeof(*out));
    // Walk past include_directories + file_names, leaving cur at the
    // start of the line number program body.
    static bool skip_line_program_tables(BufIter *cur) {
        while (cur->pos < cur->length && cur->data[cur->pos] != 0) {
            if (!BufReadCstr(cur))
    // to the first byte after the standard_opcode_lengths array; we
    // continue from there.
    static bool collect_cu_strings(BufIter cur, Str *pool, CuStrings *cs) {
        // include_directories
        while (cur.pos < cur.length && cur.data[cur.pos] != 0) {
    
    static bool run_line_program(
        BufIter               cur,
        const u8             *prog_end,
        const LineProgHeader *hdr,
        U64Vec pending_dir_offsets  = VecInitT(pending_dir_offsets, alloc);
    
        BufIter section_cur = BufIterFromMemory(elf->data + line_section->offset, line_section->size);
    
        bool ok = true;
            const u8 *unit_start  = section_cur.data + section_cur.pos;
            u32       unit_length = 0;
            BufIter   peek        = section_cur;
            if (!BufReadU32LE(&peek, &unit_length)) {
                ok = false;
            // tables once to populate the shared string pool, then run
            // the program body.
            BufIter        hdr_cur = section_cur;
            LineProgHeader hdr;
            if (!decode_line_program_header(&hdr_cur, &hdr)) {
            // up to the end of this CU.
            size    strings_start_pos = (size)(hdr.strings_start - section_cur.data);
            BufIter str_cur           = BufIterFromMemory(section_cur.data, unit_end_pos);
            str_cur.pos               = strings_start_pos;
            if (!collect_cu_strings(str_cur, &out->string_pool, &cs)) {
    
            // Skip past the tables to find the program body start.
            BufIter prog_anchor = BufIterFromMemory(section_cur.data, unit_end_pos);
            prog_anchor.pos     = strings_start_pos;
            if (!skip_line_program_tables(&prog_anchor)) {
            }
    
            BufIter prog_cur = prog_anchor;
            if (!run_line_program(prog_cur, unit_end, &hdr, &cs, out, &pending_file_offsets, &pending_dir_offsets)) {
                cu_strings_deinit(&cs);
    // file-relative virtual address of `c->p` (used by PCREL). On success
    // `*out` receives the decoded absolute file-relative VA.
    static bool decode_eh_ptr(BufIter *c, u8 encoding, u64 here_vaddr, u64 *out) {
        if (encoding == DW_EH_PE_OMIT) {
            return false;
    // ---------------------------------------------------------------------------
    
    static bool parse_cie(BufIter *body, u64 cie_offset, DwarfCie *out) {
        MemSet(out, 0, sizeof(*out));
        out->offset = cie_offset;
    
    static bool parse_fde(
        BufIter        *body,
        const u8       *body_start,
        u64             cie_offset,
        const u8 *section_data = elf->data + eh->offset;
    
        BufIter section_cur = BufIterFromMemory(section_data, eh->size);
        while (IterRemainingLength(&section_cur) > 0) {
            const u8 *rec_start = section_cur.data + section_cur.pos;
                // Body iter starts just after the id field (since parse_cie
                // doesn't re-read id) and spans the remainder of the record.
                BufIter body = BufIterFromMemory(section_cur.data + section_cur.pos, length32 - 4);
                if (parse_cie(&body, cie_offset, &cie)) {
                    if (!VecPushBackR(&out->cies, cie)) {
                u64      cie_offset   = id_field_off - id;
                DwarfFde fde;
                BufIter  body = BufIterFromMemory(section_cur.data + section_cur.pos, length32 - 4);
                if (parse_fde(&body, rec_start, cie_offset, out, section_data, eh->addr, &fde)) {
                    if (!VecPushBackR(&out->fdes, fde)) {
    // One instruction. `stop_at` lets the caller bail mid-stream once an
    // advance_loc has covered the requested target PC.
    static bool cfi_vm_step(CfiVm *vm, BufIter *cur, u64 stop_at, bool *stop_now) {
        u8 op = 0;
        if (!BufReadU8(cur, &op))
    
    static bool cfi_vm_run(CfiVm *vm, const u8 *insns, u64 insns_size, u64 stop_at) {
        BufIter cur      = BufIterFromMemory(insns, insns_size);
        bool    stop_now = false;
        while (IterRemainingLength(&cur) > 0) {
    // Parse the abbrev table starting at `start`. The table is terminated
    // by an entry with code 0. Returns false on malformed input.
    static bool parse_abbrev_table(BufIter cur, AbbrevTable *out, Allocator *alloc) {
        *out = VecInitT(*out, alloc);
        while (IterRemainingLength(&cur) > 0) {
    // Returns false only on truncated / malformed bytes; unknown forms
    // fail-closed so the caller can stop cleanly.
    static bool read_form(BufIter *cur, u32 form, u8 addr_size, AttrVal *out) {
        *out = (AttrVal) {.kind = ATTR_VAL_NONE};
        switch (form) {
    
    static bool walk_cu_dies(
        BufIter            cu_cur, // positioned past CU header
        const AbbrevTable *abbrevs,
        u8                 addr_size,
        }
    
        BufIter info_cur = BufIterFromMemory(info_bytes, info_size);
    
        PendingFns pending = VecInitT(pending, alloc);
    
            AbbrevTable abbrevs;
            BufIter     abbrev_cur = BufIterFromMemory(abbrev_bytes + abbrev_offset, abbrev_size - abbrev_offset);
            if (!parse_abbrev_table(abbrev_cur, &abbrevs, alloc)) {
                ok = false;
            // DIE iter spans the DIE body within this CU (from current
            // info_cur position up to unit_end_pos).
            BufIter die_cur = BufIterFromMemory(info_cur.data + info_cur.pos, unit_end_pos - info_cur.pos);
            if (!walk_cu_dies(die_cur, &abbrevs, addr_size, str_bytes, str_size, &out->string_pool, &pending)) {
                abbrev_table_deinit(&abbrevs);
            return false;
        }
        BufIter c = BufIterFromMemory(m->data, m->data_size);
        u32     magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, reserved;
        if (!BufReadFmt(
        // cmd_p layout: cmd(4) cmdsize(4) segname[16] then FMT_MACHO_SEGMENT64_BODY_LE.
        copy_fixed16(seg.name, cmd_p + 8);
        BufIter c = BufIterFromMemory(cmd_p + 24, cmdsize - 24);
        u32     maxprot, initprot;
        if (!BufReadFmt(
            copy_fixed16(sec.section, s + 0);
            copy_fixed16(sec.segment, s + 16);
            BufIter sc = BufIterFromMemory(s + 32, SECT64_SIZE - 32);
            u32     align, reloff, nreloc, reserved1, reserved2, reserved3;
            if (!BufReadFmt(
            return false;
        }
        BufIter c = BufIterFromMemory(cmd_p + 8, cmdsize - 8);
        if (!BufReadFmt(&c, FMT_MACHO_SYMTAB_BODY_LE, ctx->symoff, ctx->nsyms, ctx->stroff, ctx->strsize)) {
            LOG_ERROR("MachO: LC_SYMTAB body truncated");
                return false;
            const u8 *cmd_p = ctx->out->data + cur;
            BufIter   pc    = BufIterFromMemory(cmd_p, end - cur);
            u32       cmd, cmdsize;
            if (!BufReadFmt(&pc, FMT_MACHO_LC_PREFIX_LE, cmd, cmdsize)) {
        }
        const u8 *str_base = ctx->out->data + ctx->stroff;
        BufIter   tab      = BufIterFromMemory(ctx->out->data + ctx->symoff, (u64)ctx->nsyms * NLIST64_SIZE);
        for (u32 i = 0; i < ctx->nsyms; ++i) {
            u32 n_strx;
    // ---------------------------------------------------------------------------
    
    bool buf_read_fmt(BufIter *iter, const char *fmtstr, TypeSpecificIO *argv, u64 argc) {
        if (!iter || !fmtstr) {
            LOG_FATAL("buf_read_fmt: NULL iter or fmtstr");
    /// Construct a BufIter over `[data, data + length)`.
    #define BufIterFromMemory(data_, length_)                                                                              \
        ((BufIter) {.data = (data_), .length = (length_), .pos = 0, .alignment = 1, .dir = 1})
    
    /// Construct a BufIter over a Buf's bytes.
    // ---------------------------------------------------------------------------
    
    static inline bool BufReadU8(BufIter *c, u8 *out) {
        if (c->pos >= c->length) {
            return false;
    }
    
    static inline bool BufReadU16LE(BufIter *c, u16 *out) {
        if (c->pos + 2 > c->length) {
            return false;
    }
    
    static inline bool BufReadU16BE(BufIter *c, u16 *out) {
        if (c->pos + 2 > c->length) {
            return false;
    }
    
    static inline bool BufReadU32LE(BufIter *c, u32 *out) {
        if (c->pos + 4 > c->length) {
            return false;
    }
    
    static inline bool BufReadU32BE(BufIter *c, u32 *out) {
        if (c->pos + 4 > c->length) {
            return false;
    }
    
    static inline bool BufReadU64LE(BufIter *c, u64 *out) {
        if (c->pos + 8 > c->length) {
            return false;
    }
    
    static inline bool BufReadU64BE(BufIter *c, u64 *out) {
        if (c->pos + 8 > c->length) {
            return false;
    
    /// LEB128 unsigned. Fails on truncation or width overflow.
    static inline bool BufReadULeb128(BufIter *c, u64 *out) {
        u64 result = 0;
        u32 shift  = 0;
    /// LEB128 signed. Decoded in unsigned space and reinterpreted to
    /// avoid signed-shift UB at high bit positions.
    static inline bool BufReadSLeb128(BufIter *c, i64 *out) {
        u64 uresult = 0;
        u32 shift   = 0;
    /// NUL-terminated string starting at the cursor. Returns the start;
    /// advances past the terminator. NULL on truncation.
    static inline const char *BufReadCstr(BufIter *c) {
        const char *s = (const char *)(c->data + c->pos);
        while (c->pos < c->length && c->data[c->pos] != 0) {
    // ---------------------------------------------------------------------------
    
    bool buf_read_fmt(BufIter *iter, const char *fmtstr, TypeSpecificIO *argv, u64 argc);
    bool buf_append_fmt(Buf *out, const char *fmtstr, TypeSpecificIO *argv, u64 argc);
    bool buf_write_fmt(Buf *out, const char *fmtstr, TypeSpecificIO *argv, u64 argc);
Last updated on