blob: d07bd864f41b9b03eda7095e13c5e60c7adcb8b5 [file] [edit]
//===-- llvm-strings.cpp - Printable String dumping utility ---------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This program is a utility that works like binutils "strings", that is, it
// prints out printable strings in a binary, objdump, or archive file.
//
//===----------------------------------------------------------------------===//
#include "Opts.inc"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Object/Binary.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/WithColor.h"
#include <cctype>
#include <string>
using namespace llvm;
using namespace llvm::object;
namespace {
enum ID {
OPT_INVALID = 0, // This is not an option ID.
#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
#include "Opts.inc"
#undef OPTION
};
using namespace llvm::opt;
#define OPTTABLE_CODE
#include "Opts.inc"
class StringsOptTable : public opt::OptTable {
public:
StringsOptTable() : OptTable(optionTables()) {
setGroupedShortOptions(true);
setDashDashParsing(true);
}
};
static StringRef ToolName;
static cl::list<std::string> InputFileNames(cl::Positional,
cl::desc("<input object files>"));
static constexpr int DefaultMinLength = 4;
static int MinLength = DefaultMinLength;
static bool PrintFileName;
enum class Radix { None, Octal, Hexadecimal, Decimal };
static Radix Radix;
} // namespace
[[noreturn]] static void reportCmdLineError(const Twine &Message) {
WithColor::error(errs(), ToolName) << Message << "\n";
exit(1);
}
template <typename T>
static void parseIntArg(const opt::InputArgList &Args, int ID, T &Value) {
if (const opt::Arg *A = Args.getLastArg(ID)) {
StringRef V(A->getValue());
if (!llvm::to_integer(V, Value, 0) || Value <= 0)
reportCmdLineError("expected a positive integer, but got '" + V + "'");
}
}
static bool isStringChar(char C) { return isPrint(C) || C == '\t'; }
static void strings(raw_ostream &OS, StringRef FileName,
sys::fs::file_t Handle) {
SmallString<sys::fs::DefaultReadChunkSize> Buffer;
auto PrintHeader = [&OS, FileName](size_t StringStart) {
if (PrintFileName)
OS << FileName << ": ";
switch (Radix) {
case Radix::None:
break;
case Radix::Octal:
OS << format("%7o ", StringStart);
break;
case Radix::Hexadecimal:
OS << format("%7x ", StringStart);
break;
case Radix::Decimal:
OS << format("%7u ", StringStart);
break;
}
};
// To handle very large files without consuming excessive memory, we read the
// file in a little at a time and process it then rather than reading the
// entire file at once.
//
// A string is only buffered until it is known to be long enough to print;
// from then on it is streamed out directly, so an arbitrarily long string
// never needs an arbitrarily large buffer. Candidate therefore only ever
// holds a run that is shorter than MinLength and that was cut off by the end
// of a chunk.
const size_t Min = MinLength;
SmallString<DefaultMinLength> Candidate;
bool InString = false;
// Offset of the start of the current chunk within the file.
size_t ChunkOffset = 0;
Buffer.resize_for_overwrite(sys::fs::DefaultReadChunkSize);
while (true) {
Expected<size_t> ReadBytesOrErr = sys::fs::readNativeFile(
Handle, MutableArrayRef(Buffer.data(), Buffer.size()));
if (!ReadBytesOrErr) {
errs() << FileName << ": "
<< errorToErrorCode(ReadBytesOrErr.takeError()).message() << '\n';
return;
}
size_t ChunkSize = *ReadBytesOrErr;
if (ChunkSize == 0)
break;
// To prevent performance regression under O0, access the raw pointer
// instead of using methods provided by the standard library, which are not
// inlined under O0.
const char *const Begin = Buffer.data();
const char *const End = Begin + ChunkSize;
const char *Cur = Begin;
// Handle the remaining part from the previous chunk.
// The previous chunk can be either shorter than MinSize or part of the
// string.
// Keep the buffer size bounded. With a small Min, a long string spanning
// multiple chunks will have at most DefaultReadChunkSize bytes, since the
// buffer is printed immediately with the header (guarded by the second if).
// With a large Min, the buffer must hold at least Min bytes, since we need
// enough data to decide whether to print it.
if (InString || !Candidate.empty()) {
// Find the end of the current string.
while (Cur != End && isStringChar(*Cur))
++Cur;
size_t Len = Cur - Begin;
if (InString) {
// Print the remaining part if the previous chunk has already printed
// the header. E.g. header: aaaaa | bbbbb, where | is the chunk
// boundary.
// Output: Header: aaaaabbbbb, where bbbbb is printed in here.
OS << StringRef(Begin, Len);
} else if (Candidate.size() + Len >= Min) {
// If the header hasn't been printed yet (e.g. the previous candidate
// was smaller than Min), but we can print it now, print the header
// first, followed by the candidate from the previous chunk and the
// current string. E.g. aa | bbbbbb
// Output Header: aabbbbbb, where aabbbbbb is printed in here.
PrintHeader(ChunkOffset - Candidate.size());
OS << Candidate << StringRef(Begin, Len);
Candidate.clear();
InString = true;
} else if (Cur == End) {
// If the current chunk + previous candidate is still smaller than Min,
// append it to Candidate.
Candidate.append(Begin, End);
} else {
// If the string has terminated but is still smaller than Min, clear the
// buffer since it is too short to print.
Candidate.clear();
}
if (Cur == End) {
// Finish handling the current chunk and update ChunkOffset.
ChunkOffset += ChunkSize;
continue;
}
if (InString) {
// We haven't reached the end of the chunk, which means the string is
// terminated. Add a '\n' to start printing a new string.
OS << '\n';
InString = false;
}
}
// At this point, we are always at the start of a new string because the
// remaining part of the previous string has already been handled.
const char *StrHead = nullptr;
for (; Cur != End; ++Cur) {
if (isStringChar(*Cur)) {
// Find the start of the next string.
if (!StrHead)
StrHead = Cur;
} else if (StrHead) {
// If it is not a printable character, we have reached the end of the
// current string. Print it if long enough.
if (static_cast<size_t>(Cur - StrHead) >= Min) {
PrintHeader(ChunkOffset + (StrHead - Begin));
OS << StringRef(StrHead, Cur - StrHead) << '\n';
}
StrHead = nullptr;
}
}
// The last string could span multiple chunks. If it is larger than Min,
// print the header immediately and set the InString flag to avoid printing
// it again.
if (StrHead) {
size_t Len = End - StrHead;
// Print it, or append it to Candidate if it is too short.
if (Len >= Min) {
PrintHeader(ChunkOffset + (StrHead - Begin));
OS << StringRef(StrHead, Len);
InString = true;
} else {
Candidate.append(StrHead, End);
}
}
ChunkOffset += ChunkSize;
}
if (InString)
OS << '\n';
}
int main(int argc, char **argv) {
InitLLVM X(argc, argv);
BumpPtrAllocator A;
StringSaver Saver(A);
StringsOptTable Tbl;
ToolName = argv[0];
opt::InputArgList Args =
Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver,
[&](StringRef Msg) { reportCmdLineError(Msg); });
if (Args.hasArg(OPT_help)) {
Tbl.printHelp(
outs(),
(Twine(ToolName) + " [options] <input object files>").str().c_str(),
"llvm string dumper");
// TODO Replace this with OptTable API once it adds extrahelp support.
outs() << "\nPass @FILE as argument to read options from FILE.\n";
return 0;
}
if (Args.hasArg(OPT_version)) {
outs() << ToolName << '\n';
cl::PrintVersionMessage();
return 0;
}
parseIntArg(Args, OPT_bytes_EQ, MinLength);
PrintFileName = Args.hasArg(OPT_print_file_name);
Arg *RadixArg = Args.getLastArg(OPT_radix_EQ);
if (!RadixArg) {
Radix = Radix::None;
} else {
Radix = llvm::StringSwitch<enum Radix>(RadixArg->getValue())
.Case("o", Radix::Octal)
.Case("d", Radix::Decimal)
.Case("x", Radix::Hexadecimal)
.Default(Radix::None);
if (Radix == Radix::None)
reportCmdLineError("'" + StringRef(RadixArg->getValue()) +
"' is not a valid value for '" +
RadixArg->getSpelling() + "'");
}
if (MinLength == 0) {
errs() << "invalid minimum string length 0\n";
return EXIT_FAILURE;
}
std::vector<std::string> InputFileNames = Args.getAllArgValues(OPT_INPUT);
if (InputFileNames.empty())
InputFileNames.push_back("-");
for (const auto &File : InputFileNames) {
if (File == "-") {
strings(llvm::outs(), "{standard input}", sys::fs::getStdinHandle());
} else {
Expected<sys::fs::file_t> FDOrErr =
sys::fs::openNativeFileForRead(File, sys::fs::OF_TextWithCRLF);
if (!FDOrErr) {
errs() << File
<< ": cannot open file: " << toString(FDOrErr.takeError())
<< '\n';
continue;
}
strings(llvm::outs(), File, *FDOrErr);
}
}
return EXIT_SUCCESS;
}