Skip to content

Commit d6c8504

Browse files
jeffreytan81satyajanga
authored andcommitted
Add new "target module replace" command
Summary: For dump debugging (both minidump and coredump), the dump most likely only contains the process memory without real modules so placeholder object files are created for the modules. LLDB lacks a feature to hydrate/upgrade these dummy modules into real modules. This diff bridges the gap with a new `target module replace` command. The new command would replace the place holder object file in target module with input new/real object file and refresh the symbol table/stack. The target module is located with file name matching, users can also specify the target module via "-s" option if the module names differ. Another change is placeholder modules will have `(*)` added at the end as hint. The workflow would be: * lldb -c <path_to_dump> * `image list` to see place holder modules * download fbpkg and unzip it * `target module replace <downloaded_fbpkg_module>` Test Plan: A new unit test is added. c4crasher end-to-end debugging is tested. c4crasher testing workflow is documented here: https://www.internalfb.com/intern/wiki/Coredumper/Developer_Guide/Development_Process/ Reviewers: wanyi, #lldb_team Reviewed By: wanyi Subscribers: gclayton, #lldb_team Differential Revision: https://phabricator.intern.facebook.com/D43756670
1 parent d906079 commit d6c8504

File tree

4 files changed

+424
-190
lines changed

4 files changed

+424
-190
lines changed

lldb/include/lldb/Core/Module.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,22 @@ class Module : public std::enable_shared_from_this<Module>,
547547
/// remains valid as long as the object is around.
548548
virtual ObjectFile *GetObjectFile();
549549

550+
/// Replace existing backing object file with new \param object_file.
551+
///
552+
/// The old object file being replaced will be kept alive to prevent from
553+
/// dangling symbol references.
554+
/// UUID and underlying symbol file will be reparsed during further access.
555+
/// A common use case is to replace an Placeholder object file with a real
556+
/// one during dump debugging.
557+
///
558+
/// \param[in] target
559+
/// The target to update object file load address.
560+
///
561+
/// \param[in] object_file
562+
/// The new object file spec to replace the existing one.
563+
virtual void ReplaceObjectFile(Target &target, FileSpec object_file,
564+
uint64_t object_offset);
565+
550566
/// Get the unified section list for the module. This is the section list
551567
/// created by the module's object file and any debug info and symbol files
552568
/// created by the symbol vendor.
@@ -1032,6 +1048,9 @@ class Module : public std::enable_shared_from_this<Module>,
10321048
lldb::ObjectFileSP m_objfile_sp; ///< A shared pointer to the object file
10331049
/// parser for this module as it may or may
10341050
/// not be shared with the SymbolFile
1051+
lldb::ObjectFileSP m_old_objfile_sp; /// Strong reference to keep the old
1052+
/// object file being replaced alive.
1053+
10351054
UnwindTable m_unwind_table; ///< Table of FuncUnwinders
10361055
/// objects created for this
10371056
/// Module's functions
@@ -1093,6 +1112,8 @@ class Module : public std::enable_shared_from_this<Module>,
10931112
private:
10941113
Module(); // Only used internally by CreateJITModule ()
10951114

1115+
void LoadObjectFile();
1116+
10961117
Module(const Module &) = delete;
10971118
const Module &operator=(const Module &) = delete;
10981119

lldb/source/Commands/CommandObjectTarget.cpp

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2866,6 +2866,150 @@ class CommandObjectTargetModulesAdd : public CommandObjectParsed {
28662866
}
28672867
};
28682868

