Skip to content

Commit

Permalink
Add target modules dump separate-debug-info (#66035)
Browse files Browse the repository at this point in the history
Add a new command
```
target modules dump separate-debug-info [-j] [<filename> [<filename> [...]]]
```
or
```
image dump separate-debug-info [-j] [<filename> [<filename> [...]]]
```
(since `image` is an alias for `target modules`).
This lists the separate debug info files and their current status
(loaded or not loaded) for the specified modules. This diff implements
this command for mach-O files with OSO and ELF files with dwo.
Example dwo:
```
(lldb) image dump separate-debug-info
Symbol file: /home/toyang/workspace/dwo-scratch/a.out
Type: "dwo"
Dwo ID             Err Dwo Path
------------------ --- -----------------------------------------
0x9a429da5abb6faae     /home/toyang/workspace/scratch-dwo/a-main.dwo
0xbcc129959e76ff33     /home/toyang/workspace/scratch-dwo/a-foo.dwo

(lldb) image dump separate-debug-info -j
[
  {
    "separate-debug-info-files": [
      {
        "comp_dir": "/home/toyang/workspace/dwo-scratch",
        "dwo_id": 11115620165179865774,
        "dwo_name": "a-main.dwo",
        "loaded": true,
        "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a-main.dwo"
      },
      {
        "comp_dir": "/home/toyang/workspace/dwo-scratch",
        "dwo_id": 13601198072221073203,
        "dwo_name": "a-foo.dwo",
        "loaded": true,
        "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a-foo.dwo"
      }
    ],
    "symfile": "/home/toyang/workspace/dwo-scratch/a.out",
    "type": "dwo"
  }
]
```
Example dwo with missing dwo:
```
(lldb) image dump separate-debug-info
Symbol file: /home/toyang/workspace/dwo-scratch/a.out
Type: "dwo"
Dwo ID             Err Dwo Path
------------------ --- -----------------------------------------
0x9a429da5abb6faae E   unable to locate .dwo debug file "/home/toyang/workspace/scratch-dwo/b.out-main.dwo" for skeleton DIE 0x0000000000000014
0xbcc129959e76ff33 E   unable to locate .dwo debug file "/home/toyang/workspace/scratch-dwo/b.out-foo.dwo" for skeleton DIE 0x000000000000003c

(lldb) image dump separate-debug-info -j
[
  {
    "separate-debug-info-files": [
      {
        "comp_dir": "/home/toyang/workspace/dwo-scratch",
        "dwo_id": 11115620165179865774,
        "dwo_name": "a-main.dwo",
        "error": "unable to locate .dwo debug file \"/home/toyang/workspace/dwo-scratch/a-main.dwo\" for skeleton DIE 0x0000000000000014",
        "loaded": false
      },
      {
        "comp_dir": "/home/toyang/workspace/dwo-scratch",
        "dwo_id": 13601198072221073203,
        "dwo_name": "a-foo.dwo",
        "error": "unable to locate .dwo debug file \"/home/toyang/workspace/dwo-scratch/a-foo.dwo\" for skeleton DIE 0x000000000000003c",
        "loaded": false
      }
    ],
    "symfile": "/home/toyang/workspace/dwo-scratch/a.out",
    "type": "dwo"
  }
]
```
Example output with dwp:
```
(lldb) image dump separate-debug-info
Symbol file: /home/toyang/workspace/dwo-scratch/a.out
Type: "dwo"
Dwo ID             Err Dwo Path
------------------ --- -----------------------------------------
0x9a429da5abb6faae     /home/toyang/workspace/dwo-scratch/a.out.dwp(a-main.dwo)
0xbcc129959e76ff33     /home/toyang/workspace/dwo-scratch/a.out.dwp(a-foo.dwo)
(lldb) image dump separate-debug-info -j
[
  {
    "separate-debug-info-files": [
      {
        "comp_dir": "/home/toyang/workspace/dwo-scratch",
        "dwo_id": 11115620165179865774,
        "dwo_name": "a-main.dwo",
        "loaded": true,
        "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a.out.dwp"
      },
      {
        "comp_dir": "/home/toyang/workspace/dwo-scratch",
        "dwo_id": 13601198072221073203,
        "dwo_name": "a-foo.dwo",
        "loaded": true,
        "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a.out.dwp"
      }
    ],
    "symfile": "/home/toyang/workspace/dwo-scratch/a.out",
    "type": "dwo"
  }
]
```
Example oso on my Mac:
```
(lldb) image dump separate-debug-info
Symbol file: /Users/toyang/workspace/scratch/a.out
Type: "oso"
Mod Time           Err Oso Path
------------------ --- ---------------------
0x0000000064e64868     /Users/toyang/workspace/scratch/foo.a(foo.o)
0x0000000064e64868     /Users/toyang/workspace/scratch/foo.a(main.o)

(lldb) image dump separate-debug-info -j
[
  {
    "separate-debug-info-files": [
      {
        "loaded": true,
        "oso_mod_time": 1692813416,
        "oso_path": "/Users/toyang/workspace/scratch/foo.a(foo.o)",
        "so_file": "/Users/toyang/workspace/scratch/foo.cpp"
      },
      {
        "loaded": true,
        "oso_mod_time": 1692813416,
        "oso_path": "/Users/toyang/workspace/scratch/foo.a(main.o)",
        "so_file": "/Users/toyang/workspace/scratch/main.cpp"
      }
    ],
    "symfile": "/Users/toyang/workspace/scratch/a.out",
    "type": "oso"
  }
]
```

Test Plan:
Tested on Mac OS and Linux.
```
lldb-dotest -p TestDumpDwo
lldb-dotest -p TestDumpOso
```

---------

Co-authored-by: Tom Yang <toyang@fb.com>
  • Loading branch information
zhyty and Tom Yang committed Oct 12, 2023
1 parent dd0f642 commit 64d78d8
Show file tree
Hide file tree
Showing 18 changed files with 667 additions and 5 deletions.
13 changes: 13 additions & 0 deletions lldb/include/lldb/Symbol/SymbolFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "lldb/Symbol/TypeList.h"
#include "lldb/Symbol/TypeSystem.h"
#include "lldb/Target/Statistics.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/XcodeSDK.h"
#include "lldb/lldb-private.h"
#include "llvm/ADT/DenseSet.h"
Expand Down Expand Up @@ -434,6 +435,18 @@ class SymbolFile : public PluginInterface {
virtual bool GetDebugInfoHadFrameVariableErrors() const = 0;
virtual void SetDebugInfoHadFrameVariableErrors() = 0;

/// Return true if separate debug info files are supported and this function
/// succeeded, false otherwise.
///
/// \param[out] d
/// If this function succeeded, then this will be a dictionary that
/// contains the keys "type", "symfile", and "separate-debug-info-files".
/// "type" can be used to assume the structure of each object in
/// "separate-debug-info-files".
virtual bool GetSeparateDebugInfo(StructuredData::Dictionary &d) {
return false;
};

virtual lldb::TypeSP
MakeType(lldb::user_id_t uid, ConstString name,
std::optional<uint64_t> byte_size, SymbolContextScope *context,
Expand Down
260 changes: 259 additions & 1 deletion lldb/source/Commands/CommandObjectTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/State.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/Timer.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-private-enumerations.h"
Expand All @@ -61,6 +62,7 @@
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendActions.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatAdapters.h"

Expand Down Expand Up @@ -1462,6 +1464,87 @@ static bool DumpModuleSymbolFile(Stream &strm, Module *module) {
return false;
}

static bool GetSeparateDebugInfoList(StructuredData::Array &list,
Module *module) {
if (module) {
if (SymbolFile *symbol_file = module->GetSymbolFile(/*can_create=*/true)) {
StructuredData::Dictionary d;
if (symbol_file->GetSeparateDebugInfo(d)) {
list.AddItem(
std::make_shared<StructuredData::Dictionary>(std::move(d)));
return true;
}
}
}
return false;
}

static void DumpDwoFilesTable(Stream &strm,
StructuredData::Array &dwo_listings) {
strm.PutCString("Dwo ID Err Dwo Path");
strm.EOL();
strm.PutCString(
"------------------ --- -----------------------------------------");
strm.EOL();
dwo_listings.ForEach([&strm](StructuredData::Object *dwo) {
StructuredData::Dictionary *dict = dwo->GetAsDictionary();
if (!dict)
return false;

uint64_t dwo_id;
if (dict->GetValueForKeyAsInteger("dwo_id", dwo_id))
strm.Printf("0x%16.16" PRIx64 " ", dwo_id);
else
strm.Printf("0x???????????????? ");

llvm::StringRef error;
if (dict->GetValueForKeyAsString("error", error))
strm << "E " << error;
else {
llvm::StringRef resolved_dwo_path;
if (dict->GetValueForKeyAsString("resolved_dwo_path",
resolved_dwo_path)) {
strm << " " << resolved_dwo_path;
if (resolved_dwo_path.ends_with(".dwp")) {
llvm::StringRef dwo_name;
if (dict->GetValueForKeyAsString("dwo_name", dwo_name))
strm << "(" << dwo_name << ")";
}
}
}
strm.EOL();
return true;
});
}

static void DumpOsoFilesTable(Stream &strm,
StructuredData::Array &oso_listings) {
strm.PutCString("Mod Time Err Oso Path");
strm.EOL();
strm.PutCString("------------------ --- ---------------------");
strm.EOL();
oso_listings.ForEach([&strm](StructuredData::Object *oso) {
StructuredData::Dictionary *dict = oso->GetAsDictionary();
if (!dict)
return false;

uint32_t oso_mod_time;
if (dict->GetValueForKeyAsInteger("oso_mod_time", oso_mod_time))
strm.Printf("0x%16.16" PRIx32 " ", oso_mod_time);

llvm::StringRef error;
if (dict->GetValueForKeyAsString("error", error))
strm << "E " << error;
else {
llvm::StringRef oso_path;
if (dict->GetValueForKeyAsString("oso_path", oso_path))
strm << " " << oso_path;
}
strm.EOL();
return true;
});
}

static void DumpAddress(ExecutionContextScope *exe_scope,
const Address &so_addr, bool verbose, bool all_ranges,
Stream &strm) {
Expand Down Expand Up @@ -2462,6 +2545,176 @@ class CommandObjectTargetModulesDumpLineTable
CommandOptions m_options;
};

#pragma mark CommandObjectTargetModulesDumpSeparateDebugInfoFiles
#define LLDB_OPTIONS_target_modules_dump_separate_debug_info
#include "CommandOptions.inc"

// Image debug separate debug info dumping command

class CommandObjectTargetModulesDumpSeparateDebugInfoFiles
: public CommandObjectTargetModulesModuleAutoComplete {
public:
CommandObjectTargetModulesDumpSeparateDebugInfoFiles(
CommandInterpreter &interpreter)
: CommandObjectTargetModulesModuleAutoComplete(
interpreter, "target modules dump separate-debug-info",
"List the separate debug info symbol files for one or more target "
"modules.",
nullptr, eCommandRequiresTarget) {}

~CommandObjectTargetModulesDumpSeparateDebugInfoFiles() override = default;

Options *GetOptions() override { return &m_options; }

class CommandOptions : public Options {
public:
CommandOptions() = default;

~CommandOptions() override = default;

Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
Status error;
const int short_option = m_getopt_table[option_idx].val;

switch (short_option) {
case 'j':
m_json.SetCurrentValue(true);
m_json.SetOptionWasSet();
break;

default:
llvm_unreachable("Unimplemented option");
}
return error;
}

void OptionParsingStarting(ExecutionContext *execution_context) override {
m_json.Clear();
}

llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::ArrayRef(g_target_modules_dump_separate_debug_info_options);
}

OptionValueBoolean m_json = false;
};

protected:
bool DoExecute(Args &command, CommandReturnObject &result) override {
Target &target = GetSelectedTarget();
uint32_t num_dumped = 0;

uint32_t addr_byte_size = target.GetArchitecture().GetAddressByteSize();
result.GetOutputStream().SetAddressByteSize(addr_byte_size);
result.GetErrorStream().SetAddressByteSize(addr_byte_size);

StructuredData::Array separate_debug_info_lists_by_module;
if (command.GetArgumentCount() == 0) {
// Dump all sections for all modules images
const ModuleList &target_modules = target.GetImages();
std::lock_guard<std::recursive_mutex> guard(target_modules.GetMutex());
const size_t num_modules = target_modules.GetSize();
if (num_modules == 0) {
result.AppendError("the target has no associated executable images");
return false;
}
for (ModuleSP module_sp : target_modules.ModulesNoLocking()) {
if (INTERRUPT_REQUESTED(
GetDebugger(),
"Interrupted in dumping all "
"separate debug info with {0} of {1} modules dumped",
num_dumped, num_modules))
break;

if (GetSeparateDebugInfoList(separate_debug_info_lists_by_module,
module_sp.get()))
num_dumped++;
}
} else {
// Dump specified images (by basename or fullpath)
const char *arg_cstr;
for (int arg_idx = 0;
(arg_cstr = command.GetArgumentAtIndex(arg_idx)) != nullptr;
++arg_idx) {
ModuleList module_list;
const size_t num_matches =
FindModulesByName(&target, arg_cstr, module_list, true);
if (num_matches > 0) {
for (size_t i = 0; i < num_matches; ++i) {
if (INTERRUPT_REQUESTED(GetDebugger(),
"Interrupted dumping {0} "
"of {1} requested modules",
i, num_matches))
break;
Module *module = module_list.GetModulePointerAtIndex(i);
if (GetSeparateDebugInfoList(separate_debug_info_lists_by_module,
module))
num_dumped++;
}
} else
result.AppendWarningWithFormat(
"Unable to find an image that matches '%s'.\n", arg_cstr);
}
}

if (num_dumped > 0) {
Stream &strm = result.GetOutputStream();
if (m_options.m_json) {
separate_debug_info_lists_by_module.Dump(strm,
/*pretty_print=*/true);
} else {
// List the debug info files in human readable form.
separate_debug_info_lists_by_module.ForEach(
[&result, &strm](StructuredData::Object *obj) {
if (!obj) {
return false;
}

// Each item in `separate_debug_info_lists_by_module` should be a
// valid structured data dictionary.
StructuredData::Dictionary *separate_debug_info_list =
obj->GetAsDictionary();
if (!separate_debug_info_list) {
return false;
}

llvm::StringRef type;
llvm::StringRef symfile;
StructuredData::Array *files;
assert(separate_debug_info_list->GetValueForKeyAsString("type",
type));
assert(separate_debug_info_list->GetValueForKeyAsString("symfile",
symfile));
assert(separate_debug_info_list->GetValueForKeyAsArray(

This comment has been minimized.

Copy link
@kazutakahirata

kazutakahirata Oct 12, 2023

Contributor

In release builds, I'm getting:

llvm-project/lldb/source/Commands/CommandObjectTarget.cpp:2697:42: error: variable 'files' is uninitialized when used here [-Werror,-Wuninitialized]
                DumpDwoFilesTable(strm, *files);
                                         ^~~~~

IIUC, we are relying on GetValueForKeyAsArray to initialize files when asserts are enabled. When asserts are disabled, files ends up being uninitialized.

If you need *files later, should we call GetValueForKeyAsArray outside assert?

This comment has been minimized.

Copy link
@zhyty

zhyty Oct 12, 2023

Author Contributor

Good catch, I'll fix that ASAP

This comment has been minimized.

Copy link
@metaflow

metaflow Oct 13, 2023

Contributor
"separate-debug-info-files", files));

strm << "Symbol file: " << symfile;
strm.EOL();
strm << "Type: \"" << type << "\"";
strm.EOL();
if (type == "dwo") {
DumpDwoFilesTable(strm, *files);
} else if (type == "oso") {
DumpOsoFilesTable(strm, *files);
} else {
result.AppendWarningWithFormat(
"Found unsupported debug info type '%s'.\n",
type.str().c_str());
}
return true;
});
}
result.SetStatus(eReturnStatusSuccessFinishResult);
} else {
result.AppendError("no matching executable images found");
}
return result.Succeeded();
}

CommandOptions m_options;
};

#pragma mark CommandObjectTargetModulesDump

// Dump multi-word command for target modules
Expand All @@ -2475,7 +2728,8 @@ class CommandObjectTargetModulesDump : public CommandObjectMultiword {
"Commands for dumping information about one or more target "
"modules.",
"target modules dump "
"[objfile|symtab|sections|ast|symfile|line-table|pcm-info] "
"[objfile|symtab|sections|ast|symfile|line-table|pcm-info|separate-"
"debug-info] "
"[<file1> <file2> ...]") {
LoadSubCommand("objfile",
CommandObjectSP(
Expand All @@ -2499,6 +2753,10 @@ class CommandObjectTargetModulesDump : public CommandObjectMultiword {
"pcm-info",
CommandObjectSP(
new CommandObjectTargetModulesDumpClangPCMInfo(interpreter)));
LoadSubCommand("separate-debug-info",
CommandObjectSP(
new CommandObjectTargetModulesDumpSeparateDebugInfoFiles(
interpreter)));
}

~CommandObjectTargetModulesDump() override = default;
Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Commands/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ let Command = "target modules dump symtab" in {
Desc<"Do not demangle symbol names before showing them.">;
}

let Command = "target modules dump separate debug info" in {
def tm_json : Option<"json", "j">, Group<1>,
Desc<"Output the details in JSON format.">;
}

let Command = "help" in {
def help_hide_aliases : Option<"hide-aliases", "a">,
Desc<"Hide aliases in the command list.">;
Expand Down
Loading

1 comment on commit 64d78d8

@zhyty
Copy link
Contributor Author

@zhyty zhyty commented on 64d78d8 Oct 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this hasn't been reverted already, pushing a quick fix ASAP (adding a @skipIfWindows annotation).

Please sign in to comment.