Add the ability to show when variables fails to be available when debug info is valid.
authorGreg Clayton <gclayton@fb.com>
Tue, 30 Aug 2022 22:46:57 +0000 (15:46 -0700)
committerGreg Clayton <gclayton@fb.com>
Mon, 12 Sep 2022 20:59:05 +0000 (13:59 -0700)
Summary:
Many times when debugging variables might not be available even though a user can successfully set breakpoints and stops somewhere. Letting the user know will help users fix these kinds of issues and have a better debugging experience.

Examples of this include:
- enabling -gline-tables-only and being able to set file and line breakpoints and yet see no variables
- unable to open object file for DWARF in .o file debugging for darwin targets due to modification time mismatch or not being able to locate the N_OSO file.

This patch adds an new API to SBValueList:

  lldb::SBError lldb::SBValueList::GetError();

object so that if you request a stack frame's variables using SBValueList SBFrame::GetVariables(...), you can get an error the describes why the variables were not available.

This patch adds the ability to get an error back when requesting variables from a lldb_private::StackFrame when calling GetVariableList.

It also now shows an error in response to "frame variable" if we have debug info and are unable to get varialbes due to an error as mentioned above:

(lldb) frame variable
error: "a.o" object from the "/tmp/libfoo.a" archive: either the .o file doesn't exist in the archive or the modification time (0x63111541) of the .o file doesn't match

Reviewers: labath JDevlieghere aadsm yinghuitan jdoerfert sscalpone

Subscribers:

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

23 files changed:
lldb/bindings/interface/SBValueList.i
lldb/include/lldb/API/SBError.h
lldb/include/lldb/API/SBValueList.h
lldb/include/lldb/Symbol/SymbolFile.h
lldb/include/lldb/Target/StackFrame.h
lldb/packages/Python/lldbsuite/test/builders/builder.py
lldb/packages/Python/lldbsuite/test/lldbtest.py
lldb/source/API/SBFrame.cpp
lldb/source/API/SBValueList.cpp
lldb/source/Commands/CommandObjectFrame.cpp
lldb/source/Core/IOHandlerCursesGUI.cpp
lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.h
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
lldb/source/Symbol/Variable.cpp
lldb/source/Target/StackFrame.cpp
lldb/test/API/commands/frame/var/TestFrameVar.py
lldb/test/API/functionalities/archives/Makefile
lldb/test/API/functionalities/archives/TestBSDArchives.py

index 76fa937..32543af 100644 (file)
@@ -102,6 +102,8 @@ public:
     lldb::SBValue
     GetFirstValueByName (const char* name) const;
 
+    lldb::SBError GetError();
+
     %extend {
        %nothreadallow;
        std::string lldb::SBValueList::__str__ (){
index f8289e2..b34014a 100644 (file)
@@ -64,6 +64,7 @@ protected:
   friend class SBCommunication;
   friend class SBData;
   friend class SBDebugger;
+  friend class SBFile;
   friend class SBHostOS;
   friend class SBPlatform;
   friend class SBProcess;
@@ -73,8 +74,8 @@ protected:
   friend class SBThread;
   friend class SBTrace;
   friend class SBValue;
+  friend class SBValueList;
   friend class SBWatchpoint;
-  friend class SBFile;
 
   friend class lldb_private::ScriptInterpreter;
 
index dc8389b..a5017bc 100644 (file)
@@ -43,6 +43,33 @@ public:
 
   const lldb::SBValueList &operator=(const lldb::SBValueList &rhs);
 
+  // Get an error for why this list is empty.
+  //
+  // If this list is empty, check for an underlying error in the debug
+  // information that prevented this list from being populated. This is not
+  // meant to return an error if there is no debug information as it is ok for a
+  // value list to be empty and no error should be returned in that case. If the
+  // debug info is for an assembly file or language that doesn't have any
+  // variables, no error should be returned.
+  //
+  // This is designed as a way to let users know when they enable certain
+  // compiler options that enable debug information but provide a degraded
+  // debug information content, like -gline-tables-only, which is a compiler
+  // option that allows users to set file and line breakpoints, but users get
+  // confused when no variables show up during debugging.
+  //
+  // It is also designed to inform a user that debug information might be
+  // available if an external file, like a .dwo file, but that file doesn't
+  // exist or wasn't able to be loaded due to a mismatched ID. When debugging
+  // with fission enabled, the line tables are linked into the main executable,
+  // but if the .dwo or .dwp files are not available or have been modified,
+  // users can get confused if they can stop at a file and line breakpoint but
+  // can't see variables in this case.
+  //
+  // This error can give vital clues to the user about the cause is and allow
+  // the user to fix the issue.
+  lldb::SBError GetError();
+
 protected:
   // only useful for visualizing the pointer or comparing two SBValueLists to
   // see if they are backed by the same underlying Impl.
@@ -68,6 +95,8 @@ private:
   ValueListImpl &ref();
 
   std::unique_ptr<ValueListImpl> m_opaque_up;
+
+  void SetError(const lldb_private::Status &status);
 };
 
 } // namespace lldb
