Repository: FireyFly/hexd Branch: master Commit: ebfa9b5f76b5 Files: 7 Total size: 12.8 KB Directory structure: gitextract_i2xq9q4j/ ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── hexd.1 └── hexd.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /hexd ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 1.1.0 * default to colours/formatting based on whether output is a TTY * add verbose option to show all bytes (and not omit repeated lines) * add -h as an option to show usage * fix misaligned output in case -w width doesn't divide BUFSIZ * fix downcasting issue from `off_t` to a potentially smaller size ## 1.0.0 initial release ================================================ FILE: LICENSE ================================================ Copyright (C) 2013 Jonas ‘FireFly’ Höglund Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ CFLAGS += -Wall -std=c11 PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/share/man .PHONY: all all: hexd .PHONY: clean clean: rm -f hexd .PHONY: install install: hexd mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 install hexd $(DESTDIR)$(BINDIR)/hexd install hexd.1 $(DESTDIR)$(MANDIR)/man1/hexd.1 ================================================ FILE: README.md ================================================ **hexd** prints a human-readable hexdump of the specified files, or standard input if omitted. Its main distinguishing feature is the use of colours to visually indicate which range of values an octet belongs to, aiding in spotting patterns in binary data. By default, *hexd* relies on 256-color SGR escape sequences. Most terminal emulators should support these today, but technically they're only defacto standard. However, you can override the formatting used with the `HEXD_COLORS` environment variable (see manpage), or use the `-p` option for plaintext output. ## Screenshot ![Screenshot](meta/screenshot.png "Output after being run on an ELF binary") ## License MIT license. ## See also * [pixd](http://github.com/FireyFly/pixd): like *hexd*, but visualizes the data fed to it using a 256-colour palette and half-block ("▀") characters. ================================================ FILE: hexd.1 ================================================ .Dd February 18, 2022 .Dt HEXD 1 .Os .Sh NAME .Nm hexd .Nd human-friendly hexdump tool .Sh SYNOPSIS .Nm .Op Fl p .Op Fl P .Op Fl v .Op Fl g Ar groupsize .Op Fl r Ar range .Op Fl w Ar width .Op Ar .Sh DESCRIPTION .Nm prints a human-readable hexdump of the specified files, or standard input if omitted. Its main distinguishing feature is the use of colours to visually indicate which range of values an octet belongs to, aiding in spotting patterns in binary data. .Pp The ranges an octet is classified into are .Em zero .Li ( 0x00 ) , .Em low .Li ( 0x01..0x1F ) , .Em printable .Li ( 0x20..0x7E ) , .Em high .Li ( 0x7F..0xFE ) and .Em all .Li ( 0xFF ) . .Pp By default, colours are used if output is a terminal, and omitted if not. .Pp Repeated output lines are collapsed into only one copy followed by a line with only '*', unless .Fl v is used. .Sh OPTIONS If no .Ar file operands are specified, standard input is read instead. Available options are listed below. .Bl -tag -width Ds .It Fl h Print usage information. .It Fl p Plain: disable colours/formatting. .It Fl P Pretty: enable colours/formatting. .It Fl v Verbose: show every hexdump line (don't collapse repetition with '*'). .It Fl g Ar groupsize Number of octets per group, set to .Li 8 by default. .It Fl r Ar range Range of octets to print from each file. Specified as either .Em start-end or .Em start+count , where .Em start and .Em end Ns / Ns Em count are positive integers specified in either decimal, hexadecimal or octal (C-style notation). .Pp When the former syntax is used, both ends of the range are optional and default to the start or end of the file when omitted. .It Fl w Ar width Number of octets per line, separated into groups (see .Fl g ) . Set to .Li 16 by default. .El .Sh ENVIRONMENT .Ev HEXD_COLORS can be used to override the formatting used by .Nm to classify octets. If set, it should consist of space-separated pairs of the form .Em key=value , where .Em key is one of 'zero', 'low', 'printable', 'high' or 'all', and .Em value is an SGR formatting string. SGR formatting is interpreted by your terminal emulator; consult its documentation or ECMA-48 for more details. .Pp For example, the default formatting used when .Ev HEXD_COLORS is not defined corresponds to the value .Pp .D1 Em zero=38;5;238 low=38;5;150 high=38;5;141 all=38;5;167 .Sh EXAMPLES Here are some examples of useful uses of hexd's features. .Bl -tag -width Ds .It Em hexd -r0x1000+0x200 foo.bin Display the 512-byte range in 'foo.bin' starting at offset 0x1000. Useful when files contain other embedded files/formats at a certain location (e.g. archive files). .It Em hexd -r-0x10 *.bin Show the first 16 bytes of each of the *.bin files, with a heading above each file (if more than one). This is useful for example to compare headers of several samples of an unknown format. .It Em curl -s http://example.com | hexd -P | less -R .Nm works as a filter, too. For paging long hexdumps, .Xr less 1 Ns 's .Fl R flag is useful. .El .Sh SEE ALSO .Xr hexdump 1 , .Xr od 1 , .Xr xxd 1 .Sh AUTHORS Written by .An FireFly ================================================ FILE: hexd.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #define MIN(X,Y) ((X) < (Y)? (X) : (Y)) typedef uint8_t u8; //-- Options ---------------- struct offset_range { off_t start, end; }; size_t option_columns = 16; size_t option_groupsize = 8; bool option_use_formatting = true; bool option_collapse_repetition = true; struct offset_range option_range = { 0, -1 }; const char *formatting_zero = "38;5;238"; const char *formatting_all = "38;5;167"; const char *formatting_low = "38;5;150"; const char *formatting_high = "38;5;141"; const char *formatting_printable = ""; //-- Hexdump impl ----------- const char *format_of(int v) { return v == 0x00? formatting_zero : v == 0xFF? formatting_all : v < 0x20? formatting_low : v >= 0x7F? formatting_high : formatting_printable; } const char *CHAR_AREA_HIGH_LUT[] = { "€", ".", "‚", "ƒ", "„", "…", "†", "‡", "ˆ", "‰", "Š", "‹", "Œ", ".", "Ž", ".", ".", "‘", "’", "“", "”", "•", "–", "—", "˜", "™", "š", "›", "œ", ".", "ž", "Ÿ", ".", "¡", "¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", ".", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", }; void hexdump(FILE *f, const char *filename) { u8 line[option_columns]; u8 prev_line[option_columns]; bool first_line = true, printed_asterisk = false; // Seek to start; fall back to a consuming loop for non-seekable files if (fseeko(f, option_range.start, SEEK_SET) < 0) { off_t remaining = option_range.start; while (remaining != 0 && fgetc(f) != EOF) remaining--; if (ferror(f)) err(1, "(while seeking) %s", filename); } for (off_t offset = option_range.start; offset < option_range.end || option_range.end == -1; offset += option_columns) { off_t read = offset - option_range.start; size_t n = fread(line, 1, option_columns, f); if (n == 0) break; // Contract repeated identical lines if (!first_line && option_collapse_repetition && memcmp(line, prev_line, option_columns) == 0 && n == option_columns) { if (!printed_asterisk) { printf("%8s\n", "*"); printed_asterisk = true; } continue; } printed_asterisk = false; // Offset intmax_t offset = option_range.start + read; printf("%5jx%03jx", offset >> 12, offset & 0xFFF); // Print hex area const char *prev_fmt = NULL; for (size_t j = 0; j < option_columns; j++) { if (option_groupsize != 0 && j % option_groupsize == 0) printf(" "); if (j < n) { const char *fmt = format_of(line[j]); if (prev_fmt != fmt && option_use_formatting) printf("\x1B[%sm", fmt); printf(" %02x", line[j]); prev_fmt = fmt; } else { printf(" "); } } putchar(' '); // Print char area for (size_t j = 0; j < option_columns; j++) { if (option_groupsize != 0 && j % option_groupsize == 0) printf(" "); if (j < n) { const char *fmt = format_of(line[j]); if (prev_fmt != fmt && option_use_formatting) printf("\x1B[%sm", fmt); if (line[j] >= 0x80) printf("%s", CHAR_AREA_HIGH_LUT[line[j] - 0x80]); else putchar(isprint(line[j])? line[j] : '.'); prev_fmt = fmt; } else { putchar(' '); } } printf("%s\n", option_use_formatting? "\x1B[m" : ""); memcpy(prev_line, line, n); first_line = false; if (n < option_columns) break; } if (ferror(f)) err(1, "(while reading) %s", filename); } //-- Entry point ------------ /** Parses a range "start-end" (both ends optional) or "start+size" (neither * optional) into a `struct offset_range` instance. */ struct offset_range parse_range(const char *str) { struct offset_range res = { 0, -1 }; const char *first = str, *delim = str + strcspn(str, "+-"), *second = delim + 1; if (*delim == '\0') errx(1, "no delimiter in range %s", str); char *end; if (first != delim) { res.start = strtoimax(first, &end, 0); if (!isdigit(*first) || end != delim) errx(1, "invalid start value in range %s", str); } if (*second != '\0') { res.end = strtoimax(second, &end, 0); if (!isdigit(*second) || *end != '\0') errx(1, "invalid end/size value in range %s", str); } if (*delim == '+') { if (first == delim) errx(1, "start unspecified in range %s", str); if (*second == '\0') errx(1, "size unspecified in range %s", str); res.end += res.start; } if (res.end < res.start && res.end != -1) errx(1, "end was less than start in range %s", str); return res; } int main(int argc, char *argv[]) { // Default to colourful output if output is a TTY option_use_formatting = isatty(1); // Parse options int opt; while (opt = getopt(argc, argv, "g:hpPr:w:v"), opt != -1) { switch (opt) { case 'g': option_groupsize = atol(optarg); break; case 'p': option_use_formatting = false; break; case 'P': option_use_formatting = true; break; case 'r': option_range = parse_range(optarg); break; case 'v': option_collapse_repetition = false; break; case 'w': option_columns = atol(optarg); break; case 'h': // fall through default: fprintf(stderr, "usage: hexd [-p] [-P] [-v] [-g groupsize] [-r range] [-w width]\n"); return 1; } } argc -= optind; argv += optind; // Parse HEXD_COLORS char *colors_var = getenv("HEXD_COLORS"); if (colors_var != NULL) { colors_var = strdup(colors_var); if (colors_var == NULL) errx(1, "strdup"); for (char *p = colors_var, *token; token = strtok(p, " "), token != NULL; p = NULL) { char *key = token, *value = strchr(token, '='); if (value == NULL) warnx("no '=' found in HEXD_COLORS property '%s'", p); *value++ = '\0'; if (strcmp(key, "zero") == 0) formatting_zero = value; else if (strcmp(key, "low") == 0) formatting_low = value; else if (strcmp(key, "printable") == 0) formatting_printable = value; else if (strcmp(key, "high") == 0) formatting_high = value; else if (strcmp(key, "all") == 0) formatting_all = value; else warnx("unknown HEXD_COLORS property '%s'", key); } } // Hexdump files if (argc == 0) { hexdump(stdin, "(stdin)"); } else { for (int i = 0; i < argc; i++) { FILE *f = fopen(argv[i], "r"); if (f == NULL) { warn("%s", argv[i]); continue; } if (argc > 1) { printf("%s====> %s%s%s <====\n", i > 0? "\n" : "", option_use_formatting? "\x1B[1m" : "", argv[i], option_use_formatting? "\x1B[m" : ""); } hexdump(f, argv[i]); fclose(f); } } free(colors_var); return 0; }