clayborg created this revision. clayborg added reviewers: labath, JDevlieghere, jingham, aadsm, yinghuitan. Herald added a project: All. clayborg requested review of this revision. Herald added a reviewer: jdoerfert. Herald added subscribers: lldb-commits, sstefan1. Herald added a project: LLDB.
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 Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D133164 Files: 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/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: lldb/test/API/functionalities/archives/TestBSDArchives.py =================================================================== --- lldb/test/API/functionalities/archives/TestBSDArchives.py +++ lldb/test/API/functionalities/archives/TestBSDArchives.py @@ -6,10 +6,16 @@ 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 @@ 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 @@ 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_target="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) Index: lldb/test/API/functionalities/archives/Makefile =================================================================== --- lldb/test/API/functionalities/archives/Makefile +++ lldb/test/API/functionalities/archives/Makefile @@ -9,7 +9,7 @@ libfoo.a: a.o b.o $(AR) $(ARFLAGS) $@ $^ - $(RM) $^ + # $(RM) $^ # This tests whether lldb can load a thin archive libbar.a: c.o Index: lldb/test/API/commands/frame/var/TestFrameVar.py =================================================================== --- lldb/test/API/commands/frame/var/TestFrameVar.py +++ lldb/test/API/commands/frame/var/TestFrameVar.py @@ -6,8 +6,10 @@ 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,102 @@ 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.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_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 + @skipUnlessDarwin + 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'}, + env={"RC_DEBUG_OPTIONS": "1"}) + 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 = [ + '-gline-tables-only enabled, no variable information is available in debug info for this compile unit' + ] + self.check_frame_variable_errors(thread, error_strings) Index: lldb/source/Target/StackFrame.cpp =================================================================== --- lldb/source/Target/StackFrame.cpp +++ lldb/source/Target/StackFrame.cpp @@ -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 @@ 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 @@ 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 @@ } } + 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 @@ 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 @@ } const bool get_file_globals = false; - VariableList *variables = GetVariableList(get_file_globals); + VariableList *variables = GetVariableList(get_file_globals, nullptr); if (!variables) { return ValueObjectSP(); Index: lldb/source/Symbol/Variable.cpp =================================================================== --- lldb/source/Symbol/Variable.cpp +++ lldb/source/Symbol/Variable.cpp @@ -578,7 +578,8 @@ 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 @@ 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: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h =================================================================== --- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h +++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h @@ -101,6 +101,10 @@ 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 @@ 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: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp =================================================================== --- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp +++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp @@ -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 @@ // 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 @@ 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 @@ 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 @@ }); 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: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h =================================================================== --- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h +++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h @@ -163,6 +163,9 @@ 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, @@ -519,6 +522,22 @@ void InitializeFirstCodeAddress(); + /// Get the compile command with options if avaiable in the DWARF. + /// + /// Some compiler flags can be used to save the compiler command line to the + /// DWARF. On Darwin compilers, if you define RC_DEBUG_OPTIONS in the + /// environment, you can get the compiler command line saved in each compile + /// unit's debug info in the DW_AT_APPLE_flags attribute. On other systems you + /// can specify -grecord-command-line to save the command line options that + /// get appended to the DW_AT_producer. This function will look for the + /// options string and return them if they are found. + /// + /// \returns + /// Valid string with options if the DW_AT_APPLE_flags attribute is found + /// on the DW_TAG_compile_unit DIE, or if the DW_AT_producer contains + /// compiler options. + llvm::Optional<llvm::StringRef> GetCompileCommand(DWARFCompileUnit *dwarf_cu); + lldb::ModuleWP m_debug_map_module_wp; SymbolFileDWARFDebugMap *m_debug_map_symfile; Index: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp =================================================================== --- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -4121,3 +4121,47 @@ return m_index->GetIndexTime(); return {}; } + +llvm::Optional<llvm::StringRef> +SymbolFileDWARF::GetCompileCommand(DWARFCompileUnit *dwarf_cu) { + if (!dwarf_cu) + return llvm::None; + const DWARFBaseDIE cu_die = dwarf_cu->GetNonSkeletonUnit().GetUnitDIEOnly(); + if (!cu_die) + return llvm::None; + // When debug information is tuned for lldb or if we build on Darwin and have + // RC_DEBUG_OPTIONS defined in the environment when compiling, or + llvm::StringRef command; + command = cu_die.GetAttributeValueAsString(DW_AT_APPLE_flags, nullptr); + if (!command.empty()) + return command; + // Sometimes with -grecord-command-line the command line options to the + // compiler are appended to the normal compiler producer line. We check for + // the producer string and also make sure it contains a space and a '-' + // character to indicate some options might be in the producer string. + command = cu_die.GetAttributeValueAsString(DW_AT_producer, nullptr); + if (!command.empty() && command.contains(" -")) + return command; + return llvm::None; +} + +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(); + + llvm::Optional<llvm::StringRef> command = GetCompileCommand(dwarf_cu); + + if (command) { + if (command->contains(" -gline-tables-only")) + return Status("-gline-tables-only enabled, no variable information is " + "available in debug info for this compile unit"); + } + + return Status(); +} Index: lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp +++ lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp @@ -866,7 +866,7 @@ // 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 @@ // 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: lldb/source/Core/IOHandlerCursesGUI.cpp =================================================================== --- lldb/source/Core/IOHandlerCursesGUI.cpp +++ lldb/source/Core/IOHandlerCursesGUI.cpp @@ -5908,7 +5908,7 @@ 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: lldb/source/Commands/CommandObjectFrame.cpp =================================================================== --- lldb/source/Commands/CommandObjectFrame.cpp +++ lldb/source/Commands/CommandObjectFrame.cpp @@ -483,9 +483,14 @@ // 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: lldb/source/API/SBValueList.cpp =================================================================== --- lldb/source/API/SBValueList.cpp +++ lldb/source/API/SBValueList.cpp @@ -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 @@ if (this == &rhs) return *this; m_values = rhs.m_values; + m_error = rhs.m_error; return *this; } @@ -63,8 +65,13 @@ 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 @@ 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: lldb/source/API/SBFrame.cpp =================================================================== --- lldb/source/API/SBFrame.cpp +++ lldb/source/API/SBFrame.cpp @@ -602,7 +602,8 @@ &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 @@ 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: lldb/packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- lldb/packages/Python/lldbsuite/test/lldbtest.py +++ lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1411,7 +1411,9 @@ debug_info=None, architecture=None, compiler=None, - dictionary=None): + dictionary=None, + env=None, + make_target=None): """Platform specific way to build binaries.""" if not architecture and configuration.arch: architecture = configuration.arch @@ -1426,16 +1428,25 @@ module = builder_module() command = builder_module().getBuildCommand(debug_info, architecture, - compiler, dictionary, testdir, testname) + compiler, dictionary, testdir, testname, make_target) if command is None: raise Exception("Don't know how to build binary") - self.runBuildCommand(command) + self.runBuildCommand(command, env) - def runBuildCommand(self, command): + def runBuildCommand(self, command, env=None): self.trace(seven.join_for_shell(command)) try: - output = check_output(command, stderr=STDOUT, errors="replace") + if env is None: + output = check_output(command, stderr=STDOUT, errors="replace") + else: + # If we have extra environment flags needed for running a build + # command, grab the current environment and make a copy and then + # update the environment to include more variables. + build_env = os.environ.copy() + build_env.update(env) + output = check_output(command, stderr=STDOUT, env=build_env, + errors="replace") except CalledProcessError as cpe: raise build_exception.BuildError(cpe) self.trace(output) Index: lldb/packages/Python/lldbsuite/test/builders/builder.py =================================================================== --- lldb/packages/Python/lldbsuite/test/builders/builder.py +++ lldb/packages/Python/lldbsuite/test/builders/builder.py @@ -138,13 +138,14 @@ return None def getBuildCommand(self, debug_info, architecture=None, compiler=None, - dictionary=None, testdir=None, testname=None): + dictionary=None, testdir=None, testname=None, make_target=None): debug_info_args = self._getDebugInfoArgs(debug_info) if debug_info_args is None: return None - + if make_target is None: + make_target = "all" command_parts = [ - self.getMake(testdir, testname), debug_info_args, ["all"], + self.getMake(testdir, testname), debug_info_args, [make_target], self.getArchCFlags(architecture), self.getArchSpec(architecture), self.getCCSpec(compiler), self.getExtraMakeArgs(), self.getSDKRootSpec(), self.getModuleCacheSpec(), Index: lldb/include/lldb/Target/StackFrame.h =================================================================== --- lldb/include/lldb/Target/StackFrame.h +++ lldb/include/lldb/Target/StackFrame.h @@ -254,9 +254,14 @@ /// 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: lldb/include/lldb/Symbol/SymbolFile.h =================================================================== --- lldb/include/lldb/Symbol/SymbolFile.h +++ lldb/include/lldb/Symbol/SymbolFile.h @@ -222,6 +222,38 @@ 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: lldb/include/lldb/API/SBValueList.h =================================================================== --- lldb/include/lldb/API/SBValueList.h +++ lldb/include/lldb/API/SBValueList.h @@ -43,6 +43,33 @@ 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 @@ ValueListImpl &ref(); std::unique_ptr<ValueListImpl> m_opaque_up; + + void SetError(const lldb_private::Status &status); }; } // namespace lldb Index: lldb/include/lldb/API/SBError.h =================================================================== --- lldb/include/lldb/API/SBError.h +++ lldb/include/lldb/API/SBError.h @@ -64,6 +64,7 @@ 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 @@ friend class SBThread; friend class SBTrace; friend class SBValue; + friend class SBValueList; friend class SBWatchpoint; - friend class SBFile; friend class lldb_private::ScriptInterpreter; Index: lldb/bindings/interface/SBValueList.i =================================================================== --- lldb/bindings/interface/SBValueList.i +++ lldb/bindings/interface/SBValueList.i @@ -102,6 +102,8 @@ lldb::SBValue GetFirstValueByName (const char* name) const; + lldb::SBError GetError(); + %extend { %nothreadallow; std::string lldb::SBValueList::__str__ (){
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits