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