Help us shape the future of Go! Take the 2026 Go Developer Survey

winstrings

command module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 15 Imported by: 0

README

winstrings

strings for Windows — a clone of the GNU binutils strings(1) command, in pure Go.

It prints the sequences of printable characters found in a file: the classic way to get a first look at a binary. Every option from the strings(1) man page is implemented, including the object-file modes (-d, -T), the wide-character encodings (-e), the UTF-8 display modes (-U), and @file option files.

Written for Windows, but the source is portable and builds and runs on Linux too.

Installation

Option 1 — Download binary

Download the binary for your platform from the latest release and place it anywhere on your PATH. Windows and Linux are published, for both amd64 and arm64, along with a SHA256SUMS.txt.

Platform File
Windows (amd64) winstrings-windows-amd64.exe
Windows (arm64) winstrings-windows-arm64.exe
Linux (amd64) winstrings-linux-amd64
Linux (arm64) winstrings-linux-arm64

Rename it to winstrings.exe (or winstrings on Linux) — the tool reports itself by whatever the file is called, so it is worth renaming rather than running under the download name.

Option 2 — Go install

If you have Go 1.22+ installed:

go install github.com/fermat-tech/winstrings@latest

This builds and places winstrings.exe in your GOBIN directory (typically %USERPROFILE%\go\bin), which should already be on your PATH.

Option 3 — Build from source

git clone https://github.com/fermat-tech/winstrings.git
cd winstrings
go build -o winstrings.exe .

That is the whole build. winstrings --version reports the right version by itself: the Go toolchain stamps it from the repository's tag, so there is no -ldflags incantation to remember and nothing to keep in sync. A build with uncommitted changes reports the version with +dirty appended, and a build from a source archive with no repository in it falls back to the version compiled into the source.

To cross-compile, set GOOS and GOARCH. This is exactly how the release binaries are produced — -trimpath keeps local paths out of the binary and -s -w drops the symbol table:

$env:GOOS="linux"; $env:GOARCH="arm64"
go build -trimpath -ldflags "-s -w" -o winstrings-linux-arm64 .

Like the rest of the win* suite, the tool takes its identity from argv[0]: whatever you name the file is what it calls itself in its usage and error messages. Name it strings.exe and it reports itself as strings.

Usage

winstrings [option(s)] [file(s)]

With no file arguments it reads standard input.

Option Meaning
-a, --all, - Scan the entire file, not just the data sections. Default.
-d, --data Only scan initialized, loaded data sections of object files.
-f, --print-file-name Print the file name before each string.
-n N, --bytes=N, -N Minimum string length; default 4.
-t {o,d,x}, --radix= Print each string's file offset in octal, decimal or hex.
-o Alias for -t o.
-w, --include-all-whitespace Treat every whitespace character as part of a string.
-e {s,S,b,l,B,L}, --encoding= Character size and endianness.
-U MODE, --unicode=MODE How to display UTF-8 multibyte characters.
-T NAME, --target=NAME Object file format to assume (only affects -d).
-s STR, --output-separator=STR Output record separator; default is a newline.
@FILE Read further options from FILE.
-h, --help Print usage and exit.
-v, -V, --version Print version information and exit.

By default a string ends at any non-printable character. Tab and space are always part of a string; other whitespace, including newline and carriage return, is not, unless -w is given.

-n accepts C integer syntax, so -n 0x10 is 16 and -n 010 is 8. A bare -NUMBER sets the same thing.

Encodings (-e)
Value Meaning
s single 7-bit-byte characters (ASCII) — the default
S single 8-bit-byte characters (every byte above 127 counts as printable)
b / l 16-bit big-/little-endian, for UTF-16 and UCS-2 text
B / L 32-bit big-/little-endian
Unicode display (-U)

These control what happens to valid UTF-8 multibyte sequences. Anything other than default implies -e S, and minimum length is then counted in characters rather than bytes.

