1//===-- llvm-strings.cpp - Printable String dumping utility ---------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This program is a utility that works like binutils "strings", that is, it
10// prints out printable strings in a binary, objdump, or archive file.
11//
12//===----------------------------------------------------------------------===//
13
14#include "Opts.inc"
15#include "llvm/ADT/StringExtras.h"
16#include "llvm/Object/Binary.h"
17#include "llvm/Option/Arg.h"
18#include "llvm/Option/ArgList.h"
19#include "llvm/Option/Option.h"
20#include "llvm/Support/CommandLine.h"
21#include "llvm/Support/Error.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/Format.h"
24#include "llvm/Support/InitLLVM.h"
25#include "llvm/Support/MemoryBuffer.h"
26#include "llvm/Support/Program.h"
27#include "llvm/Support/WithColor.h"
28#include <cctype>
29#include <string>
30
31using namespace llvm;
32using namespace llvm::object;
33
34namespace {
35enum ID {
36 OPT_INVALID = 0, // This is not an option ID.
37#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
38#include "Opts.inc"
39#undef OPTION
40};
41
42using namespace llvm::opt;
43#define OPTTABLE_CODE
44#include "Opts.inc"
45
46class StringsOptTable : public opt::OptTable {
47public:
48 StringsOptTable() : OptTable(optionTables()) {
49 setGroupedShortOptions(true);
50 setDashDashParsing(true);
51 }
52};
53
54static StringRef ToolName;
55
56static cl::list<std::string> InputFileNames(cl::Positional,
57 cl::desc("<input object files>"));
58
59static constexpr int DefaultMinLength = 4;
60static int MinLength = DefaultMinLength;
61static bool PrintFileName;
62
63enum class Radix { None, Octal, Hexadecimal, Decimal };
64static Radix Radix;
65} // namespace
66
67[[noreturn]] static void reportCmdLineError(const Twine &Message) {
68 WithColor::error(OS&: errs(), Prefix: ToolName) << Message << "\n";
69 exit(status: 1);
70}
71
72template <typename T>
73static void parseIntArg(const opt::InputArgList &Args, int ID, T &Value) {
74 if (const opt::Arg *A = Args.getLastArg(Ids: ID)) {
75 StringRef V(A->getValue());
76 if (!llvm::to_integer(V, Value, 0) || Value <= 0)
77 reportCmdLineError(Message: "expected a positive integer, but got '" + V + "'");
78 }
79}
80
81static bool isStringChar(char C) { return isPrint(C) || C == '\t'; }
82
83static void strings(raw_ostream &OS, StringRef FileName,
84 sys::fs::file_t Handle) {
85 SmallString<sys::fs::DefaultReadChunkSize> Buffer;
86 auto PrintHeader = [&OS, FileName](size_t StringStart) {
87 if (PrintFileName)
88 OS << FileName << ": ";
89 switch (Radix) {
90 case Radix::None:
91 break;
92 case Radix::Octal:
93 OS << format(Fmt: "%7o ", Vals: StringStart);
94 break;
95 case Radix::Hexadecimal:
96 OS << format(Fmt: "%7x ", Vals: StringStart);
97 break;
98 case Radix::Decimal:
99 OS << format(Fmt: "%7u ", Vals: StringStart);
100 break;
101 }
102 };
103
104 // To handle very large files without consuming excessive memory, we read the
105 // file in a little at a time and process it then rather than reading the
106 // entire file at once.
107 //
108 // A string is only buffered until it is known to be long enough to print;
109 // from then on it is streamed out directly, so an arbitrarily long string
110 // never needs an arbitrarily large buffer. Candidate therefore only ever
111 // holds a run that is shorter than MinLength and that was cut off by the end
112 // of a chunk.
113 const size_t Min = MinLength;
114 SmallString<DefaultMinLength> Candidate;
115 bool InString = false;
116 // Offset of the start of the current chunk within the file.
117 size_t ChunkOffset = 0;
118
119 Buffer.resize_for_overwrite(N: sys::fs::DefaultReadChunkSize);
120
121 while (true) {
122 Expected<size_t> ReadBytesOrErr = sys::fs::readNativeFile(
123 FileHandle: Handle, Buf: MutableArrayRef(Buffer.data(), Buffer.size()));
124 if (!ReadBytesOrErr) {
125 errs() << FileName << ": "
126 << errorToErrorCode(Err: ReadBytesOrErr.takeError()).message() << '\n';
127 return;
128 }
129 size_t ChunkSize = *ReadBytesOrErr;
130 if (ChunkSize == 0)
131 break;
132
133 // To prevent performance regression under O0, access the raw pointer
134 // instead of using methods provided by the standard library, which are not
135 // inlined under O0.
136 const char *const Begin = Buffer.data();
137 const char *const End = Begin + ChunkSize;
138 const char *Cur = Begin;
139
140 // Handle the remaining part from the previous chunk.
141 // The previous chunk can be either shorter than MinSize or part of the
142 // string.
143 // Keep the buffer size bounded. With a small Min, a long string spanning
144 // multiple chunks will have at most DefaultReadChunkSize bytes, since the
145 // buffer is printed immediately with the header (guarded by the second if).
146 // With a large Min, the buffer must hold at least Min bytes, since we need
147 // enough data to decide whether to print it.
148 if (InString || !Candidate.empty()) {
149 // Find the end of the current string.
150 while (Cur != End && isStringChar(C: *Cur))
151 ++Cur;
152 size_t Len = Cur - Begin;
153 if (InString) {
154 // Print the remaining part if the previous chunk has already printed
155 // the header. E.g. header: aaaaa | bbbbb, where | is the chunk
156 // boundary.
157 // Output: Header: aaaaabbbbb, where bbbbb is printed in here.
158 OS << StringRef(Begin, Len);
159 } else if (Candidate.size() + Len >= Min) {
160 // If the header hasn't been printed yet (e.g. the previous candidate
161 // was smaller than Min), but we can print it now, print the header
162 // first, followed by the candidate from the previous chunk and the
163 // current string. E.g. aa | bbbbbb
164 // Output Header: aabbbbbb, where aabbbbbb is printed in here.
165 PrintHeader(ChunkOffset - Candidate.size());
166 OS << Candidate << StringRef(Begin, Len);
167 Candidate.clear();
168 InString = true;
169 } else if (Cur == End) {
170 // If the current chunk + previous candidate is still smaller than Min,
171 // append it to Candidate.
172 Candidate.append(in_start: Begin, in_end: End);
173 } else {
174 // If the string has terminated but is still smaller than Min, clear the
175 // buffer since it is too short to print.
176 Candidate.clear();
177 }
178
179 if (Cur == End) {
180 // Finish handling the current chunk and update ChunkOffset.
181 ChunkOffset += ChunkSize;
182 continue;
183 }
184 if (InString) {
185 // We haven't reached the end of the chunk, which means the string is
186 // terminated. Add a '\n' to start printing a new string.
187 OS << '\n';
188 InString = false;
189 }
190 }
191
192 // At this point, we are always at the start of a new string because the
193 // remaining part of the previous string has already been handled.
194 const char *StrHead = nullptr;
195 for (; Cur != End; ++Cur) {
196 if (isStringChar(C: *Cur)) {
197 // Find the start of the next string.
198 if (!StrHead)
199 StrHead = Cur;
200 } else if (StrHead) {
201 // If it is not a printable character, we have reached the end of the
202 // current string. Print it if long enough.
203 if (static_cast<size_t>(Cur - StrHead) >= Min) {
204 PrintHeader(ChunkOffset + (StrHead - Begin));
205 OS << StringRef(StrHead, Cur - StrHead) << '\n';
206 }
207 StrHead = nullptr;
208 }
209 }
210
211 // The last string could span multiple chunks. If it is larger than Min,
212 // print the header immediately and set the InString flag to avoid printing
213 // it again.
214 if (StrHead) {
215 size_t Len = End - StrHead;
216 // Print it, or append it to Candidate if it is too short.
217 if (Len >= Min) {
218 PrintHeader(ChunkOffset + (StrHead - Begin));
219 OS << StringRef(StrHead, Len);
220 InString = true;
221 } else {
222 Candidate.append(in_start: StrHead, in_end: End);
223 }
224 }
225 ChunkOffset += ChunkSize;
226 }
227
228 if (InString)
229 OS << '\n';
230}
231
232int main(int argc, char **argv) {
233 InitLLVM X(argc, argv);
234 BumpPtrAllocator A;
235 StringSaver Saver(A);
236 StringsOptTable Tbl;
237 ToolName = argv[0];
238 opt::InputArgList Args =
239 Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver,
240 ErrorFn: [&](StringRef Msg) { reportCmdLineError(Message: Msg); });
241 if (Args.hasArg(Ids: OPT_help)) {
242 Tbl.printHelp(
243 OS&: outs(),
244 Usage: (Twine(ToolName) + " [options] <input object files>").str().c_str(),
245 Title: "llvm string dumper");
246 // TODO Replace this with OptTable API once it adds extrahelp support.
247 outs() << "\nPass @FILE as argument to read options from FILE.\n";
248 return 0;
249 }
250 if (Args.hasArg(Ids: OPT_version)) {
251 outs() << ToolName << '\n';
252 cl::PrintVersionMessage();
253 return 0;
254 }
255
256 parseIntArg(Args, ID: OPT_bytes_EQ, Value&: MinLength);
257 PrintFileName = Args.hasArg(Ids: OPT_print_file_name);
258 Arg *RadixArg = Args.getLastArg(Ids: OPT_radix_EQ);
259 if (!RadixArg) {
260 Radix = Radix::None;
261 } else {
262 Radix = llvm::StringSwitch<enum Radix>(RadixArg->getValue())
263 .Case(S: "o", Value: Radix::Octal)
264 .Case(S: "d", Value: Radix::Decimal)
265 .Case(S: "x", Value: Radix::Hexadecimal)
266 .Default(Value: Radix::None);
267 if (Radix == Radix::None)
268 reportCmdLineError(Message: "'" + StringRef(RadixArg->getValue()) +
269 "' is not a valid value for '" +
270 RadixArg->getSpelling() + "'");
271 }
272
273 if (MinLength == 0) {
274 errs() << "invalid minimum string length 0\n";
275 return EXIT_FAILURE;
276 }
277
278 std::vector<std::string> InputFileNames = Args.getAllArgValues(Id: OPT_INPUT);
279 if (InputFileNames.empty())
280 InputFileNames.push_back(x: "-");
281
282 for (const auto &File : InputFileNames) {
283 if (File == "-") {
284 strings(OS&: llvm::outs(), FileName: "{standard input}", Handle: sys::fs::getStdinHandle());
285 } else {
286 Expected<sys::fs::file_t> FDOrErr =
287 sys::fs::openNativeFileForRead(Name: File, Flags: sys::fs::OF_TextWithCRLF);
288 if (!FDOrErr) {
289 errs() << File
290 << ": cannot open file: " << toString(E: FDOrErr.takeError())
291 << '\n';
292 continue;
293 }
294 strings(OS&: llvm::outs(), FileName: File, Handle: *FDOrErr);
295 }
296 }
297
298 return EXIT_SUCCESS;
299}
300