github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/dwarf/line/state_machine_test.go (about) 1 package line 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "debug/dwarf" 7 "debug/macho" 8 "encoding/binary" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "testing" 14 15 pdwarf "github.com/go-delve/delve/pkg/dwarf" 16 "github.com/go-delve/delve/pkg/dwarf/leb128" 17 ) 18 19 func slurpGzip(path string) ([]byte, error) { 20 fh, err := os.Open(path) 21 if err != nil { 22 return nil, err 23 } 24 defer fh.Close() 25 gzin, err := gzip.NewReader(fh) 26 if err != nil { 27 return nil, err 28 } 29 defer gzin.Close() 30 return ioutil.ReadAll(gzin) 31 } 32 33 func TestGrafana(t *testing.T) { 34 // Compares a full execution of our state machine on the debug_line section 35 // of grafana to the output generated using debug/dwarf.LineReader on the 36 // same section. 37 38 debugBytes, err := slurpGzip("_testdata/debug.grafana.debug.gz") 39 if err != nil { 40 t.Fatal(err) 41 } 42 exe, err := macho.NewFile(bytes.NewReader(debugBytes)) 43 if err != nil { 44 t.Fatal(err) 45 } 46 47 sec := exe.Section("__debug_line") 48 debugLineBytes, err := sec.Data() 49 if err != nil { 50 t.Fatal(err) 51 } 52 53 data, err := exe.DWARF() 54 if err != nil { 55 t.Fatal(err) 56 } 57 58 debugLineBuffer := bytes.NewBuffer(debugLineBytes) 59 rdr := data.Reader() 60 for { 61 e, err := rdr.Next() 62 if err != nil { 63 t.Fatal(err) 64 } 65 if e == nil { 66 break 67 } 68 rdr.SkipChildren() 69 if e.Tag != dwarf.TagCompileUnit { 70 continue 71 } 72 cuname, _ := e.Val(dwarf.AttrName).(string) 73 74 lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, nil, t.Logf, 0, false, 8) 75 lineInfo.endSeqIsValid = true 76 sm := newStateMachine(lineInfo, lineInfo.Instructions, 8) 77 78 lnrdr, err := data.LineReader(e) 79 if err != nil { 80 t.Fatal(err) 81 } 82 83 checkCompileUnit(t, cuname, lnrdr, sm) 84 } 85 } 86 87 func checkCompileUnit(t *testing.T, cuname string, lnrdr *dwarf.LineReader, sm *StateMachine) { 88 var lne dwarf.LineEntry 89 for { 90 if err := sm.next(); err != nil { 91 if err != io.EOF { 92 t.Fatalf("state machine next error: %v", err) 93 } 94 break 95 } 96 if !sm.valid { 97 continue 98 } 99 100 err := lnrdr.Next(&lne) 101 if err == io.EOF { 102 t.Fatalf("line reader ended before our state machine for compile unit %s", cuname) 103 } 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 tgt := fmt.Sprintf("%#x %s:%d isstmt:%v prologue_end:%v epilogue_begin:%v", lne.Address, lne.File.Name, lne.Line, lne.IsStmt, lne.PrologueEnd, lne.EpilogueBegin) 109 110 out := fmt.Sprintf("%#x %s:%d isstmt:%v prologue_end:%v epilogue_begin:%v", sm.address, sm.file, sm.line, sm.isStmt, sm.prologueEnd, sm.epilogueBegin) 111 if out != tgt { 112 t.Errorf("mismatch:\n") 113 t.Errorf("got:\t%s\n", out) 114 t.Errorf("expected:\t%s\n", tgt) 115 t.Fatal("previous error") 116 } 117 } 118 119 err := lnrdr.Next(&lne) 120 if err != io.EOF { 121 t.Fatalf("state machine ended before the line reader for compile unit %s", cuname) 122 } 123 } 124 125 func TestMultipleSequences(t *testing.T) { 126 // Check that our state machine (specifically PCToLine and AllPCsBetween) 127 // are correct when dealing with units containing more than one sequence. 128 129 const thefile = "thefile.go" 130 131 instr := bytes.NewBuffer(nil) 132 ptrSize := ptrSizeByRuntimeArch() 133 134 write_DW_LNE_set_address := func(addr uint64) { 135 instr.WriteByte(0) 136 leb128.EncodeUnsigned(instr, 9) // 1 + ptr_size 137 instr.WriteByte(DW_LINE_set_address) 138 pdwarf.WriteUint(instr, binary.LittleEndian, ptrSize, addr) 139 } 140 141 write_DW_LNS_copy := func() { 142 instr.WriteByte(DW_LNS_copy) 143 } 144 145 write_DW_LNS_advance_pc := func(off uint64) { 146 instr.WriteByte(DW_LNS_advance_pc) 147 leb128.EncodeUnsigned(instr, off) 148 } 149 150 write_DW_LNS_advance_line := func(off int64) { 151 instr.WriteByte(DW_LNS_advance_line) 152 leb128.EncodeSigned(instr, off) 153 } 154 155 write_DW_LNE_end_sequence := func() { 156 instr.WriteByte(0) 157 leb128.EncodeUnsigned(instr, 1) 158 instr.WriteByte(DW_LINE_end_sequence) 159 } 160 161 write_DW_LNE_set_address(0x400000) 162 write_DW_LNS_copy() // thefile.go:1 0x400000 163 write_DW_LNS_advance_pc(0x2) 164 write_DW_LNS_advance_line(1) 165 write_DW_LNS_copy() // thefile.go:2 0x400002 166 write_DW_LNS_advance_pc(0x2) 167 write_DW_LNS_advance_line(1) 168 write_DW_LNS_copy() // thefile.go:3 0x400004 169 write_DW_LNS_advance_pc(0x2) 170 write_DW_LNE_end_sequence() // thefile.go:3 ends the byte before 0x400006 171 172 write_DW_LNE_set_address(0x600000) 173 write_DW_LNS_advance_line(10) 174 write_DW_LNS_copy() // thefile.go:11 0x600000 175 write_DW_LNS_advance_pc(0x2) 176 write_DW_LNS_advance_line(1) 177 write_DW_LNS_copy() // thefile.go:12 0x600002 178 write_DW_LNS_advance_pc(0x2) 179 write_DW_LNS_advance_line(1) 180 write_DW_LNS_copy() // thefile.go:13 0x600004 181 write_DW_LNS_advance_pc(0x2) 182 write_DW_LNE_end_sequence() // thefile.go:13 ends the byte before 0x600006 183 184 write_DW_LNE_set_address(0x500000) 185 write_DW_LNS_advance_line(20) 186 write_DW_LNS_copy() // thefile.go:21 0x500000 187 write_DW_LNS_advance_pc(0x2) 188 write_DW_LNS_advance_line(1) 189 write_DW_LNS_copy() // thefile.go:22 0x500002 190 write_DW_LNS_advance_pc(0x2) 191 write_DW_LNS_advance_line(1) 192 write_DW_LNS_copy() // thefile.go:23 0x500004 193 write_DW_LNS_advance_pc(0x2) 194 write_DW_LNE_end_sequence() // thefile.go:23 ends the byte before 0x500006 195 196 lines := &DebugLineInfo{ 197 Prologue: &DebugLinePrologue{ 198 UnitLength: 1, 199 Version: 2, 200 MinInstrLength: 1, 201 InitialIsStmt: 1, 202 LineBase: -3, 203 LineRange: 12, 204 OpcodeBase: 13, 205 StdOpLengths: []uint8{0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1}, 206 }, 207 IncludeDirs: []string{}, 208 FileNames: []*FileEntry{&FileEntry{Path: thefile}}, 209 Instructions: instr.Bytes(), 210 ptrSize: ptrSize, 211 } 212 213 // Test that PCToLine is correct for all three sequences 214 215 for _, testCase := range []struct { 216 pc uint64 217 line int 218 }{ 219 {0x400000, 1}, 220 {0x400002, 2}, 221 {0x400004, 3}, 222 223 {0x500000, 21}, 224 {0x500002, 22}, 225 {0x500004, 23}, 226 227 {0x600000, 11}, 228 {0x600002, 12}, 229 {0x600004, 13}, 230 } { 231 sm := newStateMachine(lines, lines.Instructions, lines.ptrSize) 232 file, curline, ok := sm.PCToLine(testCase.pc) 233 if !ok { 234 t.Fatalf("Could not find %#x", testCase.pc) 235 } 236 if file != thefile { 237 t.Fatalf("Wrong file returned for %#x %q", testCase.pc, file) 238 } 239 if curline != testCase.line { 240 t.Errorf("Wrong line returned for %#x: got %d expected %d", testCase.pc, curline, testCase.line) 241 } 242 243 } 244 245 // Test that AllPCsBetween is correct for all three sequences 246 for _, testCase := range []struct { 247 start, end uint64 248 tgt []uint64 249 }{ 250 {0x400000, 0x400005, []uint64{0x400000, 0x400002, 0x400004}}, 251 {0x500000, 0x500005, []uint64{0x500000, 0x500002, 0x500004}}, 252 {0x600000, 0x600005, []uint64{0x600000, 0x600002, 0x600004}}, 253 } { 254 out, err := lines.AllPCsBetween(testCase.start, testCase.end, "", -1) 255 if err != nil { 256 t.Fatalf("AllPCsBetween(%#x, %#x): %v", testCase.start, testCase.end, err) 257 } 258 259 if len(out) != len(testCase.tgt) { 260 t.Errorf("AllPCsBetween(%#x, %#x): expected: %#x got: %#x", testCase.start, testCase.end, testCase.tgt, out) 261 } 262 } 263 }