Value Short Effect
default d No special treatment; -e decides.
invalid i Multibyte characters are non-printable, so they end a string.
locale l Display in the current locale.
hex x Display as <0xc3a9>.
escape e Display as \u00e9.
highlight h Like escape, but coloured red when stdout is a terminal.

Note that show / -U s is not accepted, despite appearing in the --help text of binutils 2.38 and earlier. That was a documentation bug upstream — the real binary rejects it too — and it was corrected in binutils 2.45. This implementation follows the man page and the corrected help text.

Object files (-d)

-d restricts the scan to sections that are allocated, loaded and have contents, which usually cuts out a lot of noise. Understood formats:

  • PE / COFF — .exe, .dll, .sys, .obj, both PE32 and PE32+. Sections are selected on IMAGE_SCN_CNT_CODE or IMAGE_SCN_CNT_INITIALIZED_DATA; .debug*, .zdebug*, .stab* and the other names BFD classifies as debug information are excluded, and section sizes are clamped to the virtual size so the file-alignment padding is not scanned. Long /N and //B section names are resolved through the COFF string table, which is what keeps the debug sections out of a Go- or clang-built binary.
  • ELF — 32- and 64-bit, either endianness. Sections are selected on SHF_ALLOC with a type other than SHT_NOBITS.

Anything else — a plain file, an ar archive, a .NET bundle — is not an object file as far as -d is concerned, and the whole file is scanned instead. That matches GNU strings, which does the same for archives.

-T binary presents the whole file as one loaded section. A target naming a format that does not match the file is only a preference: as in BFD, the file is then probed normally. An unrecognised target name makes -d fall back to a whole-file scan.

A caveat about target names

--help lists the formats -d can actually read. Six further BFD target names are recognised but cannot be read by this build:

srec  symbolsrec  verilog  tekhex  ihex  plugin

Passing one of these is accepted and has no effect: the file is probed and scanned as whatever it really is. That is not a special case — it is precisely what BFD does with any target that does not match the file, silently, and GNU strings behaves the same way. They are kept out of the --help list on purpose, because honouring a name is not the same as parsing the format, and listing srec as supported would imply this tool reads S-records. It does not.

Worth knowing either way: -T has no effect at all without -d, in this tool and in GNU strings alike, because the target is only consulted when opening a file as an object.

Environment

Variable Effect
NO_COLOR If set and non-empty, -U highlight emits no colour.
WINSTRINGS_COLOR always / never overrides the terminal check.

Exit status

0 on success, 1 if any file could not be read or an option was invalid.

Compatibility

The scanning engine is a direct port of binutils/strings.c (2.45), so output is intended to be byte-for-byte identical to GNU strings. That is verified by differential testing rather than asserted: the same argument lists are run against this tool on Windows and against GNU strings on a Linux box, and the outputs, stderr and exit statuses are compared byte for byte.

Suite Cases Identical
Synthetic fixtures, ELF executable, Go-built PE 170 154
Real MSVC binaries (64- and 32-bit kernel32.dll, notepad.exe) 12 12
ELF .o object and ar archive 8 8

All 16 remaining differences are accounted for below: 11 are the deliberate divergences, and 5 are version drift between binutils 2.38 and 2.46.

Deliberate divergences

1. Digits anywhere in a short-option cluster set the minimum length. strings -a6 and strings -6a both mean "scan everything, minimum length 6". GNU strings misparses these: it reads the minimum length from the wrong argv element and fails with invalid integer argument a6 — or, for -6a, with invalid integer argument trings, having read the program's own name. A bare -6 works correctly in both.

2. Offsets after a UTF-8 pushback are correct. When -U is in effect and a byte has been pushed back after a failed UTF-8 sequence, GNU strings underflows an unsigned int counter and reports an offset 2³² too large — 4294967340 where the string is actually at offset 44. This prints the true offset. Affects the streaming path only; -d was never wrong.

