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