diff --git a/clang/include/clang/Frontend/FrontendActions.h b/clang/include/clang/Frontend/FrontendActions.h --- a/clang/include/clang/Frontend/FrontendActions.h +++ b/clang/include/clang/Frontend/FrontendActions.h @@ -190,6 +190,10 @@ /// Dump information about the given module file, to be used for /// basic debugging and discovery. class DumpModuleInfoAction : public ASTFrontendAction { +public: + // Allow other tools (ex lldb) to direct output for their use. + llvm::raw_ostream *OutputStream = nullptr; + protected: std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override; diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -854,8 +854,9 @@ std::error_code EC; OutFile.reset(new llvm::raw_fd_ostream(OutputFileName.str(), EC, llvm::sys::fs::OF_TextWithCRLF)); + OutputStream = OutFile.get(); } - llvm::raw_ostream &Out = OutFile.get()? *OutFile.get() : llvm::outs(); + llvm::raw_ostream &Out = OutputStream ? *OutputStream : llvm::outs(); Out << "Information for module file '" << getCurrentFile() << "':\n"; auto &FileMgr = getCompilerInstance().getFileManager(); diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -47,12 +47,17 @@ #include "lldb/Target/Thread.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/Args.h" +#include "lldb/Utility/FileSpec.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Timer.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-private-enumerations.h" +#include "clang/CodeGen/ObjectFilePCHContainerOperations.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatAdapters.h" @@ -2155,6 +2160,63 @@ } }; +class CommandObjectTargetModulesDumpClangPCMInfo : public CommandObjectParsed { +public: + CommandObjectTargetModulesDumpClangPCMInfo(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target modules dump pcm-info", + "Dump information about the given clang module (pcm).") { + // Take a single file argument. + CommandArgumentData arg{eArgTypeFilename, eArgRepeatPlain}; + m_arguments.push_back({arg}); + } + + ~CommandObjectTargetModulesDumpClangPCMInfo() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() != 1) { + result.AppendErrorWithFormat( + "'%s' takes exactly one pcm path argument.\n", m_cmd_name.c_str()); + return false; + } + + const char *pcm_path = command.GetArgumentAtIndex(0); + FileSpec file_spec(pcm_path); + if (!FileSystem::Instance().Exists(FileSpec(pcm_path))) { + result.AppendErrorWithFormat("file does not exist"); + return false; + } + + clang::CompilerInstance compiler; + + const char *clang_args[] = {"clang", pcm_path}; + compiler.setInvocation(clang::createInvocation(clang_args)); + + if (!compiler.hasInvocation()) { + result.AppendErrorWithFormat("input is not a pcm file"); + return false; + } + + auto input_kind = compiler.getFrontendOpts().DashX; + if (input_kind.getFormat() != clang::InputKind::Format::Precompiled) { + result.AppendErrorWithFormat("input is not a pcm file"); + return false; + } + + clang::DumpModuleInfoAction dump_module_info; + dump_module_info.OutputStream = &result.GetOutputStream().AsRawOstream(); + // DumpModuleInfoAction requires ObjectFilePCHContainerReader. + compiler.getPCHContainerOperations()->registerReader( + std::make_unique()); + + if (compiler.ExecuteAction(dump_module_info)) + result.SetStatus(eReturnStatusSuccessFinishResult); + + return result.Succeeded(); + } +}; + #pragma mark CommandObjectTargetModulesDumpClangAST // Clang AST dumping command @@ -2406,10 +2468,10 @@ CommandObjectTargetModulesDump(CommandInterpreter &interpreter) : CommandObjectMultiword( interpreter, "target modules dump", - "Commands for dumping information about one or " - "more target modules.", + "Commands for dumping information about one or more target " + "modules.", "target modules dump " - "[headers|symtab|sections|ast|symfile|line-table] " + "[objfile|symtab|sections|ast|symfile|line-table|pcm-info] " "[ ...]") { LoadSubCommand("objfile", CommandObjectSP( @@ -2429,6 +2491,10 @@ LoadSubCommand("line-table", CommandObjectSP(new CommandObjectTargetModulesDumpLineTable( interpreter))); + LoadSubCommand( + "pcm-info", + CommandObjectSP( + new CommandObjectTargetModulesDumpClangPCMInfo(interpreter))); } ~CommandObjectTargetModulesDump() override = default; diff --git a/lldb/test/API/commands/target/dump-pcm-info/Makefile b/lldb/test/API/commands/target/dump-pcm-info/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/target/dump-pcm-info/Makefile @@ -0,0 +1,4 @@ +OBJC_SOURCES := main.m +USE_PRIVATE_MODULE_CACHE = YES +include Makefile.rules + diff --git a/lldb/test/API/commands/target/dump-pcm-info/TestDumpPCMInfo.py b/lldb/test/API/commands/target/dump-pcm-info/TestDumpPCMInfo.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/target/dump-pcm-info/TestDumpPCMInfo.py @@ -0,0 +1,40 @@ +""" +Test 'target modules dump pcm-info'. +""" + +import os +import shutil +import glob + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestCase(TestBase): + @no_debug_info_test + @skipUnlessDarwin + def test(self): + self.build() + + lldbutil.run_to_source_breakpoint( + self, "return", lldb.SBFileSpec("main.m")) + + mod_cache = self.getBuildArtifact("private-module-cache") + if os.path.isdir(mod_cache): + shutil.rmtree(mod_cache) + + self.runCmd(f"settings set symbols.clang-modules-cache-path '{mod_cache}'") + + # Cause lldb to generate a Darwin-*.pcm + self.runCmd("p @import Darwin") + + # root//-.pcm + pcm_paths = glob.glob(os.path.join(mod_cache, '*', 'Darwin-*.pcm')) + self.assertEqual(len(pcm_paths), 1, "Expected one Darwin pcm") + pcm_path = pcm_paths[0] + + self.expect( + f"target modules dump pcm-info '{pcm_path}'", + startstr=f"Information for module file '{pcm_path}'", + substrs=["Module name: Darwin"]) diff --git a/lldb/test/API/commands/target/dump-pcm-info/main.m b/lldb/test/API/commands/target/dump-pcm-info/main.m new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/target/dump-pcm-info/main.m @@ -0,0 +1 @@ +int main() { return 0; }