From 767e7680ec7ed541ccc2e1f32d04b077f5422d25 Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Tue, 12 Nov 2024 12:02:17 -0500 Subject: [PATCH] cmd/link, cmd/internal/dwarf: add DWARF5 line table support This patch rolls out the necessary changes to migrate the DWARF line table support in the compiler and linker to DWARF version 5, gated by the "dwarf5" GOEXPERIMENT. DWARF version 5 includes a number of changes to the line table, notably a revamped prolog section and a change in the indexing system used to refer to files and directories within the line table program. Specifically, prior to DWARF 4 a compilation's directory table was considered to have an implicit zero entry containing the compilation directory of the translation unit (package), and the file table was considered to have an implicit zero entry storing the "primary source file" (stored in the compilation unit DIE name). DWARF 5 does away with these implicity entries meaning that files and dirs are now effectively a 0-based index. Updates #26379. Change-Id: I9b4f1be5415aacec1ba57366d60bd48819c56ea5 Reviewed-on: https://go-review.googlesource.com/c/go/+/633879 Reviewed-by: Alessandro Arzilli Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/cmd/internal/dwarf/dwarf.go | 10 ++- src/cmd/link/internal/ld/dwarf.go | 139 ++++++++++++++++++++++++++---- 2 files changed, 128 insertions(+), 21 deletions(-) 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. + + // 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]) + } - // 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 + // ... 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 -- 2.50.0