diff --git a/lldb/include/lldb/Symbol/Block.h b/lldb/include/lldb/Symbol/Block.h --- a/lldb/include/lldb/Symbol/Block.h +++ b/lldb/include/lldb/Symbol/Block.h @@ -338,6 +338,8 @@ Block *FindBlockByID(lldb::user_id_t block_id); + Block *FindInnermostBlockByOffset(const lldb::addr_t offset); + size_t GetNumRanges() const { return m_ranges.GetSize(); } bool GetRangeContainingOffset(const lldb::addr_t offset, Range &range); diff --git a/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.h b/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.h --- a/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.h +++ b/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.h @@ -20,7 +20,18 @@ class Record { public: - enum Kind { Module, Info, File, Func, Line, Public, StackCFI, StackWin }; + enum Kind { + Module, + Info, + File, + Func, + Inline, + InlineOrigin, + Line, + Public, + StackCFI, + StackWin + }; /// Attempt to guess the kind of the record present in the argument without /// doing a full parse. The returned kind will always be correct for valid @@ -89,6 +100,23 @@ } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const FileRecord &R); +class InlineOriginRecord : public Record { +public: + static llvm::Optional parse(llvm::StringRef Line); + InlineOriginRecord(size_t Number, llvm::StringRef Name) + : Record(InlineOrigin), Number(Number), Name(Name) {} + + size_t Number; + llvm::StringRef Name; +}; + +inline bool operator==(const InlineOriginRecord &L, + const InlineOriginRecord &R) { + return L.Number == R.Number && L.Name == R.Name; +} +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const InlineOriginRecord &R); + class FuncRecord : public Record { public: static llvm::Optional parse(llvm::StringRef Line); @@ -107,6 +135,26 @@ bool operator==(const FuncRecord &L, const FuncRecord &R); llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const FuncRecord &R); +class InlineRecord : public Record { +public: + static llvm::Optional parse(llvm::StringRef Line); + InlineRecord(size_t InlineNestLevel, uint32_t CallSiteLineNum, + size_t CallSiteFileNum, size_t OriginNum) + : Record(Inline), InlineNestLevel(InlineNestLevel), + CallSiteLineNum(CallSiteLineNum), CallSiteFileNum(CallSiteFileNum), + OriginNum(OriginNum) {} + + size_t InlineNestLevel; + uint32_t CallSiteLineNum; + size_t CallSiteFileNum; + size_t OriginNum; + // A vector of address range covered by this inline + std::vector> Ranges; +}; + +bool operator==(const InlineRecord &L, const InlineRecord &R); +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const InlineRecord &R); + class LineRecord : public Record { public: static llvm::Optional parse(llvm::StringRef Line); diff --git a/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.cpp b/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.cpp --- a/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.cpp +++ b/lldb/source/Plugins/ObjectFile/Breakpad/BreakpadRecords.cpp @@ -23,6 +23,8 @@ CodeID, File, Func, + Inline, + InlineOrigin, Public, Stack, CFI, @@ -41,6 +43,8 @@ .Case("CODE_ID", Token::CodeID) .Case("FILE", Token::File) .Case("FUNC", Token::Func) + .Case("INLINE", Token::Inline) + .Case("INLINE_ORIGIN", Token::InlineOrigin) .Case("PUBLIC", Token::Public) .Case("STACK", Token::Stack) .Case("CFI", Token::CFI) @@ -145,7 +149,10 @@ default: return llvm::None; } - + case Token::Inline: + return Record::Inline; + case Token::InlineOrigin: + return Record::InlineOrigin; case Token::Unknown: // Optimistically assume that any unrecognised token means this is a line // record, those don't have a special keyword and start directly with a @@ -216,9 +223,11 @@ return OS << "INFO CODE_ID " << R.ID.GetAsString(); } -llvm::Optional FileRecord::parse(llvm::StringRef Line) { - // FILE number name - if (consume(Line) != Token::File) +template +static llvm::Optional parseNumberName(llvm::StringRef Line, + Token TokenType) { + // TOKEN number name + if (consume(Line) != TokenType) return llvm::None; llvm::StringRef Str; @@ -231,7 +240,12 @@ if (Name.empty()) return llvm::None; - return FileRecord(Number, Name); + return T(Number, Name); +} + +llvm::Optional FileRecord::parse(llvm::StringRef Line) { + // FILE number name + return parseNumberName(Line, Token::File); } llvm::raw_ostream &breakpad::operator<<(llvm::raw_ostream &OS, @@ -239,6 +253,17 @@ return OS << "FILE " << R.Number << " " << R.Name; } +llvm::Optional +InlineOriginRecord::parse(llvm::StringRef Line) { + // INLINE_ORIGIN number name + return parseNumberName(Line, Token::InlineOrigin); +} + +llvm::raw_ostream &breakpad::operator<<(llvm::raw_ostream &OS, + const InlineOriginRecord &R) { + return OS << "INLINE_ORIGIN " << R.Number << " " << R.Name; +} + static bool parsePublicOrFunc(llvm::StringRef Line, bool &Multiple, lldb::addr_t &Address, lldb::addr_t *Size, lldb::addr_t &ParamSize, llvm::StringRef &Name) { @@ -299,6 +324,58 @@ R.ParamSize, R.Name); } +llvm::Optional InlineRecord::parse(llvm::StringRef Line) { + // INLINE inline_nest_level call_site_line call_site_file_num origin_num + // [address size]+ + if (consume(Line) != Token::Inline) + return llvm::None; + + llvm::SmallVector Tokens; + SplitString(Line, Tokens, " "); + if (Tokens.size() < 6 || Tokens.size() % 2 == 1) + return llvm::None; + + size_t InlineNestLevel; + uint32_t CallSiteLineNum; + size_t CallSiteFileNum; + size_t OriginNum; + if (!(to_integer(Tokens[0], InlineNestLevel) && + to_integer(Tokens[1], CallSiteLineNum) && + to_integer(Tokens[2], CallSiteFileNum) && + to_integer(Tokens[3], OriginNum))) + return llvm::None; + + InlineRecord Record = InlineRecord(InlineNestLevel, CallSiteLineNum, + CallSiteFileNum, OriginNum); + for (size_t i = 4; i < Tokens.size(); i += 2) { + lldb::addr_t Address; + if (!to_integer(Tokens[i], Address, 16)) + return llvm::None; + lldb::addr_t Size; + if (!to_integer(Tokens[i + 1].trim(), Size, 16)) + return llvm::None; + Record.Ranges.emplace_back(Address, Size); + } + return Record; +} + +bool breakpad::operator==(const InlineRecord &L, const InlineRecord &R) { + return L.InlineNestLevel == R.InlineNestLevel && + L.CallSiteLineNum == R.CallSiteLineNum && + L.CallSiteFileNum == R.CallSiteFileNum && L.OriginNum == R.OriginNum && + L.Ranges == R.Ranges; +} + +llvm::raw_ostream &breakpad::operator<<(llvm::raw_ostream &OS, + const InlineRecord &R) { + OS << llvm::formatv("INLINE {0} {1} {2} {3}", R.InlineNestLevel, + R.CallSiteLineNum, R.CallSiteFileNum, R.OriginNum); + for (const auto &range : R.Ranges) { + OS << llvm::formatv(" {0:x-} {1:x-}", range.first, range.second); + } + return OS; +} + llvm::Optional LineRecord::parse(llvm::StringRef Line) { lldb::addr_t Address; llvm::StringRef Str; @@ -490,6 +567,10 @@ return "FILE"; case Record::Func: return "FUNC"; + case Record::Inline: + return "INLINE"; + case Record::InlineOrigin: + return "INLINE_ORIGIN"; case Record::Line: return "LINE"; case Record::Public: diff --git a/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp b/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp --- a/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp +++ b/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp @@ -148,9 +148,9 @@ std::tie(line, text) = text.split('\n'); llvm::Optional next_section = Record::classify(line); - if (next_section == Record::Line) { - // Line records logically belong to the preceding Func record, so we put - // them in the same section. + if (next_section == Record::Line || next_section == Record::Inline) { + // Line/Inline records logically belong to the preceding Func record, so + // we put them in the same section. next_section = Record::Func; } if (next_section == current_section) diff --git a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h --- a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h +++ b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h @@ -81,7 +81,7 @@ return false; } - size_t ParseBlocksRecursive(Function &func) override { return 0; } + size_t ParseBlocksRecursive(Function &func) override; void FindGlobalVariables(ConstString name, const CompilerDeclContext &parent_decl_ctx, @@ -223,11 +223,13 @@ UnwindPlan::Row &row); lldb::UnwindPlanSP ParseWinUnwindPlan(const Bookmark &bookmark, const RegisterInfoResolver &resolver); + void ParseInlineOriginRecords(); using CompUnitMap = RangeDataVector; llvm::Optional> m_files; llvm::Optional m_cu_data; + llvm::Optional> m_inline_origins; using UnwindMap = RangeDataVector; struct UnwindData { diff --git a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp --- a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp +++ b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp @@ -204,6 +204,10 @@ End(*m_objfile_sp); assert(Record::classify(*It) == Record::Func); ++It; // Skip FUNC record. + // Skip INLINE records. + while (It != End && Record::classify(*It) == Record::Inline) + ++It; + if (It != End) { auto record = LineRecord::parse(*It); if (record && record->FileNum < m_files->size()) @@ -282,13 +286,88 @@ return true; } +size_t SymbolFileBreakpad::ParseBlocksRecursive(Function &func) { + std::lock_guard guard(GetModuleMutex()); + CompileUnit *comp_unit = func.GetCompileUnit(); + lldbassert(comp_unit); + ParseInlineOriginRecords(); + // A vector of current each level's parent block. For example, when parsing + // "INLINE 0 ...", the current level is 0 and its parent block is the + // funciton block at index 0. + std::vector blocks; + Block &block = func.GetBlock(false); + block.AddRange(Block::Range(0, func.GetAddressRange().GetByteSize())); + blocks.push_back(&block); + + size_t blocks_added = 0; + addr_t func_base = func.GetAddressRange().GetBaseAddress().GetOffset(); + CompUnitData &data = m_cu_data->GetEntryRef(comp_unit->GetID()).data; + LineIterator It(*m_objfile_sp, Record::Func, data.bookmark), + End(*m_objfile_sp); + ++It; // Skip the FUNC record. + size_t last_added_nest_level = 0; + while (It != End && Record::classify(*It) == Record::Inline) { + if (auto record = InlineRecord::parse(*It)) { + if (record->InlineNestLevel == 0 || + record->InlineNestLevel <= last_added_nest_level + 1) { + last_added_nest_level = record->InlineNestLevel; + BlockSP block_sp = std::make_shared(It.GetBookmark().offset); + FileSpec callsite_file; + if (record->CallSiteFileNum < m_files->size()) + callsite_file = (*m_files)[record->CallSiteFileNum]; + llvm::StringRef name; + if (record->OriginNum < m_inline_origins->size()) + name = (*m_inline_origins)[record->OriginNum]; + + Declaration callsite(callsite_file, record->CallSiteLineNum); + block_sp->SetInlinedFunctionInfo(name.str().c_str(), + /*mangled=*/nullptr, + /*decl_ptr=*/nullptr, &callsite); + for (const auto &range : record->Ranges) { + block_sp->AddRange( + Block::Range(range.first - func_base, range.second)); + } + block_sp->FinalizeRanges(); + + blocks[record->InlineNestLevel]->AddChild(block_sp); + if (record->InlineNestLevel + 1 >= blocks.size()) { + blocks.resize(blocks.size() + 1); + } + blocks[record->InlineNestLevel + 1] = block_sp.get(); + ++blocks_added; + } + } + ++It; + } + return blocks_added; +} + +void SymbolFileBreakpad::ParseInlineOriginRecords() { + if (m_inline_origins) + return; + m_inline_origins.emplace(); + + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_SYMBOLS); + for (llvm::StringRef line : lines(Record::InlineOrigin)) { + auto record = InlineOriginRecord::parse(line); + if (!record) { + LLDB_LOG(log, "Failed to parse: {0}. Skipping record.", line); + continue; + } + + if (record->Number >= m_inline_origins->size()) + m_inline_origins->resize(record->Number + 1); + (*m_inline_origins)[record->Number] = record->Name; + } +} + uint32_t SymbolFileBreakpad::ResolveSymbolContext(const Address &so_addr, SymbolContextItem resolve_scope, SymbolContext &sc) { std::lock_guard guard(GetModuleMutex()); if (!(resolve_scope & (eSymbolContextCompUnit | eSymbolContextLineEntry | - eSymbolContextFunction))) + eSymbolContextFunction | eSymbolContextBlock))) return 0; ParseCUData(); @@ -305,11 +384,20 @@ result |= eSymbolContextLineEntry; } } - if (resolve_scope & eSymbolContextFunction) { + + if (resolve_scope & (eSymbolContextFunction | eSymbolContextBlock)) { FunctionSP func_sp = GetOrCreateFunction(*sc.comp_unit); if (func_sp) { sc.function = func_sp.get(); result |= eSymbolContextFunction; + if (resolve_scope & eSymbolContextBlock) { + Block &block = func_sp->GetBlock(true); + sc.block = block.FindInnermostBlockByOffset( + so_addr.GetFileAddress() - + sc.function->GetAddressRange().GetBaseAddress().GetFileAddress()); + if (sc.block) + result |= eSymbolContextBlock; + } } } @@ -774,6 +862,10 @@ End(*m_objfile_sp); assert(Record::classify(*It) == Record::Func); for (++It; It != End; ++It) { + // Skip INLINE records + if (Record::classify(*It) == Record::Inline) + continue; + auto record = LineRecord::parse(*It); if (!record) break; diff --git a/lldb/source/Symbol/Block.cpp b/lldb/source/Symbol/Block.cpp --- a/lldb/source/Symbol/Block.cpp +++ b/lldb/source/Symbol/Block.cpp @@ -122,6 +122,16 @@ return matching_block; } +Block *Block::FindInnermostBlockByOffset(const lldb::addr_t offset) { + if (!Contains(offset)) + return nullptr; + for (const BlockSP &block_sp : m_children) { + if (Block *block = block_sp->FindInnermostBlockByOffset(offset)) + return block; + } + return this; +} + void Block::CalculateSymbolContext(SymbolContext *sc) { if (m_parent_scope) m_parent_scope->CalculateSymbolContext(sc); diff --git a/lldb/test/Shell/SymbolFile/Breakpad/Inputs/inline-record.syms b/lldb/test/Shell/SymbolFile/Breakpad/Inputs/inline-record.syms new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/Breakpad/Inputs/inline-record.syms @@ -0,0 +1,17 @@ +MODULE Linux x86_64 761550E08086333960A9074A9CE2895C0 a.out +INFO CODE_ID E05015768680393360A9074A9CE2895C +FILE 0 /tmp/a.c +FILE 1 /tmp/b.c +INLINE_ORIGIN 0 inlined_f1 +INLINE_ORIGIN 1 inlined_f2 +FUNC 10 10 0 f1 +INLINE 0 8 0 0 10 5 17 4 +10 10 3 0 +FUNC 20 10 0 f2 +INLINE 0 3 0 0 23 a +INLINE 1 4 1 1 23 5 +20 10 2 1 +FUNC 30 10 0 f3 +INLINE 0 3 999 0 30 5 +INLINE 0 3 0 999 35 5 +30 10 3 1 diff --git a/lldb/test/Shell/SymbolFile/Breakpad/inline-record.test b/lldb/test/Shell/SymbolFile/Breakpad/inline-record.test new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/Breakpad/inline-record.test @@ -0,0 +1,56 @@ +# RUN: yaml2obj %S/Inputs/basic-elf.yaml -o %T/inline-record.out +# RUN: %lldb %T/inline-record.out -o "target symbols add -s inline-record.out %S/Inputs/inline-record.syms" \ +# RUN: -s %s | FileCheck --match-full-lines %s + +# CHECK-LABEL: (lldb) image lookup -a 0x400010 -v +# CHECK: Summary: inline-record.out`f1 [inlined] inlined_f1 at a.c:3 +# CHECK-NEXT: inline-record.out`f1 at a.c:8 +# CHECK: Function: id = {0x00000000}, name = "f1", range = [0x0000000000400010-0x0000000000400020) +# CHECK-NEXT: Blocks: id = {0x00000000}, range = [0x00400010-0x00400020) +# CHECK-NEXT: id = {0x00000010}, ranges = [0x00400010-0x00400015)[0x00400017-0x0040001b), name = "inlined_f1" + +# CHECK-LABEL: (lldb) image lookup -a 0x400016 -v +# CHECK: Summary: inline-record.out`f1 + 6 at a.c:3 +# CHECK-NOT: inline-record.out`f1 +# CHECK: Function: id = {0x00000000}, name = "f1", range = [0x0000000000400010-0x0000000000400020) +# CHECK-NEXT: Blocks: id = {0x00000000}, range = [0x00400010-0x00400020) + +# CHECK-LABEL: (lldb) image lookup -a 0x400023 -v +# CHECK: Summary: inline-record.out`f2 + 3 [inlined] inlined_f2 at b.c:2 +# CHECK-NEXT: inline-record.out`f2 + 3 [inlined] inlined_f1 at b.c:4 +# CHECK-NEXT: inline-record.out`f2 + 3 at a.c:3 +# CHECK: Function: id = {0x00000001}, name = "f2", range = [0x0000000000400020-0x0000000000400030) +# CHECK-NEXT: Blocks: id = {0x00000001}, range = [0x00400020-0x00400030) +# CHECK-NEXT: id = {0x00000043}, range = [0x00400023-0x0040002d), name = "inlined_f1" +# CHECK-NEXT: id = {0x00000057}, range = [0x00400023-0x00400028), name = "inlined_f2" + +# CHECK-LABEL: (lldb) image lookup -a 0x400029 -v +# CHECK: Summary: inline-record.out`f2 + 9 [inlined] inlined_f1 + 6 at b.c:2 +# CHECK-NEXT: inline-record.out`f2 + 3 at a.c:3 +# CHECK: Function: id = {0x00000001}, name = "f2", range = [0x0000000000400020-0x0000000000400030) +# CHECK-NEXT: Blocks: id = {0x00000001}, range = [0x00400020-0x00400030) +# CHECK-NEXT: id = {0x00000043}, range = [0x00400023-0x0040002d), name = "inlined_f1" + +# CHECK-LABEL: (lldb) image lookup -a 0x400030 -v +# CHECK: Summary: inline-record.out`f3 [inlined] inlined_f1 at b.c:3 +# CHECK-NEXT: inline-record.out`f3 at 3 +# CHECK: Function: id = {0x00000002}, name = "f3", range = [0x0000000000400030-0x0000000000400040) +# CHECK-NEXT: Blocks: id = {0x00000002}, range = [0x00400030-0x00400040) +# CHECK-NEXT: id = {0x00000085}, range = [0x00400030-0x00400035), name = "inlined_f1" + + +# CHECK-LABEL: (lldb) image lookup -a 0x400035 -v +# CHECK: Summary: inline-record.out`f3 + 5 [inlined] at b.c:3 +# CHECK-NEXT: inline-record.out`f3 + 5 at a.c:3 +# CHECK: Function: id = {0x00000002}, name = "f3", range = [0x0000000000400030-0x0000000000400040) +# CHECK-NEXT: Blocks: id = {0x00000002}, range = [0x00400030-0x00400040) +# CHECK-NEXT: id = {0x0000009b}, range = [0x00400035-0x0040003a) + +image lookup -a 0x400010 -v +image lookup -a 0x400016 -v +image lookup -a 0x400023 -v +image lookup -a 0x400029 -v +# Folling addresses are inside INLINE records that have file index or origin index out of range. +image lookup -a 0x400030 -v +image lookup -a 0x400035 -v +exit diff --git a/lldb/unittests/ObjectFile/Breakpad/BreakpadRecordsTest.cpp b/lldb/unittests/ObjectFile/Breakpad/BreakpadRecordsTest.cpp --- a/lldb/unittests/ObjectFile/Breakpad/BreakpadRecordsTest.cpp +++ b/lldb/unittests/ObjectFile/Breakpad/BreakpadRecordsTest.cpp @@ -18,6 +18,8 @@ EXPECT_EQ(Record::Info, Record::classify("INFO")); EXPECT_EQ(Record::File, Record::classify("FILE")); EXPECT_EQ(Record::Func, Record::classify("FUNC")); + EXPECT_EQ(Record::Inline, Record::classify("INLINE")); + EXPECT_EQ(Record::InlineOrigin, Record::classify("INLINE_ORIGIN")); EXPECT_EQ(Record::Public, Record::classify("PUBLIC")); EXPECT_EQ(Record::StackCFI, Record::classify("STACK CFI")); EXPECT_EQ(Record::StackWin, Record::classify("STACK WIN")); @@ -76,6 +78,27 @@ EXPECT_EQ(llvm::None, FuncRecord::parse("FUNC")); } +TEST(InlineOriginRecord, parse) { + EXPECT_EQ(InlineOriginRecord(47, "foo"), + InlineOriginRecord::parse("INLINE_ORIGIN 47 foo")); + EXPECT_EQ(llvm::None, InlineOriginRecord::parse("INLINE_ORIGIN 47")); + EXPECT_EQ(llvm::None, InlineOriginRecord::parse("INLINE_ORIGIN")); + EXPECT_EQ(llvm::None, InlineOriginRecord::parse("")); +} + +TEST(InlineRecord, parse) { + InlineRecord record1 = InlineRecord(0, 1, 2, 3); + record1.Ranges.emplace_back(4, 5); + EXPECT_EQ(record1, InlineRecord::parse("INLINE 0 1 2 3 4 5")); + record1.Ranges.emplace_back(6, 7); + EXPECT_EQ(record1, InlineRecord::parse("INLINE 0 1 2 3 4 5 6 7")); + EXPECT_EQ(llvm::None, InlineRecord::parse("INLINE 0 1 2 3")); + EXPECT_EQ(llvm::None, InlineRecord::parse("INLINE 0 1 2 3 4 5 6")); + EXPECT_EQ(llvm::None, InlineRecord::parse("INLNIE 0")); + EXPECT_EQ(llvm::None, InlineRecord::parse("")); + EXPECT_EQ(llvm::None, InlineRecord::parse("FUNC")); +} + TEST(LineRecord, parse) { EXPECT_EQ(LineRecord(0x47, 0x74, 47, 74), LineRecord::parse("47 74 47 74")); EXPECT_EQ(llvm::None, LineRecord::parse("47 74 47"));