github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/dwarf/line/line_parser.go (about) 1 package line 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "path" 7 "strings" 8 9 "github.com/go-delve/delve/pkg/dwarf" 10 "github.com/go-delve/delve/pkg/dwarf/leb128" 11 ) 12 13 // DebugLinePrologue prologue of .debug_line data. 14 type DebugLinePrologue struct { 15 UnitLength uint32 16 Version uint16 17 Length uint32 18 MinInstrLength uint8 19 MaxOpPerInstr uint8 20 InitialIsStmt uint8 21 LineBase int8 22 LineRange uint8 23 OpcodeBase uint8 24 StdOpLengths []uint8 25 } 26 27 // DebugLineInfo info of .debug_line data. 28 type DebugLineInfo struct { 29 Prologue *DebugLinePrologue 30 IncludeDirs []string 31 FileNames []*FileEntry 32 Instructions []byte 33 Lookup map[string]*FileEntry 34 35 Logf func(string, ...interface{}) 36 37 // stateMachineCache[pc] is a state machine stopped at pc 38 stateMachineCache map[uint64]*StateMachine 39 40 // lastMachineCache[pc] is a state machine stopped at an address after pc 41 lastMachineCache map[uint64]*StateMachine 42 43 // debugLineStr is the contents of the .debug_line_str section. 44 debugLineStr []byte 45 46 // staticBase is the address at which the executable is loaded, 0 for non-PIEs 47 staticBase uint64 48 49 // if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/) 50 normalizeBackslash bool 51 ptrSize int 52 endSeqIsValid bool 53 } 54 55 // FileEntry file entry in File Name Table. 56 type FileEntry struct { 57 Path string 58 DirIdx uint64 59 LastModTime uint64 60 Length uint64 61 } 62 63 type DebugLines []*DebugLineInfo 64 65 // ParseAll parses all debug_line segments found in data 66 func ParseAll(data []byte, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines { 67 var ( 68 lines = make(DebugLines, 0) 69 buf = bytes.NewBuffer(data) 70 ) 71 72 // We have to parse multiple file name tables here. 73 for buf.Len() > 0 { 74 lines = append(lines, Parse("", buf, debugLineStr, logfn, staticBase, normalizeBackslash, ptrSize)) 75 } 76 77 return lines 78 } 79 80 // Parse parses a single debug_line segment from buf. Compdir is the 81 // DW_AT_comp_dir attribute of the associated compile unit. 82 func Parse(compdir string, buf *bytes.Buffer, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo { 83 dbl := new(DebugLineInfo) 84 dbl.Logf = logfn 85 if logfn == nil { 86 dbl.Logf = func(string, ...interface{}) {} 87 } 88 dbl.staticBase = staticBase 89 dbl.ptrSize = ptrSize 90 dbl.Lookup = make(map[string]*FileEntry) 91 dbl.IncludeDirs = append(dbl.IncludeDirs, compdir) 92 93 dbl.stateMachineCache = make(map[uint64]*StateMachine) 94 dbl.lastMachineCache = make(map[uint64]*StateMachine) 95 dbl.normalizeBackslash = normalizeBackslash 96 dbl.debugLineStr = debugLineStr 97 98 parseDebugLinePrologue(dbl, buf) 99 if dbl.Prologue.Version >= 5 { 100 if !parseIncludeDirs5(dbl, buf) { 101 return nil 102 } 103 if !parseFileEntries5(dbl, buf) { 104 return nil 105 } 106 } else { 107 if !parseIncludeDirs2(dbl, buf) { 108 return nil 109 } 110 if !parseFileEntries2(dbl, buf) { 111 return nil 112 } 113 } 114 115 // Instructions size calculation breakdown: 116 // - dbl.Prologue.UnitLength is the length of the entire unit, not including the 4 bytes to represent that length. 117 // - dbl.Prologue.Length is the length of the prologue not including unit length, version or prologue length itself. 118 // - So you have UnitLength - PrologueLength - (version_length_bytes(2) + prologue_length_bytes(4)). 119 dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6)) 120 121 return dbl 122 } 123 124 func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) { 125 p := new(DebugLinePrologue) 126 127 p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4)) 128 p.Version = binary.LittleEndian.Uint16(buf.Next(2)) 129 if p.Version >= 5 { 130 dbl.ptrSize = int(buf.Next(1)[0]) // address_size 131 dbl.ptrSize += int(buf.Next(1)[0]) // segment_selector_size 132 } 133 134 p.Length = binary.LittleEndian.Uint32(buf.Next(4)) 135 p.MinInstrLength = uint8(buf.Next(1)[0]) 136 if p.Version >= 4 { 137 p.MaxOpPerInstr = uint8(buf.Next(1)[0]) 138 } else { 139 p.MaxOpPerInstr = 1 140 } 141 p.InitialIsStmt = uint8(buf.Next(1)[0]) 142 p.LineBase = int8(buf.Next(1)[0]) 143 p.LineRange = uint8(buf.Next(1)[0]) 144 p.OpcodeBase = uint8(buf.Next(1)[0]) 145 146 p.StdOpLengths = make([]uint8, p.OpcodeBase-1) 147 binary.Read(buf, binary.LittleEndian, &p.StdOpLengths) 148 149 dbl.Prologue = p 150 } 151 152 // parseIncludeDirs2 parses the directory table for DWARF version 2 through 4. 153 func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) bool { 154 for { 155 str, err := dwarf.ReadString(buf) 156 if err != nil { 157 if info.Logf != nil { 158 info.Logf("error reading string: %v", err) 159 } 160 return false 161 } 162 if str == "" { 163 break 164 } 165 166 info.IncludeDirs = append(info.IncludeDirs, str) 167 } 168 return true 169 } 170 171 // parseIncludeDirs5 parses the directory table for DWARF version 5. 172 func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool { 173 dirEntryFormReader := readEntryFormat(buf, info.Logf) 174 if dirEntryFormReader == nil { 175 return false 176 } 177 dirCount, _ := leb128.DecodeUnsigned(buf) 178 info.IncludeDirs = make([]string, 0, dirCount) 179 for i := uint64(0); i < dirCount; i++ { 180 dirEntryFormReader.reset() 181 for dirEntryFormReader.next(buf) { 182 switch dirEntryFormReader.contentType { 183 case _DW_LNCT_path: 184 switch dirEntryFormReader.formCode { 185 case _DW_FORM_string: 186 info.IncludeDirs = append(info.IncludeDirs, dirEntryFormReader.str) 187 case _DW_FORM_line_strp: 188 buf := bytes.NewBuffer(info.debugLineStr[dirEntryFormReader.u64:]) 189 dir, _ := dwarf.ReadString(buf) 190 info.IncludeDirs = append(info.IncludeDirs, dir) 191 default: 192 info.Logf("unsupported string form %#x", dirEntryFormReader.formCode) 193 } 194 case _DW_LNCT_directory_index: 195 case _DW_LNCT_timestamp: 196 case _DW_LNCT_size: 197 case _DW_LNCT_MD5: 198 } 199 } 200 if dirEntryFormReader.err != nil { 201 if info.Logf != nil { 202 info.Logf("error reading directory entries table: %v", dirEntryFormReader.err) 203 } 204 return false 205 } 206 } 207 return true 208 } 209 210 // parseFileEntries2 parses the file table for DWARF 2 through 4 211 func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) bool { 212 for { 213 entry := readFileEntry(info, buf, true) 214 if entry == nil { 215 return false 216 } 217 if entry.Path == "" { 218 break 219 } 220 221 info.FileNames = append(info.FileNames, entry) 222 info.Lookup[entry.Path] = entry 223 } 224 return true 225 } 226 227 func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *FileEntry { 228 entry := new(FileEntry) 229 230 var err error 231 entry.Path, err = dwarf.ReadString(buf) 232 if err != nil { 233 if info.Logf != nil { 234 info.Logf("error reading file entry: %v", err) 235 } 236 return nil 237 } 238 if entry.Path == "" && exitOnEmptyPath { 239 return entry 240 } 241 242 if info.normalizeBackslash { 243 entry.Path = strings.ReplaceAll(entry.Path, "\\", "/") 244 } 245 246 entry.DirIdx, _ = leb128.DecodeUnsigned(buf) 247 entry.LastModTime, _ = leb128.DecodeUnsigned(buf) 248 entry.Length, _ = leb128.DecodeUnsigned(buf) 249 if !pathIsAbs(entry.Path) { 250 if entry.DirIdx < uint64(len(info.IncludeDirs)) { 251 entry.Path = path.Join(info.IncludeDirs[entry.DirIdx], entry.Path) 252 } 253 } 254 255 return entry 256 } 257 258 // pathIsAbs returns true if this is an absolute path. 259 // We can not use path.IsAbs because it will not recognize windows paths as 260 // absolute. We also can not use filepath.Abs because we want this 261 // processing to be independent of the host operating system (we could be 262 // reading an executable file produced on windows on a unix machine or vice 263 // versa). 264 func pathIsAbs(s string) bool { 265 if len(s) >= 1 && s[0] == '/' { 266 return true 267 } 268 if len(s) >= 2 && s[1] == ':' && (('a' <= s[0] && s[0] <= 'z') || ('A' <= s[0] && s[0] <= 'Z')) { 269 return true 270 } 271 return false 272 } 273 274 // parseFileEntries5 parses the file table for DWARF 5 275 func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool { 276 fileEntryFormReader := readEntryFormat(buf, info.Logf) 277 if fileEntryFormReader == nil { 278 return false 279 } 280 fileCount, _ := leb128.DecodeUnsigned(buf) 281 info.FileNames = make([]*FileEntry, 0, fileCount) 282 for i := 0; i < int(fileCount); i++ { 283 var ( 284 p string 285 diridx int 286 287 entry = new(FileEntry) 288 ) 289 290 fileEntryFormReader.reset() 291 292 for fileEntryFormReader.next(buf) { 293 diridx = -1 294 295 switch fileEntryFormReader.contentType { 296 case _DW_LNCT_path: 297 switch fileEntryFormReader.formCode { 298 case _DW_FORM_string: 299 p = fileEntryFormReader.str 300 case _DW_FORM_line_strp: 301 buf := bytes.NewBuffer(info.debugLineStr[fileEntryFormReader.u64:]) 302 p, _ = dwarf.ReadString(buf) 303 default: 304 info.Logf("unsupported string form %#x", fileEntryFormReader.formCode) 305 } 306 case _DW_LNCT_directory_index: 307 diridx = int(fileEntryFormReader.u64) 308 case _DW_LNCT_timestamp: 309 entry.LastModTime = fileEntryFormReader.u64 310 case _DW_LNCT_size: 311 entry.Length = fileEntryFormReader.u64 312 case _DW_LNCT_MD5: 313 // not implemented 314 } 315 } 316 if fileEntryFormReader.err != nil { 317 if info.Logf != nil { 318 info.Logf("error reading file entries table: %v", fileEntryFormReader.err) 319 } 320 return false 321 } 322 323 if diridx >= 0 && diridx < len(info.IncludeDirs) { 324 p = path.Join(info.IncludeDirs[diridx], p) 325 } 326 if info.normalizeBackslash { 327 p = strings.ReplaceAll(p, "\\", "/") 328 } 329 entry.Path = p 330 info.FileNames = append(info.FileNames, entry) 331 info.Lookup[entry.Path] = entry 332 } 333 return true 334 }