[Lldb-commits] [lldb] r333583 - [lldb-test] Add a testing harness for the JIT's IRMemoryMap

Vedant Kumar via lldb-commits lldb-commits at lists.llvm.org
Wed May 30 12:39:10 PDT 2018


Author: vedantk
Date: Wed May 30 12:39:10 2018
New Revision: 333583

URL: http://llvm.org/viewvc/llvm-project?rev=333583&view=rev
Log:
[lldb-test] Add a testing harness for the JIT's IRMemoryMap

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.

This has uncovered two bugs so far. The first bug is that Malloc
performs an adjustment on the pointer returned from 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 these bugs 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 add this testing harness. 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 to the Phab review. Here's an example:

```
Command: malloc(size=1024, alignment=32)
Malloc: address = 0xca000
Command: malloc(size=64, alignment=16)
Malloc: address = 0xca400
Command: malloc(size=1024, alignment=16)
Malloc: address = 0xca440
Command: malloc(size=16, alignment=8)
Malloc: address = 0xca840
Command: malloc(size=2048, alignment=16)
Malloc: address = 0xcb000
Command: malloc(size=64, alignment=32)
Malloc: address = 0xca860
Command: malloc(size=1024, alignment=16)
Malloc: address = 0xca890
Malloc error: overlapping allocation detected, previous allocation at [0xca860, 0xca8a0)
```

{F6288839}

Differential Revision: https://reviews.llvm.org/D47508

Added:
    lldb/trunk/lit/Expr/TestIRMemoryMap.test
Modified:
    lldb/trunk/source/Target/Process.cpp
    lldb/trunk/tools/lldb-test/lldb-test.cpp

Added: lldb/trunk/lit/Expr/TestIRMemoryMap.test
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/lit/Expr/TestIRMemoryMap.test?rev=333583&view=auto
==============================================================================
--- lldb/trunk/lit/Expr/TestIRMemoryMap.test (added)
+++ lldb/trunk/lit/Expr/TestIRMemoryMap.test Wed May 30 12:39:10 2018
@@ -0,0 +1,28 @@
+# 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 2
+malloc 128 4
+malloc 128 128
+
+malloc 2048 1
+malloc 2048 2
+malloc 2048 4
+
+malloc 3968 1
+malloc 3968 2
+malloc 3968 4
+
+malloc 0 1

Modified: lldb/trunk/source/Target/Process.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Target/Process.cpp?rev=333583&r1=333582&r2=333583&view=diff
==============================================================================
--- lldb/trunk/source/Target/Process.cpp (original)
+++ lldb/trunk/source/Target/Process.cpp Wed May 30 12:39:10 2018
@@ -2539,8 +2539,10 @@ Status Process::WriteObjectFile(std::vec
 #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);

Modified: lldb/trunk/tools/lldb-test/lldb-test.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/lldb-test/lldb-test.cpp?rev=333583&r1=333582&r2=333583&view=diff
==============================================================================
--- lldb/trunk/tools/lldb-test/lldb-test.cpp (original)
+++ lldb/trunk/tools/lldb-test/lldb-test.cpp Wed May 30 12:39:10 2018
@@ -15,6 +15,7 @@
 #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"
@@ -23,17 +24,22 @@
 #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"
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/WithColor.h"
+#include <cstdio>
 #include <thread>
 
 using namespace lldb;
@@ -46,6 +52,15 @@ static cl::SubCommand BreakpointSubcomma
 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));
+
+/// Create a target using the file pointed to by \p Filename, or abort.
+TargetSP createTarget(Debugger &Dbg, const std::string &Filename);
+
+/// Read \p Filename into a null-terminated buffer, or abort.
+std::unique_ptr<MemoryBuffer> openFile(const std::string &Filename);
 
 namespace breakpoint {
 static cl::opt<std::string> Target(cl::Positional, cl::desc("<target>"),
@@ -135,8 +150,49 @@ static Error dumpModule(lldb_private::Mo
 
 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 AllocationT = std::pair<addr_t, addr_t>;
+bool areAllocationsOverlapping(const AllocationT &L, const AllocationT &R);
+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 +233,7 @@ std::string opts::breakpoint::substitute
     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 +248,11 @@ std::string opts::breakpoint::substitute
 }
 
 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 +500,125 @@ static int dumpModules(Debugger &Dbg) {
   return HadErrors;
 }
 
+/// Check if two half-open intervals intersect:
+///   http://world.std.com/~swmcd/steven/tech/interval.html
+bool opts::irmemorymap::areAllocationsOverlapping(const AllocationT &L,
+                                                  const AllocationT &R) {
+  return R.first < L.second && L.first < R.second;
+}
+
+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 %zu %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() << formatv("Malloc: address = {0: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. Do so by
+  // testing each allocation which may cover the interval [Addr, EndOfRegion).
+  addr_t EndOfRegion = Addr + Size;
+  auto Probe = AllocatedIntervals.begin();
+  Probe.advanceTo(Addr); //< First interval s.t stop >= Addr.
+  AllocationT NewAllocation = {Addr, EndOfRegion};
+  if (Probe != AllocatedIntervals.end()) {
+    while (Probe.start() < EndOfRegion) {
+      AllocationT ProbeAllocation = {Probe.start(), Probe.stop()};
+      if (areAllocationsOverlapping(ProbeAllocation, NewAllocation)) {
+        outs() << "Malloc error: overlapping allocation detected"
+               << formatv(", previous allocation at [{0:x}, {1:x})\n",
+                          Probe.start(), Probe.stop());
+        exit(1);
+      }
+      ++Probe;
+    }
+  }
+
+  // Insert the new allocation into the interval map.
+  if (Size)
+    AllocatedIntervals.insert(Addr, EndOfRegion, 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() || !Process->CanJIT()) {
+    outs() << "Cannot use process 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 +634,17 @@ int main(int argc, const char *argv[]) {
 
   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;




More information about the lldb-commits mailing list