index 6d34e4c..62c2207 100644 (file)
@@ -222,6 +222,38 @@ public:
   virtual uint32_t ResolveSymbolContext(const Address &so_addr,
                                         lldb::SymbolContextItem resolve_scope,
                                         SymbolContext &sc) = 0;
+
+  /// Get an error that describes why variables might be missing for a given
+  /// symbol context.
+  ///
+  /// If there is an error in the debug information that prevents variables from
+  /// being fetched, this error will get filled in. If there is no debug
+  /// informaiton, no error should be returned. But if there is debug
+  /// information and something prevents the variables from being available a
+  /// valid error should be returned. Valid cases include:
+  /// - compiler option that removes variables (-gline-tables-only)
+  /// - missing external files
+  ///   - .dwo files in fission are not accessible or missing
+  ///   - .o files on darwin when not using dSYM files that are not accessible
+  ///     or missing
+  /// - mismatched exteral files
+  ///   - .dwo files in fission where the DWO ID doesn't match
+  ///   - .o files on darwin when modification timestamp doesn't match
+  /// - corrupted debug info
+  ///
+  /// \param[in] frame
+  ///   The stack frame to use as a basis for the context to check. The frame
+  ///   address can be used if there is not debug info due to it not being able
+  ///   to be loaded, or if there is a debug info context, like a compile unit,
+  ///   or function, it can be used to track down more information on why
+  ///   variables are missing.
+  ///
+  /// \returns
+  ///   An error specifying why there should have been debug info with variable
+  ///   information but the variables were not able to be resolved.
+  virtual Status GetFrameVariableError(StackFrame &frame) {
+    return Status();
+  }
   virtual uint32_t
   ResolveSymbolContext(const SourceLocationSpec &src_location_spec,
                        lldb::SymbolContextItem resolve_scope,
index 7c4340d..6824d91 100644 (file)
@@ -254,9 +254,14 @@ public:
   ///     that are visible to the entire compilation unit (e.g. file
   ///     static in C, globals that are homed in this CU).
   ///
+  /// \param [out] error_ptr
+  ///   If there is an error in the debug information that prevents variables
+  ///   from being fetched. \see SymbolFile::GetFrameVariableError() for full
+  ///   details.
+  ///
   /// \return
   ///     A pointer to a list of variables.
-  VariableList *GetVariableList(bool get_file_globals);
+  VariableList *GetVariableList(bool get_file_globals, Status *error_ptr);
 
   /// Retrieve the list of variables that are in scope at this StackFrame's
   /// pc.
index e260431..3cd4837 100644 (file)
@@ -138,13 +138,14 @@ class Builder:
         return None
 
     def getBuildCommand(self, debug_info, architecture=None, compiler=None,
-            dictionary=None, testdir=None, testname=None):
+            dictionary=None, testdir=None, testname=None, make_targets=None):
         debug_info_args = self._getDebugInfoArgs(debug_info)
         if debug_info_args is None:
             return None
-
+        if make_targets is None:
+            make_targets = ["all"]
         command_parts = [
-            self.getMake(testdir, testname), debug_info_args, ["all"],
+            self.getMake(testdir, testname), debug_info_args, make_targets,
             self.getArchCFlags(architecture), self.getArchSpec(architecture),
             self.getCCSpec(compiler), self.getExtraMakeArgs(),
             self.getSDKRootSpec(), self.getModuleCacheSpec(),
index 46eac45..101921f 100644 (file)
@@ -1411,7 +1411,8 @@ class Base(unittest2.TestCase):
             debug_info=None,
             architecture=None,
             compiler=None,
-            dictionary=None):
+            dictionary=None,
+            make_targets=None):
         """Platform specific way to build binaries."""
         if not architecture and configuration.arch:
             architecture = configuration.arch
@@ -1426,7 +1427,7 @@ class Base(unittest2.TestCase):
 
         module = builder_module()
         command = builder_module().getBuildCommand(debug_info, architecture,
-                compiler, dictionary, testdir, testname)
+                compiler, dictionary, testdir, testname, make_targets)
         if command is None:
             raise Exception("Don't know how to build binary")
 
