diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/debug/dwarf | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/debug/dwarf')
44 files changed, 6005 insertions, 0 deletions
diff --git a/src/debug/dwarf/attr_string.go b/src/debug/dwarf/attr_string.go new file mode 100644 index 0000000..8a4fff8 --- /dev/null +++ b/src/debug/dwarf/attr_string.go @@ -0,0 +1,265 @@ +// Code generated by "stringer -type Attr -trimprefix=Attr"; DO NOT EDIT. + +package dwarf + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AttrSibling-1] + _ = x[AttrLocation-2] + _ = x[AttrName-3] + _ = x[AttrOrdering-9] + _ = x[AttrByteSize-11] + _ = x[AttrBitOffset-12] + _ = x[AttrBitSize-13] + _ = x[AttrStmtList-16] + _ = x[AttrLowpc-17] + _ = x[AttrHighpc-18] + _ = x[AttrLanguage-19] + _ = x[AttrDiscr-21] + _ = x[AttrDiscrValue-22] + _ = x[AttrVisibility-23] + _ = x[AttrImport-24] + _ = x[AttrStringLength-25] + _ = x[AttrCommonRef-26] + _ = x[AttrCompDir-27] + _ = x[AttrConstValue-28] + _ = x[AttrContainingType-29] + _ = x[AttrDefaultValue-30] + _ = x[AttrInline-32] + _ = x[AttrIsOptional-33] + _ = x[AttrLowerBound-34] + _ = x[AttrProducer-37] + _ = x[AttrPrototyped-39] + _ = x[AttrReturnAddr-42] + _ = x[AttrStartScope-44] + _ = x[AttrStrideSize-46] + _ = x[AttrUpperBound-47] + _ = x[AttrAbstractOrigin-49] + _ = x[AttrAccessibility-50] + _ = x[AttrAddrClass-51] + _ = x[AttrArtificial-52] + _ = x[AttrBaseTypes-53] + _ = x[AttrCalling-54] + _ = x[AttrCount-55] + _ = x[AttrDataMemberLoc-56] + _ = x[AttrDeclColumn-57] + _ = x[AttrDeclFile-58] + _ = x[AttrDeclLine-59] + _ = x[AttrDeclaration-60] + _ = x[AttrDiscrList-61] + _ = x[AttrEncoding-62] + _ = x[AttrExternal-63] + _ = x[AttrFrameBase-64] + _ = x[AttrFriend-65] + _ = x[AttrIdentifierCase-66] + _ = x[AttrMacroInfo-67] + _ = x[AttrNamelistItem-68] + _ = x[AttrPriority-69] + _ = x[AttrSegment-70] + _ = x[AttrSpecification-71] + _ = x[AttrStaticLink-72] + _ = x[AttrType-73] + _ = x[AttrUseLocation-74] + _ = x[AttrVarParam-75] + _ = x[AttrVirtuality-76] + _ = x[AttrVtableElemLoc-77] + _ = x[AttrAllocated-78] + _ = x[AttrAssociated-79] + _ = x[AttrDataLocation-80] + _ = x[AttrStride-81] + _ = x[AttrEntrypc-82] + _ = x[AttrUseUTF8-83] + _ = x[AttrExtension-84] + _ = x[AttrRanges-85] + _ = x[AttrTrampoline-86] + _ = x[AttrCallColumn-87] + _ = x[AttrCallFile-88] + _ = x[AttrCallLine-89] + _ = x[AttrDescription-90] + _ = x[AttrBinaryScale-91] + _ = x[AttrDecimalScale-92] + _ = x[AttrSmall-93] + _ = x[AttrDecimalSign-94] + _ = x[AttrDigitCount-95] + _ = x[AttrPictureString-96] + _ = x[AttrMutable-97] + _ = x[AttrThreadsScaled-98] + _ = x[AttrExplicit-99] + _ = x[AttrObjectPointer-100] + _ = x[AttrEndianity-101] + _ = x[AttrElemental-102] + _ = x[AttrPure-103] + _ = x[AttrRecursive-104] + _ = x[AttrSignature-105] + _ = x[AttrMainSubprogram-106] + _ = x[AttrDataBitOffset-107] + _ = x[AttrConstExpr-108] + _ = x[AttrEnumClass-109] + _ = x[AttrLinkageName-110] + _ = x[AttrStringLengthBitSize-111] + _ = x[AttrStringLengthByteSize-112] + _ = x[AttrRank-113] + _ = x[AttrStrOffsetsBase-114] + _ = x[AttrAddrBase-115] + _ = x[AttrRnglistsBase-116] + _ = x[AttrDwoName-118] + _ = x[AttrReference-119] + _ = x[AttrRvalueReference-120] + _ = x[AttrMacros-121] + _ = x[AttrCallAllCalls-122] + _ = x[AttrCallAllSourceCalls-123] + _ = x[AttrCallAllTailCalls-124] + _ = x[AttrCallReturnPC-125] + _ = x[AttrCallValue-126] + _ = x[AttrCallOrigin-127] + _ = x[AttrCallParameter-128] + _ = x[AttrCallPC-129] + _ = x[AttrCallTailCall-130] + _ = x[AttrCallTarget-131] + _ = x[AttrCallTargetClobbered-132] + _ = x[AttrCallDataLocation-133] + _ = x[AttrCallDataValue-134] + _ = x[AttrNoreturn-135] + _ = x[AttrAlignment-136] + _ = x[AttrExportSymbols-137] + _ = x[AttrDeleted-138] + _ = x[AttrDefaulted-139] + _ = x[AttrLoclistsBase-140] +} + +const _Attr_name = "SiblingLocationNameOrderingByteSizeBitOffsetBitSizeStmtListLowpcHighpcLanguageDiscrDiscrValueVisibilityImportStringLengthCommonRefCompDirConstValueContainingTypeDefaultValueInlineIsOptionalLowerBoundProducerPrototypedReturnAddrStartScopeStrideSizeUpperBoundAbstractOriginAccessibilityAddrClassArtificialBaseTypesCallingCountDataMemberLocDeclColumnDeclFileDeclLineDeclarationDiscrListEncodingExternalFrameBaseFriendIdentifierCaseMacroInfoNamelistItemPrioritySegmentSpecificationStaticLinkTypeUseLocationVarParamVirtualityVtableElemLocAllocatedAssociatedDataLocationStrideEntrypcUseUTF8ExtensionRangesTrampolineCallColumnCallFileCallLineDescriptionBinaryScaleDecimalScaleSmallDecimalSignDigitCountPictureStringMutableThreadsScaledExplicitObjectPointerEndianityElementalPureRecursiveSignatureMainSubprogramDataBitOffsetConstExprEnumClassLinkageNameStringLengthBitSizeStringLengthByteSizeRankStrOffsetsBaseAddrBaseRnglistsBaseDwoNameReferenceRvalueReferenceMacrosCallAllCallsCallAllSourceCallsCallAllTailCallsCallReturnPCCallValueCallOriginCallParameterCallPCCallTailCallCallTargetCallTargetClobberedCallDataLocationCallDataValueNoreturnAlignmentExportSymbolsDeletedDefaultedLoclistsBase" + +var _Attr_map = map[Attr]string{ + 1: _Attr_name[0:7], + 2: _Attr_name[7:15], + 3: _Attr_name[15:19], + 9: _Attr_name[19:27], + 11: _Attr_name[27:35], + 12: _Attr_name[35:44], + 13: _Attr_name[44:51], + 16: _Attr_name[51:59], + 17: _Attr_name[59:64], + 18: _Attr_name[64:70], + 19: _Attr_name[70:78], + 21: _Attr_name[78:83], + 22: _Attr_name[83:93], + 23: _Attr_name[93:103], + 24: _Attr_name[103:109], + 25: _Attr_name[109:121], + 26: _Attr_name[121:130], + 27: _Attr_name[130:137], + 28: _Attr_name[137:147], + 29: _Attr_name[147:161], + 30: _Attr_name[161:173], + 32: _Attr_name[173:179], + 33: _Attr_name[179:189], + 34: _Attr_name[189:199], + 37: _Attr_name[199:207], + 39: _Attr_name[207:217], + 42: _Attr_name[217:227], + 44: _Attr_name[227:237], + 46: _Attr_name[237:247], + 47: _Attr_name[247:257], + 49: _Attr_name[257:271], + 50: _Attr_name[271:284], + 51: _Attr_name[284:293], + 52: _Attr_name[293:303], + 53: _Attr_name[303:312], + 54: _Attr_name[312:319], + 55: _Attr_name[319:324], + 56: _Attr_name[324:337], + 57: _Attr_name[337:347], + 58: _Attr_name[347:355], + 59: _Attr_name[355:363], + 60: _Attr_name[363:374], + 61: _Attr_name[374:383], + 62: _Attr_name[383:391], + 63: _Attr_name[391:399], + 64: _Attr_name[399:408], + 65: _Attr_name[408:414], + 66: _Attr_name[414:428], + 67: _Attr_name[428:437], + 68: _Attr_name[437:449], + 69: _Attr_name[449:457], + 70: _Attr_name[457:464], + 71: _Attr_name[464:477], + 72: _Attr_name[477:487], + 73: _Attr_name[487:491], + 74: _Attr_name[491:502], + 75: _Attr_name[502:510], + 76: _Attr_name[510:520], + 77: _Attr_name[520:533], + 78: _Attr_name[533:542], + 79: _Attr_name[542:552], + 80: _Attr_name[552:564], + 81: _Attr_name[564:570], + 82: _Attr_name[570:577], + 83: _Attr_name[577:584], + 84: _Attr_name[584:593], + 85: _Attr_name[593:599], + 86: _Attr_name[599:609], + 87: _Attr_name[609:619], + 88: _Attr_name[619:627], + 89: _Attr_name[627:635], + 90: _Attr_name[635:646], + 91: _Attr_name[646:657], + 92: _Attr_name[657:669], + 93: _Attr_name[669:674], + 94: _Attr_name[674:685], + 95: _Attr_name[685:695], + 96: _Attr_name[695:708], + 97: _Attr_name[708:715], + 98: _Attr_name[715:728], + 99: _Attr_name[728:736], + 100: _Attr_name[736:749], + 101: _Attr_name[749:758], + 102: _Attr_name[758:767], + 103: _Attr_name[767:771], + 104: _Attr_name[771:780], + 105: _Attr_name[780:789], + 106: _Attr_name[789:803], + 107: _Attr_name[803:816], + 108: _Attr_name[816:825], + 109: _Attr_name[825:834], + 110: _Attr_name[834:845], + 111: _Attr_name[845:864], + 112: _Attr_name[864:884], + 113: _Attr_name[884:888], + 114: _Attr_name[888:902], + 115: _Attr_name[902:910], + 116: _Attr_name[910:922], + 118: _Attr_name[922:929], + 119: _Attr_name[929:938], + 120: _Attr_name[938:953], + 121: _Attr_name[953:959], + 122: _Attr_name[959:971], + 123: _Attr_name[971:989], + 124: _Attr_name[989:1005], + 125: _Attr_name[1005:1017], + 126: _Attr_name[1017:1026], + 127: _Attr_name[1026:1036], + 128: _Attr_name[1036:1049], + 129: _Attr_name[1049:1055], + 130: _Attr_name[1055:1067], + 131: _Attr_name[1067:1077], + 132: _Attr_name[1077:1096], + 133: _Attr_name[1096:1112], + 134: _Attr_name[1112:1125], + 135: _Attr_name[1125:1133], + 136: _Attr_name[1133:1142], + 137: _Attr_name[1142:1155], + 138: _Attr_name[1155:1162], + 139: _Attr_name[1162:1171], + 140: _Attr_name[1171:1183], +} + +func (i Attr) String() string { + if str, ok := _Attr_map[i]; ok { + return str + } + return "Attr(" + strconv.FormatInt(int64(i), 10) + ")" +} diff --git a/src/debug/dwarf/buf.go b/src/debug/dwarf/buf.go new file mode 100644 index 0000000..7ac53ef --- /dev/null +++ b/src/debug/dwarf/buf.go @@ -0,0 +1,205 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Buffered reading and decoding of DWARF data streams. + +package dwarf + +import ( + "bytes" + "encoding/binary" + "strconv" +) + +// Data buffer being decoded. +type buf struct { + dwarf *Data + order binary.ByteOrder + format dataFormat + name string + off Offset + data []byte + err error +} + +// Data format, other than byte order. This affects the handling of +// certain field formats. +type dataFormat interface { + // DWARF version number. Zero means unknown. + version() int + + // 64-bit DWARF format? + dwarf64() (dwarf64 bool, isKnown bool) + + // Size of an address, in bytes. Zero means unknown. + addrsize() int +} + +// Some parts of DWARF have no data format, e.g., abbrevs. +type unknownFormat struct{} + +func (u unknownFormat) version() int { + return 0 +} + +func (u unknownFormat) dwarf64() (bool, bool) { + return false, false +} + +func (u unknownFormat) addrsize() int { + return 0 +} + +func makeBuf(d *Data, format dataFormat, name string, off Offset, data []byte) buf { + return buf{d, d.order, format, name, off, data, nil} +} + +func (b *buf) uint8() uint8 { + if len(b.data) < 1 { + b.error("underflow") + return 0 + } + val := b.data[0] + b.data = b.data[1:] + b.off++ + return val +} + +func (b *buf) bytes(n int) []byte { + if n < 0 || len(b.data) < n { + b.error("underflow") + return nil + } + data := b.data[0:n] + b.data = b.data[n:] + b.off += Offset(n) + return data +} + +func (b *buf) skip(n int) { b.bytes(n) } + +func (b *buf) string() string { + i := bytes.IndexByte(b.data, 0) + if i < 0 { + b.error("underflow") + return "" + } + + s := string(b.data[0:i]) + b.data = b.data[i+1:] + b.off += Offset(i + 1) + return s +} + +func (b *buf) uint16() uint16 { + a := b.bytes(2) + if a == nil { + return 0 + } + return b.order.Uint16(a) +} + +func (b *buf) uint24() uint32 { + a := b.bytes(3) + if a == nil { + return 0 + } + if b.dwarf.bigEndian { + return uint32(a[2]) | uint32(a[1])<<8 | uint32(a[0])<<16 + } else { + return uint32(a[0]) | uint32(a[1])<<8 | uint32(a[2])<<16 + } +} + +func (b *buf) uint32() uint32 { + a := b.bytes(4) + if a == nil { + return 0 + } + return b.order.Uint32(a) +} + +func (b *buf) uint64() uint64 { + a := b.bytes(8) + if a == nil { + return 0 + } + return b.order.Uint64(a) +} + +// Read a varint, which is 7 bits per byte, little endian. +// the 0x80 bit means read another byte. +func (b *buf) varint() (c uint64, bits uint) { + for i := 0; i < len(b.data); i++ { + byte := b.data[i] + c |= uint64(byte&0x7F) << bits + bits += 7 + if byte&0x80 == 0 { + b.off += Offset(i + 1) + b.data = b.data[i+1:] + return c, bits + } + } + return 0, 0 +} + +// Unsigned int is just a varint. +func (b *buf) uint() uint64 { + x, _ := b.varint() + return x +} + +// Signed int is a sign-extended varint. +func (b *buf) int() int64 { + ux, bits := b.varint() + x := int64(ux) + if x&(1<<(bits-1)) != 0 { + x |= -1 << bits + } + return x +} + +// Address-sized uint. +func (b *buf) addr() uint64 { + switch b.format.addrsize() { + case 1: + return uint64(b.uint8()) + case 2: + return uint64(b.uint16()) + case 4: + return uint64(b.uint32()) + case 8: + return b.uint64() + } + b.error("unknown address size") + return 0 +} + +func (b *buf) unitLength() (length Offset, dwarf64 bool) { + length = Offset(b.uint32()) + if length == 0xffffffff { + dwarf64 = true + length = Offset(b.uint64()) + } else if length >= 0xfffffff0 { + b.error("unit length has reserved value") + } + return +} + +func (b *buf) error(s string) { + if b.err == nil { + b.data = nil + b.err = DecodeError{b.name, b.off, s} + } +} + +type DecodeError struct { + Name string + Offset Offset + Err string +} + +func (e DecodeError) Error() string { + return "decoding dwarf section " + e.Name + " at offset 0x" + strconv.FormatInt(int64(e.Offset), 16) + ": " + e.Err +} diff --git a/src/debug/dwarf/class_string.go b/src/debug/dwarf/class_string.go new file mode 100644 index 0000000..163bed7 --- /dev/null +++ b/src/debug/dwarf/class_string.go @@ -0,0 +1,42 @@ +// Code generated by "stringer -type=Class"; DO NOT EDIT. + +package dwarf + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ClassUnknown-0] + _ = x[ClassAddress-1] + _ = x[ClassBlock-2] + _ = x[ClassConstant-3] + _ = x[ClassExprLoc-4] + _ = x[ClassFlag-5] + _ = x[ClassLinePtr-6] + _ = x[ClassLocListPtr-7] + _ = x[ClassMacPtr-8] + _ = x[ClassRangeListPtr-9] + _ = x[ClassReference-10] + _ = x[ClassReferenceSig-11] + _ = x[ClassString-12] + _ = x[ClassReferenceAlt-13] + _ = x[ClassStringAlt-14] + _ = x[ClassAddrPtr-15] + _ = x[ClassLocList-16] + _ = x[ClassRngList-17] + _ = x[ClassRngListsPtr-18] + _ = x[ClassStrOffsetsPtr-19] +} + +const _Class_name = "ClassUnknownClassAddressClassBlockClassConstantClassExprLocClassFlagClassLinePtrClassLocListPtrClassMacPtrClassRangeListPtrClassReferenceClassReferenceSigClassStringClassReferenceAltClassStringAltClassAddrPtrClassLocListClassRngListClassRngListsPtrClassStrOffsetsPtr" + +var _Class_index = [...]uint16{0, 12, 24, 34, 47, 59, 68, 80, 95, 106, 123, 137, 154, 165, 182, 196, 208, 220, 232, 248, 266} + +func (i Class) String() string { + if i < 0 || i >= Class(len(_Class_index)-1) { + return "Class(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Class_name[_Class_index[i]:_Class_index[i+1]] +} diff --git a/src/debug/dwarf/const.go b/src/debug/dwarf/const.go new file mode 100644 index 0000000..ea52460 --- /dev/null +++ b/src/debug/dwarf/const.go @@ -0,0 +1,475 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Constants + +package dwarf + +//go:generate stringer -type Attr -trimprefix=Attr + +// An Attr identifies the attribute type in a DWARF [Entry.Field]. +type Attr uint32 + +const ( + AttrSibling Attr = 0x01 + AttrLocation Attr = 0x02 + AttrName Attr = 0x03 + AttrOrdering Attr = 0x09 + AttrByteSize Attr = 0x0B + AttrBitOffset Attr = 0x0C + AttrBitSize Attr = 0x0D + AttrStmtList Attr = 0x10 + AttrLowpc Attr = 0x11 + AttrHighpc Attr = 0x12 + AttrLanguage Attr = 0x13 + AttrDiscr Attr = 0x15 + AttrDiscrValue Attr = 0x16 + AttrVisibility Attr = 0x17 + AttrImport Attr = 0x18 + AttrStringLength Attr = 0x19 + AttrCommonRef Attr = 0x1A + AttrCompDir Attr = 0x1B + AttrConstValue Attr = 0x1C + AttrContainingType Attr = 0x1D + AttrDefaultValue Attr = 0x1E + AttrInline Attr = 0x20 + AttrIsOptional Attr = 0x21 + AttrLowerBound Attr = 0x22 + AttrProducer Attr = 0x25 + AttrPrototyped Attr = 0x27 + AttrReturnAddr Attr = 0x2A + AttrStartScope Attr = 0x2C + AttrStrideSize Attr = 0x2E + AttrUpperBound Attr = 0x2F + AttrAbstractOrigin Attr = 0x31 + AttrAccessibility Attr = 0x32 + AttrAddrClass Attr = 0x33 + AttrArtificial Attr = 0x34 + AttrBaseTypes Attr = 0x35 + AttrCalling Attr = 0x36 + AttrCount Attr = 0x37 + AttrDataMemberLoc Attr = 0x38 + AttrDeclColumn Attr = 0x39 + AttrDeclFile Attr = 0x3A + AttrDeclLine Attr = 0x3B + AttrDeclaration Attr = 0x3C + AttrDiscrList Attr = 0x3D + AttrEncoding Attr = 0x3E + AttrExternal Attr = 0x3F + AttrFrameBase Attr = 0x40 + AttrFriend Attr = 0x41 + AttrIdentifierCase Attr = 0x42 + AttrMacroInfo Attr = 0x43 + AttrNamelistItem Attr = 0x44 + AttrPriority Attr = 0x45 + AttrSegment Attr = 0x46 + AttrSpecification Attr = 0x47 + AttrStaticLink Attr = 0x48 + AttrType Attr = 0x49 + AttrUseLocation Attr = 0x4A + AttrVarParam Attr = 0x4B + AttrVirtuality Attr = 0x4C + AttrVtableElemLoc Attr = 0x4D + // The following are new in DWARF 3. + AttrAllocated Attr = 0x4E + AttrAssociated Attr = 0x4F + AttrDataLocation Attr = 0x50 + AttrStride Attr = 0x51 + AttrEntrypc Attr = 0x52 + AttrUseUTF8 Attr = 0x53 + AttrExtension Attr = 0x54 + AttrRanges Attr = 0x55 + AttrTrampoline Attr = 0x56 + AttrCallColumn Attr = 0x57 + AttrCallFile Attr = 0x58 + AttrCallLine Attr = 0x59 + AttrDescription Attr = 0x5A + AttrBinaryScale Attr = 0x5B + AttrDecimalScale Attr = 0x5C + AttrSmall Attr = 0x5D + AttrDecimalSign Attr = 0x5E + AttrDigitCount Attr = 0x5F + AttrPictureString Attr = 0x60 + AttrMutable Attr = 0x61 + AttrThreadsScaled Attr = 0x62 + AttrExplicit Attr = 0x63 + AttrObjectPointer Attr = 0x64 + AttrEndianity Attr = 0x65 + AttrElemental Attr = 0x66 + AttrPure Attr = 0x67 + AttrRecursive Attr = 0x68 + // The following are new in DWARF 4. + AttrSignature Attr = 0x69 + AttrMainSubprogram Attr = 0x6A + AttrDataBitOffset Attr = 0x6B + AttrConstExpr Attr = 0x6C + AttrEnumClass Attr = 0x6D + AttrLinkageName Attr = 0x6E + // The following are new in DWARF 5. + AttrStringLengthBitSize Attr = 0x6F + AttrStringLengthByteSize Attr = 0x70 + AttrRank Attr = 0x71 + AttrStrOffsetsBase Attr = 0x72 + AttrAddrBase Attr = 0x73 + AttrRnglistsBase Attr = 0x74 + AttrDwoName Attr = 0x76 + AttrReference Attr = 0x77 + AttrRvalueReference Attr = 0x78 + AttrMacros Attr = 0x79 + AttrCallAllCalls Attr = 0x7A + AttrCallAllSourceCalls Attr = 0x7B + AttrCallAllTailCalls Attr = 0x7C + AttrCallReturnPC Attr = 0x7D + AttrCallValue Attr = 0x7E + AttrCallOrigin Attr = 0x7F + AttrCallParameter Attr = 0x80 + AttrCallPC Attr = 0x81 + AttrCallTailCall Attr = 0x82 + AttrCallTarget Attr = 0x83 + AttrCallTargetClobbered Attr = 0x84 + AttrCallDataLocation Attr = 0x85 + AttrCallDataValue Attr = 0x86 + AttrNoreturn Attr = 0x87 + AttrAlignment Attr = 0x88 + AttrExportSymbols Attr = 0x89 + AttrDeleted Attr = 0x8A + AttrDefaulted Attr = 0x8B + AttrLoclistsBase Attr = 0x8C +) + +func (a Attr) GoString() string { + if str, ok := _Attr_map[a]; ok { + return "dwarf.Attr" + str + } + return "dwarf." + a.String() +} + +// A format is a DWARF data encoding format. +type format uint32 + +const ( + // value formats + formAddr format = 0x01 + formDwarfBlock2 format = 0x03 + formDwarfBlock4 format = 0x04 + formData2 format = 0x05 + formData4 format = 0x06 + formData8 format = 0x07 + formString format = 0x08 + formDwarfBlock format = 0x09 + formDwarfBlock1 format = 0x0A + formData1 format = 0x0B + formFlag format = 0x0C + formSdata format = 0x0D + formStrp format = 0x0E + formUdata format = 0x0F + formRefAddr format = 0x10 + formRef1 format = 0x11 + formRef2 format = 0x12 + formRef4 format = 0x13 + formRef8 format = 0x14 + formRefUdata format = 0x15 + formIndirect format = 0x16 + // The following are new in DWARF 4. + formSecOffset format = 0x17 + formExprloc format = 0x18 + formFlagPresent format = 0x19 + formRefSig8 format = 0x20 + // The following are new in DWARF 5. + formStrx format = 0x1A + formAddrx format = 0x1B + formRefSup4 format = 0x1C + formStrpSup format = 0x1D + formData16 format = 0x1E + formLineStrp format = 0x1F + formImplicitConst format = 0x21 + formLoclistx format = 0x22 + formRnglistx format = 0x23 + formRefSup8 format = 0x24 + formStrx1 format = 0x25 + formStrx2 format = 0x26 + formStrx3 format = 0x27 + formStrx4 format = 0x28 + formAddrx1 format = 0x29 + formAddrx2 format = 0x2A + formAddrx3 format = 0x2B + formAddrx4 format = 0x2C + // Extensions for multi-file compression (.dwz) + // http://www.dwarfstd.org/ShowIssue.php?issue=120604.1 + formGnuRefAlt format = 0x1f20 + formGnuStrpAlt format = 0x1f21 +) + +//go:generate stringer -type Tag -trimprefix=Tag + +// A Tag is the classification (the type) of an [Entry]. +type Tag uint32 + +const ( + TagArrayType Tag = 0x01 + TagClassType Tag = 0x02 + TagEntryPoint Tag = 0x03 + TagEnumerationType Tag = 0x04 + TagFormalParameter Tag = 0x05 + TagImportedDeclaration Tag = 0x08 + TagLabel Tag = 0x0A + TagLexDwarfBlock Tag = 0x0B + TagMember Tag = 0x0D + TagPointerType Tag = 0x0F + TagReferenceType Tag = 0x10 + TagCompileUnit Tag = 0x11 + TagStringType Tag = 0x12 + TagStructType Tag = 0x13 + TagSubroutineType Tag = 0x15 + TagTypedef Tag = 0x16 + TagUnionType Tag = 0x17 + TagUnspecifiedParameters Tag = 0x18 + TagVariant Tag = 0x19 + TagCommonDwarfBlock Tag = 0x1A + TagCommonInclusion Tag = 0x1B + TagInheritance Tag = 0x1C + TagInlinedSubroutine Tag = 0x1D + TagModule Tag = 0x1E + TagPtrToMemberType Tag = 0x1F + TagSetType Tag = 0x20 + TagSubrangeType Tag = 0x21 + TagWithStmt Tag = 0x22 + TagAccessDeclaration Tag = 0x23 + TagBaseType Tag = 0x24 + TagCatchDwarfBlock Tag = 0x25 + TagConstType Tag = 0x26 + TagConstant Tag = 0x27 + TagEnumerator Tag = 0x28 + TagFileType Tag = 0x29 + TagFriend Tag = 0x2A + TagNamelist Tag = 0x2B + TagNamelistItem Tag = 0x2C + TagPackedType Tag = 0x2D + TagSubprogram Tag = 0x2E + TagTemplateTypeParameter Tag = 0x2F + TagTemplateValueParameter Tag = 0x30 + TagThrownType Tag = 0x31 + TagTryDwarfBlock Tag = 0x32 + TagVariantPart Tag = 0x33 + TagVariable Tag = 0x34 + TagVolatileType Tag = 0x35 + // The following are new in DWARF 3. + TagDwarfProcedure Tag = 0x36 + TagRestrictType Tag = 0x37 + TagInterfaceType Tag = 0x38 + TagNamespace Tag = 0x39 + TagImportedModule Tag = 0x3A + TagUnspecifiedType Tag = 0x3B + TagPartialUnit Tag = 0x3C + TagImportedUnit Tag = 0x3D + TagMutableType Tag = 0x3E // Later removed from DWARF. + TagCondition Tag = 0x3F + TagSharedType Tag = 0x40 + // The following are new in DWARF 4. + TagTypeUnit Tag = 0x41 + TagRvalueReferenceType Tag = 0x42 + TagTemplateAlias Tag = 0x43 + // The following are new in DWARF 5. + TagCoarrayType Tag = 0x44 + TagGenericSubrange Tag = 0x45 + TagDynamicType Tag = 0x46 + TagAtomicType Tag = 0x47 + TagCallSite Tag = 0x48 + TagCallSiteParameter Tag = 0x49 + TagSkeletonUnit Tag = 0x4A + TagImmutableType Tag = 0x4B +) + +func (t Tag) GoString() string { + if t <= TagTemplateAlias { + return "dwarf.Tag" + t.String() + } + return "dwarf." + t.String() +} + +// Location expression operators. +// The debug info encodes value locations like 8(R3) +// as a sequence of these op codes. +// This package does not implement full expressions; +// the opPlusUconst operator is expected by the type parser. +const ( + opAddr = 0x03 /* 1 op, const addr */ + opDeref = 0x06 + opConst1u = 0x08 /* 1 op, 1 byte const */ + opConst1s = 0x09 /* " signed */ + opConst2u = 0x0A /* 1 op, 2 byte const */ + opConst2s = 0x0B /* " signed */ + opConst4u = 0x0C /* 1 op, 4 byte const */ + opConst4s = 0x0D /* " signed */ + opConst8u = 0x0E /* 1 op, 8 byte const */ + opConst8s = 0x0F /* " signed */ + opConstu = 0x10 /* 1 op, LEB128 const */ + opConsts = 0x11 /* " signed */ + opDup = 0x12 + opDrop = 0x13 + opOver = 0x14 + opPick = 0x15 /* 1 op, 1 byte stack index */ + opSwap = 0x16 + opRot = 0x17 + opXderef = 0x18 + opAbs = 0x19 + opAnd = 0x1A + opDiv = 0x1B + opMinus = 0x1C + opMod = 0x1D + opMul = 0x1E + opNeg = 0x1F + opNot = 0x20 + opOr = 0x21 + opPlus = 0x22 + opPlusUconst = 0x23 /* 1 op, ULEB128 addend */ + opShl = 0x24 + opShr = 0x25 + opShra = 0x26 + opXor = 0x27 + opSkip = 0x2F /* 1 op, signed 2-byte constant */ + opBra = 0x28 /* 1 op, signed 2-byte constant */ + opEq = 0x29 + opGe = 0x2A + opGt = 0x2B + opLe = 0x2C + opLt = 0x2D + opNe = 0x2E + opLit0 = 0x30 + /* OpLitN = OpLit0 + N for N = 0..31 */ + opReg0 = 0x50 + /* OpRegN = OpReg0 + N for N = 0..31 */ + opBreg0 = 0x70 /* 1 op, signed LEB128 constant */ + /* OpBregN = OpBreg0 + N for N = 0..31 */ + opRegx = 0x90 /* 1 op, ULEB128 register */ + opFbreg = 0x91 /* 1 op, SLEB128 offset */ + opBregx = 0x92 /* 2 op, ULEB128 reg; SLEB128 off */ + opPiece = 0x93 /* 1 op, ULEB128 size of piece */ + opDerefSize = 0x94 /* 1-byte size of data retrieved */ + opXderefSize = 0x95 /* 1-byte size of data retrieved */ + opNop = 0x96 + // The following are new in DWARF 3. + opPushObjAddr = 0x97 + opCall2 = 0x98 /* 2-byte offset of DIE */ + opCall4 = 0x99 /* 4-byte offset of DIE */ + opCallRef = 0x9A /* 4- or 8- byte offset of DIE */ + opFormTLSAddress = 0x9B + opCallFrameCFA = 0x9C + opBitPiece = 0x9D + // The following are new in DWARF 4. + opImplicitValue = 0x9E + opStackValue = 0x9F + // The following a new in DWARF 5. + opImplicitPointer = 0xA0 + opAddrx = 0xA1 + opConstx = 0xA2 + opEntryValue = 0xA3 + opConstType = 0xA4 + opRegvalType = 0xA5 + opDerefType = 0xA6 + opXderefType = 0xA7 + opConvert = 0xA8 + opReinterpret = 0xA9 + /* 0xE0-0xFF reserved for user-specific */ +) + +// Basic type encodings -- the value for AttrEncoding in a TagBaseType Entry. +const ( + encAddress = 0x01 + encBoolean = 0x02 + encComplexFloat = 0x03 + encFloat = 0x04 + encSigned = 0x05 + encSignedChar = 0x06 + encUnsigned = 0x07 + encUnsignedChar = 0x08 + // The following are new in DWARF 3. + encImaginaryFloat = 0x09 + encPackedDecimal = 0x0A + encNumericString = 0x0B + encEdited = 0x0C + encSignedFixed = 0x0D + encUnsignedFixed = 0x0E + encDecimalFloat = 0x0F + // The following are new in DWARF 4. + encUTF = 0x10 + // The following are new in DWARF 5. + encUCS = 0x11 + encASCII = 0x12 +) + +// Statement program standard opcode encodings. +const ( + lnsCopy = 1 + lnsAdvancePC = 2 + lnsAdvanceLine = 3 + lnsSetFile = 4 + lnsSetColumn = 5 + lnsNegateStmt = 6 + lnsSetBasicBlock = 7 + lnsConstAddPC = 8 + lnsFixedAdvancePC = 9 + + // DWARF 3 + lnsSetPrologueEnd = 10 + lnsSetEpilogueBegin = 11 + lnsSetISA = 12 +) + +// Statement program extended opcode encodings. +const ( + lneEndSequence = 1 + lneSetAddress = 2 + lneDefineFile = 3 + + // DWARF 4 + lneSetDiscriminator = 4 +) + +// Line table directory and file name entry formats. +// These are new in DWARF 5. +const ( + lnctPath = 0x01 + lnctDirectoryIndex = 0x02 + lnctTimestamp = 0x03 + lnctSize = 0x04 + lnctMD5 = 0x05 +) + +// Location list entry codes. +// These are new in DWARF 5. +const ( + lleEndOfList = 0x00 + lleBaseAddressx = 0x01 + lleStartxEndx = 0x02 + lleStartxLength = 0x03 + lleOffsetPair = 0x04 + lleDefaultLocation = 0x05 + lleBaseAddress = 0x06 + lleStartEnd = 0x07 + lleStartLength = 0x08 +) + +// Unit header unit type encodings. +// These are new in DWARF 5. +const ( + utCompile = 0x01 + utType = 0x02 + utPartial = 0x03 + utSkeleton = 0x04 + utSplitCompile = 0x05 + utSplitType = 0x06 +) + +// Opcodes for DWARFv5 debug_rnglists section. +const ( + rleEndOfList = 0x0 + rleBaseAddressx = 0x1 + rleStartxEndx = 0x2 + rleStartxLength = 0x3 + rleOffsetPair = 0x4 + rleBaseAddress = 0x5 + rleStartEnd = 0x6 + rleStartLength = 0x7 +) diff --git a/src/debug/dwarf/dwarf5ranges_test.go b/src/debug/dwarf/dwarf5ranges_test.go new file mode 100644 index 0000000..8bc50bc --- /dev/null +++ b/src/debug/dwarf/dwarf5ranges_test.go @@ -0,0 +1,41 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf + +import ( + "encoding/binary" + "os" + "reflect" + "testing" +) + +func TestDwarf5Ranges(t *testing.T) { + rngLists, err := os.ReadFile("testdata/debug_rnglists") + if err != nil { + t.Fatalf("could not read test data: %v", err) + } + + d := &Data{} + d.order = binary.LittleEndian + if err := d.AddSection(".debug_rnglists", rngLists); err != nil { + t.Fatal(err) + } + u := &unit{ + asize: 8, + vers: 5, + is64: true, + } + ret, err := d.dwarf5Ranges(u, nil, 0x5fbd, 0xc, [][2]uint64{}) + if err != nil { + t.Fatalf("could not read rnglist: %v", err) + } + t.Logf("%#v", ret) + + tgt := [][2]uint64{{0x0000000000006712, 0x000000000000679f}, {0x00000000000067af}, {0x00000000000067b3}} + + if reflect.DeepEqual(ret, tgt) { + t.Errorf("expected %#v got %#x", tgt, ret) + } +} diff --git a/src/debug/dwarf/entry.go b/src/debug/dwarf/entry.go new file mode 100644 index 0000000..4541d74 --- /dev/null +++ b/src/debug/dwarf/entry.go @@ -0,0 +1,1221 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// DWARF debug information entry parser. +// An entry is a sequence of data items of a given format. +// The first word in the entry is an index into what DWARF +// calls the ``abbreviation table.'' An abbreviation is really +// just a type descriptor: it's an array of attribute tag/value format pairs. + +package dwarf + +import ( + "encoding/binary" + "errors" + "fmt" + "strconv" +) + +// a single entry's description: a sequence of attributes +type abbrev struct { + tag Tag + children bool + field []afield +} + +type afield struct { + attr Attr + fmt format + class Class + val int64 // for formImplicitConst +} + +// a map from entry format ids to their descriptions +type abbrevTable map[uint32]abbrev + +// parseAbbrev returns the abbreviation table that starts at byte off +// in the .debug_abbrev section. +func (d *Data) parseAbbrev(off uint64, vers int) (abbrevTable, error) { + if m, ok := d.abbrevCache[off]; ok { + return m, nil + } + + data := d.abbrev + if off > uint64(len(data)) { + data = nil + } else { + data = data[off:] + } + b := makeBuf(d, unknownFormat{}, "abbrev", 0, data) + + // Error handling is simplified by the buf getters + // returning an endless stream of 0s after an error. + m := make(abbrevTable) + for { + // Table ends with id == 0. + id := uint32(b.uint()) + if id == 0 { + break + } + + // Walk over attributes, counting. + n := 0 + b1 := b // Read from copy of b. + b1.uint() + b1.uint8() + for { + tag := b1.uint() + fmt := b1.uint() + if tag == 0 && fmt == 0 { + break + } + if format(fmt) == formImplicitConst { + b1.int() + } + n++ + } + if b1.err != nil { + return nil, b1.err + } + + // Walk over attributes again, this time writing them down. + var a abbrev + a.tag = Tag(b.uint()) + a.children = b.uint8() != 0 + a.field = make([]afield, n) + for i := range a.field { + a.field[i].attr = Attr(b.uint()) + a.field[i].fmt = format(b.uint()) + a.field[i].class = formToClass(a.field[i].fmt, a.field[i].attr, vers, &b) + if a.field[i].fmt == formImplicitConst { + a.field[i].val = b.int() + } + } + b.uint() + b.uint() + + m[id] = a + } + if b.err != nil { + return nil, b.err + } + d.abbrevCache[off] = m + return m, nil +} + +// attrIsExprloc indicates attributes that allow exprloc values that +// are encoded as block values in DWARF 2 and 3. See DWARF 4, Figure +// 20. +var attrIsExprloc = map[Attr]bool{ + AttrLocation: true, + AttrByteSize: true, + AttrBitOffset: true, + AttrBitSize: true, + AttrStringLength: true, + AttrLowerBound: true, + AttrReturnAddr: true, + AttrStrideSize: true, + AttrUpperBound: true, + AttrCount: true, + AttrDataMemberLoc: true, + AttrFrameBase: true, + AttrSegment: true, + AttrStaticLink: true, + AttrUseLocation: true, + AttrVtableElemLoc: true, + AttrAllocated: true, + AttrAssociated: true, + AttrDataLocation: true, + AttrStride: true, +} + +// attrPtrClass indicates the *ptr class of attributes that have +// encoding formSecOffset in DWARF 4 or formData* in DWARF 2 and 3. +var attrPtrClass = map[Attr]Class{ + AttrLocation: ClassLocListPtr, + AttrStmtList: ClassLinePtr, + AttrStringLength: ClassLocListPtr, + AttrReturnAddr: ClassLocListPtr, + AttrStartScope: ClassRangeListPtr, + AttrDataMemberLoc: ClassLocListPtr, + AttrFrameBase: ClassLocListPtr, + AttrMacroInfo: ClassMacPtr, + AttrSegment: ClassLocListPtr, + AttrStaticLink: ClassLocListPtr, + AttrUseLocation: ClassLocListPtr, + AttrVtableElemLoc: ClassLocListPtr, + AttrRanges: ClassRangeListPtr, + // The following are new in DWARF 5. + AttrStrOffsetsBase: ClassStrOffsetsPtr, + AttrAddrBase: ClassAddrPtr, + AttrRnglistsBase: ClassRngListsPtr, + AttrLoclistsBase: ClassLocListPtr, +} + +// formToClass returns the DWARF 4 Class for the given form. If the +// DWARF version is less then 4, it will disambiguate some forms +// depending on the attribute. +func formToClass(form format, attr Attr, vers int, b *buf) Class { + switch form { + default: + b.error("cannot determine class of unknown attribute form") + return 0 + + case formIndirect: + return ClassUnknown + + case formAddr, formAddrx, formAddrx1, formAddrx2, formAddrx3, formAddrx4: + return ClassAddress + + case formDwarfBlock1, formDwarfBlock2, formDwarfBlock4, formDwarfBlock: + // In DWARF 2 and 3, ClassExprLoc was encoded as a + // block. DWARF 4 distinguishes ClassBlock and + // ClassExprLoc, but there are no attributes that can + // be both, so we also promote ClassBlock values in + // DWARF 4 that should be ClassExprLoc in case + // producers get this wrong. + if attrIsExprloc[attr] { + return ClassExprLoc + } + return ClassBlock + + case formData1, formData2, formData4, formData8, formSdata, formUdata, formData16, formImplicitConst: + // In DWARF 2 and 3, ClassPtr was encoded as a + // constant. Unlike ClassExprLoc/ClassBlock, some + // DWARF 4 attributes need to distinguish Class*Ptr + // from ClassConstant, so we only do this promotion + // for versions 2 and 3. + if class, ok := attrPtrClass[attr]; vers < 4 && ok { + return class + } + return ClassConstant + + case formFlag, formFlagPresent: + return ClassFlag + + case formRefAddr, formRef1, formRef2, formRef4, formRef8, formRefUdata, formRefSup4, formRefSup8: + return ClassReference + + case formRefSig8: + return ClassReferenceSig + + case formString, formStrp, formStrx, formStrpSup, formLineStrp, formStrx1, formStrx2, formStrx3, formStrx4: + return ClassString + + case formSecOffset: + // DWARF 4 defines four *ptr classes, but doesn't + // distinguish them in the encoding. Disambiguate + // these classes using the attribute. + if class, ok := attrPtrClass[attr]; ok { + return class + } + return ClassUnknown + + case formExprloc: + return ClassExprLoc + + case formGnuRefAlt: + return ClassReferenceAlt + + case formGnuStrpAlt: + return ClassStringAlt + + case formLoclistx: + return ClassLocList + + case formRnglistx: + return ClassRngList + } +} + +// An entry is a sequence of attribute/value pairs. +type Entry struct { + Offset Offset // offset of Entry in DWARF info + Tag Tag // tag (kind of Entry) + Children bool // whether Entry is followed by children + Field []Field +} + +// A Field is a single attribute/value pair in an [Entry]. +// +// A value can be one of several "attribute classes" defined by DWARF. +// The Go types corresponding to each class are: +// +// DWARF class Go type Class +// ----------- ------- ----- +// address uint64 ClassAddress +// block []byte ClassBlock +// constant int64 ClassConstant +// flag bool ClassFlag +// reference +// to info dwarf.Offset ClassReference +// to type unit uint64 ClassReferenceSig +// string string ClassString +// exprloc []byte ClassExprLoc +// lineptr int64 ClassLinePtr +// loclistptr int64 ClassLocListPtr +// macptr int64 ClassMacPtr +// rangelistptr int64 ClassRangeListPtr +// +// For unrecognized or vendor-defined attributes, [Class] may be +// [ClassUnknown]. +type Field struct { + Attr Attr + Val any + Class Class +} + +// A Class is the DWARF 4 class of an attribute value. +// +// In general, a given attribute's value may take on one of several +// possible classes defined by DWARF, each of which leads to a +// slightly different interpretation of the attribute. +// +// DWARF version 4 distinguishes attribute value classes more finely +// than previous versions of DWARF. The reader will disambiguate +// coarser classes from earlier versions of DWARF into the appropriate +// DWARF 4 class. For example, DWARF 2 uses "constant" for constants +// as well as all types of section offsets, but the reader will +// canonicalize attributes in DWARF 2 files that refer to section +// offsets to one of the Class*Ptr classes, even though these classes +// were only defined in DWARF 3. +type Class int + +const ( + // ClassUnknown represents values of unknown DWARF class. + ClassUnknown Class = iota + + // ClassAddress represents values of type uint64 that are + // addresses on the target machine. + ClassAddress + + // ClassBlock represents values of type []byte whose + // interpretation depends on the attribute. + ClassBlock + + // ClassConstant represents values of type int64 that are + // constants. The interpretation of this constant depends on + // the attribute. + ClassConstant + + // ClassExprLoc represents values of type []byte that contain + // an encoded DWARF expression or location description. + ClassExprLoc + + // ClassFlag represents values of type bool. + ClassFlag + + // ClassLinePtr represents values that are an int64 offset + // into the "line" section. + ClassLinePtr + + // ClassLocListPtr represents values that are an int64 offset + // into the "loclist" section. + ClassLocListPtr + + // ClassMacPtr represents values that are an int64 offset into + // the "mac" section. + ClassMacPtr + + // ClassRangeListPtr represents values that are an int64 offset into + // the "rangelist" section. + ClassRangeListPtr + + // ClassReference represents values that are an Offset offset + // of an Entry in the info section (for use with Reader.Seek). + // The DWARF specification combines ClassReference and + // ClassReferenceSig into class "reference". + ClassReference + + // ClassReferenceSig represents values that are a uint64 type + // signature referencing a type Entry. + ClassReferenceSig + + // ClassString represents values that are strings. If the + // compilation unit specifies the AttrUseUTF8 flag (strongly + // recommended), the string value will be encoded in UTF-8. + // Otherwise, the encoding is unspecified. + ClassString + + // ClassReferenceAlt represents values of type int64 that are + // an offset into the DWARF "info" section of an alternate + // object file. + ClassReferenceAlt + + // ClassStringAlt represents values of type int64 that are an + // offset into the DWARF string section of an alternate object + // file. + ClassStringAlt + + // ClassAddrPtr represents values that are an int64 offset + // into the "addr" section. + ClassAddrPtr + + // ClassLocList represents values that are an int64 offset + // into the "loclists" section. + ClassLocList + + // ClassRngList represents values that are a uint64 offset + // from the base of the "rnglists" section. + ClassRngList + + // ClassRngListsPtr represents values that are an int64 offset + // into the "rnglists" section. These are used as the base for + // ClassRngList values. + ClassRngListsPtr + + // ClassStrOffsetsPtr represents values that are an int64 + // offset into the "str_offsets" section. + ClassStrOffsetsPtr +) + +//go:generate stringer -type=Class + +func (i Class) GoString() string { + return "dwarf." + i.String() +} + +// Val returns the value associated with attribute [Attr] in [Entry], +// or nil if there is no such attribute. +// +// A common idiom is to merge the check for nil return with +// the check that the value has the expected dynamic type, as in: +// +// v, ok := e.Val(AttrSibling).(int64) +func (e *Entry) Val(a Attr) any { + if f := e.AttrField(a); f != nil { + return f.Val + } + return nil +} + +// AttrField returns the [Field] associated with attribute [Attr] in +// [Entry], or nil if there is no such attribute. +func (e *Entry) AttrField(a Attr) *Field { + for i, f := range e.Field { + if f.Attr == a { + return &e.Field[i] + } + } + return nil +} + +// An Offset represents the location of an [Entry] within the DWARF info. +// (See [Reader.Seek].) +type Offset uint32 + +// Entry reads a single entry from buf, decoding +// according to the given abbreviation table. +func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry { + off := b.off + id := uint32(b.uint()) + if id == 0 { + return &Entry{} + } + a, ok := atab[id] + if !ok { + b.error("unknown abbreviation table index") + return nil + } + e := &Entry{ + Offset: off, + Tag: a.tag, + Children: a.children, + Field: make([]Field, len(a.field)), + } + + // If we are currently parsing the compilation unit, + // we can't evaluate Addrx or Strx until we've seen the + // relevant base entry. + type delayed struct { + idx int + off uint64 + fmt format + } + var delay []delayed + + resolveStrx := func(strBase, off uint64) string { + off += strBase + if uint64(int(off)) != off { + b.error("DW_FORM_strx offset out of range") + } + + b1 := makeBuf(b.dwarf, b.format, "str_offsets", 0, b.dwarf.strOffsets) + b1.skip(int(off)) + is64, _ := b.format.dwarf64() + if is64 { + off = b1.uint64() + } else { + off = uint64(b1.uint32()) + } + if b1.err != nil { + b.err = b1.err + return "" + } + if uint64(int(off)) != off { + b.error("DW_FORM_strx indirect offset out of range") + } + b1 = makeBuf(b.dwarf, b.format, "str", 0, b.dwarf.str) + b1.skip(int(off)) + val := b1.string() + if b1.err != nil { + b.err = b1.err + } + return val + } + + resolveRnglistx := func(rnglistsBase, off uint64) uint64 { + is64, _ := b.format.dwarf64() + if is64 { + off *= 8 + } else { + off *= 4 + } + off += rnglistsBase + if uint64(int(off)) != off { + b.error("DW_FORM_rnglistx offset out of range") + } + + b1 := makeBuf(b.dwarf, b.format, "rnglists", 0, b.dwarf.rngLists) + b1.skip(int(off)) + if is64 { + off = b1.uint64() + } else { + off = uint64(b1.uint32()) + } + if b1.err != nil { + b.err = b1.err + return 0 + } + if uint64(int(off)) != off { + b.error("DW_FORM_rnglistx indirect offset out of range") + } + return rnglistsBase + off + } + + for i := range e.Field { + e.Field[i].Attr = a.field[i].attr + e.Field[i].Class = a.field[i].class + fmt := a.field[i].fmt + if fmt == formIndirect { + fmt = format(b.uint()) + e.Field[i].Class = formToClass(fmt, a.field[i].attr, vers, b) + } + var val any + switch fmt { + default: + b.error("unknown entry attr format 0x" + strconv.FormatInt(int64(fmt), 16)) + + // address + case formAddr: + val = b.addr() + case formAddrx, formAddrx1, formAddrx2, formAddrx3, formAddrx4: + var off uint64 + switch fmt { + case formAddrx: + off = b.uint() + case formAddrx1: + off = uint64(b.uint8()) + case formAddrx2: + off = uint64(b.uint16()) + case formAddrx3: + off = uint64(b.uint24()) + case formAddrx4: + off = uint64(b.uint32()) + } + if b.dwarf.addr == nil { + b.error("DW_FORM_addrx with no .debug_addr section") + } + if b.err != nil { + return nil + } + + // We have to adjust by the offset of the + // compilation unit. This won't work if the + // program uses Reader.Seek to skip over the + // unit. Not much we can do about that. + var addrBase int64 + if cu != nil { + addrBase, _ = cu.Val(AttrAddrBase).(int64) + } else if a.tag == TagCompileUnit { + delay = append(delay, delayed{i, off, formAddrx}) + break + } + + var err error + val, err = b.dwarf.debugAddr(b.format, uint64(addrBase), off) + if err != nil { + if b.err == nil { + b.err = err + } + return nil + } + + // block + case formDwarfBlock1: + val = b.bytes(int(b.uint8())) + case formDwarfBlock2: + val = b.bytes(int(b.uint16())) + case formDwarfBlock4: + val = b.bytes(int(b.uint32())) + case formDwarfBlock: + val = b.bytes(int(b.uint())) + + // constant + case formData1: + val = int64(b.uint8()) + case formData2: + val = int64(b.uint16()) + case formData4: + val = int64(b.uint32()) + case formData8: + val = int64(b.uint64()) + case formData16: + val = b.bytes(16) + case formSdata: + val = int64(b.int()) + case formUdata: + val = int64(b.uint()) + case formImplicitConst: + val = a.field[i].val + + // flag + case formFlag: + val = b.uint8() == 1 + // New in DWARF 4. + case formFlagPresent: + // The attribute is implicitly indicated as present, and no value is + // encoded in the debugging information entry itself. + val = true + + // reference to other entry + case formRefAddr: + vers := b.format.version() + if vers == 0 { + b.error("unknown version for DW_FORM_ref_addr") + } else if vers == 2 { + val = Offset(b.addr()) + } else { + is64, known := b.format.dwarf64() + if !known { + b.error("unknown size for DW_FORM_ref_addr") + } else if is64 { + val = Offset(b.uint64()) + } else { + val = Offset(b.uint32()) + } + } + case formRef1: + val = Offset(b.uint8()) + ubase + case formRef2: + val = Offset(b.uint16()) + ubase + case formRef4: + val = Offset(b.uint32()) + ubase + case formRef8: + val = Offset(b.uint64()) + ubase + case formRefUdata: + val = Offset(b.uint()) + ubase + + // string + case formString: + val = b.string() + case formStrp, formLineStrp: + var off uint64 // offset into .debug_str + is64, known := b.format.dwarf64() + if !known { + b.error("unknown size for DW_FORM_strp/line_strp") + } else if is64 { + off = b.uint64() + } else { + off = uint64(b.uint32()) + } + if uint64(int(off)) != off { + b.error("DW_FORM_strp/line_strp offset out of range") + } + if b.err != nil { + return nil + } + var b1 buf + if fmt == formStrp { + b1 = makeBuf(b.dwarf, b.format, "str", 0, b.dwarf.str) + } else { + if len(b.dwarf.lineStr) == 0 { + b.error("DW_FORM_line_strp with no .debug_line_str section") + return nil + } + b1 = makeBuf(b.dwarf, b.format, "line_str", 0, b.dwarf.lineStr) + } + b1.skip(int(off)) + val = b1.string() + if b1.err != nil { + b.err = b1.err + return nil + } + case formStrx, formStrx1, formStrx2, formStrx3, formStrx4: + var off uint64 + switch fmt { + case formStrx: + off = b.uint() + case formStrx1: + off = uint64(b.uint8()) + case formStrx2: + off = uint64(b.uint16()) + case formStrx3: + off = uint64(b.uint24()) + case formStrx4: + off = uint64(b.uint32()) + } + if len(b.dwarf.strOffsets) == 0 { + b.error("DW_FORM_strx with no .debug_str_offsets section") + } + is64, known := b.format.dwarf64() + if !known { + b.error("unknown offset size for DW_FORM_strx") + } + if b.err != nil { + return nil + } + if is64 { + off *= 8 + } else { + off *= 4 + } + + // We have to adjust by the offset of the + // compilation unit. This won't work if the + // program uses Reader.Seek to skip over the + // unit. Not much we can do about that. + var strBase int64 + if cu != nil { + strBase, _ = cu.Val(AttrStrOffsetsBase).(int64) + } else if a.tag == TagCompileUnit { + delay = append(delay, delayed{i, off, formStrx}) + break + } + + val = resolveStrx(uint64(strBase), off) + + case formStrpSup: + is64, known := b.format.dwarf64() + if !known { + b.error("unknown size for DW_FORM_strp_sup") + } else if is64 { + val = b.uint64() + } else { + val = b.uint32() + } + + // lineptr, loclistptr, macptr, rangelistptr + // New in DWARF 4, but clang can generate them with -gdwarf-2. + // Section reference, replacing use of formData4 and formData8. + case formSecOffset, formGnuRefAlt, formGnuStrpAlt: + is64, known := b.format.dwarf64() + if !known { + b.error("unknown size for form 0x" + strconv.FormatInt(int64(fmt), 16)) + } else if is64 { + val = int64(b.uint64()) + } else { + val = int64(b.uint32()) + } + + // exprloc + // New in DWARF 4. + case formExprloc: + val = b.bytes(int(b.uint())) + + // reference + // New in DWARF 4. + case formRefSig8: + // 64-bit type signature. + val = b.uint64() + case formRefSup4: + val = b.uint32() + case formRefSup8: + val = b.uint64() + + // loclist + case formLoclistx: + val = b.uint() + + // rnglist + case formRnglistx: + off := b.uint() + + // We have to adjust by the rnglists_base of + // the compilation unit. This won't work if + // the program uses Reader.Seek to skip over + // the unit. Not much we can do about that. + var rnglistsBase int64 + if cu != nil { + rnglistsBase, _ = cu.Val(AttrRnglistsBase).(int64) + } else if a.tag == TagCompileUnit { + delay = append(delay, delayed{i, off, formRnglistx}) + break + } + + val = resolveRnglistx(uint64(rnglistsBase), off) + } + + e.Field[i].Val = val + } + if b.err != nil { + return nil + } + + for _, del := range delay { + switch del.fmt { + case formAddrx: + addrBase, _ := e.Val(AttrAddrBase).(int64) + val, err := b.dwarf.debugAddr(b.format, uint64(addrBase), del.off) + if err != nil { + b.err = err + return nil + } + e.Field[del.idx].Val = val + case formStrx: + strBase, _ := e.Val(AttrStrOffsetsBase).(int64) + e.Field[del.idx].Val = resolveStrx(uint64(strBase), del.off) + if b.err != nil { + return nil + } + case formRnglistx: + rnglistsBase, _ := e.Val(AttrRnglistsBase).(int64) + e.Field[del.idx].Val = resolveRnglistx(uint64(rnglistsBase), del.off) + if b.err != nil { + return nil + } + } + } + + return e +} + +// A Reader allows reading [Entry] structures from a DWARF “info” section. +// The [Entry] structures are arranged in a tree. The [Reader.Next] function +// return successive entries from a pre-order traversal of the tree. +// If an entry has children, its Children field will be true, and the children +// follow, terminated by an [Entry] with [Tag] 0. +type Reader struct { + b buf + d *Data + err error + unit int + lastUnit bool // set if last entry returned by Next is TagCompileUnit/TagPartialUnit + lastChildren bool // .Children of last entry returned by Next + lastSibling Offset // .Val(AttrSibling) of last entry returned by Next + cu *Entry // current compilation unit +} + +// Reader returns a new Reader for [Data]. +// The reader is positioned at byte offset 0 in the DWARF “info” section. +func (d *Data) Reader() *Reader { + r := &Reader{d: d} + r.Seek(0) + return r +} + +// AddressSize returns the size in bytes of addresses in the current compilation +// unit. +func (r *Reader) AddressSize() int { + return r.d.unit[r.unit].asize +} + +// ByteOrder returns the byte order in the current compilation unit. +func (r *Reader) ByteOrder() binary.ByteOrder { + return r.b.order +} + +// Seek positions the [Reader] at offset off in the encoded entry stream. +// Offset 0 can be used to denote the first entry. +func (r *Reader) Seek(off Offset) { + d := r.d + r.err = nil + r.lastChildren = false + if off == 0 { + if len(d.unit) == 0 { + return + } + u := &d.unit[0] + r.unit = 0 + r.b = makeBuf(r.d, u, "info", u.off, u.data) + r.cu = nil + return + } + + i := d.offsetToUnit(off) + if i == -1 { + r.err = errors.New("offset out of range") + return + } + if i != r.unit { + r.cu = nil + } + u := &d.unit[i] + r.unit = i + r.b = makeBuf(r.d, u, "info", off, u.data[off-u.off:]) +} + +// maybeNextUnit advances to the next unit if this one is finished. +func (r *Reader) maybeNextUnit() { + for len(r.b.data) == 0 && r.unit+1 < len(r.d.unit) { + r.nextUnit() + } +} + +// nextUnit advances to the next unit. +func (r *Reader) nextUnit() { + r.unit++ + u := &r.d.unit[r.unit] + r.b = makeBuf(r.d, u, "info", u.off, u.data) + r.cu = nil +} + +// Next reads the next entry from the encoded entry stream. +// It returns nil, nil when it reaches the end of the section. +// It returns an error if the current offset is invalid or the data at the +// offset cannot be decoded as a valid [Entry]. +func (r *Reader) Next() (*Entry, error) { + if r.err != nil { + return nil, r.err + } + r.maybeNextUnit() + if len(r.b.data) == 0 { + return nil, nil + } + u := &r.d.unit[r.unit] + e := r.b.entry(r.cu, u.atable, u.base, u.vers) + if r.b.err != nil { + r.err = r.b.err + return nil, r.err + } + r.lastUnit = false + if e != nil { + r.lastChildren = e.Children + if r.lastChildren { + r.lastSibling, _ = e.Val(AttrSibling).(Offset) + } + if e.Tag == TagCompileUnit || e.Tag == TagPartialUnit { + r.lastUnit = true + r.cu = e + } + } else { + r.lastChildren = false + } + return e, nil +} + +// SkipChildren skips over the child entries associated with +// the last [Entry] returned by [Reader.Next]. If that [Entry] did not have +// children or [Reader.Next] has not been called, SkipChildren is a no-op. +func (r *Reader) SkipChildren() { + if r.err != nil || !r.lastChildren { + return + } + + // If the last entry had a sibling attribute, + // that attribute gives the offset of the next + // sibling, so we can avoid decoding the + // child subtrees. + if r.lastSibling >= r.b.off { + r.Seek(r.lastSibling) + return + } + + if r.lastUnit && r.unit+1 < len(r.d.unit) { + r.nextUnit() + return + } + + for { + e, err := r.Next() + if err != nil || e == nil || e.Tag == 0 { + break + } + if e.Children { + r.SkipChildren() + } + } +} + +// clone returns a copy of the reader. This is used by the typeReader +// interface. +func (r *Reader) clone() typeReader { + return r.d.Reader() +} + +// offset returns the current buffer offset. This is used by the +// typeReader interface. +func (r *Reader) offset() Offset { + return r.b.off +} + +// SeekPC returns the [Entry] for the compilation unit that includes pc, +// and positions the reader to read the children of that unit. If pc +// is not covered by any unit, SeekPC returns [ErrUnknownPC] and the +// position of the reader is undefined. +// +// Because compilation units can describe multiple regions of the +// executable, in the worst case SeekPC must search through all the +// ranges in all the compilation units. Each call to SeekPC starts the +// search at the compilation unit of the last call, so in general +// looking up a series of PCs will be faster if they are sorted. If +// the caller wishes to do repeated fast PC lookups, it should build +// an appropriate index using the Ranges method. +func (r *Reader) SeekPC(pc uint64) (*Entry, error) { + unit := r.unit + for i := 0; i < len(r.d.unit); i++ { + if unit >= len(r.d.unit) { + unit = 0 + } + r.err = nil + r.lastChildren = false + r.unit = unit + r.cu = nil + u := &r.d.unit[unit] + r.b = makeBuf(r.d, u, "info", u.off, u.data) + e, err := r.Next() + if err != nil { + return nil, err + } + if e == nil || e.Tag == 0 { + return nil, ErrUnknownPC + } + ranges, err := r.d.Ranges(e) + if err != nil { + return nil, err + } + for _, pcs := range ranges { + if pcs[0] <= pc && pc < pcs[1] { + return e, nil + } + } + unit++ + } + return nil, ErrUnknownPC +} + +// Ranges returns the PC ranges covered by e, a slice of [low,high) pairs. +// Only some entry types, such as [TagCompileUnit] or [TagSubprogram], have PC +// ranges; for others, this will return nil with no error. +func (d *Data) Ranges(e *Entry) ([][2]uint64, error) { + var ret [][2]uint64 + + low, lowOK := e.Val(AttrLowpc).(uint64) + + var high uint64 + var highOK bool + highField := e.AttrField(AttrHighpc) + if highField != nil { + switch highField.Class { + case ClassAddress: + high, highOK = highField.Val.(uint64) + case ClassConstant: + off, ok := highField.Val.(int64) + if ok { + high = low + uint64(off) + highOK = true + } + } + } + + if lowOK && highOK { + ret = append(ret, [2]uint64{low, high}) + } + + var u *unit + if uidx := d.offsetToUnit(e.Offset); uidx >= 0 && uidx < len(d.unit) { + u = &d.unit[uidx] + } + + if u != nil && u.vers >= 5 && d.rngLists != nil { + // DWARF version 5 and later + field := e.AttrField(AttrRanges) + if field == nil { + return ret, nil + } + switch field.Class { + case ClassRangeListPtr: + ranges, rangesOK := field.Val.(int64) + if !rangesOK { + return ret, nil + } + cu, base, err := d.baseAddressForEntry(e) + if err != nil { + return nil, err + } + return d.dwarf5Ranges(u, cu, base, ranges, ret) + + case ClassRngList: + rnglist, ok := field.Val.(uint64) + if !ok { + return ret, nil + } + cu, base, err := d.baseAddressForEntry(e) + if err != nil { + return nil, err + } + return d.dwarf5Ranges(u, cu, base, int64(rnglist), ret) + + default: + return ret, nil + } + } + + // DWARF version 2 through 4 + ranges, rangesOK := e.Val(AttrRanges).(int64) + if rangesOK && d.ranges != nil { + _, base, err := d.baseAddressForEntry(e) + if err != nil { + return nil, err + } + return d.dwarf2Ranges(u, base, ranges, ret) + } + + return ret, nil +} + +// baseAddressForEntry returns the initial base address to be used when +// looking up the range list of entry e. +// DWARF specifies that this should be the lowpc attribute of the enclosing +// compilation unit, however comments in gdb/dwarf2read.c say that some +// versions of GCC use the entrypc attribute, so we check that too. +func (d *Data) baseAddressForEntry(e *Entry) (*Entry, uint64, error) { + var cu *Entry + if e.Tag == TagCompileUnit { + cu = e + } else { + i := d.offsetToUnit(e.Offset) + if i == -1 { + return nil, 0, errors.New("no unit for entry") + } + u := &d.unit[i] + b := makeBuf(d, u, "info", u.off, u.data) + cu = b.entry(nil, u.atable, u.base, u.vers) + if b.err != nil { + return nil, 0, b.err + } + } + + if cuEntry, cuEntryOK := cu.Val(AttrEntrypc).(uint64); cuEntryOK { + return cu, cuEntry, nil + } else if cuLow, cuLowOK := cu.Val(AttrLowpc).(uint64); cuLowOK { + return cu, cuLow, nil + } + + return cu, 0, nil +} + +func (d *Data) dwarf2Ranges(u *unit, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) { + if ranges < 0 || ranges > int64(len(d.ranges)) { + return nil, fmt.Errorf("invalid range offset %d (max %d)", ranges, len(d.ranges)) + } + buf := makeBuf(d, u, "ranges", Offset(ranges), d.ranges[ranges:]) + for len(buf.data) > 0 { + low := buf.addr() + high := buf.addr() + + if low == 0 && high == 0 { + break + } + + if low == ^uint64(0)>>uint((8-u.addrsize())*8) { + base = high + } else { + ret = append(ret, [2]uint64{base + low, base + high}) + } + } + + return ret, nil +} + +// dwarf5Ranges interprets a debug_rnglists sequence, see DWARFv5 section +// 2.17.3 (page 53). +func (d *Data) dwarf5Ranges(u *unit, cu *Entry, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) { + if ranges < 0 || ranges > int64(len(d.rngLists)) { + return nil, fmt.Errorf("invalid rnglist offset %d (max %d)", ranges, len(d.ranges)) + } + var addrBase int64 + if cu != nil { + addrBase, _ = cu.Val(AttrAddrBase).(int64) + } + + buf := makeBuf(d, u, "rnglists", 0, d.rngLists) + buf.skip(int(ranges)) + for { + opcode := buf.uint8() + switch opcode { + case rleEndOfList: + if buf.err != nil { + return nil, buf.err + } + return ret, nil + + case rleBaseAddressx: + baseIdx := buf.uint() + var err error + base, err = d.debugAddr(u, uint64(addrBase), baseIdx) + if err != nil { + return nil, err + } + + case rleStartxEndx: + startIdx := buf.uint() + endIdx := buf.uint() + + start, err := d.debugAddr(u, uint64(addrBase), startIdx) + if err != nil { + return nil, err + } + end, err := d.debugAddr(u, uint64(addrBase), endIdx) + if err != nil { + return nil, err + } + ret = append(ret, [2]uint64{start, end}) + + case rleStartxLength: + startIdx := buf.uint() + len := buf.uint() + start, err := d.debugAddr(u, uint64(addrBase), startIdx) + if err != nil { + return nil, err + } + ret = append(ret, [2]uint64{start, start + len}) + + case rleOffsetPair: + off1 := buf.uint() + off2 := buf.uint() + ret = append(ret, [2]uint64{base + off1, base + off2}) + + case rleBaseAddress: + base = buf.addr() + + case rleStartEnd: + start := buf.addr() + end := buf.addr() + ret = append(ret, [2]uint64{start, end}) + + case rleStartLength: + start := buf.addr() + len := buf.uint() + ret = append(ret, [2]uint64{start, start + len}) + } + } +} + +// debugAddr returns the address at idx in debug_addr +func (d *Data) debugAddr(format dataFormat, addrBase, idx uint64) (uint64, error) { + off := idx*uint64(format.addrsize()) + addrBase + + if uint64(int(off)) != off { + return 0, errors.New("offset out of range") + } + + b := makeBuf(d, format, "addr", 0, d.addr) + b.skip(int(off)) + val := b.addr() + if b.err != nil { + return 0, b.err + } + return val, nil +} diff --git a/src/debug/dwarf/entry_test.go b/src/debug/dwarf/entry_test.go new file mode 100644 index 0000000..1ce1c98 --- /dev/null +++ b/src/debug/dwarf/entry_test.go @@ -0,0 +1,459 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf_test + +import ( + . "debug/dwarf" + "encoding/binary" + "path/filepath" + "reflect" + "testing" +) + +func TestSplit(t *testing.T) { + // debug/dwarf doesn't (currently) support split DWARF, but + // the attributes that pointed to the split DWARF used to + // cause loading the DWARF data to fail entirely (issue + // #12592). Test that we can at least read the DWARF data. + d := elfData(t, "testdata/split.elf") + r := d.Reader() + e, err := r.Next() + if err != nil { + t.Fatal(err) + } + if e.Tag != TagCompileUnit { + t.Fatalf("bad tag: have %s, want %s", e.Tag, TagCompileUnit) + } + // Check that we were able to parse the unknown section offset + // field, even if we can't figure out its DWARF class. + const AttrGNUAddrBase Attr = 0x2133 + f := e.AttrField(AttrGNUAddrBase) + if _, ok := f.Val.(int64); !ok { + t.Fatalf("bad attribute value type: have %T, want int64", f.Val) + } + if f.Class != ClassUnknown { + t.Fatalf("bad class: have %s, want %s", f.Class, ClassUnknown) + } +} + +// wantRange maps from a PC to the ranges of the compilation unit +// containing that PC. +type wantRange struct { + pc uint64 + ranges [][2]uint64 +} + +func TestReaderSeek(t *testing.T) { + want := []wantRange{ + {0x40059d, [][2]uint64{{0x40059d, 0x400601}}}, + {0x400600, [][2]uint64{{0x40059d, 0x400601}}}, + {0x400601, [][2]uint64{{0x400601, 0x400611}}}, + {0x4005f0, [][2]uint64{{0x40059d, 0x400601}}}, // loop test + {0x10, nil}, + {0x400611, nil}, + } + testRanges(t, "testdata/line-gcc.elf", want) + + want = []wantRange{ + {0x401122, [][2]uint64{{0x401122, 0x401166}}}, + {0x401165, [][2]uint64{{0x401122, 0x401166}}}, + {0x401166, [][2]uint64{{0x401166, 0x401179}}}, + } + testRanges(t, "testdata/line-gcc-dwarf5.elf", want) + + want = []wantRange{ + {0x401130, [][2]uint64{{0x401130, 0x40117e}}}, + {0x40117d, [][2]uint64{{0x401130, 0x40117e}}}, + {0x40117e, nil}, + } + testRanges(t, "testdata/line-clang-dwarf5.elf", want) + + want = []wantRange{ + {0x401126, [][2]uint64{{0x401126, 0x40116a}}}, + {0x40116a, [][2]uint64{{0x40116a, 0x401180}}}, + } + testRanges(t, "testdata/line-gcc-zstd.elf", want) +} + +func TestRangesSection(t *testing.T) { + want := []wantRange{ + {0x400500, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}}, + {0x400400, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}}, + {0x400548, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}}, + {0x400407, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}}, + {0x400408, nil}, + {0x400449, nil}, + {0x4003ff, nil}, + } + testRanges(t, "testdata/ranges.elf", want) +} + +func TestRangesRnglistx(t *testing.T) { + want := []wantRange{ + {0x401000, [][2]uint64{{0x401020, 0x40102c}, {0x401000, 0x40101d}}}, + {0x40101c, [][2]uint64{{0x401020, 0x40102c}, {0x401000, 0x40101d}}}, + {0x40101d, nil}, + {0x40101f, nil}, + {0x401020, [][2]uint64{{0x401020, 0x40102c}, {0x401000, 0x40101d}}}, + {0x40102b, [][2]uint64{{0x401020, 0x40102c}, {0x401000, 0x40101d}}}, + {0x40102c, nil}, + } + testRanges(t, "testdata/rnglistx.elf", want) +} + +func testRanges(t *testing.T, name string, want []wantRange) { + d := elfData(t, name) + r := d.Reader() + for _, w := range want { + entry, err := r.SeekPC(w.pc) + if err != nil { + if w.ranges != nil { + t.Errorf("%s: missing Entry for %#x", name, w.pc) + } + if err != ErrUnknownPC { + t.Errorf("%s: expected ErrUnknownPC for %#x, got %v", name, w.pc, err) + } + continue + } + + ranges, err := d.Ranges(entry) + if err != nil { + t.Errorf("%s: %v", name, err) + continue + } + if !reflect.DeepEqual(ranges, w.ranges) { + t.Errorf("%s: for %#x got %x, expected %x", name, w.pc, ranges, w.ranges) + } + } +} + +func TestReaderRanges(t *testing.T) { + type subprograms []struct { + name string + ranges [][2]uint64 + } + tests := []struct { + filename string + subprograms subprograms + }{ + { + "testdata/line-gcc.elf", + subprograms{ + {"f1", [][2]uint64{{0x40059d, 0x4005e7}}}, + {"main", [][2]uint64{{0x4005e7, 0x400601}}}, + {"f2", [][2]uint64{{0x400601, 0x400611}}}, + }, + }, + { + "testdata/line-gcc-dwarf5.elf", + subprograms{ + {"main", [][2]uint64{{0x401147, 0x401166}}}, + {"f1", [][2]uint64{{0x401122, 0x401147}}}, + {"f2", [][2]uint64{{0x401166, 0x401179}}}, + }, + }, + { + "testdata/line-clang-dwarf5.elf", + subprograms{ + {"main", [][2]uint64{{0x401130, 0x401144}}}, + {"f1", [][2]uint64{{0x401150, 0x40117e}}}, + {"f2", [][2]uint64{{0x401180, 0x401197}}}, + }, + }, + { + "testdata/line-gcc-zstd.elf", + subprograms{ + {"f2", nil}, + {"main", [][2]uint64{{0x40114b, 0x40116a}}}, + {"f1", [][2]uint64{{0x401126, 0x40114b}}}, + {"f2", [][2]uint64{{0x40116a, 0x401180}}}, + }, + }, + } + + for _, test := range tests { + d := elfData(t, test.filename) + subprograms := test.subprograms + + r := d.Reader() + i := 0 + for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() { + if entry.Tag != TagSubprogram { + continue + } + + if i > len(subprograms) { + t.Fatalf("%s: too many subprograms (expected at most %d)", test.filename, i) + } + + if got := entry.Val(AttrName).(string); got != subprograms[i].name { + t.Errorf("%s: subprogram %d name is %s, expected %s", test.filename, i, got, subprograms[i].name) + } + ranges, err := d.Ranges(entry) + if err != nil { + t.Errorf("%s: subprogram %d: %v", test.filename, i, err) + continue + } + if !reflect.DeepEqual(ranges, subprograms[i].ranges) { + t.Errorf("%s: subprogram %d ranges are %x, expected %x", test.filename, i, ranges, subprograms[i].ranges) + } + i++ + } + + if i < len(subprograms) { + t.Errorf("%s: saw only %d subprograms, expected %d", test.filename, i, len(subprograms)) + } + } +} + +func Test64Bit(t *testing.T) { + // I don't know how to generate a 64-bit DWARF debug + // compilation unit except by using XCOFF, so this is + // hand-written. + tests := []struct { + name string + info []byte + addrSize int + byteOrder binary.ByteOrder + }{ + { + "32-bit little", + []byte{0x30, 0, 0, 0, // comp unit length + 4, 0, // DWARF version 4 + 0, 0, 0, 0, // abbrev offset + 8, // address size + 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + 8, binary.LittleEndian, + }, + { + "64-bit little", + []byte{0xff, 0xff, 0xff, 0xff, // 64-bit DWARF + 0x30, 0, 0, 0, 0, 0, 0, 0, // comp unit length + 4, 0, // DWARF version 4 + 0, 0, 0, 0, 0, 0, 0, 0, // abbrev offset + 8, // address size + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + 8, binary.LittleEndian, + }, + { + "64-bit big", + []byte{0xff, 0xff, 0xff, 0xff, // 64-bit DWARF + 0, 0, 0, 0, 0, 0, 0, 0x30, // comp unit length + 0, 4, // DWARF version 4 + 0, 0, 0, 0, 0, 0, 0, 0, // abbrev offset + 8, // address size + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + 8, binary.BigEndian, + }, + } + + for _, test := range tests { + data, err := New(nil, nil, nil, test.info, nil, nil, nil, nil) + if err != nil { + t.Errorf("%s: %v", test.name, err) + } + + r := data.Reader() + if r.AddressSize() != test.addrSize { + t.Errorf("%s: got address size %d, want %d", test.name, r.AddressSize(), test.addrSize) + } + if r.ByteOrder() != test.byteOrder { + t.Errorf("%s: got byte order %s, want %s", test.name, r.ByteOrder(), test.byteOrder) + } + } +} + +func TestUnitIteration(t *testing.T) { + // Iterate over all ELF test files we have and ensure that + // we get the same set of compilation units skipping (method 0) + // and not skipping (method 1) CU children. + files, err := filepath.Glob(filepath.Join("testdata", "*.elf")) + if err != nil { + t.Fatal(err) + } + for _, file := range files { + t.Run(file, func(t *testing.T) { + d := elfData(t, file) + var units [2][]any + for method := range units { + for r := d.Reader(); ; { + ent, err := r.Next() + if err != nil { + t.Fatal(err) + } + if ent == nil { + break + } + if ent.Tag == TagCompileUnit { + units[method] = append(units[method], ent.Val(AttrName)) + } + if method == 0 { + if ent.Tag != TagCompileUnit { + t.Fatalf("found unexpected tag %v on top level", ent.Tag) + } + r.SkipChildren() + } + } + } + t.Logf("skipping CUs: %v", units[0]) + t.Logf("not-skipping CUs: %v", units[1]) + if !reflect.DeepEqual(units[0], units[1]) { + t.Fatal("set of CUs differ") + } + }) + } +} + +func TestIssue51758(t *testing.T) { + abbrev := []byte{0x21, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x22, 0x5c, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x3a, 0x20, + 0x5c, 0x22, 0x5c, 0x5c, 0x30, 0x30, 0x35, 0x5c, 0x5c, 0x30, 0x30, + 0x30, 0x5c, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x5c, 0x30, 0x30, 0x30, + 0x5c, 0x5c, 0x30, 0x30, 0x34, 0x5c, 0x5c, 0x30, 0x30, 0x30, 0x5c, + 0x5c, 0x30, 0x30, 0x30, 0x2d, 0x5c, 0x5c, 0x30, 0x30, 0x30, 0x5c, + 0x22, 0x5c, 0x6e, 0x20, 0x20, 0x7d, 0x5c, 0x6e, 0x7d, 0x5c, 0x6e, + 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x3a, 0x20, 0x22, 0x21, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x33, 0x37, 0x37, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x6e, 0x66, 0x6f, 0x3a, 0x20, 0x22, 0x5c, 0x30, 0x30, 0x35, 0x5c, + 0x30, 0x30, 0x30, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x30, 0x30, 0x30, + 0x5c, 0x30, 0x30, 0x34, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x30, 0x30, + 0x30, 0x2d, 0x5c, 0x30, 0x30, 0x30, 0x22, 0x0a, 0x20, 0x20, 0x7d, + 0x0a, 0x7d, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x7d, + 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x7d, 0x0a, 0x6c, + 0x69, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x4e, 0x65, 0x77, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x62, 0x62, 0x72, + 0x65, 0x76, 0x3a, 0x20, 0x22, 0x5c, 0x30, 0x30, 0x35, 0x5c, 0x30, + 0x30, 0x30, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x30, 0x30, 0x30, 0x5c, + 0x30, 0x30, 0x34, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x30, 0x30, 0x30, + 0x2d, 0x5c, 0x30, 0x30, 0x30, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x7b, + 0x5c, 0x6e, 0x20, 0x20, 0x4e, 0x65, 0x77, 0x20, 0x7b, 0x5c, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x61, 0x62, 0x62, 0x72, 0x65, 0x76, 0x3a, + 0x20, 0x5c, 0x22, 0x21, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x22, 0x5c, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x3a, 0x20, 0x5c, 0x22, 0x5c, 0x5c, + 0x30, 0x30, 0x35, 0x5c, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x5c, 0x30, + 0x30, 0x30, 0x5c, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x5c, 0x30, 0x30, + 0x34, 0x5c, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x5c, 0x30, 0x30, 0x30, + 0x2d, 0x5c, 0x5c, 0x30, 0x30, 0x30, 0x5c, 0x22, 0x5c, 0x6e, 0x20, + 0x20, 0x7d, 0x5c, 0x6e, 0x7d, 0x5c, 0x6e, 0x22, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x22, 0x21, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, + 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, + 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, + 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, + 0x37, 0x5c, 0x33, 0x37, 0x37, 0x5c, 0x33, 0x37, 0x37, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff} + aranges := []byte{0x2c} + frame := []byte{} + info := []byte{0x5, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x2d, 0x0, 0x5, + 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x2d, 0x0} + + // The input above is malformed; the goal here it just to make sure + // that we don't get a panic or other bad behavior while trying to + // construct a dwarf.Data object from the input. For good measure, + // test to make sure we can handle the case where the input is + // truncated as well. + for i := 0; i <= len(info); i++ { + truncated := info[:i] + dw, err := New(abbrev, aranges, frame, truncated, nil, nil, nil, nil) + if err == nil { + t.Errorf("expected error") + } else { + if dw != nil { + t.Errorf("got non-nil dw, wanted nil") + } + } + } +} + +func TestIssue52045(t *testing.T) { + var abbrev, aranges, frame, line, pubnames, ranges, str []byte + info := []byte{0x7, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + + // A hand-crafted input corresponding to a minimal-size + // .debug_info (header only, no DIEs) and an empty abbrev table. + data0, _ := New(abbrev, aranges, frame, info, line, pubnames, ranges, str) + reader0 := data0.Reader() + entry0, _ := reader0.SeekPC(0x0) + // main goal is to make sure we can get here without crashing + if entry0 != nil { + t.Errorf("got non-nil entry0, wanted nil") + } +} diff --git a/src/debug/dwarf/export_test.go b/src/debug/dwarf/export_test.go new file mode 100644 index 0000000..b8a25ff --- /dev/null +++ b/src/debug/dwarf/export_test.go @@ -0,0 +1,7 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf + +var PathJoin = pathJoin diff --git a/src/debug/dwarf/line.go b/src/debug/dwarf/line.go new file mode 100644 index 0000000..3a02c8e --- /dev/null +++ b/src/debug/dwarf/line.go @@ -0,0 +1,852 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf + +import ( + "errors" + "fmt" + "io" + "path" + "strings" +) + +// A LineReader reads a sequence of [LineEntry] structures from a DWARF +// "line" section for a single compilation unit. LineEntries occur in +// order of increasing PC and each [LineEntry] gives metadata for the +// instructions from that [LineEntry]'s PC to just before the next +// [LineEntry]'s PC. The last entry will have the [LineEntry.EndSequence] field set. +type LineReader struct { + buf buf + + // Original .debug_line section data. Used by Seek. + section []byte + + str []byte // .debug_str + lineStr []byte // .debug_line_str + + // Header information + version uint16 + addrsize int + segmentSelectorSize int + minInstructionLength int + maxOpsPerInstruction int + defaultIsStmt bool + lineBase int + lineRange int + opcodeBase int + opcodeLengths []int + directories []string + fileEntries []*LineFile + + programOffset Offset // section offset of line number program + endOffset Offset // section offset of byte following program + + initialFileEntries int // initial length of fileEntries + + // Current line number program state machine registers + state LineEntry // public state + fileIndex int // private state +} + +// A LineEntry is a row in a DWARF line table. +type LineEntry struct { + // Address is the program-counter value of a machine + // instruction generated by the compiler. This LineEntry + // applies to each instruction from Address to just before the + // Address of the next LineEntry. + Address uint64 + + // OpIndex is the index of an operation within a VLIW + // instruction. The index of the first operation is 0. For + // non-VLIW architectures, it will always be 0. Address and + // OpIndex together form an operation pointer that can + // reference any individual operation within the instruction + // stream. + OpIndex int + + // File is the source file corresponding to these + // instructions. + File *LineFile + + // Line is the source code line number corresponding to these + // instructions. Lines are numbered beginning at 1. It may be + // 0 if these instructions cannot be attributed to any source + // line. + Line int + + // Column is the column number within the source line of these + // instructions. Columns are numbered beginning at 1. It may + // be 0 to indicate the "left edge" of the line. + Column int + + // IsStmt indicates that Address is a recommended breakpoint + // location, such as the beginning of a line, statement, or a + // distinct subpart of a statement. + IsStmt bool + + // BasicBlock indicates that Address is the beginning of a + // basic block. + BasicBlock bool + + // PrologueEnd indicates that Address is one (of possibly + // many) PCs where execution should be suspended for a + // breakpoint on entry to the containing function. + // + // Added in DWARF 3. + PrologueEnd bool + + // EpilogueBegin indicates that Address is one (of possibly + // many) PCs where execution should be suspended for a + // breakpoint on exit from this function. + // + // Added in DWARF 3. + EpilogueBegin bool + + // ISA is the instruction set architecture for these + // instructions. Possible ISA values should be defined by the + // applicable ABI specification. + // + // Added in DWARF 3. + ISA int + + // Discriminator is an arbitrary integer indicating the block + // to which these instructions belong. It serves to + // distinguish among multiple blocks that may all have with + // the same source file, line, and column. Where only one + // block exists for a given source position, it should be 0. + // + // Added in DWARF 3. + Discriminator int + + // EndSequence indicates that Address is the first byte after + // the end of a sequence of target machine instructions. If it + // is set, only this and the Address field are meaningful. A + // line number table may contain information for multiple + // potentially disjoint instruction sequences. The last entry + // in a line table should always have EndSequence set. + EndSequence bool +} + +// A LineFile is a source file referenced by a DWARF line table entry. +type LineFile struct { + Name string + Mtime uint64 // Implementation defined modification time, or 0 if unknown + Length int // File length, or 0 if unknown +} + +// LineReader returns a new reader for the line table of compilation +// unit cu, which must be an [Entry] with tag [TagCompileUnit]. +// +// If this compilation unit has no line table, it returns nil, nil. +func (d *Data) LineReader(cu *Entry) (*LineReader, error) { + if d.line == nil { + // No line tables available. + return nil, nil + } + + // Get line table information from cu. + off, ok := cu.Val(AttrStmtList).(int64) + if !ok { + // cu has no line table. + return nil, nil + } + if off < 0 || off > int64(len(d.line)) { + return nil, errors.New("AttrStmtList value out of range") + } + // AttrCompDir is optional if all file names are absolute. Use + // the empty string if it's not present. + compDir, _ := cu.Val(AttrCompDir).(string) + + // Create the LineReader. + u := &d.unit[d.offsetToUnit(cu.Offset)] + buf := makeBuf(d, u, "line", Offset(off), d.line[off:]) + // The compilation directory is implicitly directories[0]. + r := LineReader{ + buf: buf, + section: d.line, + str: d.str, + lineStr: d.lineStr, + } + + // Read the header. + if err := r.readHeader(compDir); err != nil { + return nil, err + } + + // Initialize line reader state. + r.Reset() + + return &r, nil +} + +// readHeader reads the line number program header from r.buf and sets +// all of the header fields in r. +func (r *LineReader) readHeader(compDir string) error { + buf := &r.buf + + // Read basic header fields [DWARF2 6.2.4]. + hdrOffset := buf.off + unitLength, dwarf64 := buf.unitLength() + r.endOffset = buf.off + unitLength + if r.endOffset > buf.off+Offset(len(buf.data)) { + return DecodeError{"line", hdrOffset, fmt.Sprintf("line table end %d exceeds section size %d", r.endOffset, buf.off+Offset(len(buf.data)))} + } + r.version = buf.uint16() + if buf.err == nil && (r.version < 2 || r.version > 5) { + // DWARF goes to all this effort to make new opcodes + // backward-compatible, and then adds fields right in + // the middle of the header in new versions, so we're + // picky about only supporting known line table + // versions. + return DecodeError{"line", hdrOffset, fmt.Sprintf("unknown line table version %d", r.version)} + } + if r.version >= 5 { + r.addrsize = int(buf.uint8()) + r.segmentSelectorSize = int(buf.uint8()) + } else { + r.addrsize = buf.format.addrsize() + r.segmentSelectorSize = 0 + } + var headerLength Offset + if dwarf64 { + headerLength = Offset(buf.uint64()) + } else { + headerLength = Offset(buf.uint32()) + } + programOffset := buf.off + headerLength + if programOffset > r.endOffset { + return DecodeError{"line", hdrOffset, fmt.Sprintf("malformed line table: program offset %d exceeds end offset %d", programOffset, r.endOffset)} + } + r.programOffset = programOffset + r.minInstructionLength = int(buf.uint8()) + if r.version >= 4 { + // [DWARF4 6.2.4] + r.maxOpsPerInstruction = int(buf.uint8()) + } else { + r.maxOpsPerInstruction = 1 + } + r.defaultIsStmt = buf.uint8() != 0 + r.lineBase = int(int8(buf.uint8())) + r.lineRange = int(buf.uint8()) + + // Validate header. + if buf.err != nil { + return buf.err + } + if r.maxOpsPerInstruction == 0 { + return DecodeError{"line", hdrOffset, "invalid maximum operations per instruction: 0"} + } + if r.lineRange == 0 { + return DecodeError{"line", hdrOffset, "invalid line range: 0"} + } + + // Read standard opcode length table. This table starts with opcode 1. + r.opcodeBase = int(buf.uint8()) + r.opcodeLengths = make([]int, r.opcodeBase) + for i := 1; i < r.opcodeBase; i++ { + r.opcodeLengths[i] = int(buf.uint8()) + } + + // Validate opcode lengths. + if buf.err != nil { + return buf.err + } + for i, length := range r.opcodeLengths { + if known, ok := knownOpcodeLengths[i]; ok && known != length { + return DecodeError{"line", hdrOffset, fmt.Sprintf("opcode %d expected to have length %d, but has length %d", i, known, length)} + } + } + + if r.version < 5 { + // Read include directories table. + r.directories = []string{compDir} + for { + directory := buf.string() + if buf.err != nil { + return buf.err + } + if len(directory) == 0 { + break + } + if !pathIsAbs(directory) { + // Relative paths are implicitly relative to + // the compilation directory. + directory = pathJoin(compDir, directory) + } + r.directories = append(r.directories, directory) + } + + // Read file name list. File numbering starts with 1, + // so leave the first entry nil. + r.fileEntries = make([]*LineFile, 1) + for { + if done, err := r.readFileEntry(); err != nil { + return err + } else if done { + break + } + } + } else { + dirFormat := r.readLNCTFormat() + c := buf.uint() + r.directories = make([]string, c) + for i := range r.directories { + dir, _, _, err := r.readLNCT(dirFormat, dwarf64) + if err != nil { + return err + } + r.directories[i] = dir + } + fileFormat := r.readLNCTFormat() + c = buf.uint() + r.fileEntries = make([]*LineFile, c) + for i := range r.fileEntries { + name, mtime, size, err := r.readLNCT(fileFormat, dwarf64) + if err != nil { + return err + } + r.fileEntries[i] = &LineFile{name, mtime, int(size)} + } + } + + r.initialFileEntries = len(r.fileEntries) + + return buf.err +} + +// lnctForm is a pair of an LNCT code and a form. This represents an +// entry in the directory name or file name description in the DWARF 5 +// line number program header. +type lnctForm struct { + lnct int + form format +} + +// readLNCTFormat reads an LNCT format description. +func (r *LineReader) readLNCTFormat() []lnctForm { + c := r.buf.uint8() + ret := make([]lnctForm, c) + for i := range ret { + ret[i].lnct = int(r.buf.uint()) + ret[i].form = format(r.buf.uint()) + } + return ret +} + +// readLNCT reads a sequence of LNCT entries and returns path information. +func (r *LineReader) readLNCT(s []lnctForm, dwarf64 bool) (path string, mtime uint64, size uint64, err error) { + var dir string + for _, lf := range s { + var str string + var val uint64 + switch lf.form { + case formString: + str = r.buf.string() + case formStrp, formLineStrp: + var off uint64 + if dwarf64 { + off = r.buf.uint64() + } else { + off = uint64(r.buf.uint32()) + } + if uint64(int(off)) != off { + return "", 0, 0, DecodeError{"line", r.buf.off, "strp/line_strp offset out of range"} + } + var b1 buf + if lf.form == formStrp { + b1 = makeBuf(r.buf.dwarf, r.buf.format, "str", 0, r.str) + } else { + b1 = makeBuf(r.buf.dwarf, r.buf.format, "line_str", 0, r.lineStr) + } + b1.skip(int(off)) + str = b1.string() + if b1.err != nil { + return "", 0, 0, DecodeError{"line", r.buf.off, b1.err.Error()} + } + case formStrpSup: + // Supplemental sections not yet supported. + if dwarf64 { + r.buf.uint64() + } else { + r.buf.uint32() + } + case formStrx: + // .debug_line.dwo sections not yet supported. + r.buf.uint() + case formStrx1: + r.buf.uint8() + case formStrx2: + r.buf.uint16() + case formStrx3: + r.buf.uint24() + case formStrx4: + r.buf.uint32() + case formData1: + val = uint64(r.buf.uint8()) + case formData2: + val = uint64(r.buf.uint16()) + case formData4: + val = uint64(r.buf.uint32()) + case formData8: + val = r.buf.uint64() + case formData16: + r.buf.bytes(16) + case formDwarfBlock: + r.buf.bytes(int(r.buf.uint())) + case formUdata: + val = r.buf.uint() + } + + switch lf.lnct { + case lnctPath: + path = str + case lnctDirectoryIndex: + if val >= uint64(len(r.directories)) { + return "", 0, 0, DecodeError{"line", r.buf.off, "directory index out of range"} + } + dir = r.directories[val] + case lnctTimestamp: + mtime = val + case lnctSize: + size = val + case lnctMD5: + // Ignored. + } + } + + if dir != "" && path != "" { + path = pathJoin(dir, path) + } + + return path, mtime, size, nil +} + +// readFileEntry reads a file entry from either the header or a +// DW_LNE_define_file extended opcode and adds it to r.fileEntries. A +// true return value indicates that there are no more entries to read. +func (r *LineReader) readFileEntry() (bool, error) { + name := r.buf.string() + if r.buf.err != nil { + return false, r.buf.err + } + if len(name) == 0 { + return true, nil + } + off := r.buf.off + dirIndex := int(r.buf.uint()) + if !pathIsAbs(name) { + if dirIndex >= len(r.directories) { + return false, DecodeError{"line", off, "directory index too large"} + } + name = pathJoin(r.directories[dirIndex], name) + } + mtime := r.buf.uint() + length := int(r.buf.uint()) + + // If this is a dynamically added path and the cursor was + // backed up, we may have already added this entry. Avoid + // updating existing line table entries in this case. This + // avoids an allocation and potential racy access to the slice + // backing store if the user called Files. + if len(r.fileEntries) < cap(r.fileEntries) { + fe := r.fileEntries[:len(r.fileEntries)+1] + if fe[len(fe)-1] != nil { + // We already processed this addition. + r.fileEntries = fe + return false, nil + } + } + r.fileEntries = append(r.fileEntries, &LineFile{name, mtime, length}) + return false, nil +} + +// updateFile updates r.state.File after r.fileIndex has +// changed or r.fileEntries has changed. +func (r *LineReader) updateFile() { + if r.fileIndex < len(r.fileEntries) { + r.state.File = r.fileEntries[r.fileIndex] + } else { + r.state.File = nil + } +} + +// Next sets *entry to the next row in this line table and moves to +// the next row. If there are no more entries and the line table is +// properly terminated, it returns [io.EOF]. +// +// Rows are always in order of increasing entry.Address, but +// entry.Line may go forward or backward. +func (r *LineReader) Next(entry *LineEntry) error { + if r.buf.err != nil { + return r.buf.err + } + + // Execute opcodes until we reach an opcode that emits a line + // table entry. + for { + if len(r.buf.data) == 0 { + return io.EOF + } + emit := r.step(entry) + if r.buf.err != nil { + return r.buf.err + } + if emit { + return nil + } + } +} + +// knownOpcodeLengths gives the opcode lengths (in varint arguments) +// of known standard opcodes. +var knownOpcodeLengths = map[int]int{ + lnsCopy: 0, + lnsAdvancePC: 1, + lnsAdvanceLine: 1, + lnsSetFile: 1, + lnsNegateStmt: 0, + lnsSetBasicBlock: 0, + lnsConstAddPC: 0, + lnsSetPrologueEnd: 0, + lnsSetEpilogueBegin: 0, + lnsSetISA: 1, + // lnsFixedAdvancePC takes a uint8 rather than a varint; it's + // unclear what length the header is supposed to claim, so + // ignore it. +} + +// step processes the next opcode and updates r.state. If the opcode +// emits a row in the line table, this updates *entry and returns +// true. +func (r *LineReader) step(entry *LineEntry) bool { + opcode := int(r.buf.uint8()) + + if opcode >= r.opcodeBase { + // Special opcode [DWARF2 6.2.5.1, DWARF4 6.2.5.1] + adjustedOpcode := opcode - r.opcodeBase + r.advancePC(adjustedOpcode / r.lineRange) + lineDelta := r.lineBase + adjustedOpcode%r.lineRange + r.state.Line += lineDelta + goto emit + } + + switch opcode { + case 0: + // Extended opcode [DWARF2 6.2.5.3] + length := Offset(r.buf.uint()) + startOff := r.buf.off + opcode := r.buf.uint8() + + switch opcode { + case lneEndSequence: + r.state.EndSequence = true + *entry = r.state + r.resetState() + + case lneSetAddress: + switch r.addrsize { + case 1: + r.state.Address = uint64(r.buf.uint8()) + case 2: + r.state.Address = uint64(r.buf.uint16()) + case 4: + r.state.Address = uint64(r.buf.uint32()) + case 8: + r.state.Address = r.buf.uint64() + default: + r.buf.error("unknown address size") + } + + case lneDefineFile: + if done, err := r.readFileEntry(); err != nil { + r.buf.err = err + return false + } else if done { + r.buf.err = DecodeError{"line", startOff, "malformed DW_LNE_define_file operation"} + return false + } + r.updateFile() + + case lneSetDiscriminator: + // [DWARF4 6.2.5.3] + r.state.Discriminator = int(r.buf.uint()) + } + + r.buf.skip(int(startOff + length - r.buf.off)) + + if opcode == lneEndSequence { + return true + } + + // Standard opcodes [DWARF2 6.2.5.2] + case lnsCopy: + goto emit + + case lnsAdvancePC: + r.advancePC(int(r.buf.uint())) + + case lnsAdvanceLine: + r.state.Line += int(r.buf.int()) + + case lnsSetFile: + r.fileIndex = int(r.buf.uint()) + r.updateFile() + + case lnsSetColumn: + r.state.Column = int(r.buf.uint()) + + case lnsNegateStmt: + r.state.IsStmt = !r.state.IsStmt + + case lnsSetBasicBlock: + r.state.BasicBlock = true + + case lnsConstAddPC: + r.advancePC((255 - r.opcodeBase) / r.lineRange) + + case lnsFixedAdvancePC: + r.state.Address += uint64(r.buf.uint16()) + + // DWARF3 standard opcodes [DWARF3 6.2.5.2] + case lnsSetPrologueEnd: + r.state.PrologueEnd = true + + case lnsSetEpilogueBegin: + r.state.EpilogueBegin = true + + case lnsSetISA: + r.state.ISA = int(r.buf.uint()) + + default: + // Unhandled standard opcode. Skip the number of + // arguments that the prologue says this opcode has. + for i := 0; i < r.opcodeLengths[opcode]; i++ { + r.buf.uint() + } + } + return false + +emit: + *entry = r.state + r.state.BasicBlock = false + r.state.PrologueEnd = false + r.state.EpilogueBegin = false + r.state.Discriminator = 0 + return true +} + +// advancePC advances "operation pointer" (the combination of Address +// and OpIndex) in r.state by opAdvance steps. +func (r *LineReader) advancePC(opAdvance int) { + opIndex := r.state.OpIndex + opAdvance + r.state.Address += uint64(r.minInstructionLength * (opIndex / r.maxOpsPerInstruction)) + r.state.OpIndex = opIndex % r.maxOpsPerInstruction +} + +// A LineReaderPos represents a position in a line table. +type LineReaderPos struct { + // off is the current offset in the DWARF line section. + off Offset + // numFileEntries is the length of fileEntries. + numFileEntries int + // state and fileIndex are the statement machine state at + // offset off. + state LineEntry + fileIndex int +} + +// Tell returns the current position in the line table. +func (r *LineReader) Tell() LineReaderPos { + return LineReaderPos{r.buf.off, len(r.fileEntries), r.state, r.fileIndex} +} + +// Seek restores the line table reader to a position returned by [LineReader.Tell]. +// +// The argument pos must have been returned by a call to [LineReader.Tell] on this +// line table. +func (r *LineReader) Seek(pos LineReaderPos) { + r.buf.off = pos.off + r.buf.data = r.section[r.buf.off:r.endOffset] + r.fileEntries = r.fileEntries[:pos.numFileEntries] + r.state = pos.state + r.fileIndex = pos.fileIndex +} + +// Reset repositions the line table reader at the beginning of the +// line table. +func (r *LineReader) Reset() { + // Reset buffer to the line number program offset. + r.buf.off = r.programOffset + r.buf.data = r.section[r.buf.off:r.endOffset] + + // Reset file entries list. + r.fileEntries = r.fileEntries[:r.initialFileEntries] + + // Reset line number program state. + r.resetState() +} + +// resetState resets r.state to its default values +func (r *LineReader) resetState() { + // Reset the state machine registers to the defaults given in + // [DWARF4 6.2.2]. + r.state = LineEntry{ + Address: 0, + OpIndex: 0, + File: nil, + Line: 1, + Column: 0, + IsStmt: r.defaultIsStmt, + BasicBlock: false, + PrologueEnd: false, + EpilogueBegin: false, + ISA: 0, + Discriminator: 0, + } + r.fileIndex = 1 + r.updateFile() +} + +// Files returns the file name table of this compilation unit as of +// the current position in the line table. The file name table may be +// referenced from attributes in this compilation unit such as +// [AttrDeclFile]. +// +// Entry 0 is always nil, since file index 0 represents "no file". +// +// The file name table of a compilation unit is not fixed. Files +// returns the file table as of the current position in the line +// table. This may contain more entries than the file table at an +// earlier position in the line table, though existing entries never +// change. +func (r *LineReader) Files() []*LineFile { + return r.fileEntries +} + +// ErrUnknownPC is the error returned by LineReader.ScanPC when the +// seek PC is not covered by any entry in the line table. +var ErrUnknownPC = errors.New("ErrUnknownPC") + +// SeekPC sets *entry to the [LineEntry] that includes pc and positions +// the reader on the next entry in the line table. If necessary, this +// will seek backwards to find pc. +// +// If pc is not covered by any entry in this line table, SeekPC +// returns [ErrUnknownPC]. In this case, *entry and the final seek +// position are unspecified. +// +// Note that DWARF line tables only permit sequential, forward scans. +// Hence, in the worst case, this takes time linear in the size of the +// line table. If the caller wishes to do repeated fast PC lookups, it +// should build an appropriate index of the line table. +func (r *LineReader) SeekPC(pc uint64, entry *LineEntry) error { + if err := r.Next(entry); err != nil { + return err + } + if entry.Address > pc { + // We're too far. Start at the beginning of the table. + r.Reset() + if err := r.Next(entry); err != nil { + return err + } + if entry.Address > pc { + // The whole table starts after pc. + r.Reset() + return ErrUnknownPC + } + } + + // Scan until we pass pc, then back up one. + for { + var next LineEntry + pos := r.Tell() + if err := r.Next(&next); err != nil { + if err == io.EOF { + return ErrUnknownPC + } + return err + } + if next.Address > pc { + if entry.EndSequence { + // pc is in a hole in the table. + return ErrUnknownPC + } + // entry is the desired entry. Back up the + // cursor to "next" and return success. + r.Seek(pos) + return nil + } + *entry = next + } +} + +// pathIsAbs reports whether path is an absolute path (or "full path +// name" in DWARF parlance). This is in "whatever form makes sense for +// the host system", so this accepts both UNIX-style and DOS-style +// absolute paths. We avoid the filepath package because we want this +// to behave the same regardless of our host system and because we +// don't know what system the paths came from. +func pathIsAbs(path string) bool { + _, path = splitDrive(path) + return len(path) > 0 && (path[0] == '/' || path[0] == '\\') +} + +// pathJoin joins dirname and filename. filename must be relative. +// DWARF paths can be UNIX-style or DOS-style, so this handles both. +func pathJoin(dirname, filename string) string { + if len(dirname) == 0 { + return filename + } + // dirname should be absolute, which means we can determine + // whether it's a DOS path reasonably reliably by looking for + // a drive letter or UNC path. + drive, dirname := splitDrive(dirname) + if drive == "" { + // UNIX-style path. + return path.Join(dirname, filename) + } + // DOS-style path. + drive2, filename := splitDrive(filename) + if drive2 != "" { + if !strings.EqualFold(drive, drive2) { + // Different drives. There's not much we can + // do here, so just ignore the directory. + return drive2 + filename + } + // Drives are the same. Ignore drive on filename. + } + if !(strings.HasSuffix(dirname, "/") || strings.HasSuffix(dirname, `\`)) && dirname != "" { + sep := `\` + if strings.HasPrefix(dirname, "/") { + sep = `/` + } + dirname += sep + } + return drive + dirname + filename +} + +// splitDrive splits the DOS drive letter or UNC share point from +// path, if any. path == drive + rest +func splitDrive(path string) (drive, rest string) { + if len(path) >= 2 && path[1] == ':' { + if c := path[0]; 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' { + return path[:2], path[2:] + } + } + if len(path) > 3 && (path[0] == '\\' || path[0] == '/') && (path[1] == '\\' || path[1] == '/') { + // Normalize the path so we can search for just \ below. + npath := strings.Replace(path, "/", `\`, -1) + // Get the host part, which must be non-empty. + slash1 := strings.IndexByte(npath[2:], '\\') + 2 + if slash1 > 2 { + // Get the mount-point part, which must be non-empty. + slash2 := strings.IndexByte(npath[slash1+1:], '\\') + slash1 + 1 + if slash2 > slash1 { + return path[:slash2], path[slash2:] + } + } + } + return "", path +} diff --git a/src/debug/dwarf/line_test.go b/src/debug/dwarf/line_test.go new file mode 100644 index 0000000..e947d99 --- /dev/null +++ b/src/debug/dwarf/line_test.go @@ -0,0 +1,462 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf_test + +import ( + . "debug/dwarf" + "io" + "strings" + "testing" +) + +var ( + file1C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.c"} + file1H = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.h"} + file2C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line2.c"} +) + +func TestLineELFGCC(t *testing.T) { + // Generated by: + // # gcc --version | head -n1 + // gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2 + // # gcc -g -o line-gcc.elf line*.c + + // Line table based on readelf --debug-dump=rawline,decodedline + want := []LineEntry{ + {Address: 0x40059d, File: file1H, Line: 2, IsStmt: true}, + {Address: 0x4005a5, File: file1H, Line: 2, IsStmt: true}, + {Address: 0x4005b4, File: file1H, Line: 5, IsStmt: true}, + {Address: 0x4005bd, File: file1H, Line: 6, IsStmt: true, Discriminator: 2}, + {Address: 0x4005c7, File: file1H, Line: 5, IsStmt: true, Discriminator: 2}, + {Address: 0x4005cb, File: file1H, Line: 5, IsStmt: false, Discriminator: 1}, + {Address: 0x4005d1, File: file1H, Line: 7, IsStmt: true}, + {Address: 0x4005e7, File: file1C, Line: 6, IsStmt: true}, + {Address: 0x4005eb, File: file1C, Line: 7, IsStmt: true}, + {Address: 0x4005f5, File: file1C, Line: 8, IsStmt: true}, + {Address: 0x4005ff, File: file1C, Line: 9, IsStmt: true}, + {Address: 0x400601, EndSequence: true}, + + {Address: 0x400601, File: file2C, Line: 4, IsStmt: true}, + {Address: 0x400605, File: file2C, Line: 5, IsStmt: true}, + {Address: 0x40060f, File: file2C, Line: 6, IsStmt: true}, + {Address: 0x400611, EndSequence: true}, + } + files := [][]*LineFile{{nil, file1H, file1C}, {nil, file2C}} + + testLineTable(t, want, files, elfData(t, "testdata/line-gcc.elf")) +} + +func TestLineELFGCCZstd(t *testing.T) { + // Generated by: + // # gcc --version | head -n1 + // gcc (Debian 12.2.0-10) 12.2.0 + // # gcc -g -no-pie -Wl,--compress-debug-sections=zstd line*.c + + zfile1H := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line1.h"} + zfile1C := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line1.c"} + zfile2C := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line2.c"} + + // Line table based on readelf --debug-dump=rawline,decodedline + want := []LineEntry{ + {Address: 0x401126, File: zfile1H, Line: 2, Column: 1, IsStmt: true}, + {Address: 0x40112a, File: zfile1H, Line: 5, Column: 8, IsStmt: true}, + {Address: 0x401131, File: zfile1H, Line: 5, Column: 2, IsStmt: true}, + {Address: 0x401133, File: zfile1H, Line: 6, Column: 10, IsStmt: true, Discriminator: 3}, + {Address: 0x40113d, File: zfile1H, Line: 5, Column: 22, IsStmt: true, Discriminator: 3}, + {Address: 0x401141, File: zfile1H, Line: 5, Column: 15, IsStmt: true, Discriminator: 1}, + {Address: 0x401147, File: zfile1H, Line: 7, Column: 1, IsStmt: true}, + {Address: 0x40114b, File: zfile1C, Line: 6, Column: 1, IsStmt: true}, + {Address: 0x40114f, File: zfile1C, Line: 7, Column: 2, IsStmt: true}, + {Address: 0x401159, File: zfile1C, Line: 8, Column: 2, IsStmt: true}, + {Address: 0x401168, File: zfile1C, Line: 9, Column: 1, IsStmt: true}, + {Address: 0x40116a, EndSequence: true}, + + {Address: 0x40116a, File: zfile2C, Line: 4, Column: 1, IsStmt: true}, + {Address: 0x40116e, File: zfile2C, Line: 5, Column: 2, IsStmt: true}, + {Address: 0x40117d, File: zfile2C, Line: 6, Column: 1, IsStmt: true}, + {Address: 0x401180, EndSequence: true}, + } + files := [][]*LineFile{ + {zfile1C, zfile1H, zfile1C}, + {zfile2C, zfile2C}, + } + + testLineTable(t, want, files, elfData(t, "testdata/line-gcc-zstd.elf")) +} + +func TestLineGCCWindows(t *testing.T) { + // Generated by: + // > gcc --version + // gcc (tdm64-1) 4.9.2 + // > gcc -g -o line-gcc-win.bin line1.c C:\workdir\go\src\debug\dwarf\testdata\line2.c + + toWindows := func(lf *LineFile) *LineFile { + lf2 := *lf + lf2.Name = strings.Replace(lf2.Name, "/home/austin/go.dev/", "C:\\workdir\\go\\", -1) + lf2.Name = strings.Replace(lf2.Name, "/", "\\", -1) + return &lf2 + } + file1C := toWindows(file1C) + file1H := toWindows(file1H) + file2C := toWindows(file2C) + + // Line table based on objdump --dwarf=rawline,decodedline + want := []LineEntry{ + {Address: 0x401530, File: file1H, Line: 2, IsStmt: true}, + {Address: 0x401538, File: file1H, Line: 5, IsStmt: true}, + {Address: 0x401541, File: file1H, Line: 6, IsStmt: true, Discriminator: 3}, + {Address: 0x40154b, File: file1H, Line: 5, IsStmt: true, Discriminator: 3}, + {Address: 0x40154f, File: file1H, Line: 5, IsStmt: false, Discriminator: 1}, + {Address: 0x401555, File: file1H, Line: 7, IsStmt: true}, + {Address: 0x40155b, File: file1C, Line: 6, IsStmt: true}, + {Address: 0x401563, File: file1C, Line: 6, IsStmt: true}, + {Address: 0x401568, File: file1C, Line: 7, IsStmt: true}, + {Address: 0x40156d, File: file1C, Line: 8, IsStmt: true}, + {Address: 0x401572, File: file1C, Line: 9, IsStmt: true}, + {Address: 0x401578, EndSequence: true}, + + {Address: 0x401580, File: file2C, Line: 4, IsStmt: true}, + {Address: 0x401588, File: file2C, Line: 5, IsStmt: true}, + {Address: 0x401595, File: file2C, Line: 6, IsStmt: true}, + {Address: 0x40159b, EndSequence: true}, + } + files := [][]*LineFile{{nil, file1H, file1C}, {nil, file2C}} + + testLineTable(t, want, files, peData(t, "testdata/line-gcc-win.bin")) +} + +func TestLineELFClang(t *testing.T) { + // Generated by: + // # clang --version | head -n1 + // Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4) + // # clang -g -o line-clang.elf line*. + + want := []LineEntry{ + {Address: 0x400530, File: file1C, Line: 6, IsStmt: true}, + {Address: 0x400534, File: file1C, Line: 7, IsStmt: true, PrologueEnd: true}, + {Address: 0x400539, File: file1C, Line: 8, IsStmt: true}, + {Address: 0x400545, File: file1C, Line: 9, IsStmt: true}, + {Address: 0x400550, File: file1H, Line: 2, IsStmt: true}, + {Address: 0x400554, File: file1H, Line: 5, IsStmt: true, PrologueEnd: true}, + {Address: 0x400568, File: file1H, Line: 6, IsStmt: true}, + {Address: 0x400571, File: file1H, Line: 5, IsStmt: true}, + {Address: 0x400581, File: file1H, Line: 7, IsStmt: true}, + {Address: 0x400583, EndSequence: true}, + + {Address: 0x400590, File: file2C, Line: 4, IsStmt: true}, + {Address: 0x4005a0, File: file2C, Line: 5, IsStmt: true, PrologueEnd: true}, + {Address: 0x4005a7, File: file2C, Line: 6, IsStmt: true}, + {Address: 0x4005b0, EndSequence: true}, + } + files := [][]*LineFile{{nil, file1C, file1H}, {nil, file2C}} + + testLineTable(t, want, files, elfData(t, "testdata/line-clang.elf")) +} + +func TestLineRnglists(t *testing.T) { + // Test a newer file, generated by clang. + file := &LineFile{Name: "/usr/local/google/home/iant/foo.c"} + want := []LineEntry{ + {Address: 0x401020, File: file, Line: 12, IsStmt: true}, + {Address: 0x401020, File: file, Line: 13, Column: 12, IsStmt: true, PrologueEnd: true}, + {Address: 0x401022, File: file, Line: 13, Column: 7}, + {Address: 0x401024, File: file, Line: 17, Column: 1, IsStmt: true}, + {Address: 0x401027, File: file, Line: 16, Column: 10, IsStmt: true}, + {Address: 0x40102c, EndSequence: true}, + {Address: 0x401000, File: file, Line: 2, IsStmt: true}, + {Address: 0x401000, File: file, Line: 6, Column: 17, IsStmt: true, PrologueEnd: true}, + {Address: 0x401002, File: file, Line: 6, Column: 3}, + {Address: 0x401019, File: file, Line: 9, Column: 3, IsStmt: true}, + {Address: 0x40101a, File: file, Line: 0, Column: 3}, + {Address: 0x40101c, File: file, Line: 9, Column: 3}, + {Address: 0x40101d, EndSequence: true}, + } + files := [][]*LineFile{{file}} + + testLineTable(t, want, files, elfData(t, "testdata/rnglistx.elf")) +} + +func TestLineSeek(t *testing.T) { + d := elfData(t, "testdata/line-gcc.elf") + + // Get the line table for the first CU. + cu, err := d.Reader().Next() + if err != nil { + t.Fatal("d.Reader().Next:", err) + } + lr, err := d.LineReader(cu) + if err != nil { + t.Fatal("d.LineReader:", err) + } + + // Read entries forward. + var line LineEntry + var posTable []LineReaderPos + var table []LineEntry + for { + posTable = append(posTable, lr.Tell()) + + err := lr.Next(&line) + if err != nil { + if err == io.EOF { + break + } + t.Fatal("lr.Next:", err) + } + table = append(table, line) + } + + // Test that Reset returns to the first line. + lr.Reset() + if err := lr.Next(&line); err != nil { + t.Fatal("lr.Next after Reset failed:", err) + } else if line != table[0] { + t.Fatal("lr.Next after Reset returned", line, "instead of", table[0]) + } + + // Check that entries match when seeking backward. + for i := len(posTable) - 1; i >= 0; i-- { + lr.Seek(posTable[i]) + err := lr.Next(&line) + if i == len(posTable)-1 { + if err != io.EOF { + t.Fatal("expected io.EOF after seek to end, got", err) + } + } else if err != nil { + t.Fatal("lr.Next after seek to", posTable[i], "failed:", err) + } else if line != table[i] { + t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i]) + } + } + + // Check that seeking to a PC returns the right line. + if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC { + t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err) + } + for i, testLine := range table { + if testLine.EndSequence { + if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC { + t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err) + } + continue + } + + nextPC := table[i+1].Address + for pc := testLine.Address; pc < nextPC; pc++ { + if err := lr.SeekPC(pc, &line); err != nil { + t.Fatalf("lr.SeekPC to %#x failed: %v", pc, err) + } else if line != testLine { + t.Fatalf("lr.SeekPC to %#x returned %v instead of %v", pc, line, testLine) + } + } + } +} + +func testLineTable(t *testing.T, want []LineEntry, files [][]*LineFile, d *Data) { + // Get line table from d. + var got []LineEntry + dr := d.Reader() + for { + ent, err := dr.Next() + if err != nil { + t.Fatal("dr.Next:", err) + } else if ent == nil { + break + } + + if ent.Tag != TagCompileUnit { + dr.SkipChildren() + continue + } + + // Ignore system compilation units (this happens in + // the Windows binary). We'll still decode the line + // table, but won't check it. + name := ent.Val(AttrName).(string) + ignore := strings.HasPrefix(name, "C:/crossdev/") || strings.HasPrefix(name, "../../") + + // Decode CU's line table. + lr, err := d.LineReader(ent) + if err != nil { + t.Fatal("d.LineReader:", err) + } else if lr == nil { + continue + } + + for { + var line LineEntry + err := lr.Next(&line) + if err != nil { + if err == io.EOF { + break + } + t.Fatal("lr.Next:", err) + } + // Ignore sources from the Windows build environment. + if ignore { + continue + } + got = append(got, line) + } + + // Check file table. + if !ignore { + if !compareFiles(files[0], lr.Files()) { + t.Log("File tables do not match. Got:") + dumpFiles(t, lr.Files()) + t.Log("Want:") + dumpFiles(t, files[0]) + t.Fail() + } + files = files[1:] + } + } + + // Compare line tables. + if !compareLines(t, got, want) { + t.Log("Line tables do not match. Got:") + dumpLines(t, got) + t.Log("Want:") + dumpLines(t, want) + t.FailNow() + } +} + +func compareFiles(a, b []*LineFile) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] == nil && b[i] == nil { + continue + } + if a[i] != nil && b[i] != nil && a[i].Name == b[i].Name { + continue + } + return false + } + return true +} + +func dumpFiles(t *testing.T, files []*LineFile) { + for i, f := range files { + name := "<nil>" + if f != nil { + name = f.Name + } + t.Logf(" %d %s", i, name) + } +} + +func compareLines(t *testing.T, a, b []LineEntry) bool { + t.Helper() + if len(a) != len(b) { + t.Errorf("len(a) == %d, len(b) == %d", len(a), len(b)) + return false + } + + for i := range a { + al, bl := a[i], b[i] + // If both are EndSequence, then the only other valid + // field is Address. Otherwise, test equality of all + // fields. + if al.EndSequence && bl.EndSequence && al.Address == bl.Address { + continue + } + if al.File.Name != bl.File.Name { + t.Errorf("%d: name %v != name %v", i, al.File.Name, bl.File.Name) + return false + } + al.File = nil + bl.File = nil + if al != bl { + t.Errorf("%d: %#v != %#v", i, al, bl) + return false + } + } + return true +} + +func dumpLines(t *testing.T, lines []LineEntry) { + for _, l := range lines { + t.Logf(" %+v File:%+v", l, l.File) + } +} + +type joinTest struct { + dirname, filename string + path string +} + +var joinTests = []joinTest{ + {"a", "b", "a/b"}, + {"a", "", "a"}, + {"", "b", "b"}, + {"/a", "b", "/a/b"}, + {"/a/", "b", "/a/b"}, + + {`C:\Windows\`, `System32`, `C:\Windows\System32`}, + {`C:\Windows\`, ``, `C:\Windows\`}, + {`C:\`, `Windows`, `C:\Windows`}, + {`C:\Windows\`, `C:System32`, `C:\Windows\System32`}, + {`C:\Windows`, `a/b`, `C:\Windows\a/b`}, + {`\\host\share\`, `foo`, `\\host\share\foo`}, + {`\\host\share\`, `foo\bar`, `\\host\share\foo\bar`}, + {`//host/share/`, `foo/bar`, `//host/share/foo/bar`}, + + // Note: the Go compiler currently emits DWARF line table paths + // with '/' instead of '\' (see issues #19784, #36495). These + // tests are to cover cases that might come up for Windows Go + // binaries. + {`c:/workdir/go/src/x`, `y.go`, `c:/workdir/go/src/x/y.go`}, + {`d:/some/thing/`, `b.go`, `d:/some/thing/b.go`}, + {`e:\blah\`, `foo.c`, `e:\blah\foo.c`}, + + // The following are "best effort". We shouldn't see relative + // base directories in DWARF, but these test that pathJoin + // doesn't fail miserably if it sees one. + {`C:`, `a`, `C:a`}, + {`C:`, `a\b`, `C:a\b`}, + {`C:.`, `a`, `C:.\a`}, + {`C:a`, `b`, `C:a\b`}, +} + +func TestPathJoin(t *testing.T) { + for _, test := range joinTests { + got := PathJoin(test.dirname, test.filename) + if test.path != got { + t.Errorf("pathJoin(%q, %q) = %q, want %q", test.dirname, test.filename, got, test.path) + } + } +} + +func TestPathLineReaderMalformed(t *testing.T) { + // This test case drawn from issue #52354. What's happening + // here is that the stmtList attribute in the compilation + // unit is malformed (negative). + var aranges, frame, pubnames, ranges, str []byte + abbrev := []byte{0x10, 0x20, 0x20, 0x20, 0x21, 0x20, 0x10, 0x21, 0x61, + 0x0, 0x0, 0xff, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20} + info := []byte{0x0, 0x0, 0x0, 0x9, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x20, 0x10, 0x10} + line := []byte{0x20} + Data0, err := New(abbrev, aranges, frame, info, line, pubnames, ranges, str) + if err != nil { + t.Fatalf("error unexpected: %v", err) + } + Reader0 := Data0.Reader() + Entry0, err := Reader0.Next() + if err != nil { + t.Fatalf("error unexpected: %v", err) + } + LineReader0, err := Data0.LineReader(Entry0) + if err == nil { + t.Fatalf("expected error") + } + if LineReader0 != nil { + t.Fatalf("expected nil line reader") + } +} diff --git a/src/debug/dwarf/open.go b/src/debug/dwarf/open.go new file mode 100644 index 0000000..0901341 --- /dev/null +++ b/src/debug/dwarf/open.go @@ -0,0 +1,140 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package dwarf provides access to DWARF debugging information loaded from +executable files, as defined in the DWARF 2.0 Standard at +http://dwarfstd.org/doc/dwarf-2.0.0.pdf. + +# Security + +This package is not designed to be hardened against adversarial inputs, and is +outside the scope of https://go.dev/security/policy. In particular, only basic +validation is done when parsing object files. As such, care should be taken when +parsing untrusted inputs, as parsing malformed files may consume significant +resources, or cause panics. +*/ +package dwarf + +import ( + "encoding/binary" + "errors" +) + +// Data represents the DWARF debugging information +// loaded from an executable file (for example, an ELF or Mach-O executable). +type Data struct { + // raw data + abbrev []byte + aranges []byte + frame []byte + info []byte + line []byte + pubnames []byte + ranges []byte + str []byte + + // New sections added in DWARF 5. + addr []byte + lineStr []byte + strOffsets []byte + rngLists []byte + + // parsed data + abbrevCache map[uint64]abbrevTable + bigEndian bool + order binary.ByteOrder + typeCache map[Offset]Type + typeSigs map[uint64]*typeUnit + unit []unit +} + +var errSegmentSelector = errors.New("non-zero segment_selector size not supported") + +// New returns a new [Data] object initialized from the given parameters. +// Rather than calling this function directly, clients should typically use +// the DWARF method of the File type of the appropriate package [debug/elf], +// [debug/macho], or [debug/pe]. +// +// The []byte arguments are the data from the corresponding debug section +// in the object file; for example, for an ELF object, abbrev is the contents of +// the ".debug_abbrev" section. +func New(abbrev, aranges, frame, info, line, pubnames, ranges, str []byte) (*Data, error) { + d := &Data{ + abbrev: abbrev, + aranges: aranges, + frame: frame, + info: info, + line: line, + pubnames: pubnames, + ranges: ranges, + str: str, + abbrevCache: make(map[uint64]abbrevTable), + typeCache: make(map[Offset]Type), + typeSigs: make(map[uint64]*typeUnit), + } + + // Sniff .debug_info to figure out byte order. + // 32-bit DWARF: 4 byte length, 2 byte version. + // 64-bit DWARf: 4 bytes of 0xff, 8 byte length, 2 byte version. + if len(d.info) < 6 { + return nil, DecodeError{"info", Offset(len(d.info)), "too short"} + } + offset := 4 + if d.info[0] == 0xff && d.info[1] == 0xff && d.info[2] == 0xff && d.info[3] == 0xff { + if len(d.info) < 14 { + return nil, DecodeError{"info", Offset(len(d.info)), "too short"} + } + offset = 12 + } + // Fetch the version, a tiny 16-bit number (1, 2, 3, 4, 5). + x, y := d.info[offset], d.info[offset+1] + switch { + case x == 0 && y == 0: + return nil, DecodeError{"info", 4, "unsupported version 0"} + case x == 0: + d.bigEndian = true + d.order = binary.BigEndian + case y == 0: + d.bigEndian = false + d.order = binary.LittleEndian + default: + return nil, DecodeError{"info", 4, "cannot determine byte order"} + } + + u, err := d.parseUnits() + if err != nil { + return nil, err + } + d.unit = u + return d, nil +} + +// AddTypes will add one .debug_types section to the DWARF data. A +// typical object with DWARF version 4 debug info will have multiple +// .debug_types sections. The name is used for error reporting only, +// and serves to distinguish one .debug_types section from another. +func (d *Data) AddTypes(name string, types []byte) error { + return d.parseTypes(name, types) +} + +// AddSection adds another DWARF section by name. The name should be a +// DWARF section name such as ".debug_addr", ".debug_str_offsets", and +// so forth. This approach is used for new DWARF sections added in +// DWARF 5 and later. +func (d *Data) AddSection(name string, contents []byte) error { + var err error + switch name { + case ".debug_addr": + d.addr = contents + case ".debug_line_str": + d.lineStr = contents + case ".debug_str_offsets": + d.strOffsets = contents + case ".debug_rnglists": + d.rngLists = contents + } + // Just ignore names that we don't yet support. + return err +} diff --git a/src/debug/dwarf/tag_string.go b/src/debug/dwarf/tag_string.go new file mode 100644 index 0000000..b79ea17 --- /dev/null +++ b/src/debug/dwarf/tag_string.go @@ -0,0 +1,119 @@ +// Code generated by "stringer -type Tag -trimprefix=Tag"; DO NOT EDIT. + +package dwarf + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[TagArrayType-1] + _ = x[TagClassType-2] + _ = x[TagEntryPoint-3] + _ = x[TagEnumerationType-4] + _ = x[TagFormalParameter-5] + _ = x[TagImportedDeclaration-8] + _ = x[TagLabel-10] + _ = x[TagLexDwarfBlock-11] + _ = x[TagMember-13] + _ = x[TagPointerType-15] + _ = x[TagReferenceType-16] + _ = x[TagCompileUnit-17] + _ = x[TagStringType-18] + _ = x[TagStructType-19] + _ = x[TagSubroutineType-21] + _ = x[TagTypedef-22] + _ = x[TagUnionType-23] + _ = x[TagUnspecifiedParameters-24] + _ = x[TagVariant-25] + _ = x[TagCommonDwarfBlock-26] + _ = x[TagCommonInclusion-27] + _ = x[TagInheritance-28] + _ = x[TagInlinedSubroutine-29] + _ = x[TagModule-30] + _ = x[TagPtrToMemberType-31] + _ = x[TagSetType-32] + _ = x[TagSubrangeType-33] + _ = x[TagWithStmt-34] + _ = x[TagAccessDeclaration-35] + _ = x[TagBaseType-36] + _ = x[TagCatchDwarfBlock-37] + _ = x[TagConstType-38] + _ = x[TagConstant-39] + _ = x[TagEnumerator-40] + _ = x[TagFileType-41] + _ = x[TagFriend-42] + _ = x[TagNamelist-43] + _ = x[TagNamelistItem-44] + _ = x[TagPackedType-45] + _ = x[TagSubprogram-46] + _ = x[TagTemplateTypeParameter-47] + _ = x[TagTemplateValueParameter-48] + _ = x[TagThrownType-49] + _ = x[TagTryDwarfBlock-50] + _ = x[TagVariantPart-51] + _ = x[TagVariable-52] + _ = x[TagVolatileType-53] + _ = x[TagDwarfProcedure-54] + _ = x[TagRestrictType-55] + _ = x[TagInterfaceType-56] + _ = x[TagNamespace-57] + _ = x[TagImportedModule-58] + _ = x[TagUnspecifiedType-59] + _ = x[TagPartialUnit-60] + _ = x[TagImportedUnit-61] + _ = x[TagMutableType-62] + _ = x[TagCondition-63] + _ = x[TagSharedType-64] + _ = x[TagTypeUnit-65] + _ = x[TagRvalueReferenceType-66] + _ = x[TagTemplateAlias-67] + _ = x[TagCoarrayType-68] + _ = x[TagGenericSubrange-69] + _ = x[TagDynamicType-70] + _ = x[TagAtomicType-71] + _ = x[TagCallSite-72] + _ = x[TagCallSiteParameter-73] + _ = x[TagSkeletonUnit-74] + _ = x[TagImmutableType-75] +} + +const ( + _Tag_name_0 = "ArrayTypeClassTypeEntryPointEnumerationTypeFormalParameter" + _Tag_name_1 = "ImportedDeclaration" + _Tag_name_2 = "LabelLexDwarfBlock" + _Tag_name_3 = "Member" + _Tag_name_4 = "PointerTypeReferenceTypeCompileUnitStringTypeStructType" + _Tag_name_5 = "SubroutineTypeTypedefUnionTypeUnspecifiedParametersVariantCommonDwarfBlockCommonInclusionInheritanceInlinedSubroutineModulePtrToMemberTypeSetTypeSubrangeTypeWithStmtAccessDeclarationBaseTypeCatchDwarfBlockConstTypeConstantEnumeratorFileTypeFriendNamelistNamelistItemPackedTypeSubprogramTemplateTypeParameterTemplateValueParameterThrownTypeTryDwarfBlockVariantPartVariableVolatileTypeDwarfProcedureRestrictTypeInterfaceTypeNamespaceImportedModuleUnspecifiedTypePartialUnitImportedUnitMutableTypeConditionSharedTypeTypeUnitRvalueReferenceTypeTemplateAliasCoarrayTypeGenericSubrangeDynamicTypeAtomicTypeCallSiteCallSiteParameterSkeletonUnitImmutableType" +) + +var ( + _Tag_index_0 = [...]uint8{0, 9, 18, 28, 43, 58} + _Tag_index_2 = [...]uint8{0, 5, 18} + _Tag_index_4 = [...]uint8{0, 11, 24, 35, 45, 55} + _Tag_index_5 = [...]uint16{0, 14, 21, 30, 51, 58, 74, 89, 100, 117, 123, 138, 145, 157, 165, 182, 190, 205, 214, 222, 232, 240, 246, 254, 266, 276, 286, 307, 329, 339, 352, 363, 371, 383, 397, 409, 422, 431, 445, 460, 471, 483, 494, 503, 513, 521, 540, 553, 564, 579, 590, 600, 608, 625, 637, 650} +) + +func (i Tag) String() string { + switch { + case 1 <= i && i <= 5: + i -= 1 + return _Tag_name_0[_Tag_index_0[i]:_Tag_index_0[i+1]] + case i == 8: + return _Tag_name_1 + case 10 <= i && i <= 11: + i -= 10 + return _Tag_name_2[_Tag_index_2[i]:_Tag_index_2[i+1]] + case i == 13: + return _Tag_name_3 + case 15 <= i && i <= 19: + i -= 15 + return _Tag_name_4[_Tag_index_4[i]:_Tag_index_4[i+1]] + case 21 <= i && i <= 75: + i -= 21 + return _Tag_name_5[_Tag_index_5[i]:_Tag_index_5[i+1]] + default: + return "Tag(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/src/debug/dwarf/testdata/bitfields.c b/src/debug/dwarf/testdata/bitfields.c new file mode 100644 index 0000000..0583333 --- /dev/null +++ b/src/debug/dwarf/testdata/bitfields.c @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Linux ELF: +gcc -gdwarf-4 -m64 -c bitfields.c -o bitfields.elf4 +*/ + +typedef struct another_struct { + unsigned short quix; + int xyz[0]; + unsigned x:1; + long long array[40]; +} t_another_struct; +t_another_struct q2; + diff --git a/src/debug/dwarf/testdata/bitfields.elf4 b/src/debug/dwarf/testdata/bitfields.elf4 Binary files differnew file mode 100644 index 0000000..2e06e68 --- /dev/null +++ b/src/debug/dwarf/testdata/bitfields.elf4 diff --git a/src/debug/dwarf/testdata/cppunsuptypes.cc b/src/debug/dwarf/testdata/cppunsuptypes.cc new file mode 100644 index 0000000..e9281c7 --- /dev/null +++ b/src/debug/dwarf/testdata/cppunsuptypes.cc @@ -0,0 +1,34 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// cppunsuptypes.elf built with g++ 7.3 +// g++ -g -c -o cppunsuptypes.elf cppunsuptypes.cc + +int i = 3; +double d = 3; + +// anonymous reference type +int &culprit = i; + +// named reference type +typedef double &dref; +dref dr = d; + +// incorporated into another type +typedef struct { + dref q; + int &r; +} hasrefs; + +hasrefs hr = { d, i }; + +// This code is intended to trigger a DWARF "pointer to member" type DIE +struct CS { int dm; }; + +int foo() +{ + int CS::* pdm = &CS::dm; + CS cs = {42}; + return cs.*pdm; +} diff --git a/src/debug/dwarf/testdata/cppunsuptypes.elf b/src/debug/dwarf/testdata/cppunsuptypes.elf Binary files differnew file mode 100644 index 0000000..e955512 --- /dev/null +++ b/src/debug/dwarf/testdata/cppunsuptypes.elf diff --git a/src/debug/dwarf/testdata/cycle.c b/src/debug/dwarf/testdata/cycle.c new file mode 100644 index 0000000..a0b53df --- /dev/null +++ b/src/debug/dwarf/testdata/cycle.c @@ -0,0 +1,7 @@ +typedef struct aaa *AAA; +typedef AAA BBB; +struct aaa { BBB val; }; + +AAA x(void) { + return (AAA)0; +} diff --git a/src/debug/dwarf/testdata/cycle.elf b/src/debug/dwarf/testdata/cycle.elf Binary files differnew file mode 100644 index 0000000..e0b66ca --- /dev/null +++ b/src/debug/dwarf/testdata/cycle.elf diff --git a/src/debug/dwarf/testdata/debug_rnglists b/src/debug/dwarf/testdata/debug_rnglists Binary files differnew file mode 100644 index 0000000..985ec6c --- /dev/null +++ b/src/debug/dwarf/testdata/debug_rnglists diff --git a/src/debug/dwarf/testdata/line-clang-dwarf5.elf b/src/debug/dwarf/testdata/line-clang-dwarf5.elf Binary files differnew file mode 100644 index 0000000..7b80c9c --- /dev/null +++ b/src/debug/dwarf/testdata/line-clang-dwarf5.elf diff --git a/src/debug/dwarf/testdata/line-clang.elf b/src/debug/dwarf/testdata/line-clang.elf Binary files differnew file mode 100644 index 0000000..b63cc78 --- /dev/null +++ b/src/debug/dwarf/testdata/line-clang.elf diff --git a/src/debug/dwarf/testdata/line-gcc-dwarf5.elf b/src/debug/dwarf/testdata/line-gcc-dwarf5.elf Binary files differnew file mode 100644 index 0000000..34ce17c --- /dev/null +++ b/src/debug/dwarf/testdata/line-gcc-dwarf5.elf diff --git a/src/debug/dwarf/testdata/line-gcc-win.bin b/src/debug/dwarf/testdata/line-gcc-win.bin Binary files differnew file mode 100644 index 0000000..583ad44 --- /dev/null +++ b/src/debug/dwarf/testdata/line-gcc-win.bin diff --git a/src/debug/dwarf/testdata/line-gcc-zstd.elf b/src/debug/dwarf/testdata/line-gcc-zstd.elf Binary files differnew file mode 100644 index 0000000..45cbe72 --- /dev/null +++ b/src/debug/dwarf/testdata/line-gcc-zstd.elf diff --git a/src/debug/dwarf/testdata/line-gcc.elf b/src/debug/dwarf/testdata/line-gcc.elf Binary files differnew file mode 100644 index 0000000..50500a8 --- /dev/null +++ b/src/debug/dwarf/testdata/line-gcc.elf diff --git a/src/debug/dwarf/testdata/line1.c b/src/debug/dwarf/testdata/line1.c new file mode 100644 index 0000000..f358647 --- /dev/null +++ b/src/debug/dwarf/testdata/line1.c @@ -0,0 +1,9 @@ +#include "line1.h" + +void f2(); + +int main() +{ + f1(); + f2(); +} diff --git a/src/debug/dwarf/testdata/line1.h b/src/debug/dwarf/testdata/line1.h new file mode 100644 index 0000000..974d4c8 --- /dev/null +++ b/src/debug/dwarf/testdata/line1.h @@ -0,0 +1,7 @@ +static void f1() +{ + char buf[10]; + int i; + for(i = 0; i < 10; i++) + buf[i] = 1; +} diff --git a/src/debug/dwarf/testdata/line2.c b/src/debug/dwarf/testdata/line2.c new file mode 100644 index 0000000..38d8998 --- /dev/null +++ b/src/debug/dwarf/testdata/line2.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +void f2() +{ + printf("hello\n"); +} diff --git a/src/debug/dwarf/testdata/ranges.c b/src/debug/dwarf/testdata/ranges.c new file mode 100644 index 0000000..2f208e5 --- /dev/null +++ b/src/debug/dwarf/testdata/ranges.c @@ -0,0 +1,25 @@ +// gcc -g -O2 -freorder-blocks-and-partition + +const char *arr[10000]; +const char *hot = "hot"; +const char *cold = "cold"; + +__attribute__((noinline)) +void fn(int path) { + int i; + + if (path) { + for (i = 0; i < sizeof arr / sizeof arr[0]; i++) { + arr[i] = hot; + } + } else { + for (i = 0; i < sizeof arr / sizeof arr[0]; i++) { + arr[i] = cold; + } + } +} + +int main(int argc, char *argv[]) { + fn(argc); + return 0; +} diff --git a/src/debug/dwarf/testdata/ranges.elf b/src/debug/dwarf/testdata/ranges.elf Binary files differnew file mode 100644 index 0000000..7f54138 --- /dev/null +++ b/src/debug/dwarf/testdata/ranges.elf diff --git a/src/debug/dwarf/testdata/rnglistx.c b/src/debug/dwarf/testdata/rnglistx.c new file mode 100644 index 0000000..8770435 --- /dev/null +++ b/src/debug/dwarf/testdata/rnglistx.c @@ -0,0 +1,19 @@ +// clang -gdwarf-5 -O2 -nostdlib + +__attribute__((noinline, cold)) +static int sum(int i) { + int j, s; + + s = 0; + for (j = 0; j < i; j++) { + s += j * i; + } + return s; +} + +int main(int argc, char** argv) { + if (argc == 0) { + return 0; + } + return sum(argc); +} diff --git a/src/debug/dwarf/testdata/rnglistx.elf b/src/debug/dwarf/testdata/rnglistx.elf Binary files differnew file mode 100644 index 0000000..c2d7f55 --- /dev/null +++ b/src/debug/dwarf/testdata/rnglistx.elf diff --git a/src/debug/dwarf/testdata/split.c b/src/debug/dwarf/testdata/split.c new file mode 100644 index 0000000..0ef3427 --- /dev/null +++ b/src/debug/dwarf/testdata/split.c @@ -0,0 +1,5 @@ +// gcc -gsplit-dwarf split.c -o split.elf + +int main() +{ +} diff --git a/src/debug/dwarf/testdata/split.elf b/src/debug/dwarf/testdata/split.elf Binary files differnew file mode 100644 index 0000000..99ee2c2 --- /dev/null +++ b/src/debug/dwarf/testdata/split.elf diff --git a/src/debug/dwarf/testdata/typedef.c b/src/debug/dwarf/testdata/typedef.c new file mode 100644 index 0000000..3e7e008 --- /dev/null +++ b/src/debug/dwarf/testdata/typedef.c @@ -0,0 +1,86 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Linux ELF: +gcc -gdwarf-2 -m64 -c typedef.c && gcc -gdwarf-2 -m64 -o typedef.elf typedef.o + +OS X Mach-O: +gcc -gdwarf-2 -m64 -c typedef.c -o typedef.macho +gcc -gdwarf-4 -m64 -c typedef.c -o typedef.macho4 +*/ +#include <complex.h> + +typedef volatile int* t_ptr_volatile_int; +typedef const char *t_ptr_const_char; +typedef long t_long; +typedef unsigned short t_ushort; +typedef int t_func_int_of_float_double(float, double); +typedef int (*t_ptr_func_int_of_float_double)(float, double); +typedef int (*t_ptr_func_int_of_float_complex)(float complex); +typedef int (*t_ptr_func_int_of_double_complex)(double complex); +typedef int (*t_ptr_func_int_of_long_double_complex)(long double complex); +typedef int *t_func_ptr_int_of_char_schar_uchar(char, signed char, unsigned char); +typedef void t_func_void_of_char(char); +typedef void t_func_void_of_void(void); +typedef void t_func_void_of_ptr_char_dots(char*, ...); +typedef struct my_struct { + volatile int vi; + char x : 1; + int y : 4; + int z[0]; + long long array[40]; + int zz[0]; +} t_my_struct; +typedef struct my_struct1 { + int zz [1]; +} t_my_struct1; +typedef union my_union { + volatile int vi; + char x : 1; + int y : 4; + long long array[40]; +} t_my_union; +typedef enum my_enum { + e1 = 1, + e2 = 2, + e3 = -5, + e4 = 1000000000000000LL, +} t_my_enum; + +typedef struct list t_my_list; +struct list { + short val; + t_my_list *next; +}; + +typedef struct tree { + struct tree *left, *right; + unsigned long long val; +} t_my_tree; + +t_ptr_volatile_int *a2; +t_ptr_const_char **a3a; +t_long *a4; +t_ushort *a5; +t_func_int_of_float_double *a6; +t_ptr_func_int_of_float_double *a7; +t_func_ptr_int_of_char_schar_uchar *a8; +t_func_void_of_char *a9; +t_func_void_of_void *a10; +t_func_void_of_ptr_char_dots *a11; +t_my_struct *a12; +t_my_struct1 *a12a; +t_my_union *a12b; +t_my_enum *a13; +t_my_list *a14; +t_my_tree *a15; +t_ptr_func_int_of_float_complex *a16; +t_ptr_func_int_of_double_complex *a17; +t_ptr_func_int_of_long_double_complex *a18; + +int main() +{ + return 0; +} diff --git a/src/debug/dwarf/testdata/typedef.elf b/src/debug/dwarf/testdata/typedef.elf Binary files differnew file mode 100644 index 0000000..b2062d2 --- /dev/null +++ b/src/debug/dwarf/testdata/typedef.elf diff --git a/src/debug/dwarf/testdata/typedef.elf4 b/src/debug/dwarf/testdata/typedef.elf4 Binary files differnew file mode 100644 index 0000000..3d5a5a1 --- /dev/null +++ b/src/debug/dwarf/testdata/typedef.elf4 diff --git a/src/debug/dwarf/testdata/typedef.elf5 b/src/debug/dwarf/testdata/typedef.elf5 Binary files differnew file mode 100644 index 0000000..aec48f6 --- /dev/null +++ b/src/debug/dwarf/testdata/typedef.elf5 diff --git a/src/debug/dwarf/testdata/typedef.macho b/src/debug/dwarf/testdata/typedef.macho Binary files differnew file mode 100644 index 0000000..f75afcc --- /dev/null +++ b/src/debug/dwarf/testdata/typedef.macho diff --git a/src/debug/dwarf/testdata/typedef.macho4 b/src/debug/dwarf/testdata/typedef.macho4 Binary files differnew file mode 100644 index 0000000..093ff37 --- /dev/null +++ b/src/debug/dwarf/testdata/typedef.macho4 diff --git a/src/debug/dwarf/type.go b/src/debug/dwarf/type.go new file mode 100644 index 0000000..627d3a1 --- /dev/null +++ b/src/debug/dwarf/type.go @@ -0,0 +1,870 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// DWARF type information structures. +// The format is heavily biased toward C, but for simplicity +// the String methods use a pseudo-Go syntax. + +package dwarf + +import "strconv" + +// A Type conventionally represents a pointer to any of the +// specific Type structures ([CharType], [StructType], etc.). +type Type interface { + Common() *CommonType + String() string + Size() int64 +} + +// A CommonType holds fields common to multiple types. +// If a field is not known or not applicable for a given type, +// the zero value is used. +type CommonType struct { + ByteSize int64 // size of value of this type, in bytes + Name string // name that can be used to refer to type +} + +func (c *CommonType) Common() *CommonType { return c } + +func (c *CommonType) Size() int64 { return c.ByteSize } + +// Basic types + +// A BasicType holds fields common to all basic types. +// +// See the documentation for [StructField] for more info on the interpretation of +// the BitSize/BitOffset/DataBitOffset fields. +type BasicType struct { + CommonType + BitSize int64 + BitOffset int64 + DataBitOffset int64 +} + +func (b *BasicType) Basic() *BasicType { return b } + +func (t *BasicType) String() string { + if t.Name != "" { + return t.Name + } + return "?" +} + +// A CharType represents a signed character type. +type CharType struct { + BasicType +} + +// A UcharType represents an unsigned character type. +type UcharType struct { + BasicType +} + +// An IntType represents a signed integer type. +type IntType struct { + BasicType +} + +// A UintType represents an unsigned integer type. +type UintType struct { + BasicType +} + +// A FloatType represents a floating point type. +type FloatType struct { + BasicType +} + +// A ComplexType represents a complex floating point type. +type ComplexType struct { + BasicType +} + +// A BoolType represents a boolean type. +type BoolType struct { + BasicType +} + +// An AddrType represents a machine address type. +type AddrType struct { + BasicType +} + +// An UnspecifiedType represents an implicit, unknown, ambiguous or nonexistent type. +type UnspecifiedType struct { + BasicType +} + +// qualifiers + +// A QualType represents a type that has the C/C++ "const", "restrict", or "volatile" qualifier. +type QualType struct { + CommonType + Qual string + Type Type +} + +func (t *QualType) String() string { return t.Qual + " " + t.Type.String() } + +func (t *QualType) Size() int64 { return t.Type.Size() } + +// An ArrayType represents a fixed size array type. +type ArrayType struct { + CommonType + Type Type + StrideBitSize int64 // if > 0, number of bits to hold each element + Count int64 // if == -1, an incomplete array, like char x[]. +} + +func (t *ArrayType) String() string { + return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.String() +} + +func (t *ArrayType) Size() int64 { + if t.Count == -1 { + return 0 + } + return t.Count * t.Type.Size() +} + +// A VoidType represents the C void type. +type VoidType struct { + CommonType +} + +func (t *VoidType) String() string { return "void" } + +// A PtrType represents a pointer type. +type PtrType struct { + CommonType + Type Type +} + +func (t *PtrType) String() string { return "*" + t.Type.String() } + +// A StructType represents a struct, union, or C++ class type. +type StructType struct { + CommonType + StructName string + Kind string // "struct", "union", or "class". + Field []*StructField + Incomplete bool // if true, struct, union, class is declared but not defined +} + +// A StructField represents a field in a struct, union, or C++ class type. +// +// # Bit Fields +// +// The BitSize, BitOffset, and DataBitOffset fields describe the bit +// size and offset of data members declared as bit fields in C/C++ +// struct/union/class types. +// +// BitSize is the number of bits in the bit field. +// +// DataBitOffset, if non-zero, is the number of bits from the start of +// the enclosing entity (e.g. containing struct/class/union) to the +// start of the bit field. This corresponds to the DW_AT_data_bit_offset +// DWARF attribute that was introduced in DWARF 4. +// +// BitOffset, if non-zero, is the number of bits between the most +// significant bit of the storage unit holding the bit field to the +// most significant bit of the bit field. Here "storage unit" is the +// type name before the bit field (for a field "unsigned x:17", the +// storage unit is "unsigned"). BitOffset values can vary depending on +// the endianness of the system. BitOffset corresponds to the +// DW_AT_bit_offset DWARF attribute that was deprecated in DWARF 4 and +// removed in DWARF 5. +// +// At most one of DataBitOffset and BitOffset will be non-zero; +// DataBitOffset/BitOffset will only be non-zero if BitSize is +// non-zero. Whether a C compiler uses one or the other +// will depend on compiler vintage and command line options. +// +// Here is an example of C/C++ bit field use, along with what to +// expect in terms of DWARF bit offset info. Consider this code: +// +// struct S { +// int q; +// int j:5; +// int k:6; +// int m:5; +// int n:8; +// } s; +// +// For the code above, one would expect to see the following for +// DW_AT_bit_offset values (using GCC 8): +// +// Little | Big +// Endian | Endian +// | +// "j": 27 | 0 +// "k": 21 | 5 +// "m": 16 | 11 +// "n": 8 | 16 +// +// Note that in the above the offsets are purely with respect to the +// containing storage unit for j/k/m/n -- these values won't vary based +// on the size of prior data members in the containing struct. +// +// If the compiler emits DW_AT_data_bit_offset, the expected values +// would be: +// +// "j": 32 +// "k": 37 +// "m": 43 +// "n": 48 +// +// Here the value 32 for "j" reflects the fact that the bit field is +// preceded by other data members (recall that DW_AT_data_bit_offset +// values are relative to the start of the containing struct). Hence +// DW_AT_data_bit_offset values can be quite large for structs with +// many fields. +// +// DWARF also allow for the possibility of base types that have +// non-zero bit size and bit offset, so this information is also +// captured for base types, but it is worth noting that it is not +// possible to trigger this behavior using mainstream languages. +type StructField struct { + Name string + Type Type + ByteOffset int64 + ByteSize int64 // usually zero; use Type.Size() for normal fields + BitOffset int64 + DataBitOffset int64 + BitSize int64 // zero if not a bit field +} + +func (t *StructType) String() string { + if t.StructName != "" { + return t.Kind + " " + t.StructName + } + return t.Defn() +} + +func (f *StructField) bitOffset() int64 { + if f.BitOffset != 0 { + return f.BitOffset + } + return f.DataBitOffset +} + +func (t *StructType) Defn() string { + s := t.Kind + if t.StructName != "" { + s += " " + t.StructName + } + if t.Incomplete { + s += " /*incomplete*/" + return s + } + s += " {" + for i, f := range t.Field { + if i > 0 { + s += "; " + } + s += f.Name + " " + f.Type.String() + s += "@" + strconv.FormatInt(f.ByteOffset, 10) + if f.BitSize > 0 { + s += " : " + strconv.FormatInt(f.BitSize, 10) + s += "@" + strconv.FormatInt(f.bitOffset(), 10) + } + } + s += "}" + return s +} + +// An EnumType represents an enumerated type. +// The only indication of its native integer type is its ByteSize +// (inside [CommonType]). +type EnumType struct { + CommonType + EnumName string + Val []*EnumValue +} + +// An EnumValue represents a single enumeration value. +type EnumValue struct { + Name string + Val int64 +} + +func (t *EnumType) String() string { + s := "enum" + if t.EnumName != "" { + s += " " + t.EnumName + } + s += " {" + for i, v := range t.Val { + if i > 0 { + s += "; " + } + s += v.Name + "=" + strconv.FormatInt(v.Val, 10) + } + s += "}" + return s +} + +// A FuncType represents a function type. +type FuncType struct { + CommonType + ReturnType Type + ParamType []Type +} + +func (t *FuncType) String() string { + s := "func(" + for i, t := range t.ParamType { + if i > 0 { + s += ", " + } + s += t.String() + } + s += ")" + if t.ReturnType != nil { + s += " " + t.ReturnType.String() + } + return s +} + +// A DotDotDotType represents the variadic ... function parameter. +type DotDotDotType struct { + CommonType +} + +func (t *DotDotDotType) String() string { return "..." } + +// A TypedefType represents a named type. +type TypedefType struct { + CommonType + Type Type +} + +func (t *TypedefType) String() string { return t.Name } + +func (t *TypedefType) Size() int64 { return t.Type.Size() } + +// An UnsupportedType is a placeholder returned in situations where we +// encounter a type that isn't supported. +type UnsupportedType struct { + CommonType + Tag Tag +} + +func (t *UnsupportedType) String() string { + if t.Name != "" { + return t.Name + } + return t.Name + "(unsupported type " + t.Tag.String() + ")" +} + +// typeReader is used to read from either the info section or the +// types section. +type typeReader interface { + Seek(Offset) + Next() (*Entry, error) + clone() typeReader + offset() Offset + // AddressSize returns the size in bytes of addresses in the current + // compilation unit. + AddressSize() int +} + +// Type reads the type at off in the DWARF “info” section. +func (d *Data) Type(off Offset) (Type, error) { + return d.readType("info", d.Reader(), off, d.typeCache, nil) +} + +type typeFixer struct { + typedefs []*TypedefType + arraytypes []*Type +} + +func (tf *typeFixer) recordArrayType(t *Type) { + if t == nil { + return + } + _, ok := (*t).(*ArrayType) + if ok { + tf.arraytypes = append(tf.arraytypes, t) + } +} + +func (tf *typeFixer) apply() { + for _, t := range tf.typedefs { + t.Common().ByteSize = t.Type.Size() + } + for _, t := range tf.arraytypes { + zeroArray(t) + } +} + +// readType reads a type from r at off of name. It adds types to the +// type cache, appends new typedef types to typedefs, and computes the +// sizes of types. Callers should pass nil for typedefs; this is used +// for internal recursion. +func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Offset]Type, fixups *typeFixer) (Type, error) { + if t, ok := typeCache[off]; ok { + return t, nil + } + r.Seek(off) + e, err := r.Next() + if err != nil { + return nil, err + } + addressSize := r.AddressSize() + if e == nil || e.Offset != off { + return nil, DecodeError{name, off, "no type at offset"} + } + + // If this is the root of the recursion, prepare to resolve + // typedef sizes and perform other fixups once the recursion is + // done. This must be done after the type graph is constructed + // because it may need to resolve cycles in a different order than + // readType encounters them. + if fixups == nil { + var fixer typeFixer + defer func() { + fixer.apply() + }() + fixups = &fixer + } + + // Parse type from Entry. + // Must always set typeCache[off] before calling + // d.readType recursively, to handle circular types correctly. + var typ Type + + nextDepth := 0 + + // Get next child; set err if error happens. + next := func() *Entry { + if !e.Children { + return nil + } + // Only return direct children. + // Skip over composite entries that happen to be nested + // inside this one. Most DWARF generators wouldn't generate + // such a thing, but clang does. + // See golang.org/issue/6472. + for { + kid, err1 := r.Next() + if err1 != nil { + err = err1 + return nil + } + if kid == nil { + err = DecodeError{name, r.offset(), "unexpected end of DWARF entries"} + return nil + } + if kid.Tag == 0 { + if nextDepth > 0 { + nextDepth-- + continue + } + return nil + } + if kid.Children { + nextDepth++ + } + if nextDepth > 0 { + continue + } + return kid + } + } + + // Get Type referred to by Entry's AttrType field. + // Set err if error happens. Not having a type is an error. + typeOf := func(e *Entry) Type { + tval := e.Val(AttrType) + var t Type + switch toff := tval.(type) { + case Offset: + if t, err = d.readType(name, r.clone(), toff, typeCache, fixups); err != nil { + return nil + } + case uint64: + if t, err = d.sigToType(toff); err != nil { + return nil + } + default: + // It appears that no Type means "void". + return new(VoidType) + } + return t + } + + switch e.Tag { + case TagArrayType: + // Multi-dimensional array. (DWARF v2 §5.4) + // Attributes: + // AttrType:subtype [required] + // AttrStrideSize: size in bits of each element of the array + // AttrByteSize: size of entire array + // Children: + // TagSubrangeType or TagEnumerationType giving one dimension. + // dimensions are in left to right order. + t := new(ArrayType) + typ = t + typeCache[off] = t + if t.Type = typeOf(e); err != nil { + goto Error + } + t.StrideBitSize, _ = e.Val(AttrStrideSize).(int64) + + // Accumulate dimensions, + var dims []int64 + for kid := next(); kid != nil; kid = next() { + // TODO(rsc): Can also be TagEnumerationType + // but haven't seen that in the wild yet. + switch kid.Tag { + case TagSubrangeType: + count, ok := kid.Val(AttrCount).(int64) + if !ok { + // Old binaries may have an upper bound instead. + count, ok = kid.Val(AttrUpperBound).(int64) + if ok { + count++ // Length is one more than upper bound. + } else if len(dims) == 0 { + count = -1 // As in x[]. + } + } + dims = append(dims, count) + case TagEnumerationType: + err = DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"} + goto Error + } + } + if len(dims) == 0 { + // LLVM generates this for x[]. + dims = []int64{-1} + } + + t.Count = dims[0] + for i := len(dims) - 1; i >= 1; i-- { + t.Type = &ArrayType{Type: t.Type, Count: dims[i]} + } + + case TagBaseType: + // Basic type. (DWARF v2 §5.1) + // Attributes: + // AttrName: name of base type in programming language of the compilation unit [required] + // AttrEncoding: encoding value for type (encFloat etc) [required] + // AttrByteSize: size of type in bytes [required] + // AttrBitOffset: bit offset of value within containing storage unit + // AttrDataBitOffset: bit offset of value within containing storage unit + // AttrBitSize: size in bits + // + // For most languages BitOffset/DataBitOffset/BitSize will not be present + // for base types. + name, _ := e.Val(AttrName).(string) + enc, ok := e.Val(AttrEncoding).(int64) + if !ok { + err = DecodeError{name, e.Offset, "missing encoding attribute for " + name} + goto Error + } + switch enc { + default: + err = DecodeError{name, e.Offset, "unrecognized encoding attribute value"} + goto Error + + case encAddress: + typ = new(AddrType) + case encBoolean: + typ = new(BoolType) + case encComplexFloat: + typ = new(ComplexType) + if name == "complex" { + // clang writes out 'complex' instead of 'complex float' or 'complex double'. + // clang also writes out a byte size that we can use to distinguish. + // See issue 8694. + switch byteSize, _ := e.Val(AttrByteSize).(int64); byteSize { + case 8: + name = "complex float" + case 16: + name = "complex double" + } + } + case encFloat: + typ = new(FloatType) + case encSigned: + typ = new(IntType) + case encUnsigned: + typ = new(UintType) + case encSignedChar: + typ = new(CharType) + case encUnsignedChar: + typ = new(UcharType) + } + typeCache[off] = typ + t := typ.(interface { + Basic() *BasicType + }).Basic() + t.Name = name + t.BitSize, _ = e.Val(AttrBitSize).(int64) + haveBitOffset := false + haveDataBitOffset := false + t.BitOffset, haveBitOffset = e.Val(AttrBitOffset).(int64) + t.DataBitOffset, haveDataBitOffset = e.Val(AttrDataBitOffset).(int64) + if haveBitOffset && haveDataBitOffset { + err = DecodeError{name, e.Offset, "duplicate bit offset attributes"} + goto Error + } + + case TagClassType, TagStructType, TagUnionType: + // Structure, union, or class type. (DWARF v2 §5.5) + // Attributes: + // AttrName: name of struct, union, or class + // AttrByteSize: byte size [required] + // AttrDeclaration: if true, struct/union/class is incomplete + // Children: + // TagMember to describe one member. + // AttrName: name of member [required] + // AttrType: type of member [required] + // AttrByteSize: size in bytes + // AttrBitOffset: bit offset within bytes for bit fields + // AttrDataBitOffset: field bit offset relative to struct start + // AttrBitSize: bit size for bit fields + // AttrDataMemberLoc: location within struct [required for struct, class] + // There is much more to handle C++, all ignored for now. + t := new(StructType) + typ = t + typeCache[off] = t + switch e.Tag { + case TagClassType: + t.Kind = "class" + case TagStructType: + t.Kind = "struct" + case TagUnionType: + t.Kind = "union" + } + t.StructName, _ = e.Val(AttrName).(string) + t.Incomplete = e.Val(AttrDeclaration) != nil + t.Field = make([]*StructField, 0, 8) + var lastFieldType *Type + var lastFieldBitSize int64 + var lastFieldByteOffset int64 + for kid := next(); kid != nil; kid = next() { + if kid.Tag != TagMember { + continue + } + f := new(StructField) + if f.Type = typeOf(kid); err != nil { + goto Error + } + switch loc := kid.Val(AttrDataMemberLoc).(type) { + case []byte: + // TODO: Should have original compilation + // unit here, not unknownFormat. + b := makeBuf(d, unknownFormat{}, "location", 0, loc) + if b.uint8() != opPlusUconst { + err = DecodeError{name, kid.Offset, "unexpected opcode"} + goto Error + } + f.ByteOffset = int64(b.uint()) + if b.err != nil { + err = b.err + goto Error + } + case int64: + f.ByteOffset = loc + } + + f.Name, _ = kid.Val(AttrName).(string) + f.ByteSize, _ = kid.Val(AttrByteSize).(int64) + haveBitOffset := false + haveDataBitOffset := false + f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64) + f.DataBitOffset, haveDataBitOffset = kid.Val(AttrDataBitOffset).(int64) + if haveBitOffset && haveDataBitOffset { + err = DecodeError{name, e.Offset, "duplicate bit offset attributes"} + goto Error + } + f.BitSize, _ = kid.Val(AttrBitSize).(int64) + t.Field = append(t.Field, f) + + if lastFieldBitSize == 0 && lastFieldByteOffset == f.ByteOffset && t.Kind != "union" { + // Last field was zero width. Fix array length. + // (DWARF writes out 0-length arrays as if they were 1-length arrays.) + fixups.recordArrayType(lastFieldType) + } + lastFieldType = &f.Type + lastFieldByteOffset = f.ByteOffset + lastFieldBitSize = f.BitSize + } + if t.Kind != "union" { + b, ok := e.Val(AttrByteSize).(int64) + if ok && b == lastFieldByteOffset { + // Final field must be zero width. Fix array length. + fixups.recordArrayType(lastFieldType) + } + } + + case TagConstType, TagVolatileType, TagRestrictType: + // Type modifier (DWARF v2 §5.2) + // Attributes: + // AttrType: subtype + t := new(QualType) + typ = t + typeCache[off] = t + if t.Type = typeOf(e); err != nil { + goto Error + } + switch e.Tag { + case TagConstType: + t.Qual = "const" + case TagRestrictType: + t.Qual = "restrict" + case TagVolatileType: + t.Qual = "volatile" + } + + case TagEnumerationType: + // Enumeration type (DWARF v2 §5.6) + // Attributes: + // AttrName: enum name if any + // AttrByteSize: bytes required to represent largest value + // Children: + // TagEnumerator: + // AttrName: name of constant + // AttrConstValue: value of constant + t := new(EnumType) + typ = t + typeCache[off] = t + t.EnumName, _ = e.Val(AttrName).(string) + t.Val = make([]*EnumValue, 0, 8) + for kid := next(); kid != nil; kid = next() { + if kid.Tag == TagEnumerator { + f := new(EnumValue) + f.Name, _ = kid.Val(AttrName).(string) + f.Val, _ = kid.Val(AttrConstValue).(int64) + n := len(t.Val) + if n >= cap(t.Val) { + val := make([]*EnumValue, n, n*2) + copy(val, t.Val) + t.Val = val + } + t.Val = t.Val[0 : n+1] + t.Val[n] = f + } + } + + case TagPointerType: + // Type modifier (DWARF v2 §5.2) + // Attributes: + // AttrType: subtype [not required! void* has no AttrType] + // AttrAddrClass: address class [ignored] + t := new(PtrType) + typ = t + typeCache[off] = t + if e.Val(AttrType) == nil { + t.Type = &VoidType{} + break + } + t.Type = typeOf(e) + + case TagSubroutineType: + // Subroutine type. (DWARF v2 §5.7) + // Attributes: + // AttrType: type of return value if any + // AttrName: possible name of type [ignored] + // AttrPrototyped: whether used ANSI C prototype [ignored] + // Children: + // TagFormalParameter: typed parameter + // AttrType: type of parameter + // TagUnspecifiedParameter: final ... + t := new(FuncType) + typ = t + typeCache[off] = t + if t.ReturnType = typeOf(e); err != nil { + goto Error + } + t.ParamType = make([]Type, 0, 8) + for kid := next(); kid != nil; kid = next() { + var tkid Type + switch kid.Tag { + default: + continue + case TagFormalParameter: + if tkid = typeOf(kid); err != nil { + goto Error + } + case TagUnspecifiedParameters: + tkid = &DotDotDotType{} + } + t.ParamType = append(t.ParamType, tkid) + } + + case TagTypedef: + // Typedef (DWARF v2 §5.3) + // Attributes: + // AttrName: name [required] + // AttrType: type definition [required] + t := new(TypedefType) + typ = t + typeCache[off] = t + t.Name, _ = e.Val(AttrName).(string) + t.Type = typeOf(e) + + case TagUnspecifiedType: + // Unspecified type (DWARF v3 §5.2) + // Attributes: + // AttrName: name + t := new(UnspecifiedType) + typ = t + typeCache[off] = t + t.Name, _ = e.Val(AttrName).(string) + + default: + // This is some other type DIE that we're currently not + // equipped to handle. Return an abstract "unsupported type" + // object in such cases. + t := new(UnsupportedType) + typ = t + typeCache[off] = t + t.Tag = e.Tag + t.Name, _ = e.Val(AttrName).(string) + } + + if err != nil { + goto Error + } + + { + b, ok := e.Val(AttrByteSize).(int64) + if !ok { + b = -1 + switch t := typ.(type) { + case *TypedefType: + // Record that we need to resolve this + // type's size once the type graph is + // constructed. + fixups.typedefs = append(fixups.typedefs, t) + case *PtrType: + b = int64(addressSize) + } + } + typ.Common().ByteSize = b + } + return typ, nil + +Error: + // If the parse fails, take the type out of the cache + // so that the next call with this offset doesn't hit + // the cache and return success. + delete(typeCache, off) + return nil, err +} + +func zeroArray(t *Type) { + at := (*t).(*ArrayType) + if at.Type.Size() == 0 { + return + } + // Make a copy to avoid invalidating typeCache. + tt := *at + tt.Count = 0 + *t = &tt +} diff --git a/src/debug/dwarf/type_test.go b/src/debug/dwarf/type_test.go new file mode 100644 index 0000000..5858ef5 --- /dev/null +++ b/src/debug/dwarf/type_test.go @@ -0,0 +1,335 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf_test + +import ( + . "debug/dwarf" + "debug/elf" + "debug/macho" + "debug/pe" + "fmt" + "strconv" + "testing" +) + +var typedefTests = map[string]string{ + "t_ptr_volatile_int": "*volatile int", + "t_ptr_const_char": "*const char", + "t_long": "long int", + "t_ushort": "short unsigned int", + "t_func_int_of_float_double": "func(float, double) int", + "t_ptr_func_int_of_float_double": "*func(float, double) int", + "t_ptr_func_int_of_float_complex": "*func(complex float) int", + "t_ptr_func_int_of_double_complex": "*func(complex double) int", + "t_ptr_func_int_of_long_double_complex": "*func(complex long double) int", + "t_func_ptr_int_of_char_schar_uchar": "func(char, signed char, unsigned char) *int", + "t_func_void_of_char": "func(char) void", + "t_func_void_of_void": "func() void", + "t_func_void_of_ptr_char_dots": "func(*char, ...) void", + "t_my_struct": "struct my_struct {vi volatile int@0; x char@4 : 1@7; y int@4 : 4@27; z [0]int@8; array [40]long long int@8; zz [0]int@328}", + "t_my_struct1": "struct my_struct1 {zz [1]int@0}", + "t_my_union": "union my_union {vi volatile int@0; x char@0 : 1@7; y int@0 : 4@28; array [40]long long int@0}", + "t_my_enum": "enum my_enum {e1=1; e2=2; e3=-5; e4=1000000000000000}", + "t_my_list": "struct list {val short int@0; next *t_my_list@8}", + "t_my_tree": "struct tree {left *struct tree@0; right *struct tree@8; val long long unsigned int@16}", +} + +// As Apple converts gcc to a clang-based front end +// they keep breaking the DWARF output. This map lists the +// conversion from real answer to Apple answer. +var machoBug = map[string]string{ + "func(*char, ...) void": "func(*char) void", + "enum my_enum {e1=1; e2=2; e3=-5; e4=1000000000000000}": "enum my_enum {e1=1; e2=2; e3=-5; e4=-1530494976}", +} + +func elfData(t *testing.T, name string) *Data { + f, err := elf.Open(name) + if err != nil { + t.Fatal(err) + } + + d, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + return d +} + +func machoData(t *testing.T, name string) *Data { + f, err := macho.Open(name) + if err != nil { + t.Fatal(err) + } + + d, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + return d +} + +func peData(t *testing.T, name string) *Data { + f, err := pe.Open(name) + if err != nil { + t.Fatal(err) + } + + d, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + return d +} + +func TestTypedefsELF(t *testing.T) { + testTypedefs(t, elfData(t, "testdata/typedef.elf"), "elf", typedefTests) +} + +func TestTypedefsMachO(t *testing.T) { + testTypedefs(t, machoData(t, "testdata/typedef.macho"), "macho", typedefTests) +} + +func TestTypedefsELFDwarf4(t *testing.T) { + testTypedefs(t, elfData(t, "testdata/typedef.elf4"), "elf", typedefTests) +} + +func testTypedefs(t *testing.T, d *Data, kind string, testcases map[string]string) { + r := d.Reader() + seen := make(map[string]bool) + for { + e, err := r.Next() + if err != nil { + t.Fatal("r.Next:", err) + } + if e == nil { + break + } + if e.Tag == TagTypedef { + typ, err := d.Type(e.Offset) + if err != nil { + t.Fatal("d.Type:", err) + } + t1 := typ.(*TypedefType) + var typstr string + if ts, ok := t1.Type.(*StructType); ok { + typstr = ts.Defn() + } else { + typstr = t1.Type.String() + } + + if want, ok := testcases[t1.Name]; ok { + if seen[t1.Name] { + t.Errorf("multiple definitions for %s", t1.Name) + } + seen[t1.Name] = true + if typstr != want && (kind != "macho" || typstr != machoBug[want]) { + t.Errorf("%s:\n\thave %s\n\twant %s", t1.Name, typstr, want) + } + } + } + if e.Tag != TagCompileUnit { + r.SkipChildren() + } + } + + for k := range testcases { + if !seen[k] { + t.Errorf("missing %s", k) + } + } +} + +func TestTypedefCycle(t *testing.T) { + // See issue #13039: reading a typedef cycle starting from a + // different place than the size needed to be computed from + // used to crash. + // + // cycle.elf built with GCC 4.8.4: + // gcc -g -c -o cycle.elf cycle.c + d := elfData(t, "testdata/cycle.elf") + r := d.Reader() + offsets := []Offset{} + for { + e, err := r.Next() + if err != nil { + t.Fatal("r.Next:", err) + } + if e == nil { + break + } + switch e.Tag { + case TagBaseType, TagTypedef, TagPointerType, TagStructType: + offsets = append(offsets, e.Offset) + } + } + + // Parse each type with a fresh type cache. + for _, offset := range offsets { + d := elfData(t, "testdata/cycle.elf") + _, err := d.Type(offset) + if err != nil { + t.Fatalf("d.Type(0x%x): %s", offset, err) + } + } +} + +var unsupportedTypeTests = []string{ + // varname:typename:string:size + "culprit::(unsupported type ReferenceType):8", + "pdm::(unsupported type PtrToMemberType):-1", +} + +func TestUnsupportedTypes(t *testing.T) { + // Issue 29601: + // When reading DWARF from C++ load modules, we can encounter + // oddball type DIEs. These will be returned as "UnsupportedType" + // objects; check to make sure this works properly. + d := elfData(t, "testdata/cppunsuptypes.elf") + r := d.Reader() + seen := make(map[string]bool) + for { + e, err := r.Next() + if err != nil { + t.Fatal("r.Next:", err) + } + if e == nil { + break + } + if e.Tag == TagVariable { + vname, _ := e.Val(AttrName).(string) + tAttr := e.Val(AttrType) + typOff, ok := tAttr.(Offset) + if !ok { + t.Errorf("variable at offset %v has no type", e.Offset) + continue + } + typ, err := d.Type(typOff) + if err != nil { + t.Errorf("err in type decode: %v\n", err) + continue + } + unsup, isok := typ.(*UnsupportedType) + if !isok { + continue + } + tag := vname + ":" + unsup.Name + ":" + unsup.String() + + ":" + strconv.FormatInt(unsup.Size(), 10) + seen[tag] = true + } + } + dumpseen := false + for _, v := range unsupportedTypeTests { + if !seen[v] { + t.Errorf("missing %s", v) + dumpseen = true + } + } + if dumpseen { + for k := range seen { + fmt.Printf("seen: %s\n", k) + } + } +} + +var expectedBitOffsets1 = map[string]string{ + "x": "S:1 DBO:32", + "y": "S:4 DBO:33", +} + +var expectedBitOffsets2 = map[string]string{ + "x": "S:1 BO:7", + "y": "S:4 BO:27", +} + +func TestBitOffsetsELF(t *testing.T) { + f := "testdata/typedef.elf" + testBitOffsets(t, elfData(t, f), f, expectedBitOffsets2) +} + +func TestBitOffsetsMachO(t *testing.T) { + f := "testdata/typedef.macho" + testBitOffsets(t, machoData(t, f), f, expectedBitOffsets2) +} + +func TestBitOffsetsMachO4(t *testing.T) { + f := "testdata/typedef.macho4" + testBitOffsets(t, machoData(t, f), f, expectedBitOffsets1) +} + +func TestBitOffsetsELFDwarf4(t *testing.T) { + f := "testdata/typedef.elf4" + testBitOffsets(t, elfData(t, f), f, expectedBitOffsets1) +} + +func TestBitOffsetsELFDwarf5(t *testing.T) { + f := "testdata/typedef.elf5" + testBitOffsets(t, elfData(t, f), f, expectedBitOffsets1) +} + +func testBitOffsets(t *testing.T, d *Data, tag string, expectedBitOffsets map[string]string) { + r := d.Reader() + for { + e, err := r.Next() + if err != nil { + t.Fatal("r.Next:", err) + } + if e == nil { + break + } + + if e.Tag == TagStructType { + typ, err := d.Type(e.Offset) + if err != nil { + t.Fatal("d.Type:", err) + } + + t1 := typ.(*StructType) + + bitInfoDump := func(f *StructField) string { + res := fmt.Sprintf("S:%d", f.BitSize) + if f.BitOffset != 0 { + res += fmt.Sprintf(" BO:%d", f.BitOffset) + } + if f.DataBitOffset != 0 { + res += fmt.Sprintf(" DBO:%d", f.DataBitOffset) + } + return res + } + + for _, field := range t1.Field { + // We're only testing for bitfields + if field.BitSize == 0 { + continue + } + got := bitInfoDump(field) + want := expectedBitOffsets[field.Name] + if got != want { + t.Errorf("%s: field %s in %s: got info %q want %q", tag, field.Name, t1.StructName, got, want) + } + } + } + if e.Tag != TagCompileUnit { + r.SkipChildren() + } + } +} + +var bitfieldTests = map[string]string{ + "t_another_struct": "struct another_struct {quix short unsigned int@0; xyz [0]int@4; x unsigned int@4 : 1@31; array [40]long long int@8}", +} + +// TestBitFieldZeroArrayIssue50685 checks to make sure that the DWARF +// type reading code doesn't get confused by the presence of a +// specifically-sized bitfield member immediately following a field +// whose type is a zero-length array. Prior to the fix for issue +// 50685, we would get this type for the case in testdata/bitfields.c: +// +// another_struct {quix short unsigned int@0; xyz [-1]int@4; x unsigned int@4 : 1@31; array [40]long long int@8} +// +// Note the "-1" for the xyz field, which should be zero. +func TestBitFieldZeroArrayIssue50685(t *testing.T) { + f := "testdata/bitfields.elf4" + testTypedefs(t, elfData(t, f), "elf", bitfieldTests) +} diff --git a/src/debug/dwarf/typeunit.go b/src/debug/dwarf/typeunit.go new file mode 100644 index 0000000..8ecf876 --- /dev/null +++ b/src/debug/dwarf/typeunit.go @@ -0,0 +1,160 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf + +import ( + "fmt" + "strconv" +) + +// Parse the type units stored in a DWARF4 .debug_types section. Each +// type unit defines a single primary type and an 8-byte signature. +// Other sections may then use formRefSig8 to refer to the type. + +// The typeUnit format is a single type with a signature. It holds +// the same data as a compilation unit. +type typeUnit struct { + unit + toff Offset // Offset to signature type within data. + name string // Name of .debug_type section. + cache Type // Cache the type, nil to start. +} + +// Parse a .debug_types section. +func (d *Data) parseTypes(name string, types []byte) error { + b := makeBuf(d, unknownFormat{}, name, 0, types) + for len(b.data) > 0 { + base := b.off + n, dwarf64 := b.unitLength() + if n != Offset(uint32(n)) { + b.error("type unit length overflow") + return b.err + } + hdroff := b.off + vers := int(b.uint16()) + if vers != 4 { + b.error("unsupported DWARF version " + strconv.Itoa(vers)) + return b.err + } + var ao uint64 + if !dwarf64 { + ao = uint64(b.uint32()) + } else { + ao = b.uint64() + } + atable, err := d.parseAbbrev(ao, vers) + if err != nil { + return err + } + asize := b.uint8() + sig := b.uint64() + + var toff uint32 + if !dwarf64 { + toff = b.uint32() + } else { + to64 := b.uint64() + if to64 != uint64(uint32(to64)) { + b.error("type unit type offset overflow") + return b.err + } + toff = uint32(to64) + } + + boff := b.off + d.typeSigs[sig] = &typeUnit{ + unit: unit{ + base: base, + off: boff, + data: b.bytes(int(n - (b.off - hdroff))), + atable: atable, + asize: int(asize), + vers: vers, + is64: dwarf64, + }, + toff: Offset(toff), + name: name, + } + if b.err != nil { + return b.err + } + } + return nil +} + +// Return the type for a type signature. +func (d *Data) sigToType(sig uint64) (Type, error) { + tu := d.typeSigs[sig] + if tu == nil { + return nil, fmt.Errorf("no type unit with signature %v", sig) + } + if tu.cache != nil { + return tu.cache, nil + } + + b := makeBuf(d, tu, tu.name, tu.off, tu.data) + r := &typeUnitReader{d: d, tu: tu, b: b} + t, err := d.readType(tu.name, r, tu.toff, make(map[Offset]Type), nil) + if err != nil { + return nil, err + } + + tu.cache = t + return t, nil +} + +// typeUnitReader is a typeReader for a tagTypeUnit. +type typeUnitReader struct { + d *Data + tu *typeUnit + b buf + err error +} + +// Seek to a new position in the type unit. +func (tur *typeUnitReader) Seek(off Offset) { + tur.err = nil + doff := off - tur.tu.off + if doff < 0 || doff >= Offset(len(tur.tu.data)) { + tur.err = fmt.Errorf("%s: offset %d out of range; max %d", tur.tu.name, doff, len(tur.tu.data)) + return + } + tur.b = makeBuf(tur.d, tur.tu, tur.tu.name, off, tur.tu.data[doff:]) +} + +// AddressSize returns the size in bytes of addresses in the current type unit. +func (tur *typeUnitReader) AddressSize() int { + return tur.tu.unit.asize +} + +// Next reads the next [Entry] from the type unit. +func (tur *typeUnitReader) Next() (*Entry, error) { + if tur.err != nil { + return nil, tur.err + } + if len(tur.tu.data) == 0 { + return nil, nil + } + e := tur.b.entry(nil, tur.tu.atable, tur.tu.base, tur.tu.vers) + if tur.b.err != nil { + tur.err = tur.b.err + return nil, tur.err + } + return e, nil +} + +// clone returns a new reader for the type unit. +func (tur *typeUnitReader) clone() typeReader { + return &typeUnitReader{ + d: tur.d, + tu: tur.tu, + b: makeBuf(tur.d, tur.tu, tur.tu.name, tur.tu.off, tur.tu.data), + } +} + +// offset returns the current offset. +func (tur *typeUnitReader) offset() Offset { + return tur.b.off +} diff --git a/src/debug/dwarf/unit.go b/src/debug/dwarf/unit.go new file mode 100644 index 0000000..8b810d0 --- /dev/null +++ b/src/debug/dwarf/unit.go @@ -0,0 +1,137 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf + +import ( + "sort" + "strconv" +) + +// DWARF debug info is split into a sequence of compilation units. +// Each unit has its own abbreviation table and address size. + +type unit struct { + base Offset // byte offset of header within the aggregate info + off Offset // byte offset of data within the aggregate info + data []byte + atable abbrevTable + asize int + vers int + utype uint8 // DWARF 5 unit type + is64 bool // True for 64-bit DWARF format +} + +// Implement the dataFormat interface. + +func (u *unit) version() int { + return u.vers +} + +func (u *unit) dwarf64() (bool, bool) { + return u.is64, true +} + +func (u *unit) addrsize() int { + return u.asize +} + +func (d *Data) parseUnits() ([]unit, error) { + // Count units. + nunit := 0 + b := makeBuf(d, unknownFormat{}, "info", 0, d.info) + for len(b.data) > 0 { + len, _ := b.unitLength() + if len != Offset(uint32(len)) { + b.error("unit length overflow") + break + } + b.skip(int(len)) + if len > 0 { + nunit++ + } + } + if b.err != nil { + return nil, b.err + } + + // Again, this time writing them down. + b = makeBuf(d, unknownFormat{}, "info", 0, d.info) + units := make([]unit, nunit) + for i := range units { + u := &units[i] + u.base = b.off + var n Offset + if b.err != nil { + return nil, b.err + } + for n == 0 { + n, u.is64 = b.unitLength() + } + dataOff := b.off + vers := b.uint16() + if vers < 2 || vers > 5 { + b.error("unsupported DWARF version " + strconv.Itoa(int(vers))) + break + } + u.vers = int(vers) + if vers >= 5 { + u.utype = b.uint8() + u.asize = int(b.uint8()) + } + var abbrevOff uint64 + if u.is64 { + abbrevOff = b.uint64() + } else { + abbrevOff = uint64(b.uint32()) + } + atable, err := d.parseAbbrev(abbrevOff, u.vers) + if err != nil { + if b.err == nil { + b.err = err + } + break + } + u.atable = atable + if vers < 5 { + u.asize = int(b.uint8()) + } + + switch u.utype { + case utSkeleton, utSplitCompile: + b.uint64() // unit ID + case utType, utSplitType: + b.uint64() // type signature + if u.is64 { // type offset + b.uint64() + } else { + b.uint32() + } + } + + u.off = b.off + u.data = b.bytes(int(n - (b.off - dataOff))) + } + if b.err != nil { + return nil, b.err + } + return units, nil +} + +// offsetToUnit returns the index of the unit containing offset off. +// It returns -1 if no unit contains this offset. +func (d *Data) offsetToUnit(off Offset) int { + // Find the unit after off + next := sort.Search(len(d.unit), func(i int) bool { + return d.unit[i].off > off + }) + if next == 0 { + return -1 + } + u := &d.unit[next-1] + if u.off <= off && off < u.off+Offset(len(u.data)) { + return next - 1 + } + return -1 +} |