github.com/Rookout/GoSDK@v0.1.48/pkg/services/instrumentation/dwarf/line/line_parser.go (about)

     1  // The MIT License (MIT)
     2  
     3  // Copyright (c) 2014 Derek Parker
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy of
     6  // this software and associated documentation files (the "Software"), to deal in
     7  // the Software without restriction, including without limitation the rights to
     8  // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
     9  // the Software, and to permit persons to whom the Software is furnished to do so,
    10  // subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in all
    13  // copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    17  // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    18  // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    19  // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    20  // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    21  
    22  package line
    23  
    24  import (
    25  	"bytes"
    26  	"encoding/binary"
    27  	"path"
    28  	"strings"
    29  	"sync"
    30  
    31  	"github.com/Rookout/GoSDK/pkg/services/instrumentation/dwarf/util"
    32  )
    33  
    34  
    35  type DebugLinePrologue struct {
    36  	UnitLength     uint32
    37  	Version        uint16
    38  	Length         uint32
    39  	MinInstrLength uint8
    40  	MaxOpPerInstr  uint8
    41  	InitialIsStmt  uint8
    42  	LineBase       int8
    43  	LineRange      uint8
    44  	OpcodeBase     uint8
    45  	StdOpLengths   []uint8
    46  }
    47  
    48  
    49  type DebugLineInfo struct {
    50  	Prologue     *DebugLinePrologue
    51  	IncludeDirs  []string
    52  	FileNames    []*FileEntry
    53  	Instructions []byte
    54  	Lookup       map[string]*FileEntry
    55  
    56  	Logf func(string, ...interface{})
    57  
    58  	stateMachineCacheLock *sync.RWMutex
    59  	
    60  	stateMachineCache map[uint64]*StateMachine
    61  
    62  	lastMachineCacheLock *sync.RWMutex
    63  	
    64  	lastMachineCache map[uint64]*StateMachine
    65  
    66  	
    67  	debugLineStr []byte
    68  
    69  	
    70  	staticBase uint64
    71  
    72  	
    73  	normalizeBackslash bool
    74  	ptrSize            int
    75  	endSeqIsValid      bool
    76  }
    77  
    78  
    79  type FileEntry struct {
    80  	Path        string
    81  	DirIdx      uint64
    82  	LastModTime uint64
    83  	Length      uint64
    84  }
    85  
    86  type DebugLines []*DebugLineInfo
    87  
    88  
    89  func ParseAll(data []byte, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines {
    90  	var (
    91  		lines = make(DebugLines, 0)
    92  		buf   = bytes.NewBuffer(data)
    93  	)
    94  
    95  	
    96  	for buf.Len() > 0 {
    97  		lines = append(lines, Parse("", buf, debugLineStr, logfn, staticBase, normalizeBackslash, ptrSize))
    98  	}
    99  
   100  	return lines
   101  }
   102  
   103  
   104  
   105  func Parse(compdir string, buf *bytes.Buffer, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo {
   106  	dbl := new(DebugLineInfo)
   107  	dbl.Logf = logfn
   108  	if logfn == nil {
   109  		dbl.Logf = func(string, ...interface{}) {}
   110  	}
   111  	dbl.staticBase = staticBase
   112  	dbl.ptrSize = ptrSize
   113  	dbl.Lookup = make(map[string]*FileEntry)
   114  	dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)
   115  
   116  	dbl.stateMachineCacheLock = &sync.RWMutex{}
   117  	dbl.stateMachineCacheLock.Lock()
   118  	dbl.stateMachineCache = make(map[uint64]*StateMachine)
   119  	dbl.stateMachineCacheLock.Unlock()
   120  	dbl.lastMachineCacheLock = &sync.RWMutex{}
   121  	dbl.lastMachineCacheLock.Lock()
   122  	dbl.lastMachineCache = make(map[uint64]*StateMachine)
   123  	dbl.lastMachineCacheLock.Unlock()
   124  	dbl.normalizeBackslash = normalizeBackslash
   125  	dbl.debugLineStr = debugLineStr
   126  
   127  	parseDebugLinePrologue(dbl, buf)
   128  	if dbl.Prologue.Version >= 5 {
   129  		if !parseIncludeDirs5(dbl, buf) {
   130  			return nil
   131  		}
   132  		if !parseFileEntries5(dbl, buf) {
   133  			return nil
   134  		}
   135  	} else {
   136  		if !parseIncludeDirs2(dbl, buf) {
   137  			return nil
   138  		}
   139  		if !parseFileEntries2(dbl, buf) {
   140  			return nil
   141  		}
   142  	}
   143  
   144  	
   145  	
   146  	
   147  	
   148  	dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6))
   149  
   150  	return dbl
   151  }
   152  
   153  func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
   154  	p := new(DebugLinePrologue)
   155  
   156  	p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4))
   157  	p.Version = binary.LittleEndian.Uint16(buf.Next(2))
   158  	if p.Version >= 5 {
   159  		dbl.ptrSize = int(buf.Next(1)[0])  
   160  		dbl.ptrSize += int(buf.Next(1)[0]) 
   161  	}
   162  
   163  	p.Length = binary.LittleEndian.Uint32(buf.Next(4))
   164  	p.MinInstrLength = uint8(buf.Next(1)[0])
   165  	if p.Version >= 4 {
   166  		p.MaxOpPerInstr = uint8(buf.Next(1)[0])
   167  	} else {
   168  		p.MaxOpPerInstr = 1
   169  	}
   170  	p.InitialIsStmt = uint8(buf.Next(1)[0])
   171  	p.LineBase = int8(buf.Next(1)[0])
   172  	p.LineRange = uint8(buf.Next(1)[0])
   173  	p.OpcodeBase = uint8(buf.Next(1)[0])
   174  
   175  	p.StdOpLengths = make([]uint8, p.OpcodeBase-1)
   176  	binary.Read(buf, binary.LittleEndian, &p.StdOpLengths)
   177  
   178  	dbl.Prologue = p
   179  }
   180  
   181  
   182  func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) bool {
   183  	for {
   184  		str, err := util.ParseString(buf)
   185  		if err != nil {
   186  			if info.Logf != nil {
   187  				info.Logf("error reading string: %v", err)
   188  			}
   189  			return false
   190  		}
   191  		if str == "" {
   192  			break
   193  		}
   194  
   195  		info.IncludeDirs = append(info.IncludeDirs, str)
   196  	}
   197  	return true
   198  }
   199  
   200  
   201  func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool {
   202  	dirEntryFormReader := readEntryFormat(buf, info.Logf)
   203  	if dirEntryFormReader == nil {
   204  		return false
   205  	}
   206  	dirCount, _ := util.DecodeULEB128(buf)
   207  	info.IncludeDirs = make([]string, 0, dirCount)
   208  	for i := uint64(0); i < dirCount; i++ {
   209  		dirEntryFormReader.reset()
   210  		for dirEntryFormReader.next(buf) {
   211  			switch dirEntryFormReader.contentType {
   212  			case _DW_LNCT_path:
   213  				switch dirEntryFormReader.formCode {
   214  				case _DW_FORM_string:
   215  					info.IncludeDirs = append(info.IncludeDirs, dirEntryFormReader.str)
   216  				case _DW_FORM_line_strp:
   217  					buf := bytes.NewBuffer(info.debugLineStr[dirEntryFormReader.u64:])
   218  					dir, _ := util.ParseString(buf)
   219  					info.IncludeDirs = append(info.IncludeDirs, dir)
   220  				default:
   221  					info.Logf("unsupported string form %#x", dirEntryFormReader.formCode)
   222  				}
   223  			case _DW_LNCT_directory_index:
   224  			case _DW_LNCT_timestamp:
   225  			case _DW_LNCT_size:
   226  			case _DW_LNCT_MD5:
   227  			}
   228  		}
   229  		if dirEntryFormReader.err != nil {
   230  			if info.Logf != nil {
   231  				info.Logf("error reading directory entries table: %v", dirEntryFormReader.err)
   232  			}
   233  			return false
   234  		}
   235  	}
   236  	return true
   237  }
   238  
   239  
   240  func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) bool {
   241  	for {
   242  		entry := readFileEntry(info, buf, true)
   243  		if entry == nil {
   244  			return false
   245  		}
   246  		if entry.Path == "" {
   247  			break
   248  		}
   249  
   250  		info.FileNames = append(info.FileNames, entry)
   251  		info.Lookup[entry.Path] = entry
   252  	}
   253  	return true
   254  }
   255  
   256  func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *FileEntry {
   257  	entry := new(FileEntry)
   258  
   259  	var err error
   260  	entry.Path, err = util.ParseString(buf)
   261  	if err != nil {
   262  		if info.Logf != nil {
   263  			info.Logf("error reading file entry: %v", err)
   264  		}
   265  		return nil
   266  	}
   267  	if entry.Path == "" && exitOnEmptyPath {
   268  		return entry
   269  	}
   270  
   271  	if info.normalizeBackslash {
   272  		entry.Path = strings.ReplaceAll(entry.Path, "\\", "/")
   273  	}
   274  
   275  	entry.DirIdx, _ = util.DecodeULEB128(buf)
   276  	entry.LastModTime, _ = util.DecodeULEB128(buf)
   277  	entry.Length, _ = util.DecodeULEB128(buf)
   278  	if !pathIsAbs(entry.Path) {
   279  		if entry.DirIdx < uint64(len(info.IncludeDirs)) {
   280  			entry.Path = path.Join(info.IncludeDirs[entry.DirIdx], entry.Path)
   281  		}
   282  	}
   283  
   284  	return entry
   285  }
   286  
   287  
   288  
   289  
   290  
   291  
   292  
   293  func pathIsAbs(s string) bool {
   294  	if len(s) >= 1 && s[0] == '/' {
   295  		return true
   296  	}
   297  	if len(s) >= 2 && s[1] == ':' && (('a' <= s[0] && s[0] <= 'z') || ('A' <= s[0] && s[0] <= 'Z')) {
   298  		return true
   299  	}
   300  	return false
   301  }
   302  
   303  
   304  func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool {
   305  	fileEntryFormReader := readEntryFormat(buf, info.Logf)
   306  	if fileEntryFormReader == nil {
   307  		return false
   308  	}
   309  	fileCount, _ := util.DecodeULEB128(buf)
   310  	info.FileNames = make([]*FileEntry, 0, fileCount)
   311  	for i := 0; i < int(fileCount); i++ {
   312  		fileEntryFormReader.reset()
   313  		for fileEntryFormReader.next(buf) {
   314  			entry := new(FileEntry)
   315  			var p string
   316  			var diridx int
   317  			diridx = -1
   318  
   319  			switch fileEntryFormReader.contentType {
   320  			case _DW_LNCT_path:
   321  				switch fileEntryFormReader.formCode {
   322  				case _DW_FORM_string:
   323  					p = fileEntryFormReader.str
   324  				case _DW_FORM_line_strp:
   325  					buf := bytes.NewBuffer(info.debugLineStr[fileEntryFormReader.u64:])
   326  					p, _ = util.ParseString(buf)
   327  				default:
   328  					info.Logf("unsupported string form %#x", fileEntryFormReader.formCode)
   329  				}
   330  			case _DW_LNCT_directory_index:
   331  				diridx = int(fileEntryFormReader.u64)
   332  			case _DW_LNCT_timestamp:
   333  				entry.LastModTime = fileEntryFormReader.u64
   334  			case _DW_LNCT_size:
   335  				entry.Length = fileEntryFormReader.u64
   336  			case _DW_LNCT_MD5:
   337  				
   338  			}
   339  
   340  			if info.normalizeBackslash {
   341  				p = strings.ReplaceAll(p, "\\", "/")
   342  			}
   343  
   344  			if diridx >= 0 && !pathIsAbs(p) && diridx < len(info.IncludeDirs) {
   345  				p = path.Join(info.IncludeDirs[diridx], p)
   346  			}
   347  			entry.Path = p
   348  			info.FileNames = append(info.FileNames, entry)
   349  			info.Lookup[entry.Path] = entry
   350  		}
   351  		if fileEntryFormReader.err != nil {
   352  			if info.Logf != nil {
   353  				info.Logf("error reading file entries table: %v", fileEntryFormReader.err)
   354  			}
   355  			return false
   356  		}
   357  	}
   358  	return true
   359  }