boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

hex2pp.c (24600B)


      1 /*
      2  * hex2pp.c -- reference C implementation of hex2++.
      3  *
      4  * See docs/HEX2pp.md for the spec. Brief summary:
      5  *
      6  *   Input is scanned once. Label definitions are recorded into a table
      7  *   on the fly; label references emit zero placeholders and append a
      8  *   fixup record. After the scan, fixups are resolved against the
      9  *   completed label table and patched into the output buffer.
     10  *
     11  *   Active syntax:
     12  *     digits in current byte mode    -> raw bytes (HEX or BINARY)
     13  *     :NAME                          -> label definition
     14  *     SIGIL NAME [- OTHER]           -> label reference (! @ $ ~ % &)
     15  *     .align N [PATTERN]             -> pad to N-byte boundary
     16  *     .fill N B                      -> N copies of byte B
     17  *     .scope / .endscope             -> local-label scope (nestable)
     18  *     # ... / ; ...                  -> line comment
     19  *
     20  *   Multi-byte reference values are emitted little-endian by default.
     21  */
     22 
     23 #include <ctype.h>
     24 #include <errno.h>
     25 #include <stdarg.h>
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 #include <sys/stat.h>
     30 
     31 #define MAX_INPUT_BYTES   (16 * 1024 * 1024)
     32 #define MAX_OUTPUT_BYTES  (128 * 1024 * 1024)
     33 #define MAX_LABELS        (1 << 20)
     34 #define MAX_FIXUPS        (1 << 20)
     35 #define MAX_TEXT          (8 * 1024 * 1024)
     36 #define MAX_TOKEN         4096
     37 #define MAX_SCOPE_DEPTH   32
     38 #define MAX_SCOPE_HISTORY (1 << 22)
     39 
     40 enum { HEX_MODE, BINARY_MODE };
     41 
     42 struct InFile {
     43     const char *path;
     44     char       *buf;
     45     int         len;
     46 };
     47 
     48 struct Label {
     49     int       name_off;
     50     int       name_len;
     51     long long target_ip;
     52     int       scope_id;     /* 0 = global */
     53 };
     54 
     55 struct Fixup {
     56     long long   out_off;        /* offset in output_buf to patch */
     57     long long   ip_at_ref;      /* ip immediately after the reference's bytes */
     58     const char *name;           /* points into input buffer */
     59     const char *other;          /* NULL if no -OTHER */
     60     const char *src_path;
     61     int         name_len;
     62     int         other_len;
     63     int         scope_hist_off; /* index into scope_history (depth>0 only) */
     64     int         scope_depth;
     65     int         src_line;
     66     int         sigil;
     67 };
     68 
     69 static struct InFile input_file;
     70 
     71 static char          text_buf[MAX_TEXT];
     72 static int           text_used;
     73 
     74 static struct Label  labels[MAX_LABELS];
     75 static int           label_count;
     76 
     77 static struct Fixup  fixups[MAX_FIXUPS];
     78 static int           fixup_count;
     79 
     80 static int           scope_history[MAX_SCOPE_HISTORY];
     81 static int           scope_history_used;
     82 
     83 static unsigned char output_buf[MAX_OUTPUT_BYTES];
     84 static long long     output_used;
     85 
     86 static long long     ip;
     87 static long long     base_address;
     88 static int           byte_mode = HEX_MODE;
     89 static int           big_endian;
     90 static int           non_executable;
     91 static const char   *output_path;
     92 static int           ptrsize = 4;     /* width of '&' and '%'; settable via .ptrsize */
     93 static int           ptrsize_used;    /* a '&'/'%' reference has fixed the width */
     94 
     95 static int           scope_stack[MAX_SCOPE_DEPTH];
     96 static int           scope_depth;
     97 static int           scope_seq;
     98 
     99 static const char   *cur_path;
    100 static int           cur_line;
    101 
    102 /* --- error reporting ---------------------------------------------------- */
    103 
    104 static void die(const char *fmt, ...)
    105 {
    106     va_list ap;
    107     if (cur_path != NULL) {
    108         fprintf(stderr, "%s:%d: hex2pp: ", cur_path, cur_line);
    109     } else {
    110         fprintf(stderr, "hex2pp: ");
    111     }
    112     va_start(ap, fmt);
    113     vfprintf(stderr, fmt, ap);
    114     va_end(ap);
    115     fputc('\n', stderr);
    116     exit(1);
    117 }
    118 
    119 /* --- text / label table ------------------------------------------------- */
    120 
    121 static int intern(const char *s, int len)
    122 {
    123     int off;
    124     if (text_used + len + 1 > MAX_TEXT) {
    125         die("text pool overflow");
    126     }
    127     off = text_used;
    128     memcpy(text_buf + off, s, (size_t)len);
    129     text_buf[off + len] = '\0';
    130     text_used += len + 1;
    131     return off;
    132 }
    133 
    134 static int name_eq(const struct Label *L, const char *s, int len)
    135 {
    136     return L->name_len == len && memcmp(text_buf + L->name_off, s, (size_t)len) == 0;
    137 }
    138 
    139 static void define_label(const char *s, int len, int scope_id)
    140 {
    141     if (label_count >= MAX_LABELS) {
    142         die("too many labels");
    143     }
    144     labels[label_count].name_off  = intern(s, len);
    145     labels[label_count].name_len  = len;
    146     labels[label_count].target_ip = ip;
    147     labels[label_count].scope_id  = scope_id;
    148     label_count++;
    149 }
    150 
    151 static long long lookup_label_in(const char *s, int len, const int *stack, int depth)
    152 {
    153     int i;
    154     int d;
    155     int dotted = (len > 0 && s[0] == '.' && depth > 0);
    156     if (dotted) {
    157         /* Inside a scope, walk the scope stack innermost-out. A dotted
    158          * name resolves to the nearest enclosing definition, so an inner
    159          * scope can shadow an outer one with the same local name. */
    160         for (d = depth - 1; d >= 0; d--) {
    161             int sid = stack[d];
    162             for (i = 0; i < label_count; i++) {
    163                 if (labels[i].scope_id == sid && name_eq(&labels[i], s, len)) {
    164                     return labels[i].target_ip;
    165                 }
    166             }
    167         }
    168         die("undefined local label '%.*s'", len, s);
    169     } else {
    170         for (i = 0; i < label_count; i++) {
    171             if (labels[i].scope_id == 0 && name_eq(&labels[i], s, len)) {
    172                 return labels[i].target_ip;
    173             }
    174         }
    175         die("undefined label '%.*s'", len, s);
    176     }
    177     return 0; /* unreachable */
    178 }
    179 
    180 /* --- I/O ---------------------------------------------------------------- */
    181 
    182 static void emit_byte(unsigned b)
    183 {
    184     if (output_used >= MAX_OUTPUT_BYTES) {
    185         die("output overflow");
    186     }
    187     output_buf[output_used++] = (unsigned char)b;
    188     ip++;
    189 }
    190 
    191 static long long emit_zeros(long long n)
    192 {
    193     long long off = output_used;
    194     if (output_used + n > MAX_OUTPUT_BYTES) {
    195         die("output overflow");
    196     }
    197     memset(output_buf + output_used, 0, (size_t)n);
    198     output_used += n;
    199     ip += n;
    200     return off;
    201 }
    202 
    203 static void emit_fill(long long n, unsigned char b)
    204 {
    205     if (output_used + n > MAX_OUTPUT_BYTES) {
    206         die("output overflow");
    207     }
    208     memset(output_buf + output_used, b, (size_t)n);
    209     output_used += n;
    210     ip += n;
    211 }
    212 
    213 static void write_value(long long out_off, long long v, int width, long long lo, long long hi, int range_check)
    214 {
    215     int i;
    216     unsigned char bytes[8];
    217 
    218     if (range_check && (v < lo || v > hi)) {
    219         die("reference out of range: value=%lld, allowed=[%lld,%lld]", v, lo, hi);
    220     }
    221     if (width < 1 || width > 8) {
    222         die("internal: bad reference width %d", width);
    223     }
    224 
    225     for (i = 0; i < width; i++) {
    226         bytes[i] = (unsigned char)((unsigned long long)v >> (8 * i)) & 0xff;
    227     }
    228     if (big_endian) {
    229         for (i = 0; i < width; i++) {
    230             output_buf[out_off + i] = bytes[width - 1 - i];
    231         }
    232     } else {
    233         for (i = 0; i < width; i++) {
    234             output_buf[out_off + i] = bytes[i];
    235         }
    236     }
    237 }
    238 
    239 /* --- per-file scanner state -------------------------------------------- */
    240 
    241 struct Scanner {
    242     const char *buf;
    243     int         len;
    244     int         pos;
    245 };
    246 
    247 static int eatc(struct Scanner *s)
    248 {
    249     int c;
    250     if (s->pos >= s->len) return -1;
    251     c = (unsigned char)s->buf[s->pos++];
    252     if (c == '\n') cur_line++;
    253     return c;
    254 }
    255 
    256 static int is_space_any(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; }
    257 
    258 static void skip_ws_and_comments(struct Scanner *s)
    259 {
    260     int c;
    261     while (s->pos < s->len) {
    262         c = (unsigned char)s->buf[s->pos];
    263         if (is_space_any(c)) {
    264             eatc(s);
    265         } else if (c == '#' || c == ';') {
    266             while (s->pos < s->len && s->buf[s->pos] != '\n') s->pos++;
    267         } else {
    268             break;
    269         }
    270     }
    271 }
    272 
    273 /* --- byte-mode digit handling ----------------------------------------- */
    274 
    275 static int byte_digit_count(void)
    276 {
    277     if (byte_mode == HEX_MODE) return 2;
    278     return 8; /* BINARY */
    279 }
    280 
    281 static int is_byte_digit(int c)
    282 {
    283     if (byte_mode == HEX_MODE) return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
    284     return c == '0' || c == '1';
    285 }
    286 
    287 static int byte_digit_value(int c)
    288 {
    289     if (c >= '0' && c <= '9') return c - '0';
    290     if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
    291     if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
    292     return -1;
    293 }
    294 
    295 static int parse_one_byte_literal(struct Scanner *s, unsigned char *out, int allow_multi, unsigned char *buf, int bufmax, int *outlen)
    296 {
    297     /* Parse a contiguous run of byte-mode digits (no whitespace inside,
    298      * since this is for directive arguments where digit-run terminates the
    299      * argument). Returns number of bytes produced. */
    300     int need = byte_digit_count();
    301     int acc = 0;
    302     int have = 0;
    303     int produced = 0;
    304     int c;
    305 
    306     while (s->pos < s->len) {
    307         c = (unsigned char)s->buf[s->pos];
    308         if (!is_byte_digit(c)) break;
    309         s->pos++;
    310         if (byte_mode == HEX_MODE) acc = (acc << 4) | byte_digit_value(c);
    311         else                       acc = (acc << 1) | (c - '0');
    312         have++;
    313         if (have == need) {
    314             if (allow_multi) {
    315                 if (produced >= bufmax) die("pattern too large");
    316                 buf[produced++] = (unsigned char)(acc & 0xff);
    317             } else {
    318                 if (produced > 0) die("byte literal: too many digits");
    319                 *out = (unsigned char)(acc & 0xff);
    320                 produced = 1;
    321             }
    322             acc = 0;
    323             have = 0;
    324             if (!allow_multi) break;
    325         }
    326     }
    327     if (have != 0) die("byte literal: incomplete digits (%d left over)", have);
    328     if (produced == 0) die("expected byte literal");
    329     if (outlen) *outlen = produced;
    330     return produced;
    331 }
    332 
    333 /* Parse a free-flowing byte stream: digits separated by arbitrary
    334  * whitespace and comments. Stops at any non-digit non-whitespace
    335  * non-comment character. */
    336 static void parse_byte_stream(struct Scanner *s)
    337 {
    338     int need = byte_digit_count();
    339     int acc = 0;
    340     int have = 0;
    341     int c;
    342 
    343     for (;;) {
    344         if (s->pos >= s->len) break;
    345         c = (unsigned char)s->buf[s->pos];
    346         if (is_space_any(c)) { eatc(s); continue; }
    347         if (c == '#' || c == ';') {
    348             while (s->pos < s->len && s->buf[s->pos] != '\n') s->pos++;
    349             continue;
    350         }
    351         if (!is_byte_digit(c)) break;
    352         s->pos++;
    353         if (byte_mode == HEX_MODE) acc = (acc << 4) | byte_digit_value(c);
    354         else                       acc = (acc << 1) | (c - '0');
    355         have++;
    356         if (have == need) {
    357             emit_byte((unsigned)(acc & 0xff));
    358             acc = 0;
    359             have = 0;
    360         }
    361     }
    362     if (have != 0) die("byte stream: incomplete digits at end of run (%d left over)", have);
    363 }
    364 
    365 /* --- name / token reading --------------------------------------------- */
    366 
    367 static int is_name_terminator(int c)
    368 {
    369     /* Per spec: names terminated by whitespace, '-', or '>' (the two
    370      * label-arithmetic separators). We also stop at end-of-line comments
    371      * and EOF for safety. */
    372     if (c < 0) return 1;
    373     if (is_space_any(c)) return 1;
    374     if (c == '-' || c == '>') return 1;
    375     if (c == '#' || c == ';') return 1;
    376     return 0;
    377 }
    378 
    379 /* Scan a label name; return pointer span into the input buffer (no copy). */
    380 static int scan_name(struct Scanner *s, const char **out_start)
    381 {
    382     int start = s->pos;
    383     while (s->pos < s->len) {
    384         int c = (unsigned char)s->buf[s->pos];
    385         if (is_name_terminator(c)) break;
    386         s->pos++;
    387     }
    388     if (s->pos == start) die("expected label name");
    389     *out_start = s->buf + start;
    390     return s->pos - start;
    391 }
    392 
    393 /* Decimal integer (for directive arity arguments). */
    394 static long long read_decimal(struct Scanner *s)
    395 {
    396     long long v = 0;
    397     int saw = 0;
    398     int c;
    399     while (s->pos < s->len) {
    400         c = (unsigned char)s->buf[s->pos];
    401         if (c < '0' || c > '9') break;
    402         v = v * 10 + (c - '0');
    403         saw = 1;
    404         s->pos++;
    405     }
    406     if (!saw) die("expected decimal integer");
    407     return v;
    408 }
    409 
    410 /* --- references ------------------------------------------------------- */
    411 
    412 struct SigilInfo {
    413     int  width;
    414     int  is_rel;
    415     long long lo;
    416     long long hi;
    417     int  range_check;
    418 };
    419 
    420 static struct SigilInfo sigil_info(int c)
    421 {
    422     struct SigilInfo si = {0};
    423     switch (c) {
    424     case '!': si.width = 1; si.is_rel = 1; si.lo = -128;        si.hi = 127;          si.range_check = 1; break;
    425     case '@': si.width = 2; si.is_rel = 1; si.lo = -32768;      si.hi = 32767;        si.range_check = 1; break;
    426     case '$': si.width = 2; si.is_rel = 0; si.lo = 0;           si.hi = 65535;        si.range_check = 1; break;
    427     case '~': si.width = 3; si.is_rel = 1; si.lo = -(1LL << 23); si.hi = (1LL << 23) - 1; si.range_check = 1; break;
    428     case '%': si.width = ptrsize; si.is_rel = 1; si.lo = 0;     si.hi = 0;            si.range_check = 0; break;
    429     case '&': si.width = ptrsize; si.is_rel = 0; si.lo = 0;     si.hi = 0;            si.range_check = 0; break;
    430     default:  die("internal: bad sigil 0x%02x", c);
    431     }
    432     return si;
    433 }
    434 
    435 static void record_fixup(const char *name, int name_len,
    436                          const char *other, int other_len,
    437                          int sigil, long long out_off, long long ip_after)
    438 {
    439     struct Fixup *f;
    440     if (fixup_count >= MAX_FIXUPS) die("too many references");
    441     f = &fixups[fixup_count++];
    442     f->out_off     = out_off;
    443     f->ip_at_ref   = ip_after;
    444     f->name        = name;
    445     f->name_len    = name_len;
    446     f->other       = other;
    447     f->other_len   = other_len;
    448     f->sigil       = sigil;
    449     f->src_path    = cur_path;
    450     f->src_line    = cur_line;
    451     f->scope_depth = scope_depth;
    452     if (scope_depth > 0) {
    453         if (scope_history_used + scope_depth > MAX_SCOPE_HISTORY) {
    454             die("scope history overflow");
    455         }
    456         f->scope_hist_off = scope_history_used;
    457         memcpy(&scope_history[scope_history_used], scope_stack,
    458                (size_t)scope_depth * sizeof(int));
    459         scope_history_used += scope_depth;
    460     } else {
    461         f->scope_hist_off = 0;
    462     }
    463 }
    464 
    465 static void process_reference(struct Scanner *s, int sigil)
    466 {
    467     const char *name_start;
    468     const char *other_start = NULL;
    469     int  name_len;
    470     int  other_len = 0;
    471     struct SigilInfo si = sigil_info(sigil);
    472     long long out_off;
    473 
    474     if (sigil == '&' || sigil == '%') ptrsize_used = 1;
    475 
    476     /* Sigil already consumed. Read tight LABEL. */
    477     if (s->pos >= s->len || is_name_terminator((unsigned char)s->buf[s->pos])) {
    478         die("sigil '%c' not followed by label name", sigil);
    479     }
    480     name_len = scan_name(s, &name_start);
    481 
    482     /* Optional '-' OTHER or '>' OTHER (tight, no whitespace).
    483      * '>' is a synonym for '-', accepted for hex2 compatibility. */
    484     if (s->pos < s->len && (s->buf[s->pos] == '-' || s->buf[s->pos] == '>')) {
    485         s->pos++;
    486         if (s->pos >= s->len || is_name_terminator((unsigned char)s->buf[s->pos])) {
    487             die("'-' must be followed by label name");
    488         }
    489         other_len = scan_name(s, &other_start);
    490     }
    491 
    492     out_off = emit_zeros(si.width);
    493     record_fixup(name_start, name_len, other_start, other_len,
    494                  sigil, out_off, ip);
    495 }
    496 
    497 /* --- directives ------------------------------------------------------- */
    498 
    499 static int read_directive_name(struct Scanner *s, char *out, int max)
    500 {
    501     /* '.' already consumed. Read alpha chars. */
    502     int n = 0;
    503     while (s->pos < s->len) {
    504         int c = (unsigned char)s->buf[s->pos];
    505         if (!isalpha(c)) break;
    506         if (n >= max) die("directive name too long");
    507         out[n++] = (char)c;
    508         s->pos++;
    509     }
    510     if (n == 0) die("expected directive name after '.'");
    511     return n;
    512 }
    513 
    514 static void skip_inline_ws(struct Scanner *s)
    515 {
    516     /* Directive arguments do NOT cross newlines: `.align N PATTERN` ends
    517      * at end-of-line, otherwise `.align 8\n cc` would slurp `cc` as
    518      * pattern. Skip space/tab and inline comments only. */
    519     int c;
    520     while (s->pos < s->len) {
    521         c = (unsigned char)s->buf[s->pos];
    522         if (c == ' ' || c == '\t' || c == '\r' || c == '\f' || c == '\v') {
    523             s->pos++;
    524         } else if (c == '#' || c == ';') {
    525             while (s->pos < s->len && s->buf[s->pos] != '\n') s->pos++;
    526         } else {
    527             break;
    528         }
    529     }
    530 }
    531 
    532 static void do_align(struct Scanner *s)
    533 {
    534     long long N;
    535     long long pad;
    536     long long target;
    537     long long i;
    538     unsigned char patbuf[MAX_TOKEN];
    539     int patlen = 0;
    540     int has_pattern = 0;
    541     int c;
    542 
    543     skip_inline_ws(s);
    544     N = read_decimal(s);
    545     if (N <= 0 || (N & (N - 1)) != 0) {
    546         die(".align: N must be a positive power of two (got %lld)", N);
    547     }
    548 
    549     /* Optional pattern: peek -- if next non-ws is a byte digit, parse it. */
    550     skip_inline_ws(s);
    551     if (s->pos < s->len) {
    552         c = (unsigned char)s->buf[s->pos];
    553         if (is_byte_digit(c)) {
    554             parse_one_byte_literal(s, NULL, 1, patbuf, (int)sizeof(patbuf), &patlen);
    555             has_pattern = 1;
    556         }
    557     }
    558 
    559     target = (ip + N - 1) & ~(N - 1);
    560     pad = target - ip;
    561     if (pad <= 0) return;
    562     if (output_used + pad > MAX_OUTPUT_BYTES) die("output overflow");
    563     if (!has_pattern) {
    564         memset(output_buf + output_used, 0, (size_t)pad);
    565     } else {
    566         for (i = 0; i < pad; i++) {
    567             output_buf[output_used + i] = patbuf[i % patlen];
    568         }
    569     }
    570     output_used += pad;
    571     ip += pad;
    572 }
    573 
    574 static void do_fill(struct Scanner *s)
    575 {
    576     long long N;
    577     unsigned char b;
    578 
    579     skip_inline_ws(s);
    580     N = read_decimal(s);
    581     if (N < 0) die(".fill: N must be non-negative (got %lld)", N);
    582     skip_inline_ws(s);
    583     parse_one_byte_literal(s, &b, 0, NULL, 0, NULL);
    584     if (N > 0) emit_fill(N, b);
    585 }
    586 
    587 static void do_ptrsize(struct Scanner *s)
    588 {
    589     long long N;
    590     skip_inline_ws(s);
    591     N = read_decimal(s);
    592     if (N != 4 && N != 8) {
    593         die(".ptrsize: N must be 4 or 8 (got %lld)", N);
    594     }
    595     if (ptrsize_used && (int)N != ptrsize) {
    596         die(".ptrsize %lld conflicts with already-used width %d", N, ptrsize);
    597     }
    598     ptrsize = (int)N;
    599 }
    600 
    601 static void do_scope_open(void)
    602 {
    603     if (scope_depth >= MAX_SCOPE_DEPTH) die(".scope: depth overflow");
    604     scope_seq++;
    605     scope_stack[scope_depth++] = scope_seq;
    606 }
    607 
    608 static void do_scope_close(void)
    609 {
    610     if (scope_depth <= 0) die(".endscope: not in a scope");
    611     scope_depth--;
    612 }
    613 
    614 /* --- main scanner loop ------------------------------------------------ */
    615 
    616 static void process_file(struct InFile *f)
    617 {
    618     struct Scanner s = { f->buf, f->len, 0 };
    619     cur_path = f->path;
    620     cur_line = 1;
    621 
    622     for (;;) {
    623         int c;
    624         skip_ws_and_comments(&s);
    625         if (s.pos >= s.len) break;
    626         c = (unsigned char)s.buf[s.pos];
    627 
    628         if (c == ':') {
    629             const char *name;
    630             int n;
    631             int dotted;
    632             int scope;
    633             s.pos++;
    634             n = scan_name(&s, &name);
    635             /* A dot-prefixed name is scope-local only inside a .scope;
    636              * outside, it is an ordinary global name. */
    637             dotted = (n > 0 && name[0] == '.' && scope_depth > 0);
    638             scope = dotted ? scope_stack[scope_depth - 1] : 0;
    639             define_label(name, n, scope);
    640             continue;
    641         }
    642 
    643         if (c == '.') {
    644             char dn[MAX_TOKEN];
    645             int n;
    646             s.pos++;
    647             n = read_directive_name(&s, dn, sizeof(dn));
    648             if (n == 5 && memcmp(dn, "align", 5) == 0)        do_align(&s);
    649             else if (n == 4 && memcmp(dn, "fill", 4) == 0)    do_fill(&s);
    650             else if (n == 5 && memcmp(dn, "scope", 5) == 0)   do_scope_open();
    651             else if (n == 8 && memcmp(dn, "endscope", 8) == 0) do_scope_close();
    652             else if (n == 7 && memcmp(dn, "ptrsize", 7) == 0)  do_ptrsize(&s);
    653             else die("unknown directive '.%.*s'", n, dn);
    654             continue;
    655         }
    656 
    657         if (c == '!' || c == '@' || c == '$' || c == '~' || c == '%' || c == '&') {
    658             s.pos++;
    659             process_reference(&s, c);
    660             continue;
    661         }
    662 
    663         if (is_byte_digit(c)) {
    664             parse_byte_stream(&s);
    665             continue;
    666         }
    667 
    668         die("unexpected character 0x%02x ('%c')", c, isprint(c) ? c : '?');
    669     }
    670 }
    671 
    672 /* --- fixup resolution ------------------------------------------------- */
    673 
    674 static void patch_fixups(void)
    675 {
    676     int i;
    677     for (i = 0; i < fixup_count; i++) {
    678         struct Fixup *f = &fixups[i];
    679         struct SigilInfo si = sigil_info(f->sigil);
    680         const int *stack = (f->scope_depth > 0)
    681                          ? &scope_history[f->scope_hist_off]
    682                          : NULL;
    683         long long t_label;
    684         long long value;
    685 
    686         cur_path = f->src_path;
    687         cur_line = f->src_line;
    688 
    689         t_label = lookup_label_in(f->name, f->name_len, stack, f->scope_depth);
    690         if (f->other != NULL) {
    691             long long t_other = lookup_label_in(f->other, f->other_len,
    692                                                 stack, f->scope_depth);
    693             value = t_label - t_other;
    694         } else if (si.is_rel) {
    695             value = t_label - f->ip_at_ref;
    696         } else {
    697             value = t_label + base_address;
    698         }
    699         write_value(f->out_off, value, si.width, si.lo, si.hi, si.range_check);
    700     }
    701 }
    702 
    703 /* --- argument parsing & top-level ------------------------------------- */
    704 
    705 static long long parse_long(const char *s, const char *what)
    706 {
    707     char *end;
    708     long long v;
    709     int base = 10;
    710     if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) base = 16;
    711     errno = 0;
    712     v = strtoll(s, &end, base);
    713     if (errno != 0 || *end != '\0') {
    714         fprintf(stderr, "hex2pp: invalid %s: %s\n", what, s);
    715         exit(1);
    716     }
    717     return v;
    718 }
    719 
    720 static void load_input(const char *path)
    721 {
    722     FILE *fp;
    723     long sz;
    724     char *buf;
    725 
    726     fp = fopen(path, "rb");
    727     if (fp == NULL) { perror(path); exit(1); }
    728     if (fseek(fp, 0, SEEK_END) != 0) { perror(path); exit(1); }
    729     sz = ftell(fp);
    730     if (sz < 0)               { perror(path); exit(1); }
    731     if (sz > MAX_INPUT_BYTES) { fprintf(stderr, "%s: input too large\n", path); exit(1); }
    732     rewind(fp);
    733     buf = (char *)malloc((size_t)sz + 1);
    734     if (buf == NULL) { fprintf(stderr, "out of memory\n"); exit(1); }
    735     if (sz > 0 && fread(buf, 1, (size_t)sz, fp) != (size_t)sz) {
    736         perror(path);
    737         exit(1);
    738     }
    739     buf[sz] = '\0';
    740     fclose(fp);
    741 
    742     input_file.path = path;
    743     input_file.buf  = buf;
    744     input_file.len  = (int)sz;
    745 }
    746 
    747 static void usage(const char *prog)
    748 {
    749     fprintf(stderr,
    750         "usage: %s [-B ADDR] [-E|-e] [-b] [-N] IN OUT\n",
    751         prog);
    752 }
    753 
    754 int main(int argc, char **argv)
    755 {
    756     int i;
    757     const char *in_path = NULL;
    758 
    759     for (i = 1; i < argc; i++) {
    760         const char *a = argv[i];
    761         if (strcmp(a, "-B") == 0) {
    762             if (++i >= argc) { usage(argv[0]); return 1; }
    763             base_address = parse_long(argv[i], "base address");
    764         } else if (strcmp(a, "-E") == 0) {
    765             big_endian = 1;
    766         } else if (strcmp(a, "-e") == 0) {
    767             big_endian = 0;
    768         } else if (strcmp(a, "-b") == 0) {
    769             byte_mode = BINARY_MODE;
    770         } else if (strcmp(a, "-N") == 0) {
    771             non_executable = 1;
    772         } else if (a[0] == '-' && a[1] != '\0') {
    773             fprintf(stderr, "hex2pp: unknown argument: %s\n", a);
    774             usage(argv[0]);
    775             return 1;
    776         } else if (in_path == NULL) {
    777             in_path = a;
    778         } else if (output_path == NULL) {
    779             output_path = a;
    780         } else {
    781             fprintf(stderr, "hex2pp: extra positional argument: %s\n", a);
    782             usage(argv[0]);
    783             return 1;
    784         }
    785     }
    786 
    787     if (in_path == NULL || output_path == NULL) {
    788         usage(argv[0]);
    789         return 1;
    790     }
    791     load_input(in_path);
    792 
    793     ip = 0;
    794     output_used = 0;
    795     scope_depth = 0;
    796     scope_seq = 0;
    797     ptrsize = 4;
    798     ptrsize_used = 0;
    799     process_file(&input_file);
    800     if (scope_depth != 0) die(".scope not closed at end of input");
    801     patch_fixups();
    802 
    803     /* Write output. */
    804     {
    805         FILE *fp = fopen(output_path, "wb");
    806         if (fp == NULL) { perror(output_path); return 1; }
    807         if (output_used > 0 &&
    808             fwrite(output_buf, 1, (size_t)output_used, fp) != (size_t)output_used) {
    809             perror(output_path);
    810             fclose(fp);
    811             return 1;
    812         }
    813         fclose(fp);
    814     }
    815 
    816     if (!non_executable) {
    817         struct stat st;
    818         if (stat(output_path, &st) == 0 && S_ISREG(st.st_mode)) {
    819             (void)chmod(output_path, 0750);
    820         }
    821     }
    822 
    823     return 0;
    824 }