| //===------ dwarf2yaml.cpp - obj2yaml conversion tool -----------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/BinaryFormat/Dwarf.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFContext.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFDebugAbbrev.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFDebugAddr.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFDebugArangeSet.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFDebugPubTable.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFDebugRangeList.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFFormValue.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFSection.h" | |
| #include "llvm/ObjectYAML/DWARFYAML.h" | |
| #include <algorithm> | |
| #include <optional> | |
| using namespace llvm; | |
| Error dumpDebugAbbrev(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| auto AbbrevSetPtr = DCtx.getDebugAbbrev(); | |
| if (AbbrevSetPtr) { | |
| uint64_t AbbrevTableID = 0; | |
| if (Error Err = AbbrevSetPtr->parse()) | |
| return Err; | |
| for (const auto &AbbrvDeclSet : *AbbrevSetPtr) { | |
| Y.DebugAbbrev.emplace_back(); | |
| Y.DebugAbbrev.back().ID = AbbrevTableID++; | |
| for (const DWARFAbbreviationDeclaration &AbbrvDecl : | |
| AbbrvDeclSet.second) { | |
| DWARFYAML::Abbrev Abbrv; | |
| Abbrv.Code = AbbrvDecl.getCode(); | |
| Abbrv.Tag = AbbrvDecl.getTag(); | |
| Abbrv.Children = AbbrvDecl.hasChildren() ? dwarf::DW_CHILDREN_yes | |
| : dwarf::DW_CHILDREN_no; | |
| Abbrv.Attributes.reserve(AbbrvDecl.getNumAttributes()); | |
| for (auto Attribute : AbbrvDecl.attributes()) { | |
| DWARFYAML::AttributeAbbrev AttAbrv; | |
| AttAbrv.Attribute = Attribute.Attr; | |
| AttAbrv.Form = Attribute.Form; | |
| if (AttAbrv.Form == dwarf::DW_FORM_implicit_const) | |
| AttAbrv.Value = Attribute.getImplicitConstValue(); | |
| Abbrv.Attributes.push_back(AttAbrv); | |
| } | |
| Y.DebugAbbrev.back().Table.push_back(std::move(Abbrv)); | |
| } | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| Error dumpDebugAddr(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| DWARFDebugAddrTable AddrTable; | |
| DWARFDataExtractor AddrData(DCtx.getDWARFObj(), | |
| DCtx.getDWARFObj().getAddrSection(), | |
| DCtx.isLittleEndian(), /*AddressSize=*/0); | |
| std::vector<DWARFYAML::AddrTableEntry> AddrTables; | |
| uint64_t Offset = 0; | |
| while (AddrData.isValidOffset(Offset)) { | |
| // We ignore any errors that don't prevent parsing the section, since we can | |
| // still represent such sections. | |
| if (Error Err = AddrTable.extractV5(AddrData, &Offset, /*CUAddrSize=*/0, | |
| consumeError)) | |
| return Err; | |
| AddrTables.emplace_back(); | |
| AddrTables.back().SegAddrPairs.reserve( | |
| AddrTable.getAddressEntries().size()); | |
| for (uint64_t Addr : AddrTable.getAddressEntries()) { | |
| // Currently, the parser doesn't support parsing an address table with non | |
| // linear addresses (segment_selector_size != 0). The segment selectors | |
| // are specified to be zero. | |
| AddrTables.back().SegAddrPairs.push_back( | |
| {/*SegmentSelector=*/0, /*Address=*/Addr}); | |
| } | |
| AddrTables.back().Format = AddrTable.getFormat(); | |
| AddrTables.back().Length = AddrTable.getLength(); | |
| AddrTables.back().Version = AddrTable.getVersion(); | |
| AddrTables.back().AddrSize = AddrTable.getAddressSize(); | |
| AddrTables.back().SegSelectorSize = AddrTable.getSegmentSelectorSize(); | |
| } | |
| Y.DebugAddr = std::move(AddrTables); | |
| return Error::success(); | |
| } | |
| Error dumpDebugStrings(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| DataExtractor StrData = DCtx.getStringExtractor(); | |
| uint64_t Offset = 0; | |
| std::vector<StringRef> DebugStr; | |
| Error Err = Error::success(); | |
| while (StrData.isValidOffset(Offset)) { | |
| const char *CStr = StrData.getCStr(&Offset, &Err); | |
| if (Err) | |
| return Err; | |
| DebugStr.push_back(CStr); | |
| } | |
| Y.DebugStrings = std::move(DebugStr); | |
| return Err; | |
| } | |
| Error dumpDebugARanges(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| DWARFDataExtractor ArangesData(DCtx.getDWARFObj().getArangesSection(), | |
| DCtx.isLittleEndian(), 0); | |
| uint64_t Offset = 0; | |
| DWARFDebugArangeSet Set; | |
| std::vector<DWARFYAML::ARange> DebugAranges; | |
| // We ignore any errors that don't prevent parsing the section, since we can | |
| // still represent such sections. These errors are recorded via the | |
| // WarningHandler parameter of Set.extract(). | |
| auto DiscardError = [](Error Err) { consumeError(std::move(Err)); }; | |
| while (ArangesData.isValidOffset(Offset)) { | |
| if (Error E = Set.extract(ArangesData, &Offset, DiscardError)) | |
| return E; | |
| DWARFYAML::ARange Range; | |
| Range.Format = Set.getHeader().Format; | |
| Range.Length = Set.getHeader().Length; | |
| Range.Version = Set.getHeader().Version; | |
| Range.CuOffset = Set.getHeader().CuOffset; | |
| Range.AddrSize = Set.getHeader().AddrSize; | |
| Range.SegSize = Set.getHeader().SegSize; | |
| Range.Descriptors.reserve(Set.descriptors().end() - | |
| Set.descriptors().begin()); | |
| for (auto Descriptor : Set.descriptors()) { | |
| DWARFYAML::ARangeDescriptor Desc; | |
| Desc.Address = Descriptor.Address; | |
| Desc.Length = Descriptor.Length; | |
| Range.Descriptors.push_back(Desc); | |
| } | |
| DebugAranges.push_back(std::move(Range)); | |
| } | |
| Y.DebugAranges = std::move(DebugAranges); | |
| return ErrorSuccess(); | |
| } | |
| Error dumpDebugRanges(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| // We are assuming all address byte sizes will be consistent across all | |
| // compile units. | |
| uint8_t AddrSize = 0; | |
| for (const auto &CU : DCtx.compile_units()) { | |
| const uint8_t CUAddrSize = CU->getAddressByteSize(); | |
| if (AddrSize == 0) | |
| AddrSize = CUAddrSize; | |
| else if (CUAddrSize != AddrSize) | |
| return createStringError(std::errc::invalid_argument, | |
| "address sizes vary in different compile units"); | |
| } | |
| DWARFDataExtractor Data(DCtx.getDWARFObj().getRangesSection().Data, | |
| DCtx.isLittleEndian(), AddrSize); | |
| uint64_t Offset = 0; | |
| DWARFDebugRangeList DwarfRanges; | |
| std::vector<DWARFYAML::Ranges> DebugRanges; | |
| while (Data.isValidOffset(Offset)) { | |
| DWARFYAML::Ranges YamlRanges; | |
| YamlRanges.Offset = Offset; | |
| YamlRanges.AddrSize = AddrSize; | |
| if (Error E = DwarfRanges.extract(Data, &Offset)) | |
| return E; | |
| YamlRanges.Entries.reserve(DwarfRanges.getEntries().size()); | |
| for (const auto &RLE : DwarfRanges.getEntries()) | |
| YamlRanges.Entries.push_back({RLE.StartAddress, RLE.EndAddress}); | |
| DebugRanges.push_back(std::move(YamlRanges)); | |
| } | |
| Y.DebugRanges = std::move(DebugRanges); | |
| return ErrorSuccess(); | |
| } | |
| static std::optional<DWARFYAML::PubSection> | |
| dumpPubSection(const DWARFContext &DCtx, const DWARFSection &Section, | |
| bool IsGNUStyle) { | |
| DWARFYAML::PubSection Y; | |
| DWARFDataExtractor PubSectionData(DCtx.getDWARFObj(), Section, | |
| DCtx.isLittleEndian(), 0); | |
| DWARFDebugPubTable Table; | |
| // We ignore any errors that don't prevent parsing the section, since we can | |
| // still represent such sections. | |
| Table.extract(PubSectionData, IsGNUStyle, | |
| [](Error Err) { consumeError(std::move(Err)); }); | |
| ArrayRef<DWARFDebugPubTable::Set> Sets = Table.getData(); | |
| if (Sets.empty()) | |
| return std::nullopt; | |
| // FIXME: Currently, obj2yaml only supports dumping the first pubtable. | |
| Y.Format = Sets[0].Format; | |
| Y.Length = Sets[0].Length; | |
| Y.Version = Sets[0].Version; | |
| Y.UnitOffset = Sets[0].Offset; | |
| Y.UnitSize = Sets[0].Size; | |
| Y.Entries.reserve(Sets[0].Entries.size()); | |
| for (const DWARFDebugPubTable::Entry &E : Sets[0].Entries) | |
| Y.Entries.push_back(DWARFYAML::PubEntry{(uint32_t)E.SecOffset, | |
| E.Descriptor.toBits(), E.Name}); | |
| return Y; | |
| } | |
| void dumpDebugPubSections(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| const DWARFObject &D = DCtx.getDWARFObj(); | |
| Y.PubNames = | |
| dumpPubSection(DCtx, D.getPubnamesSection(), /*IsGNUStyle=*/false); | |
| Y.PubTypes = | |
| dumpPubSection(DCtx, D.getPubtypesSection(), /*IsGNUStyle=*/false); | |
| // TODO: Test dumping .debug_gnu_pubnames section. | |
| Y.GNUPubNames = | |
| dumpPubSection(DCtx, D.getGnuPubnamesSection(), /*IsGNUStyle=*/true); | |
| // TODO: Test dumping .debug_gnu_pubtypes section. | |
| Y.GNUPubTypes = | |
| dumpPubSection(DCtx, D.getGnuPubtypesSection(), /*IsGNUStyle=*/true); | |
| } | |
| void dumpDebugInfo(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| Y.Units.reserve(DCtx.getNumCompileUnits()); | |
| for (const auto &CU : DCtx.compile_units()) { | |
| DWARFYAML::Unit NewUnit; | |
| NewUnit.Format = CU->getFormat(); | |
| NewUnit.Length = CU->getLength(); | |
| NewUnit.Version = CU->getVersion(); | |
| if (NewUnit.Version >= 5) | |
| NewUnit.Type = (dwarf::UnitType)CU->getUnitType(); | |
| const DWARFDebugAbbrev *DebugAbbrev = DCtx.getDebugAbbrev(); | |
| // FIXME: Ideally we would propagate this error upwards, but that would | |
| // prevent us from displaying any debug info at all. For now we just consume | |
| // the error and display everything that was parsed successfully. | |
| if (Error Err = DebugAbbrev->parse()) | |
| llvm::consumeError(std::move(Err)); | |
| NewUnit.AbbrevTableID = std::distance( | |
| DebugAbbrev->begin(), | |
| llvm::find_if( | |
| *DebugAbbrev, | |
| [&](const std::pair<uint64_t, DWARFAbbreviationDeclarationSet> &P) { | |
| return P.first == CU->getAbbreviations()->getOffset(); | |
| })); | |
| NewUnit.AbbrOffset = CU->getAbbreviations()->getOffset(); | |
| NewUnit.AddrSize = CU->getAddressByteSize(); | |
| NewUnit.Entries.reserve(CU->getNumDIEs()); | |
| for (auto DIE : CU->dies()) { | |
| DWARFYAML::Entry NewEntry; | |
| DataExtractor EntryData = CU->getDebugInfoExtractor(); | |
| uint64_t offset = DIE.getOffset(); | |
| assert(EntryData.isValidOffset(offset) && "Invalid DIE Offset"); | |
| if (!EntryData.isValidOffset(offset)) | |
| continue; | |
| NewEntry.AbbrCode = EntryData.getULEB128(&offset); | |
| auto AbbrevDecl = DIE.getAbbreviationDeclarationPtr(); | |
| if (AbbrevDecl) { | |
| // This reserve doesn't account for DW_FORM_indirect values, which would | |
| // result in more entries in NewEntry.Values than getNumAttributes() | |
| // implies. Not all binaries have these, and it'll reduce the number of | |
| // allocations in any case. | |
| NewEntry.Values.reserve(AbbrevDecl->getNumAttributes()); | |
| for (const auto &AttrSpec : AbbrevDecl->attributes()) { | |
| DWARFYAML::FormValue NewValue; | |
| NewValue.Value = 0xDEADBEEFDEADBEEF; | |
| DWARFDie DIEWrapper(CU.get(), &DIE); | |
| auto FormValue = DIEWrapper.find(AttrSpec.Attr); | |
| if (!FormValue) | |
| return; | |
| auto Form = FormValue->getForm(); | |
| bool indirect = false; | |
| do { | |
| indirect = false; | |
| switch (Form) { | |
| case dwarf::DW_FORM_addr: | |
| case dwarf::DW_FORM_GNU_addr_index: | |
| if (auto Val = FormValue->getAsAddress()) | |
| NewValue.Value = *Val; | |
| break; | |
| case dwarf::DW_FORM_ref_addr: | |
| case dwarf::DW_FORM_ref1: | |
| case dwarf::DW_FORM_ref2: | |
| case dwarf::DW_FORM_ref4: | |
| case dwarf::DW_FORM_ref8: | |
| case dwarf::DW_FORM_ref_udata: | |
| case dwarf::DW_FORM_ref_sig8: | |
| if (auto Val = FormValue->getAsReferenceUVal()) | |
| NewValue.Value = *Val; | |
| break; | |
| case dwarf::DW_FORM_exprloc: | |
| case dwarf::DW_FORM_block: | |
| case dwarf::DW_FORM_block1: | |
| case dwarf::DW_FORM_block2: | |
| case dwarf::DW_FORM_block4: | |
| if (auto Val = FormValue->getAsBlock()) { | |
| auto BlockData = *Val; | |
| std::copy(BlockData.begin(), BlockData.end(), | |
| std::back_inserter(NewValue.BlockData)); | |
| } | |
| NewValue.Value = NewValue.BlockData.size(); | |
| break; | |
| case dwarf::DW_FORM_data1: | |
| case dwarf::DW_FORM_flag: | |
| case dwarf::DW_FORM_data2: | |
| case dwarf::DW_FORM_data4: | |
| case dwarf::DW_FORM_data8: | |
| case dwarf::DW_FORM_sdata: | |
| case dwarf::DW_FORM_udata: | |
| case dwarf::DW_FORM_ref_sup4: | |
| case dwarf::DW_FORM_ref_sup8: | |
| if (auto Val = FormValue->getAsUnsignedConstant()) | |
| NewValue.Value = *Val; | |
| break; | |
| case dwarf::DW_FORM_string: | |
| if (auto Val = dwarf::toString(FormValue)) | |
| NewValue.CStr = *Val; | |
| break; | |
| case dwarf::DW_FORM_indirect: | |
| indirect = true; | |
| if (auto Val = FormValue->getAsUnsignedConstant()) { | |
| NewValue.Value = *Val; | |
| NewEntry.Values.push_back(NewValue); | |
| Form = static_cast<dwarf::Form>(*Val); | |
| } | |
| break; | |
| case dwarf::DW_FORM_strp: | |
| case dwarf::DW_FORM_sec_offset: | |
| case dwarf::DW_FORM_GNU_ref_alt: | |
| case dwarf::DW_FORM_GNU_strp_alt: | |
| case dwarf::DW_FORM_line_strp: | |
| case dwarf::DW_FORM_strp_sup: | |
| case dwarf::DW_FORM_GNU_str_index: | |
| case dwarf::DW_FORM_strx: | |
| if (auto Val = FormValue->getAsCStringOffset()) | |
| NewValue.Value = *Val; | |
| break; | |
| case dwarf::DW_FORM_flag_present: | |
| NewValue.Value = 1; | |
| break; | |
| default: | |
| break; | |
| } | |
| } while (indirect); | |
| NewEntry.Values.push_back(std::move(NewValue)); | |
| } | |
| } | |
| NewUnit.Entries.push_back(std::move(NewEntry)); | |
| } | |
| Y.Units.push_back(std::move(NewUnit)); | |
| } | |
| } | |
| bool dumpFileEntry(DataExtractor &Data, uint64_t &Offset, | |
| DWARFYAML::File &File) { | |
| File.Name = Data.getCStr(&Offset); | |
| if (File.Name.empty()) | |
| return false; | |
| File.DirIdx = Data.getULEB128(&Offset); | |
| File.ModTime = Data.getULEB128(&Offset); | |
| File.Length = Data.getULEB128(&Offset); | |
| return true; | |
| } | |
| void dumpDebugLines(DWARFContext &DCtx, DWARFYAML::Data &Y) { | |
| for (const auto &CU : DCtx.compile_units()) { | |
| auto CUDIE = CU->getUnitDIE(); | |
| if (!CUDIE) | |
| continue; | |
| if (auto StmtOffset = | |
| dwarf::toSectionOffset(CUDIE.find(dwarf::DW_AT_stmt_list))) { | |
| DWARFYAML::LineTable DebugLines; | |
| unsigned AddressSize = CU->getAddressByteSize(); | |
| DataExtractor LineData(DCtx.getDWARFObj().getLineSection().Data, | |
| DCtx.isLittleEndian()); | |
| uint64_t Offset = *StmtOffset; | |
| uint64_t LengthOrDWARF64Prefix = LineData.getU32(&Offset); | |
| if (LengthOrDWARF64Prefix == dwarf::DW_LENGTH_DWARF64) { | |
| DebugLines.Format = dwarf::DWARF64; | |
| DebugLines.Length = LineData.getU64(&Offset); | |
| } else { | |
| DebugLines.Format = dwarf::DWARF32; | |
| DebugLines.Length = LengthOrDWARF64Prefix; | |
| } | |
| assert(DebugLines.Length); | |
| uint64_t LineTableLength = *DebugLines.Length; | |
| uint64_t SizeOfPrologueLength = | |
| DebugLines.Format == dwarf::DWARF64 ? 8 : 4; | |
| DebugLines.Version = LineData.getU16(&Offset); | |
| DebugLines.PrologueLength = | |
| LineData.getUnsigned(&Offset, SizeOfPrologueLength); | |
| assert(DebugLines.PrologueLength); | |
| const uint64_t EndPrologue = *DebugLines.PrologueLength + Offset; | |
| DebugLines.MinInstLength = LineData.getU8(&Offset); | |
| if (DebugLines.Version >= 4) | |
| DebugLines.MaxOpsPerInst = LineData.getU8(&Offset); | |
| DebugLines.DefaultIsStmt = LineData.getU8(&Offset); | |
| DebugLines.LineBase = LineData.getU8(&Offset); | |
| DebugLines.LineRange = LineData.getU8(&Offset); | |
| DebugLines.OpcodeBase = LineData.getU8(&Offset); | |
| DebugLines.StandardOpcodeLengths.emplace(); | |
| for (uint8_t i = 1; i < DebugLines.OpcodeBase; ++i) | |
| DebugLines.StandardOpcodeLengths->push_back(LineData.getU8(&Offset)); | |
| while (Offset < EndPrologue) { | |
| StringRef Dir = LineData.getCStr(&Offset); | |
| if (!Dir.empty()) | |
| DebugLines.IncludeDirs.push_back(Dir); | |
| else | |
| break; | |
| } | |
| while (Offset < EndPrologue) { | |
| DWARFYAML::File TmpFile; | |
| if (dumpFileEntry(LineData, Offset, TmpFile)) | |
| DebugLines.Files.push_back(TmpFile); | |
| else | |
| break; | |
| } | |
| const uint64_t LineEnd = | |
| LineTableLength + *StmtOffset + SizeOfPrologueLength; | |
| while (Offset < LineEnd) { | |
| DWARFYAML::LineTableOpcode NewOp = {}; | |
| NewOp.Opcode = (dwarf::LineNumberOps)LineData.getU8(&Offset); | |
| if (NewOp.Opcode == 0) { | |
| auto StartExt = Offset; | |
| NewOp.ExtLen = LineData.getULEB128(&Offset); | |
| NewOp.SubOpcode = | |
| (dwarf::LineNumberExtendedOps)LineData.getU8(&Offset); | |
| switch (NewOp.SubOpcode) { | |
| case dwarf::DW_LNE_set_address: | |
| case dwarf::DW_LNE_set_discriminator: | |
| NewOp.Data = LineData.getUnsigned(&Offset, AddressSize); | |
| break; | |
| case dwarf::DW_LNE_define_file: | |
| dumpFileEntry(LineData, Offset, NewOp.FileEntry); | |
| break; | |
| case dwarf::DW_LNE_end_sequence: | |
| break; | |
| default: | |
| while (Offset < StartExt + *NewOp.ExtLen) | |
| NewOp.UnknownOpcodeData.push_back(LineData.getU8(&Offset)); | |
| } | |
| } else if (NewOp.Opcode < *DebugLines.OpcodeBase) { | |
| switch (NewOp.Opcode) { | |
| case dwarf::DW_LNS_copy: | |
| case dwarf::DW_LNS_negate_stmt: | |
| case dwarf::DW_LNS_set_basic_block: | |
| case dwarf::DW_LNS_const_add_pc: | |
| case dwarf::DW_LNS_set_prologue_end: | |
| case dwarf::DW_LNS_set_epilogue_begin: | |
| break; | |
| case dwarf::DW_LNS_advance_pc: | |
| case dwarf::DW_LNS_set_file: | |
| case dwarf::DW_LNS_set_column: | |
| case dwarf::DW_LNS_set_isa: | |
| NewOp.Data = LineData.getULEB128(&Offset); | |
| break; | |
| case dwarf::DW_LNS_advance_line: | |
| NewOp.SData = LineData.getSLEB128(&Offset); | |
| break; | |
| case dwarf::DW_LNS_fixed_advance_pc: | |
| NewOp.Data = LineData.getU16(&Offset); | |
| break; | |
| default: | |
| for (uint8_t i = 0; | |
| i < (*DebugLines.StandardOpcodeLengths)[NewOp.Opcode - 1]; ++i) | |
| NewOp.StandardOpcodeData.push_back(LineData.getULEB128(&Offset)); | |
| } | |
| } | |
| DebugLines.Opcodes.push_back(std::move(NewOp)); | |
| } | |
| Y.DebugLines.push_back(std::move(DebugLines)); | |
| } | |
| } | |
| } |