gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/dwarf/line/line_parser.go (about)

     1  package line
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"path"
     7  	"strings"
     8  
     9  	"gitlab.com/Raven-IO/raven-delve/pkg/dwarf"
    10  	"gitlab.com/Raven-IO/raven-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 = buf.Next(1)[0]
   136  	if p.Version >= 4 {
   137  		p.MaxOpPerInstr = buf.Next(1)[0]
   138  	} else {
   139  		p.MaxOpPerInstr = 1
   140  	}
   141  	p.InitialIsStmt = buf.Next(1)[0]
   142  	p.LineBase = int8(buf.Next(1)[0])
   143  	p.LineRange = buf.Next(1)[0]
   144  	p.OpcodeBase = 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  }