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  }