index 4157c20..eb7ec3b 100644 (file)
@@ -602,7 +602,8 @@ SBValue SBFrame::FindValue(const char *name, ValueType value_type,
                 &variable_list);
           if (value_type == eValueTypeVariableGlobal) {
             const bool get_file_globals = true;
-            VariableList *frame_vars = frame->GetVariableList(get_file_globals);
+            VariableList *frame_vars = frame->GetVariableList(get_file_globals,
+                                                              nullptr);
             if (frame_vars)
               frame_vars->AppendVariablesIfUnique(variable_list);
           }
@@ -805,7 +806,10 @@ SBValueList SBFrame::GetVariables(const lldb::SBVariablesOptions &options) {
       frame = exe_ctx.GetFramePtr();
       if (frame) {
         VariableList *variable_list = nullptr;
-        variable_list = frame->GetVariableList(true);
+        Status var_error;
+        variable_list = frame->GetVariableList(true, &var_error);
+        if (var_error.Fail())
+          value_list.SetError(var_error);
         if (variable_list) {
           const size_t num_variables = variable_list->GetSize();
           if (num_variables) {
index 4b36b04..ba7e069 100644 (file)
@@ -7,11 +7,12 @@
 //===----------------------------------------------------------------------===//
 
 #include "lldb/API/SBValueList.h"
+#include "lldb/API/SBError.h"
 #include "lldb/API/SBStream.h"
 #include "lldb/API/SBValue.h"
 #include "lldb/Core/ValueObjectList.h"
 #include "lldb/Utility/Instrumentation.h"
-
+#include "lldb/Utility/Status.h"
 #include <vector>
 
 using namespace lldb;
@@ -27,6 +28,7 @@ public:
     if (this == &rhs)
       return *this;
     m_values = rhs.m_values;
+    m_error = rhs.m_error;
     return *this;
   }
 
@@ -63,8 +65,13 @@ public:
     return lldb::SBValue();
   }
 
+  const Status &GetError() const { return m_error; }
+
+  void SetError(const Status &error) { m_error = error; }
+
 private:
   std::vector<lldb::SBValue> m_values;
+  Status m_error;
 };
 
 SBValueList::SBValueList() { LLDB_INSTRUMENT_VA(this); }
@@ -193,3 +200,15 @@ ValueListImpl &SBValueList::ref() {
   CreateIfNeeded();
   return *m_opaque_up;
 }
+
+lldb::SBError SBValueList::GetError() {
+  LLDB_INSTRUMENT_VA(this);
+  SBError sb_error;
+  if (m_opaque_up)
+    sb_error.SetError(m_opaque_up->GetError());
+  return sb_error;
+}
+
+void SBValueList::SetError(const lldb_private::Status &status) {
+  ref().SetError(status);
+}
index e5d14d6..64bd2c3 100644 (file)
@@ -483,9 +483,14 @@ protected:
     // might clear the StackFrameList for the thread.  So hold onto a shared
     // pointer to the frame so it stays alive.
 
+    Status error;
     VariableList *variable_list =
-        frame->GetVariableList(m_option_variable.show_globals);
+        frame->GetVariableList(m_option_variable.show_globals, &error);
 
+    if (error.Fail() && (!variable_list || variable_list->GetSize() == 0)) {
+      result.AppendError(error.AsCString());
+
+    }
     VariableSP var_sp;
     ValueObjectSP valobj_sp;
 
index c37c810..cca7376 100644 (file)
@@ -5908,7 +5908,7 @@ public:
       if (m_frame_block != frame_block) {
         m_frame_block = frame_block;
 
-        VariableList *locals = frame->GetVariableList(true);
+        VariableList *locals = frame->GetVariableList(true, nullptr);
         if (locals) {
           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
           for (const VariableSP &local_sp : *locals) {
index d143686..1e18b91 100644 (file)
@@ -866,7 +866,7 @@ void ClangExpressionDeclMap::LookUpLldbClass(NameSearchContext &context) {
   // emit DW_AT_object_pointer
   // for C++ so it hasn't actually been tested.
 
-  VariableList *vars = frame->GetVariableList(false);
+  VariableList *vars = frame->GetVariableList(false, nullptr);
 
   lldb::VariableSP this_var = vars->FindVariable(ConstString("this"));
 
@@ -951,7 +951,7 @@ void ClangExpressionDeclMap::LookUpLldbObjCClass(NameSearchContext &context) {
   // In that case, just look up the "self" variable in the current scope
   // and use its type.
 
-  VariableList *vars = frame->GetVariableList(false);
+  VariableList *vars = frame->GetVariableList(false, nullptr);
 
   lldb::VariableSP self_var = vars->FindVariable(ConstString("self"));
 
index beeea5e..c61ae22 100644 (file)
@@ -1060,3 +1060,18 @@ DWARFUnit::FindRnglistFromIndex(uint32_t index) {
     return maybe_offset.takeError();
   return FindRnglistFromOffset(*maybe_offset);
 }
+
+
+bool DWARFUnit::HasAny(llvm::ArrayRef<dw_tag_t> tags) {
+  ExtractUnitDIEIfNeeded();
+  if (m_dwo)
+    return m_dwo->HasAny(tags);
+
+  for (const auto &die: m_die_array) {
+    for (const auto tag: tags) {
+      if (tag == die.Tag())
+        return true;
+    }
+  }
+  return false;
+}
index fa28f7d..c28fee9 100644 (file)
@@ -257,6 +257,15 @@ public:
 
   lldb_private::DWARFDataExtractor GetLocationData() const;
 
+  /// Returns true if any DIEs in the unit match any DW_TAG values in \a tags.
+  ///
+  /// \param[in] tags
+  ///   An array of dw_tag_t values to check all abbrevitions for.
+  ///
+  /// \returns
+  ///   True if any DIEs match any tag in \a tags, false otherwise.
+  bool HasAny(llvm::ArrayRef<dw_tag_t> tags);
+
 protected:
   DWARFUnit(SymbolFileDWARF &dwarf, lldb::user_id_t uid,
             const DWARFUnitHeader &header,
index bf805d4..237b3fe 100644 (file)
@@ -4121,3 +4121,27 @@ StatsDuration::Duration SymbolFileDWARF::GetDebugInfoIndexTime() {
     return m_index->GetIndexTime();
   return {};
 }
+
+Status SymbolFileDWARF::GetFrameVariableError(StackFrame &frame) {
+  std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
+  CompileUnit *cu = frame.GetSymbolContext(eSymbolContextCompUnit).comp_unit;
+  if (!cu)
+    return Status();
+
+  DWARFCompileUnit *dwarf_cu = GetDWARFCompileUnit(cu);
+  if (!dwarf_cu)
+    return Status();
+
+  // Don't return an error for assembly files as they typically don't have
+  // varaible information.
+  if (dwarf_cu->GetDWARFLanguageType() == DW_LANG_Mips_Assembler)
+    return Status();
+
+  // Check if this compile unit has any variable DIEs. If it doesn't then there
+  // is not variable information for the entire compile unit.
+  if (dwarf_cu->HasAny({DW_TAG_variable, DW_TAG_formal_parameter}))
+    return Status();
+
+  return Status("no variable information is available in debug info for this "
+                "compile unit");
+}
index 235fa7c..7bab17c 100644 (file)
@@ -163,6 +163,9 @@ public:
                                 lldb::SymbolContextItem resolve_scope,
                                 lldb_private::SymbolContext &sc) override;
 
+  lldb_private::Status
+  GetFrameVariableError(lldb_private::StackFrame &frame) override;
+
   uint32_t ResolveSymbolContext(
       const lldb_private::SourceLocationSpec &src_location_spec,
       lldb::SymbolContextItem resolve_scope,
index 7bc6aae..71f3d3e 100644 (file)
@@ -31,6 +31,8 @@
 #include "lldb/Symbol/VariableList.h"
 #include "llvm/Support/ScopedPrinter.h"
 
+#include "lldb/Target/StackFrame.h"
+
 #include "LogChannelDWARF.h"
 #include "SymbolFileDWARF.h"
 
@@ -418,12 +420,14 @@ Module *SymbolFileDWARFDebugMap::GetModuleByCompUnitInfo(
         // modification timestamp, since it will never match.
         if (comp_unit_info->oso_mod_time != llvm::sys::TimePoint<>() &&
             oso_mod_time != comp_unit_info->oso_mod_time) {
-          obj_file->GetModule()->ReportError(
-              "debug map object file '%s' has changed (actual time is "
-              "%s, debug map time is %s"
-              ") since this executable was linked, file will be ignored",
-              oso_file.GetPath().c_str(), llvm::to_string(oso_mod_time).c_str(),
-              llvm::to_string(comp_unit_info->oso_mod_time).c_str());
+          comp_unit_info->oso_load_error.SetErrorStringWithFormat(
+              "debug map object file \"%s\" changed (actual: 0x%8.8x, debug "
+              "map: 0x%8.8x) since this executable was linked, debug info "
+              "will not be loaded", oso_file.GetPath().c_str(),
+              (uint32_t)llvm::sys::toTimeT(oso_mod_time),
+              (uint32_t)llvm::sys::toTimeT(comp_unit_info->oso_mod_time));
+          obj_file->GetModule()->ReportError("%s",
+              comp_unit_info->oso_load_error.AsCString());
           return nullptr;
         }
 
@@ -432,6 +436,10 @@ Module *SymbolFileDWARFDebugMap::GetModuleByCompUnitInfo(
 
         if (!ObjectFile::SplitArchivePathWithObject(oso_path, oso_file,
                                                     oso_object, must_exist)) {
+          comp_unit_info->oso_load_error.SetErrorStringWithFormat(
+              "debug map object file \"%s\" containing debug info does not "
+              "exist, debug info will not be loaded",
+              comp_unit_info->oso_path.GetCString());
           return nullptr;
         }
       }
@@ -454,6 +462,20 @@ Module *SymbolFileDWARFDebugMap::GetModuleByCompUnitInfo(
           obj_file->GetModule(), GetCompUnitInfoIndex(comp_unit_info), oso_file,
           oso_arch, oso_object ? &oso_object : nullptr, 0,
           oso_object ? comp_unit_info->oso_mod_time : llvm::sys::TimePoint<>());
+
+      if (!comp_unit_info->oso_sp->module_sp || !comp_unit_info->oso_sp->module_sp->GetObjectFile()) {
+        if (oso_object && FileSystem::Instance().Exists(oso_file)) {
+          // If we are loading a .o file from a .a file the "oso_object" will
+          // have a valid value name and if the .a file exists, either the .o
+          // file didn't exist in the .a file or the mod time didn't match.
+          comp_unit_info->oso_load_error.SetErrorStringWithFormat(
+              "\"%s\" object from the \"%s\" archive: "
+              "either the .o file doesn't exist in the archive or the "
+              "modification time (0x%8.8x) of the .o file doesn't match",
+              oso_object.AsCString(), oso_file.GetPath().c_str(),
+              (uint32_t)llvm::sys::toTimeT(comp_unit_info->oso_mod_time));
+        }
+      }
     }
   }
   if (comp_unit_info->oso_sp)
@@ -1430,3 +1452,48 @@ ModuleList SymbolFileDWARFDebugMap::GetDebugInfoModules() {
   });
   return oso_modules;
 }
+
+Status SymbolFileDWARFDebugMap::GetFrameVariableError(StackFrame &frame) {
+  std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
+
+  // We need to make sure that our PC value from the frame matches the module
+  // for this object file since we will lookup the PC file address in the debug
+  // map below.
+  Address pc_addr = frame.GetFrameCodeAddress();
+  if (pc_addr.GetModule() == m_objfile_sp->GetModule()) {
+    Symtab *symtab = m_objfile_sp->GetSymtab();
+    if (symtab) {
+      const DebugMap::Entry *debug_map_entry =
+          m_debug_map.FindEntryThatContains(pc_addr.GetFileAddress());
+      if (debug_map_entry) {
+        Symbol *symbol =
+            symtab->SymbolAtIndex(debug_map_entry->data.GetExeSymbolIndex());
+        if (symbol) {
+          uint32_t oso_idx = 0;
+          CompileUnitInfo *comp_unit_info =
+              GetCompileUnitInfoForSymbolWithID(symbol->GetID(), &oso_idx);
+          if (comp_unit_info) {
+            Module *oso_module = GetModuleByCompUnitInfo(comp_unit_info);
+            if (oso_module) {
+              // Check the .o file's DWARF in case it has an error to display.
+              SymbolFile *oso_sym_file = oso_module->GetSymbolFile();
+              if (oso_sym_file)
+                return oso_sym_file->GetFrameVariableError(frame);
+            }
+            // If we don't have a valid OSO module here, then something went
+            // wrong as we have a symbol for the address in the debug map, but
+            // we weren't able to open the .o file. Display an appropriate
+            // error
+            if (comp_unit_info->oso_load_error.Fail())
+              return comp_unit_info->oso_load_error;
+            else
+              return Status("unable to load debug map object file \"%s\" "
+                            "exist, debug info will not be loaded",
+                            comp_unit_info->oso_path.GetCString());
+          }
+        }
+      }
+    }
+  }
+  return Status();
+}
index 10a188f..56d3d0d 100644 (file)
@@ -101,6 +101,10 @@ public:
       const lldb_private::SourceLocationSpec &src_location_spec,
       lldb::SymbolContextItem resolve_scope,
       lldb_private::SymbolContextList &sc_list) override;
+
+  lldb_private::Status
+  GetFrameVariableError(lldb_private::StackFrame &frame) override;
+
   void
   FindGlobalVariables(lldb_private::ConstString name,
                       const lldb_private::CompilerDeclContext &parent_decl_ctx,
@@ -168,6 +172,7 @@ protected:
     lldb_private::FileSpec so_file;
     lldb_private::ConstString oso_path;
     llvm::sys::TimePoint<> oso_mod_time;
+    lldb_private::Status oso_load_error;
     OSOInfoSP oso_sp;
     lldb::CompUnitSP compile_unit_sp;
     uint32_t first_symbol_index = UINT32_MAX;
index f65e73e..5e1996b 100644 (file)
@@ -578,7 +578,8 @@ static void PrivateAutoComplete(
       if (frame) {
         const bool get_file_globals = true;
 
-        VariableList *variable_list = frame->GetVariableList(get_file_globals);
+        VariableList *variable_list = frame->GetVariableList(get_file_globals,
+                                                             nullptr);
 
         if (variable_list) {
           for (const VariableSP &var_sp : *variable_list)
@@ -674,7 +675,7 @@ static void PrivateAutoComplete(
           const bool get_file_globals = true;
 
           VariableList *variable_list =
-              frame->GetVariableList(get_file_globals);
+              frame->GetVariableList(get_file_globals, nullptr);
 
           if (!variable_list)
             break;
index 8c79c9e..6ef0cc8 100644 (file)
@@ -20,6 +20,7 @@
 #include "lldb/Symbol/Function.h"
 #include "lldb/Symbol/Symbol.h"
 #include "lldb/Symbol/SymbolContextScope.h"
+#include "lldb/Symbol/SymbolFile.h"
 #include "lldb/Symbol/Type.h"
 #include "lldb/Symbol/VariableList.h"
 #include "lldb/Target/ABI.h"
@@ -420,10 +421,12 @@ StackFrame::GetSymbolContext(SymbolContextItem resolve_scope) {
   return m_sc;
 }
 
-VariableList *StackFrame::GetVariableList(bool get_file_globals) {
+VariableList *StackFrame::GetVariableList(bool get_file_globals,
+                                          Status *error_ptr) {
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
   if (m_flags.IsClear(RESOLVED_VARIABLES)) {
     m_flags.Set(RESOLVED_VARIABLES);
+    m_variable_list_sp = std::make_shared<VariableList>();
 
     Block *frame_block = GetFrameBlock();
 
@@ -431,7 +434,6 @@ VariableList *StackFrame::GetVariableList(bool get_file_globals) {
       const bool get_child_variables = true;
       const bool can_create = true;
       const bool stop_if_child_block_is_inlined_function = true;
-      m_variable_list_sp = std::make_shared<VariableList>();
       frame_block->AppendBlockVariables(can_create, get_child_variables,
                                         stop_if_child_block_is_inlined_function,
                                         [](Variable *v) { return true; },
@@ -455,6 +457,17 @@ VariableList *StackFrame::GetVariableList(bool get_file_globals) {
     }
   }
 
+  if (error_ptr && m_variable_list_sp->GetSize() == 0) {
+    // Check with the symbol file to check if there is an error for why we
+    // don't have variables that the user might need to know about.
+    GetSymbolContext(eSymbolContextEverything);
+    if (m_sc.module_sp) {
+      SymbolFile *sym_file = m_sc.module_sp->GetSymbolFile();
+      if (sym_file)
+        *error_ptr = sym_file->GetFrameVariableError(*this);
+    }
+  }
+
   return m_variable_list_sp.get();
 }
 
@@ -1147,16 +1160,16 @@ StackFrame::GetValueObjectForFrameVariable(const VariableSP &variable_sp,
                                            DynamicValueType use_dynamic) {
   ValueObjectSP valobj_sp;
   { // Scope for stack frame mutex.  We need to drop this mutex before we figure
-    // out the dynamic value.  That will require converting the StackID in the 
-    // VO back to a StackFrame, which will in turn require locking the 
-    // StackFrameList.  If we still hold the StackFrame mutex, we could suffer 
-    // lock inversion against the pattern of getting the StackFrameList and 
+    // out the dynamic value.  That will require converting the StackID in the
+    // VO back to a StackFrame, which will in turn require locking the
+    // StackFrameList.  If we still hold the StackFrame mutex, we could suffer
+    // lock inversion against the pattern of getting the StackFrameList and
     // then the stack frame, which is fairly common.
     std::lock_guard<std::recursive_mutex> guard(m_mutex);
     if (IsHistorical()) {
       return valobj_sp;
     }
-    VariableList *var_list = GetVariableList(true);
+    VariableList *var_list = GetVariableList(true, nullptr);
     if (var_list) {
       // Make sure the variable is a frame variable
       const uint32_t var_idx = var_list->FindIndexForVariable(variable_sp.get());
@@ -1698,7 +1711,7 @@ lldb::ValueObjectSP StackFrame::GuessValueForRegisterAndOffset(ConstString reg,
   }
 
   const bool get_file_globals = false;
-  VariableList *variables = GetVariableList(get_file_globals);
+  VariableList *variables = GetVariableList(get_file_globals, nullptr);
 
   if (!variables) {
     return ValueObjectSP();
index d6a1a14..4b36748 100644 (file)
@@ -6,8 +6,10 @@ Make sure the frame variable -g, -a, and -l flags work.
 
 import lldb
 import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
-
+import os
+import time
 
 class TestFrameVar(TestBase):
 
@@ -82,4 +84,101 @@ class TestFrameVar(TestBase):
         self.assertIn("g_var", output, "Globals didn't find g_var")
 
 
-
+    def check_frame_variable_errors(self, thread, error_strings):
+        command_result = lldb.SBCommandReturnObject()
+        interp = self.dbg.GetCommandInterpreter()
+        result = interp.HandleCommand("frame variable", command_result)
+        self.assertEqual(result, lldb.eReturnStatusFailed,
+                         "frame var succeeded unexpectedly")
+        command_error = command_result.GetError()
+
+        frame = thread.GetFrameAtIndex(0)
+        var_list = frame.GetVariables(True, True, False, True)
+        self.assertEqual(var_list.GetSize(), 0)
+        api_error = var_list.GetError().GetCString()
+
+        for s in error_strings:
+            self.assertIn(s, command_error)
+        for s in error_strings:
+            self.assertIn(s, api_error)
+
+
+    @skipIfRemote
+    @skipUnlessDarwin
+    def test_darwin_dwarf_missing_obj(self):
+        '''
+            Test that if we build a binary with DWARF in .o files and we remove
+            the .o file for main.cpp, that we get an appropriate error when we
+            do 'frame variable' that explains why we aren't seeing variables.
+        '''
+        self.build(debug_info="dwarf")
+        exe = self.getBuildArtifact("a.out")
+        main_obj = self.getBuildArtifact("main.o")
+        # Delete the main.o file that contains the debug info so we force an
+        # error when we run to main and try to get variables
+        os.unlink(main_obj)
+
+        # We have to set a named breakpoint because we don't have any debug info
+        # because we deleted the main.o file.
+        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, 'main')
+        error_strings = [
+            'debug map object file "',
+            'main.o" containing debug info does not exist, debug info will not be loaded'
+        ]
+        self.check_frame_variable_errors(thread, error_strings)
+
+
+    @skipIfRemote
+    @skipUnlessDarwin
+    def test_darwin_dwarf_obj_mod_time_mismatch(self):
+        '''
+            Test that if we build a binary with DWARF in .o files and we update
+            the mod time of the .o file for main.cpp, that we get an appropriate
+            error when we do 'frame variable' that explains why we aren't seeing
+            variables.
+        '''
+        self.build(debug_info="dwarf")
+        exe = self.getBuildArtifact("a.out")
+        main_obj = self.getBuildArtifact("main.o")
+
+        # Set the modification time for main.o file to the current time after
+        # sleeping for 2 seconds. This ensures the modification time will have
+        # changed and will not match the modification time in the debug map and
+        # force an error when we run to main and try to get variables
+        time.sleep(2)
+        os.utime(main_obj, None)
+
+        # We have to set a named breakpoint because we don't have any debug info
+        # because we deleted the main.o file since the mod times don't match
+        # and debug info won't be loaded
+        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, 'main')
+
+        error_strings = [
+            'debug map object file "',
+            'main.o" changed (actual: 0x',
+            ', debug map: 0x',
+            ') since this executable was linked, debug info will not be loaded'
+        ]
+        self.check_frame_variable_errors(thread, error_strings)
+
+
+    @skipIfRemote
+    @skipIfWindows # Windows can't set breakpoints by name 'main' in this case.
+    def test_gline_tables_only(self):
+        '''
+            Test that if we build a binary with "-gline-tables-only" that we can
+            set a file and line breakpoint successfully, and get an error
+            letting us know that this build option was enabled when trying to
+            read variables.
+        '''
+        self.build(dictionary={'CFLAGS_EXTRAS': '-gline-tables-only'})
+        exe = self.getBuildArtifact("a.out")
+
+        # We have to set a named breakpoint because we don't have any debug info
+        # because we deleted the main.o file since the mod times don't match
+        # and debug info won't be loaded
+        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, 'main')
+        error_strings = [
+            'no variable information is available in debug info for this compile unit'
+        ]
+        self.check_frame_variable_errors(thread, error_strings)
index 868e2d6..43bf6b2 100644 (file)
@@ -9,13 +9,11 @@ a.out: main.o libfoo.a
 
 libfoo.a: a.o b.o
        $(AR) $(ARFLAGS) $@ $^
-       $(RM) $^
 
 # This tests whether lldb can load a thin archive
 libbar.a: c.o
        $(eval LLVM_AR := $(LLVM_TOOLS_DIR)/llvm-ar)
        $(eval LLVM_ARFLAGS := -rcsDT)
        $(LLVM_AR) $(LLVM_ARFLAGS) $@ $^
-       # Note for thin archive case, we cannot remove c.o
 
 include Makefile.rules
index f0edc62..26ca97c 100644 (file)
@@ -6,10 +6,16 @@ import lldb
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
 from lldbsuite.test import lldbutil
-
+import os
+import time
 
 class BSDArchivesTestCase(TestBase):
 
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
     def setUp(self):
         # Call super's setUp().
         TestBase.setUp(self)
@@ -17,8 +23,6 @@ class BSDArchivesTestCase(TestBase):
         self.line = line_number(
             'a.c', '// Set file and line breakpoint inside a().')
 
-    # Doesn't depend on any specific debug information.
-    @no_debug_info_test
     @expectedFailureAll(
         oslist=["windows"],
         bugnumber="llvm.org/pr24527.  Makefile.rules doesn't know how to build static libs on Windows")
@@ -65,3 +69,80 @@ class BSDArchivesTestCase(TestBase):
         num_specs = module_specs.GetSize()
         self.assertEqual(num_specs, 1)
         self.assertEqual(module_specs.GetSpecAtIndex(0).GetObjectName(), "c.o")
+
+
+    def check_frame_variable_errors(self, thread, error_strings):
+        command_result = lldb.SBCommandReturnObject()
+        interp = self.dbg.GetCommandInterpreter()
+        result = interp.HandleCommand("frame variable", command_result)
+        self.assertEqual(result, lldb.eReturnStatusFailed,
+                         "frame var succeeded unexpectedly")
+        command_error = command_result.GetError()
+
+        frame = thread.GetFrameAtIndex(0)
+        var_list = frame.GetVariables(True, True, False, True)
+        self.assertEqual(var_list.GetSize(), 0)
+        api_error = var_list.GetError().GetCString()
+
+        for s in error_strings:
+            self.assertTrue(s in command_error, 'Make sure "%s" exists in the command error "%s"' % (s, command_error))
+        for s in error_strings:
+            self.assertTrue(s in api_error, 'Make sure "%s" exists in the API error "%s"' % (s, api_error))
+
+    @skipIfRemote
+    @skipUnlessDarwin
+    def test_frame_var_errors_when_archive_missing(self):
+        """
+            Break inside a() and remove libfoo.a to make sure we can't load
+            the debug information and report an appropriate error when doing
+            'frame variable'.
+        """
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        libfoo_path = self.getBuildArtifact("libfoo.a")
+        # Delete the main.o file that contains the debug info so we force an
+        # error when we run to main and try to get variables for the a()
+        # function. Since the libfoo.a is missing, the debug info won't be
+        # loaded and we should see an error when trying to read varibles.
+        os.unlink(libfoo_path)
+
+        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(
+                self, 'a', bkpt_module=exe)
+
+        error_strings = [
+            'debug map object file "',
+            'libfoo.a(a.o)" containing debug info does not exist, debug info will not be loaded'
+        ]
+        self.check_frame_variable_errors(thread, error_strings)
+
+    @skipIfRemote
+    @skipUnlessDarwin
+    def test_frame_var_errors_when_mtime_mistmatch_for_object_in_archive(self):
+        """
+            Break inside a() and modify the modification time for "a.o" within
+            libfoo.a to make sure we can't load the debug information and
+            report an appropriate error when doing 'frame variable'.
+        """
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        a_path = self.getBuildArtifact("a.o")
+
+        # Change the modification time of the a.o object file after sleeping for
+        # 2 seconds to ensure the modification time is different. The rebuild
+        # only the "libfoo.a" target. This means the modification time of the
+        # a.o within libfoo.a will not match the debug map's modification time
+        # in a.out and will cause the debug information to not be loaded and we
+        # should get an appropriate error when reading variables.
+        time.sleep(2)
+        os.utime(a_path, None)
+        self.build(make_targets=["libfoo.a"])
+
+        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(
+                self, 'a', bkpt_module=exe)
+
+        error_strings = [
+            '"a.o" object from the "',
+            'libfoo.a" archive: either the .o file doesn\'t exist in the archive or the modification time (0x',
+            ') of the .o file doesn\'t match'
+        ]
+        self.check_frame_variable_errors(thread, error_strings)