3. Backslashes in @file option files are literal unless they escape something. libiberty lets a backslash escape any character, which on Windows quietly destroys every path in an options file: C:\Users\na\tools comes back as C:Usersnatools. Here a backslash is an escape only before a quote, another backslash, or whitespace — every case the man page's own description needs — and is a literal character otherwise.

4. Standard output is flushed before anything is written to standard error. GNU strings lets stdio buffering decide, so redirecting both streams to one file puts all the warnings before all the output. Here warnings appear in sequence with the strings around them.

Of the 16 differing cases: divergence 1 accounts for 2, divergence 2 for 9, and the 5 remaining are version drift (below). Divergences 3 and 4 account for none — no case exercises a Windows path in an @file, and the harness captures stdout and stderr separately. The unreadable target names described under A caveat about target names are not a divergence: all seven cases covering them match GNU byte for byte.

Two further differences show up only against binutils 2.38, the version used for differential testing, and are version drift rather than divergence:

  • The supported-target line printed at the end of the usage message, which reflects how each build was configured. This accounts for the 5 usage-text cases above; the rest of the usage message matches to the byte, including the --unicode= line, where the tool follows 2.45's corrected wording rather than 2.38's show/-U s typo.
  • The -n overflow diagnostics. This tool follows 2.45/2.46, which added minimum string length is too big: N and minimum string length is too small: N; 2.38 has neither check and silently accepts -n -1 as a minimum of 4294967295.
GNU quirks reproduced on purpose

These are upstream bugs that are kept, because their output is well-defined, loses no information, and a user diffing against GNU strings would otherwise see spurious differences:

  • -U escape mis-packs 4-byte UTF-8. U+1F600 prints as \u07c600, not \u1f600: upstream repacks the 21 payload bits into three bytes on the wrong boundaries. No information is lost — the escape still uniquely identifies the character — so the exact output is reproduced.
  • -U locale prints only the lead byte of each multibyte character, because upstream formats it with printf("%.1s").
  • A bare -NUMBER overrides -n regardless of order. strings -7 -n 3 and strings -n 3 -7 both use 7, because upstream applies the numeric option after the option loop has finished.
  • The pushback order after an invalid 4-byte sequence under -U invalid is upstream's (bytes 4, 2, 3 rather than 4, 3, 2), so that scanning resumes at the same place GNU strings resumes at.

Tests

go test ./...      # unit tests
go vet ./...

The unit tests cover the scanner in every encoding, the -U modes through both the buffered and streaming paths, option parsing, @file expansion, C strtoul semantics, and PE/ELF section selection against synthesized object files. The differential suite against real GNU strings lives outside the repo; see Compatibility above for what it covers.

Licence

MIT — see LICENSE.

Documentation

Overview

winstrings prints the sequences of printable characters in files.

Usage:

winstrings [option(s)] [file(s)]

winstrings is a Windows clone of GNU strings(1) from binutils. For each file given it prints the printable character sequences that are at least 4 characters long (or the number given with -n) and are followed by an unprintable character or end of file.

Options:

-a - --all                  scan the entire file (default)
-d --data                   only scan initialized, loaded data sections
-f --print-file-name        print the file name before each string
-n N --bytes=N              minimum string length (default 4)
-N                          same, as a bare number (e.g. -6)
-t --radix={o,d,x}          print the offset of each string
-o                          alias for --radix=o
-w --include-all-whitespace treat all whitespace as string characters
-e --encoding={s,S,b,l,B,L} character size and endianness
-U --unicode=MODE           how to display UTF-8 multibyte characters
-T --target=BFDNAME         object file format for -d
-s --output-separator=STR   output record separator (default newline)
@FILE                       read further options from FILE
-h --help                   display usage and exit
-v -V --version             output version information and exit

The scanning engine is a direct port of binutils' strings.c so that output is byte-for-byte identical to GNU strings; see README.md for the handful of deliberate divergences.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL