vsk created this revision.
vsk added reviewers: jingham, davide, labath, zturner.

This teaches lldb-test how to launch a process, set up an IRMemoryMap,
and issue memory allocations in the target process through the map. This
makes it possible to test IRMemoryMap in a targeted way.

The main motivation for testing IRMemoryMap is to uncover bugs. I've
noticed two so far. The first bug is that IRMemoryMap::Malloc performs
an adjustment on the pointer returned from Process::AllocateMemory (for
alignment purposes) which ultimately allows overlapping memory regions
to be created. The second bug is that after most of the address space on
the host side is exhausted, Malloc may return the same address multiple
times. These bugs (and hopefully more!) can be uncovered and tested for
with targeted lldb-test commands.

At an even higher level, the motivation for addressing bugs in
IRMemoryMap::Malloc is that they can lead to strange user-visible
failures (e.g, variables assume the wrong value during expression
evaluation, or the debugger crashes). See my third comment on this
swift-lldb PR for an example:

  https://github.com/apple/swift-lldb/pull/652

I hope lldb-test is the right place to test IRMemoryMap. Setting up a
gtest-style unit test proved too cumbersome (you need to recreate or
mock way too much debugger state), as did writing end-to-end tests (it's
hard to write a test that actually hits a buggy path).

With lldb-test, it's easy to read/generate the test input and parse the
test output. I'll attach a simple "fuzz" tester which generates failing test
cases with ease.


https://reviews.llvm.org/D47508

Files:
  lit/Expr/TestIRMemoryMap.test
  source/Target/Process.cpp
  tools/lldb-test/lldb-test.cpp

Index: tools/lldb-test/lldb-test.cpp
===================================================================
--- tools/lldb-test/lldb-test.cpp
+++ tools/lldb-test/lldb-test.cpp
@@ -15,20 +15,25 @@
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Module.h"
 #include "lldb/Core/Section.h"
+#include "lldb/Expression/IRMemoryMap.h"
 #include "lldb/Initialization/SystemLifetimeManager.h"
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
 #include "lldb/Symbol/ClangASTContext.h"
 #include "lldb/Symbol/ClangASTImporter.h"
 #include "lldb/Symbol/SymbolVendor.h"
 #include "lldb/Symbol/TypeList.h"
 #include "lldb/Symbol/VariableList.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/Target.h"
 #include "lldb/Utility/CleanUp.h"
 #include "lldb/Utility/DataExtractor.h"
 #include "lldb/Utility/StreamString.h"
 
+#include "llvm/ADT/IntervalMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/CommandLine.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/ManagedStatic.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/PrettyStackTrace.h"
@@ -41,11 +46,18 @@
 using namespace llvm;
 
 namespace opts {
+
+TargetSP createTarget(Debugger &Dbg, const std::string &Filename);
+std::unique_ptr<MemoryBuffer> openFile(const std::string &Filename);
+
 static cl::SubCommand BreakpointSubcommand("breakpoints",
                                            "Test breakpoint resolution");
 cl::SubCommand ModuleSubcommand("module-sections",
                                 "Display LLDB Module Information");
 cl::SubCommand SymbolsSubcommand("symbols", "Dump symbols for an object file");
+cl::SubCommand IRMemoryMapSubcommand("ir-memory-map", "Test IRMemoryMap");
+cl::opt<std::string> Log("log", cl::desc("Path to a log file"), cl::init(""),
+                         cl::sub(IRMemoryMapSubcommand));
 
 namespace breakpoint {
 static cl::opt<std::string> Target(cl::Positional, cl::desc("<target>"),
@@ -135,8 +147,47 @@
 
 static int dumpSymbols(Debugger &Dbg);
 }
+
+namespace irmemorymap {
+static cl::opt<std::string> Target(cl::Positional, cl::desc("<target>"),
+                                   cl::Required,
+                                   cl::sub(IRMemoryMapSubcommand));
+static cl::opt<std::string> CommandFile(cl::Positional,
+                                        cl::desc("<command-file>"),
+                                        cl::init("-"),
+                                        cl::sub(IRMemoryMapSubcommand));
+using AddrIntervalMap =
+      IntervalMap<addr_t, bool, 8, IntervalMapHalfOpenInfo<addr_t>>;
+bool evalMalloc(IRMemoryMap &IRMemMap, StringRef Line,
+                AddrIntervalMap &AllocatedIntervals);
+int evaluateMemoryMapCommands(Debugger &Dbg);
+} // namespace irmemorymap
+
 } // namespace opts
 
+TargetSP opts::createTarget(Debugger &Dbg, const std::string &Filename) {
+  TargetSP Target;
+  Status ST =
+      Dbg.GetTargetList().CreateTarget(Dbg, Filename, /*triple*/ "",
+                                       /*get_dependent_modules*/ false,
+                                       /*platform_options*/ nullptr, Target);
+  if (ST.Fail()) {
+    errs() << formatv("Failed to create target '{0}: {1}\n", Filename, ST);
+    exit(1);
+  }
+  return Target;
+}
+
+std::unique_ptr<MemoryBuffer> opts::openFile(const std::string &Filename) {
+  auto MB = MemoryBuffer::getFileOrSTDIN(Filename);
+  if (!MB) {
+    errs() << formatv("Could not open file '{0}: {1}\n", Filename,
+                      MB.getError().message());
+    exit(1);
+  }
+  return std::move(*MB);
+}
+
 void opts::breakpoint::dumpState(const BreakpointList &List, LinePrinter &P) {
   P.formatLine("{0} breakpoint{1}", List.GetSize(), plural(List.GetSize()));
   if (List.GetSize() > 0)
@@ -177,7 +228,7 @@
     switch (Cmd[0]) {
     case '%':
       if (Cmd.consume_front("%p") && (Cmd.empty() || !isalnum(Cmd[0]))) {
-        OS << sys::path::parent_path(CommandFile);
+        OS << sys::path::parent_path(breakpoint::CommandFile);
         break;
       }
       // fall through
@@ -192,26 +243,11 @@
 }
 
 int opts::breakpoint::evaluateBreakpoints(Debugger &Dbg) {
-  TargetSP Target;
-  Status ST =
-      Dbg.GetTargetList().CreateTarget(Dbg, breakpoint::Target, /*triple*/ "",
-                                       /*get_dependent_modules*/ false,
-                                       /*platform_options*/ nullptr, Target);
-  if (ST.Fail()) {
-    errs() << formatv("Failed to create target '{0}: {1}\n", breakpoint::Target,
-                      ST);
-    exit(1);
-  }
-
-  auto MB = MemoryBuffer::getFileOrSTDIN(CommandFile);
-  if (!MB) {
-    errs() << formatv("Could not open file '{0}: {1}\n", CommandFile,
-                      MB.getError().message());
-    exit(1);
-  }
+  TargetSP Target = opts::createTarget(Dbg, breakpoint::Target);
+  std::unique_ptr<MemoryBuffer> MB = opts::openFile(breakpoint::CommandFile);
 
   LinePrinter P(4, outs());
-  StringRef Rest = (*MB)->getBuffer();
+  StringRef Rest = MB->getBuffer();
   int HadErrors = 0;
   while (!Rest.empty()) {
     StringRef Line;
@@ -459,6 +495,112 @@
   return HadErrors;
 }
 
+bool opts::irmemorymap::evalMalloc(IRMemoryMap &IRMemMap, StringRef Line,
+                                   AddrIntervalMap &AllocatedIntervals) {
+  // ::= malloc <size> <alignment>
+  size_t Size;
+  uint8_t Alignment;
+  int Matches = sscanf(Line.data(), "malloc %lu %hhu", &Size, &Alignment);
+  if (Matches != 2)
+    return false;
+  outs() << formatv("Command: malloc(size={0}, alignment={1})\n", Size,
+                    Alignment);
+  if (!isPowerOf2_32(Alignment)) {
+    outs() << "Malloc error: alignment is not a power of 2\n";
+    exit(1);
+  }
+
+  // Issue the malloc in the target process with "-rw" permissions.
+  const uint32_t Permissions = 0x3;
+  const bool ZeroMemory = false;
+  IRMemoryMap::AllocationPolicy Policy =
+      IRMemoryMap::eAllocationPolicyProcessOnly;
+  Status ST;
+  addr_t Addr =
+      IRMemMap.Malloc(Size, Alignment, Permissions, Policy, ZeroMemory, ST);
+  if (ST.Fail()) {
+    outs() << formatv("Malloc error: {0}\n", ST);
+    return true;
+  }
+
+  // Print the result of the allocation before checking its validity.
+  outs() << format("Malloc: address = 0x%x\n", Addr);
+
+  // Check that the allocation is aligned.
+  if (!Addr || Addr % Alignment != 0) {
+    outs() << "Malloc error: zero or unaligned allocation detected\n";
+    exit(1);
+  }
+
+  // Check that the allocation does not overlap another allocation.
+  bool Overlaps = AllocatedIntervals.lookup(Addr, false);
+  if (Size && !Overlaps)
+    Overlaps = AllocatedIntervals.lookup(Addr + Size - 1, false);
+  if (Overlaps) {
+    outs() << "Malloc error: overlapping allocation detected\n";
+    exit(1);
+  }
+
+  // Insert the new allocation into the interval map.
+  if (Size)
+    AllocatedIntervals.insert(Addr, Addr + Size, true);
+
+  return true;
+}
+
+int opts::irmemorymap::evaluateMemoryMapCommands(Debugger &Dbg) {
+  // Set up a Target.
+  TargetSP Target = opts::createTarget(Dbg, irmemorymap::Target);
+
+  // Set up a Process. In order to allocate memory within a target, this
+  // process must be alive and must support JIT'ing.
+  CommandReturnObject Result;
+  Dbg.SetAsyncExecution(false);
+  CommandInterpreter &CI = Dbg.GetCommandInterpreter();
+  auto IssueCmd = [&](const char *Cmd) -> bool {
+    return CI.HandleCommand(Cmd, eLazyBoolNo, Result);
+  };
+  if (!IssueCmd("b main") || !IssueCmd("run")) {
+    outs() << formatv("Failed: {0}\n", Result.GetErrorData());
+    exit(1);
+  }
+
+  ProcessSP Process = Target->GetProcessSP();
+  if (!Process || !Process->IsAlive()) {
+    outs() << "Cannot use process to test IRMemoryMap\n";
+    exit(1);
+  }
+
+  if (!Process->CanJIT()) {
+    outs() << "Must be able to JIT to test IRMemoryMap\n";
+    exit(1);
+  }
+
+  // Set up an IRMemoryMap and associated testing state.
+  IRMemoryMap IRMemMap(Target);
+  AddrIntervalMap::Allocator AIMapAllocator;
+  AddrIntervalMap AllocatedIntervals(AIMapAllocator);
+
+  // Parse and apply commands from the command file.
+  std::unique_ptr<MemoryBuffer> MB = opts::openFile(irmemorymap::CommandFile);
+  StringRef Rest = MB->getBuffer();
+  while (!Rest.empty()) {
+    StringRef Line;
+    std::tie(Line, Rest) = Rest.split('\n');
+    Line = Line.ltrim();
+
+    if (Line.empty() || Line[0] == '#')
+      continue;
+
+    if (evalMalloc(IRMemMap, Line, AllocatedIntervals))
+      continue;
+
+    errs() << "Could not parse line: " << Line << "\n";
+    exit(1);
+  }
+  return 0;
+}
+
 int main(int argc, const char *argv[]) {
   StringRef ToolName = argv[0];
   sys::PrintStackTraceOnErrorSignal(ToolName);
@@ -474,12 +616,17 @@
 
   auto Dbg = lldb_private::Debugger::CreateInstance();
 
+  if (!opts::Log.empty())
+    Dbg->EnableLog("lldb", {"all"}, opts::Log, 0, errs());
+
   if (opts::BreakpointSubcommand)
     return opts::breakpoint::evaluateBreakpoints(*Dbg);
   if (opts::ModuleSubcommand)
     return dumpModules(*Dbg);
   if (opts::SymbolsSubcommand)
     return opts::symbols::dumpSymbols(*Dbg);
+  if (opts::IRMemoryMapSubcommand)
+    return opts::irmemorymap::evaluateMemoryMapCommands(*Dbg);
 
   WithColor::error() << "No command specified.\n";
   return 1;
Index: source/Target/Process.cpp
===================================================================
--- source/Target/Process.cpp
+++ source/Target/Process.cpp
@@ -2539,8 +2539,10 @@
 #define USE_ALLOCATE_MEMORY_CACHE 1
 addr_t Process::AllocateMemory(size_t size, uint32_t permissions,
                                Status &error) {
-  if (GetPrivateState() != eStateStopped)
+  if (GetPrivateState() != eStateStopped) {
+    error.SetErrorToGenericError();
     return LLDB_INVALID_ADDRESS;
+  }
 
 #if defined(USE_ALLOCATE_MEMORY_CACHE)
   return m_allocated_memory_cache.AllocateMemory(size, permissions, error);
Index: lit/Expr/TestIRMemoryMap.test
===================================================================
--- /dev/null
+++ lit/Expr/TestIRMemoryMap.test
@@ -0,0 +1,20 @@
+# RUN: %cxx %p/Inputs/call-function.cpp -g -o %t
+# RUN: lldb-test ir-memory-map %t %s
+
+malloc 0 1
+malloc 1 1
+
+malloc 2 1
+malloc 2 2
+malloc 2 4
+
+malloc 3 1
+malloc 3 2
+malloc 3 4
+
+malloc 128 1
+malloc 128 128
+malloc 128 2
+
+malloc 3968 1
+malloc 2048 2
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to