Skip to content

Commit 98e09ae

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 98e09ae

File tree

4 files changed

+426
-190
lines changed

4 files changed

+426
-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
@@ -1092,6 +1111,8 @@ class Module : public std::enable_shared_from_this<Module>,
10921111

10931112
private:
10941113
Module(); // Only used internally by CreateJITModule ()
1114+
1115+
void LoadObjectFile();
10951116

10961117
Module(const Module &) = delete;
10971118
const Module &operator=(const Module &) = delete;

lldb/source/Commands/CommandObjectTarget.cpp

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

3336-
case 'f':
3482+
case 'f': {
33373483
DumpFullpath(strm, &module->GetFileSpec(), width);
33383484
dump_object_name = true;
3339-
break;
3485+
3486+
ObjectFile *objfile = module->GetObjectFile();
3487+
if (objfile && objfile->GetPluginName() == "placeholder")
3488+
strm.Printf("(*)");
3489+
} break;
33403490

33413491
case 'd':
33423492
DumpDirectory(strm, &module->GetFileSpec(), width);
@@ -4205,6 +4355,9 @@ class CommandObjectTargetModules : public CommandObjectMultiword {
42054355
"target modules <sub-command> ...") {
42064356
LoadSubCommand(
42074357
"add", CommandObjectSP(new CommandObjectTargetModulesAdd(interpreter)));
4358+
LoadSubCommand(
4359+
"replace",
4360+
CommandObjectSP(new CommandObjectTargetModulesReplace(interpreter)));
42084361
LoadSubCommand("load", CommandObjectSP(new CommandObjectTargetModulesLoad(
42094362
interpreter)));
42104363
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 = ObjectFile::FindPlugin(
1203+
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)