// 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 dwarfimport ()// 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.typeLineReaderstruct { 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.typeLineEntrystruct {// 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.typeLineFilestruct { 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 ( *Data) ( *Entry) (*LineReader, error) {if .line == nil {// No line tables available.returnnil, nil }// Get line table information from cu. , := .Val(AttrStmtList).(int64)if ! {// cu has no line table.returnnil, nil }if < 0 || > int64(len(.line)) {returnnil, 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. , := .Val(AttrCompDir).(string)// Create the LineReader. := &.unit[.offsetToUnit(.Offset)] := makeBuf(, , "line", Offset(), .line[:])// The compilation directory is implicitly directories[0]. := LineReader{buf: ,section: .line,str: .str,lineStr: .lineStr, }// Read the header.if := .readHeader(); != nil {returnnil, }// Initialize line reader state. .Reset()return &, nil}// readHeader reads the line number program header from r.buf and sets// all of the header fields in r.func ( *LineReader) ( string) error { := &.buf// Read basic header fields [DWARF2 6.2.4]. := .off , := .unitLength() .endOffset = .off + if .endOffset > .off+Offset(len(.data)) {returnDecodeError{"line", , fmt.Sprintf("line table end %d exceeds section size %d", .endOffset, .off+Offset(len(.data)))} } .version = .uint16()if .err == nil && (.version < 2 || .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.returnDecodeError{"line", , fmt.Sprintf("unknown line table version %d", .version)} }if .version >= 5 { .addrsize = int(.uint8()) .segmentSelectorSize = int(.uint8()) } else { .addrsize = .format.addrsize() .segmentSelectorSize = 0 }varOffsetif { = Offset(.uint64()) } else { = Offset(.uint32()) } := .off + if > .endOffset {returnDecodeError{"line", , fmt.Sprintf("malformed line table: program offset %d exceeds end offset %d", , .endOffset)} } .programOffset = .minInstructionLength = int(.uint8())if .version >= 4 {// [DWARF4 6.2.4] .maxOpsPerInstruction = int(.uint8()) } else { .maxOpsPerInstruction = 1 } .defaultIsStmt = .uint8() != 0 .lineBase = int(int8(.uint8())) .lineRange = int(.uint8())// Validate header.if .err != nil {return .err }if .maxOpsPerInstruction == 0 {returnDecodeError{"line", , "invalid maximum operations per instruction: 0"} }if .lineRange == 0 {returnDecodeError{"line", , "invalid line range: 0"} }// Read standard opcode length table. This table starts with opcode 1. .opcodeBase = int(.uint8()) .opcodeLengths = make([]int, .opcodeBase)for := 1; < .opcodeBase; ++ { .opcodeLengths[] = int(.uint8()) }// Validate opcode lengths.if .err != nil {return .err }for , := range .opcodeLengths {if , := knownOpcodeLengths[]; && != {returnDecodeError{"line", , fmt.Sprintf("opcode %d expected to have length %d, but has length %d", , , )} } }if .version < 5 {// Read include directories table. .directories = []string{}for { := .string()if .err != nil {return .err }iflen() == 0 {break }if !pathIsAbs() {// Relative paths are implicitly relative to // the compilation directory. = pathJoin(, ) } .directories = append(.directories, ) }// Read file name list. File numbering starts with 1, // so leave the first entry nil. .fileEntries = make([]*LineFile, 1)for {if , := .readFileEntry(); != nil {return } elseif {break } } } else { := .readLNCTFormat() := .uint() .directories = make([]string, )for := range .directories { , , , := .readLNCT(, )if != nil {return } .directories[] = } := .readLNCTFormat() = .uint() .fileEntries = make([]*LineFile, )for := range .fileEntries { , , , := .readLNCT(, )if != nil {return } .fileEntries[] = &LineFile{, , int()} } } .initialFileEntries = len(.fileEntries)return .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 ( *LineReader) () []lnctForm { := .buf.uint8() := make([]lnctForm, )for := range { [].lnct = int(.buf.uint()) [].form = format(.buf.uint()) }return}// readLNCT reads a sequence of LNCT entries and returns path information.func ( *LineReader) ( []lnctForm, bool) ( string, uint64, uint64, error) {varstringfor , := range {varstringvaruint64switch .form {caseformString: = .buf.string()caseformStrp, formLineStrp:varuint64if { = .buf.uint64() } else { = uint64(.buf.uint32()) }ifuint64(int()) != {return"", 0, 0, DecodeError{"line", .buf.off, "strp/line_strp offset out of range"} }varbufif .form == formStrp { = makeBuf(.buf.dwarf, .buf.format, "str", 0, .str) } else { = makeBuf(.buf.dwarf, .buf.format, "line_str", 0, .lineStr) } .skip(int()) = .string()if .err != nil {return"", 0, 0, DecodeError{"line", .buf.off, .err.Error()} }caseformStrpSup:// Supplemental sections not yet supported.if { .buf.uint64() } else { .buf.uint32() }caseformStrx:// .debug_line.dwo sections not yet supported. .buf.uint()caseformStrx1: .buf.uint8()caseformStrx2: .buf.uint16()caseformStrx3: .buf.uint24()caseformStrx4: .buf.uint32()caseformData1: = uint64(.buf.uint8())caseformData2: = uint64(.buf.uint16())caseformData4: = uint64(.buf.uint32())caseformData8: = .buf.uint64()caseformData16: .buf.bytes(16)caseformDwarfBlock: .buf.bytes(int(.buf.uint()))caseformUdata: = .buf.uint() }switch .lnct {caselnctPath: = caselnctDirectoryIndex:if >= uint64(len(.directories)) {return"", 0, 0, DecodeError{"line", .buf.off, "directory index out of range"} } = .directories[]caselnctTimestamp: = caselnctSize: = caselnctMD5:// Ignored. } }if != "" && != "" { = pathJoin(, ) }return , , , 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 ( *LineReader) () (bool, error) { := .buf.string()if .buf.err != nil {returnfalse, .buf.err }iflen() == 0 {returntrue, nil } := .buf.off := int(.buf.uint())if !pathIsAbs() {if >= len(.directories) {returnfalse, DecodeError{"line", , "directory index too large"} } = pathJoin(.directories[], ) } := .buf.uint() := int(.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.iflen(.fileEntries) < cap(.fileEntries) { := .fileEntries[:len(.fileEntries)+1]if [len()-1] != nil {// We already processed this addition. .fileEntries = returnfalse, nil } } .fileEntries = append(.fileEntries, &LineFile{, , })returnfalse, nil}// updateFile updates r.state.File after r.fileIndex has// changed or r.fileEntries has changed.func ( *LineReader) () {if .fileIndex < len(.fileEntries) { .state.File = .fileEntries[.fileIndex] } else { .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 ( *LineReader) ( *LineEntry) error {if .buf.err != nil {return .buf.err }// Execute opcodes until we reach an opcode that emits a line // table entry.for {iflen(.buf.data) == 0 {returnio.EOF } := .step()if .buf.err != nil {return .buf.err }if {returnnil } }}// 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 ( *LineReader) ( *LineEntry) bool { := int(.buf.uint8())if >= .opcodeBase {// Special opcode [DWARF2 6.2.5.1, DWARF4 6.2.5.1] := - .opcodeBase .advancePC( / .lineRange) := .lineBase + %.lineRange .state.Line += goto }switch {case0:// Extended opcode [DWARF2 6.2.5.3] := Offset(.buf.uint()) := .buf.off := .buf.uint8()switch {caselneEndSequence: .state.EndSequence = true * = .state .resetState()caselneSetAddress:switch .addrsize {case1: .state.Address = uint64(.buf.uint8())case2: .state.Address = uint64(.buf.uint16())case4: .state.Address = uint64(.buf.uint32())case8: .state.Address = .buf.uint64()default: .buf.error("unknown address size") }caselneDefineFile:if , := .readFileEntry(); != nil { .buf.err = returnfalse } elseif { .buf.err = DecodeError{"line", , "malformed DW_LNE_define_file operation"}returnfalse } .updateFile()caselneSetDiscriminator:// [DWARF4 6.2.5.3] .state.Discriminator = int(.buf.uint()) } .buf.skip(int( + - .buf.off))if == lneEndSequence {returntrue }// Standard opcodes [DWARF2 6.2.5.2]caselnsCopy:gotocaselnsAdvancePC: .advancePC(int(.buf.uint()))caselnsAdvanceLine: .state.Line += int(.buf.int())caselnsSetFile: .fileIndex = int(.buf.uint()) .updateFile()caselnsSetColumn: .state.Column = int(.buf.uint())caselnsNegateStmt: .state.IsStmt = !.state.IsStmtcaselnsSetBasicBlock: .state.BasicBlock = truecaselnsConstAddPC: .advancePC((255 - .opcodeBase) / .lineRange)caselnsFixedAdvancePC: .state.Address += uint64(.buf.uint16())// DWARF3 standard opcodes [DWARF3 6.2.5.2]caselnsSetPrologueEnd: .state.PrologueEnd = truecaselnsSetEpilogueBegin: .state.EpilogueBegin = truecaselnsSetISA: .state.ISA = int(.buf.uint())default:// Unhandled standard opcode. Skip the number of // arguments that the prologue says this opcode has.for := 0; < .opcodeLengths[]; ++ { .buf.uint() } }returnfalse: * = .state .state.BasicBlock = false .state.PrologueEnd = false .state.EpilogueBegin = false .state.Discriminator = 0returntrue}// advancePC advances "operation pointer" (the combination of Address// and OpIndex) in r.state by opAdvance steps.func ( *LineReader) ( int) { := .state.OpIndex + .state.Address += uint64(.minInstructionLength * ( / .maxOpsPerInstruction)) .state.OpIndex = % .maxOpsPerInstruction}// A LineReaderPos represents a position in a line table.typeLineReaderPosstruct {// 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 ( *LineReader) () LineReaderPos {returnLineReaderPos{.buf.off, len(.fileEntries), .state, .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 ( *LineReader) ( LineReaderPos) { .buf.off = .off .buf.data = .section[.buf.off:.endOffset] .fileEntries = .fileEntries[:.numFileEntries] .state = .state .fileIndex = .fileIndex}// Reset repositions the line table reader at the beginning of the// line table.func ( *LineReader) () {// Reset buffer to the line number program offset. .buf.off = .programOffset .buf.data = .section[.buf.off:.endOffset]// Reset file entries list. .fileEntries = .fileEntries[:.initialFileEntries]// Reset line number program state. .resetState()}// resetState resets r.state to its default valuesfunc ( *LineReader) () {// Reset the state machine registers to the defaults given in // [DWARF4 6.2.2]. .state = LineEntry{Address: 0,OpIndex: 0,File: nil,Line: 1,Column: 0,IsStmt: .defaultIsStmt,BasicBlock: false,PrologueEnd: false,EpilogueBegin: false,ISA: 0,Discriminator: 0, } .fileIndex = 1 .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 ( *LineReader) () []*LineFile {return .fileEntries}// ErrUnknownPC is the error returned by LineReader.ScanPC when the// seek PC is not covered by any entry in the line table.varErrUnknownPC = 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 ( *LineReader) ( uint64, *LineEntry) error {if := .Next(); != nil {return }if .Address > {// We're too far. Start at the beginning of the table. .Reset()if := .Next(); != nil {return }if .Address > {// The whole table starts after pc. .Reset()returnErrUnknownPC } }// Scan until we pass pc, then back up one.for {varLineEntry := .Tell()if := .Next(&); != nil {if == io.EOF {returnErrUnknownPC }return }if .Address > {if .EndSequence {// pc is in a hole in the table.returnErrUnknownPC }// entry is the desired entry. Back up the // cursor to "next" and return success. .Seek()returnnil } * = }}// 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( string) bool { _, = splitDrive()returnlen() > 0 && ([0] == '/' || [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(, string) string {iflen() == 0 {return }// 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. , := splitDrive()if == "" {// UNIX-style path.returnpath.Join(, ) }// DOS-style path. , := splitDrive()if != "" {if !strings.EqualFold(, ) {// Different drives. There's not much we can // do here, so just ignore the directory.return + }// Drives are the same. Ignore drive on filename. }if !(strings.HasSuffix(, "/") || strings.HasSuffix(, `\`)) && != "" { := `\`ifstrings.HasPrefix(, "/") { = `/` } += }return + + }// splitDrive splits the DOS drive letter or UNC share point from// path, if any. path == drive + restfunc splitDrive( string) (, string) {iflen() >= 2 && [1] == ':' {if := [0]; 'a' <= && <= 'z' || 'A' <= && <= 'Z' {return [:2], [2:] } }iflen() > 3 && ([0] == '\\' || [0] == '/') && ([1] == '\\' || [1] == '/') {// Normalize the path so we can search for just \ below. := strings.Replace(, "/", `\`, -1)// Get the host part, which must be non-empty. := strings.IndexByte([2:], '\\') + 2if > 2 {// Get the mount-point part, which must be non-empty. := strings.IndexByte([+1:], '\\') + + 1if > {return [:], [:] } } }return"", }
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.