Add the ability to show when variables fails to be available when debug info is valid.

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

Differential Revision: https://reviews.llvm.org/D133164
This commit is contained in:
Greg Clayton 2022-08-30 15:46:57 -07:00
parent 8e484b522b
commit 9af089f517
23 changed files with 449 additions and 36 deletions

View File

@ -102,6 +102,8 @@ public:
lldb::SBValue
GetFirstValueByName (const char* name) const;
lldb::SBError GetError();
%extend {
%nothreadallow;
std::string lldb::SBValueList::__str__ (){

View 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;

View 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

View 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,

View 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.

View 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(),

View 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")

View 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) {

View 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);
}

View 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;

View 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) {

View 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"));

View 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;
}

View 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,

View 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");
}

View 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,

View 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();
}

View 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;

View 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;

View 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();

View 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,100 @@ 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
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 -grecord-command-line'})
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)

View 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

View 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)