diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index 02e4c94c3a..3e5e2bf6bb 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -1207,6 +1207,12 @@ func PutAbstractFunc(ctxt Context, s *FnState) error { return nil } +// dwarfFileIndex returns the DWARF file index value for the file associated +// with pos. +func dwarfFileIndex(pos src.Pos) int64 { + return int64(1 + pos.FileIndex()) +} + // Emit DWARF attributes and child DIEs for an inlined subroutine. The // first attribute of an inlined subroutine DIE is a reference back to // its corresponding 'abstract' DIE (containing location-independent @@ -1240,7 +1246,7 @@ func putInlinedFunc(ctxt Context, s *FnState, callIdx int) error { } // Emit call file, line attrs. - putattr(ctxt, s.Info, abbrev, DW_FORM_data4, DW_CLS_CONSTANT, int64(1+ic.CallPos.FileIndex()), nil) // 1-based file table + putattr(ctxt, s.Info, abbrev, DW_FORM_data4, DW_CLS_CONSTANT, dwarfFileIndex(ic.CallPos), nil) form := int(expandPseudoForm(DW_FORM_udata_pseudo)) putattr(ctxt, s.Info, abbrev, form, DW_CLS_CONSTANT, int64(ic.CallPos.RelLine()), nil) @@ -1343,7 +1349,7 @@ func PutDefaultFunc(ctxt Context, s *FnState, isWrapper bool) error { if isWrapper { putattr(ctxt, s.Info, abbrev, DW_FORM_flag, DW_CLS_FLAG, int64(1), 0) } else { - putattr(ctxt, s.Info, abbrev, DW_FORM_data4, DW_CLS_CONSTANT, int64(1+s.StartPos.FileIndex()), nil) // 1-based file index + putattr(ctxt, s.Info, abbrev, DW_FORM_data4, DW_CLS_CONSTANT, dwarfFileIndex(s.StartPos), nil) putattr(ctxt, s.Info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(s.StartPos.RelLine()), nil) var ev int64 diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index b653e09a3c..eb439ec923 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1212,18 +1212,70 @@ func expandFile(fname string) string { // writeDirFileTables emits the portion of the DWARF line table // prologue containing the include directories and file names, -// described in section 6.2.4 of the DWARF 4 standard. It walks the +// described in section 6.2.4 of the DWARF standard. It walks the // filepaths for the unit to discover any common directories, which // are emitted to the directory table first, then the file table is // emitted after that. +// +// Note that there are some differences betwen DWARF versions 4 and 5 +// regarding how files and directories are handled; the chief item is +// that in version 4 we have an implicit directory index 0 that holds +// the compilation dir, and an implicit file index 0 that holds the +// "primary" source file being compiled (with the assumption that +// we'll get these two pieces of info from attributes on the +// compilation unit DIE). DWARF version 5 does away with these +// implicit entries; if you want to refer to a file or dir, you have +// to mention it explicitly. +// +// So, let's say we have a Go package with import path +// "github.com/fruit/bowl" with two source files, "apple.go" and +// "orange.go". The directory and file table in version 4 might look +// like: +// +// Dir Table: +// 1 github.com/fruit/bowl +// +// File Table: +// Entry Dir Time Size Name +// 1 0 0 0 +// 2 1 0 0 apple.go +// 3 1 0 0 orange.go +// +// With DWARF version 5, the file and directory tables might look like +// +// Dir Table: +// 0 . +// 1 github.com/fruit/bowl +// +// File Table: +// Entry Dir Name +// 0 0 ? /* see remark below about this entry */ +// 1 0 +// 2 1 apple.go +// 3 1 orange.go +// +// Also worth noting that the line table prolog allows you to control +// what items or fields appear in file and die entries (as opposed +// to the fixed dir/time/size/name format for files in DWARF 4). func (d *dwctxt) writeDirFileTables(unit *sym.CompilationUnit, lsu *loader.SymbolBuilder) { type fileDir struct { base string dir int } dirNums := make(map[string]int) - dirs := []string{""} + dirs := []string{"."} + dirNums["."] = 0 files := []fileDir{} + if buildcfg.Experiment.Dwarf5 { + // Add a dummy zero entry (not used for anything) so as to + // ensure that the first useful file index is 1, since that's + // the default value of the line table file register). Note + // the "?"; this was originally an empty string, but doing + // this triggers crashes in some versions of GDB, see + // https://sourceware.org/bugzilla/show_bug.cgi?id=30357 for + // the details. + files = append(files, fileDir{base: "?", dir: 0}) + } // Preprocess files to collect directories. This assumes that the // file table is already de-duped. @@ -1241,7 +1293,7 @@ func (d *dwctxt) writeDirFileTables(unit *sym.CompilationUnit, lsu *loader.Symbo dir := path.Dir(name) dirIdx, ok := dirNums[dir] if !ok && dir != "." { - dirIdx = len(dirNums) + 1 + dirIdx = len(dirNums) dirNums[dir] = dirIdx dirs = append(dirs, dir) } @@ -1260,22 +1312,61 @@ func (d *dwctxt) writeDirFileTables(unit *sym.CompilationUnit, lsu *loader.Symbo } } - // Emit directory section. This is a series of nul terminated - // strings, followed by a single zero byte. lsDwsym := dwSym(lsu.Sym()) - for k := 1; k < len(dirs); k++ { - d.AddString(lsDwsym, dirs[k]) - } - lsu.AddUint8(0) // terminator + if buildcfg.Experiment.Dwarf5 { + // Emit DWARF5 directory and file sections. The key added + // element here for version 5 is that we emit a small prolog + // describing the format of each file/dir entry, then a count + // of the entries, then the entries themselves. - // Emit file section. - for k := 0; k < len(files); k++ { - d.AddString(lsDwsym, files[k].base) - dwarf.Uleb128put(d, lsDwsym, int64(files[k].dir)) - lsu.AddUint8(0) // mtime - lsu.AddUint8(0) // length + // Dir table first... + lsu.AddUint8(1) // directory_entry_format_count + // each directory entry is just a single path string. + dwarf.Uleb128put(d, lsDwsym, dwarf.DW_LNCT_path) + dwarf.Uleb128put(d, lsDwsym, dwarf.DW_FORM_string) + // emit count, then dirs + dwarf.Uleb128put(d, lsDwsym, int64(len(dirs))) + for k := 0; k < len(dirs); k++ { + d.AddString(lsDwsym, dirs[k]) + } + + // ... now file table. With DWARF version 5 we put out a + // prolog that describes the format of each file entry; our + // chosen format is a pair where F is the file and D is + // the dir index (zero-based). + lsu.AddUint8(2) // file_entry_format_count + // each file entry is just a file path followed by dir index. + dwarf.Uleb128put(d, lsDwsym, dwarf.DW_LNCT_path) + dwarf.Uleb128put(d, lsDwsym, dwarf.DW_FORM_string) + dwarf.Uleb128put(d, lsDwsym, dwarf.DW_LNCT_directory_index) + dwarf.Uleb128put(d, lsDwsym, dwarf.DW_FORM_udata) + // emit count, then files + dwarf.Uleb128put(d, lsDwsym, int64(len(files))) + for k := 0; k < len(files); k++ { + d.AddString(lsDwsym, files[k].base) + dwarf.Uleb128put(d, lsDwsym, int64(files[k].dir)) + } + } else { + // Emit DWARF4 directory and file sections. + + // Dir table first. This just a series of nul terminated + // strings, followed by a single zero byte. + for k := 1; k < len(dirs); k++ { + d.AddString(lsDwsym, dirs[k]) + } + lsu.AddUint8(0) // terminator needed in V4 case. + + // File table next. This is a series of + // file/dirindex/mtime/length tuples (where dirindex is + // 1-based) followed by a single zero byte. + for k := 0; k < len(files); k++ { + d.AddString(lsDwsym, files[k].base) + dwarf.Uleb128put(d, lsDwsym, int64(files[k].dir)) + lsu.AddUint8(0) // mtime + lsu.AddUint8(0) // length + } + lsu.AddUint8(0) // terminator } - lsu.AddUint8(0) // terminator } // writelines collects up and chains together the symbols needed to @@ -1304,13 +1395,23 @@ func (d *dwctxt) writelines(unit *sym.CompilationUnit, lineProlog loader.Sym) [] unitLengthOffset := lsu.Size() d.createUnitLength(lsu, 0) // unit_length (*), filled in at end unitstart = lsu.Size() - lsu.AddUint16(d.arch, 2) // dwarf version (appendix F) -- version 3 is incompatible w/ XCode 9.0's dsymutil, latest supported on OSX 10.12 as of 2018-05 + if buildcfg.Experiment.Dwarf5 { + lsu.AddUint16(d.arch, 5) + // DWARF5 requires address_size and segment selector sizes + // here, both ubyte format. + lsu.AddUint8(uint8(d.arch.PtrSize)) + lsu.AddUint8(0) + } else { + lsu.AddUint16(d.arch, 2) // dwarf version (appendix F) -- version 3 is incompatible w/ XCode 9.0's dsymutil, latest supported on OSX 10.12 as of 2018-05 + } headerLengthOffset := lsu.Size() d.addDwarfAddrField(lsu, 0) // header_length (*), filled in at end headerstart = lsu.Size() - // cpos == unitstart + 4 + 2 + 4 - lsu.AddUint8(1) // minimum_instruction_length + lsu.AddUint8(1) // minimum_instruction_length + if buildcfg.Experiment.Dwarf5 { + lsu.AddUint8(1) // maximum_operations_per_instruction + } lsu.AddUint8(is_stmt) // default_is_stmt lsu.AddUint8(LINE_BASE & 0xFF) // line_base lsu.AddUint8(LINE_RANGE) // line_range