2869+
class CommandObjectTargetModulesReplace : public CommandObjectParsed {
2870+
public:
2871+
CommandObjectTargetModulesReplace(CommandInterpreter &interpreter)
2872+
: CommandObjectParsed(
2873+
interpreter, "target modules replace",
2874+
"Replace module's existing object file with a new object file.",
2875+
"target modules replace [<module>]", eCommandRequiresTarget),
2876+
m_file_to_replace(LLDB_OPT_SET_1, false, "shlib", 's',
2877+
lldb::eModuleCompletion, eArgTypeShlibName,
2878+
"File name of the shared library to replace.") {
2879+
m_option_group.Append(&m_uuid_option_group, LLDB_OPT_SET_ALL,
2880+
LLDB_OPT_SET_1);
2881+
m_option_group.Append(&m_file_to_replace, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
2882+
m_option_group.Finalize();
2883+
CommandArgumentData module_arg{eArgTypePath, eArgRepeatStar};
2884+
m_arguments.push_back({module_arg});
2885+
}
2886+
2887+
~CommandObjectTargetModulesReplace() override = default;
2888+
2889+
Options *GetOptions() override { return &m_option_group; }
2890+
2891+
void
2892+
HandleArgumentCompletion(CompletionRequest &request,
2893+
OptionElementVector &opt_element_vector) override {
2894+
CommandCompletions::InvokeCommonCompletionCallbacks(
2895+
GetCommandInterpreter(), lldb::eDiskFileCompletion, request, nullptr);
2896+
}
2897+
2898+
protected:
2899+
OptionGroupOptions m_option_group;
2900+
OptionGroupUUID m_uuid_option_group;
2901+
OptionGroupFile m_file_to_replace;
2902+
2903+
void DoExecute(Args &args, CommandReturnObject &result) override {
2904+
if (args.GetArgumentCount() == 0) {
2905+
result.AppendError(
2906+
"one or more executable image paths must be specified");
2907+
return;
2908+
}
2909+
2910+
Target &target = GetTarget();
2911+
bool flush = false;
2912+
// TODO: investigate if we should only allow one module. Similar for
2913+
// CommandObjectTargetModulesAdd and CommandObjectTargetSymbolsAdd.
2914+
for (auto &entry : args.entries()) {
2915+
if (entry.ref().empty())
2916+
continue;
2917+
2918+
FileSpec file_spec(entry.ref());
2919+
if (FileSystem::Instance().Exists(file_spec)) {
2920+
ModuleSpec module_spec(file_spec);
2921+
if (m_uuid_option_group.GetOptionValue().OptionWasSet())
2922+
module_spec.GetUUID() =
2923+
m_uuid_option_group.GetOptionValue().GetCurrentValue();
2924+
if (!module_spec.GetArchitecture().IsValid())
2925+
module_spec.GetArchitecture() = target.GetArchitecture();
2926+
if (m_file_to_replace.GetOptionValue().OptionWasSet())
2927+
module_spec.GetFileSpec().SetFilename(
2928+
m_file_to_replace.GetOptionValue()
2929+
.GetCurrentValue()
2930+
.GetFilename());
2931+
2932+
ModuleList matching_modules = findMatchingModules(module_spec);
2933+
if (matching_modules.IsEmpty()) {
2934+
result.AppendErrorWithFormat("can't find matching modules for '%s'",
2935+
entry.ref().str().c_str());
2936+
return;
2937+
}
2938+
2939+
if (matching_modules.GetSize() > 1) {
2940+
result.AppendErrorWithFormat(
2941+
"multiple modules match symbol file '%s', "
2942+
"use the --uuid option to resolve the "
2943+
"ambiguity.\n",
2944+
entry.ref().str().c_str());
2945+
return;
2946+
}
2947+
2948+
assert(matching_modules.GetSize() == 1);
2949+
auto module_sp = matching_modules.GetModuleAtIndex(0);
2950+
module_sp->ReplaceObjectFile(target, file_spec, /*object_offset=*/0);
2951+
2952+
if (target.GetPreloadSymbols())
2953+
module_sp->PreloadSymbols();
2954+
2955+
flush = true;
2956+
result.SetStatus(eReturnStatusSuccessFinishResult);
2957+
} else {
2958+
std::string resolved_path = file_spec.GetPath();
2959+
if (resolved_path != entry.ref()) {
2960+
result.AppendErrorWithFormat(
2961+
"invalid module path '%s' with resolved path '%s'\n",
2962+
entry.ref().str().c_str(), resolved_path.c_str());
2963+
break;
2964+
}
2965+
result.AppendErrorWithFormat("invalid module path '%s'\n",
2966+
entry.c_str());
2967+
break;
2968+
}
2969+
}
2970+
2971+
if (flush) {
2972+
ProcessSP process = target.GetProcessSP();
2973+
if (process)
2974+
process->Flush();
2975+
}
2976+
return;
2977+
}
2978+
2979+
ModuleList findMatchingModules(const ModuleSpec &module_spec) {
2980+
Target &target = GetTarget();
2981+
ModuleList matching_modules;
2982+
lldb_private::ModuleSpecList module_specs;
2983+
if (ObjectFile::GetModuleSpecifications(module_spec.GetFileSpec(), 0, 0,
2984+
module_specs)) {
2985+
// Now extract the module spec that matches the target architecture
2986+
ModuleSpec target_arch_module_spec;
2987+
ModuleSpec arch_matched_module_spec;
2988+
target_arch_module_spec.GetArchitecture() = target.GetArchitecture();
2989+
if (module_specs.FindMatchingModuleSpec(target_arch_module_spec,
2990+
arch_matched_module_spec)) {
2991+
if (arch_matched_module_spec.GetUUID().IsValid()) {
2992+
// It has a UUID, look for this UUID in the target modules
2993+
ModuleSpec uuid_module_spec;
2994+
uuid_module_spec.GetUUID() = arch_matched_module_spec.GetUUID();
2995+
target.GetImages().FindModules(uuid_module_spec, matching_modules);
2996+
}
2997+
}
2998+
}
2999+
3000+
// Just try to match up the file by basename if we have no matches at
3001+
// this point.
3002+
if (matching_modules.IsEmpty()) {
3003+
ModuleSpec filename_only_spec;
3004+
filename_only_spec.GetFileSpec().SetFilename(
3005+
module_spec.GetFileSpec().GetFilename());
3006+
target.GetImages().FindModules(filename_only_spec, matching_modules);
3007+
}
3008+
3009+
return matching_modules;
3010+
}
3011+
};
3012+
28693013
class CommandObjectTargetModulesLoad
28703014
: public CommandObjectTargetModulesModuleAutoComplete {
28713015
public:
@@ -3333,10 +3477,14 @@ class CommandObjectTargetModulesList : public CommandObjectParsed {
33333477
DumpModuleArchitecture(strm, module, true, width);
33343478
break;
33353479

3336-
case 'f':
3480+
case 'f': {
33373481
DumpFullpath(strm, &module->GetFileSpec(), width);
33383482
dump_object_name = true;
3339-
break;
3483+
3484+
ObjectFile *objfile = module->GetObjectFile();
3485+
if (objfile && objfile->GetPluginName() == "placeholder")
3486+
strm.Printf("(*)");
3487+
} break;
33403488

33413489
case 'd':
33423490
DumpDirectory(strm, &module->GetFileSpec(), width);
@@ -4205,6 +4353,9 @@ class CommandObjectTargetModules : public CommandObjectMultiword {
42054353
"target modules <sub-command> ...") {
42064354
LoadSubCommand(
42074355
"add", CommandObjectSP(new CommandObjectTargetModulesAdd(interpreter)));
4356+
LoadSubCommand(
4357+
"replace",
4358+
CommandObjectSP(new CommandObjectTargetModulesReplace(interpreter)));
42084359
LoadSubCommand("load", CommandObjectSP(new CommandObjectTargetModulesLoad(
42094360
interpreter)));
42104361
LoadSubCommand("dump", CommandObjectSP(new CommandObjectTargetModulesDump(

lldb/source/Core/Module.cpp

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,42 +1177,69 @@ ObjectFile *Module::GetObjectFile() {
11771177
if (!m_did_load_objfile.load()) {
11781178
LLDB_SCOPED_TIMERF("Module::GetObjectFile () module = %s",
11791179
GetFileSpec().GetFilename().AsCString(""));
1180-
lldb::offset_t data_offset = 0;
1181-
lldb::offset_t file_size = 0;
1182-
1183-
if (m_data_sp)
1184-
file_size = m_data_sp->GetByteSize();
1185-
else if (m_file)
1186-
file_size = FileSystem::Instance().GetByteSize(m_file);
1187-
1188-
if (file_size > m_object_offset) {
1189-
m_did_load_objfile = true;
1190-
// FindPlugin will modify its data_sp argument. Do not let it
1191-
// modify our m_data_sp member.
1192-
auto data_sp = m_data_sp;
1193-
m_objfile_sp = ObjectFile::FindPlugin(
1194-
shared_from_this(), &m_file, m_object_offset,
1195-
file_size - m_object_offset, data_sp, data_offset);
1196-
if (m_objfile_sp) {
1197-
// Once we get the object file, update our module with the object
1198-
// file's architecture since it might differ in vendor/os if some
1199-
// parts were unknown. But since the matching arch might already be
1200-
// more specific than the generic COFF architecture, only merge in
1201-
// those values that overwrite unspecified unknown values.
1202-
m_arch.MergeFrom(m_objfile_sp->GetArchitecture());
1203-
1204-
m_unwind_table.ModuleWasUpdated();
1205-
} else {
1206-
ReportError("failed to load objfile for {0}\nDebugging will be "
1207-
"degraded for this module.",
1208-
GetFileSpec().GetPath().c_str());
1209-
}
1210-
}
1180+
LoadObjectFile();
12111181
}
12121182
}
12131183
return m_objfile_sp.get();
12141184
}
12151185

1186+
void Module::LoadObjectFile() {
1187+
lldb::offset_t data_offset = 0;
1188+
lldb::offset_t file_size = 0;
1189+
1190+
if (m_data_sp)
1191+
file_size = m_data_sp->GetByteSize();
1192+
else if (m_file)
1193+
file_size = FileSystem::Instance().GetByteSize(m_file);
1194+
1195+
if (file_size <= m_object_offset)
1196+
return;
1197+
1198+
m_did_load_objfile = true;
1199+
// FindPlugin will modify its data_sp argument. Do not let it
1200+
// modify our m_data_sp member.
1201+
auto data_sp = m_data_sp;
1202+
m_objfile_sp =
1203+
ObjectFile::FindPlugin(shared_from_this(), &m_file, m_object_offset,
1204+
file_size - m_object_offset, data_sp, data_offset);
1205+
if (m_objfile_sp) {
1206+
// Once we get the object file, update our module with the object
1207+
// file's architecture since it might differ in vendor/os if some
1208+
// parts were unknown. But since the matching arch might already be
1209+
// more specific than the generic COFF architecture, only merge in
1210+
// those values that overwrite unspecified unknown values.
1211+
m_arch.MergeFrom(m_objfile_sp->GetArchitecture());
1212+
m_unwind_table.ModuleWasUpdated();
1213+
} else {
1214+
ReportError("failed to load objfile for {0}",
1215+
GetFileSpec().GetPath().c_str());
1216+
}
1217+
}
1218+
1219+
void Module::ReplaceObjectFile(Target &target, FileSpec object_file,
1220+
uint64_t object_offset) {
1221+
m_old_objfile_sp = m_objfile_sp;
1222+
m_file = object_file;
1223+
m_object_offset = object_offset;
1224+
lldb::addr_t load_address =
1225+
GetObjectFile()->GetBaseAddress().GetLoadAddress(&target);
1226+
1227+
// Scope locking.
1228+
{
1229+
std::lock_guard<std::recursive_mutex> guard(m_mutex);
1230+
LLDB_SCOPED_TIMERF("Module::ReplaceObjectFile () module = %s",
1231+
GetFileSpec().GetFilename().AsCString(""));
1232+
LoadObjectFile();
1233+
}
1234+
1235+
bool changed = false;
1236+
SetLoadAddress(target, load_address, false, changed);
1237+
1238+
// Force reparsing UUID and symbol files.
1239+
m_did_set_uuid = false;
1240+
m_did_load_symfile = false;
1241+
}
1242+
12161243
SectionList *Module::GetSectionList() {
12171244
// Populate m_sections_up with sections from objfile.
12181245
if (!m_sections_up) {

0 commit comments

Comments
 (0)