github.com/ks888/tgo@v0.0.0-20190130135156-80bf89407292/testutils/testutils.go (about)

     1  package testutils
     2  
     3  import (
     4  	"debug/elf"
     5  	"debug/macho"
     6  	"fmt"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/ks888/tgo/log"
    13  )
    14  
    15  var (
    16  	// goBinaryPath is the path to the go binary used to build this test program.
    17  	// This go binary is used to build the testdata.
    18  	goBinaryPath string = filepath.Join(runtime.GOROOT(), "bin", "go")
    19  
    20  	ProgramHelloworld        string
    21  	ProgramHelloworldNoDwarf string
    22  	// These addresses are retrieved from the dwarf version. Assume they are same as non-dwarf version.
    23  	HelloworldAddrMain                    uint64
    24  	HelloworldAddrNoParameter             uint64
    25  	HelloworldAddrOneParameter            uint64
    26  	HelloworldAddrOneParameterAndVariable uint64
    27  	HelloworldAddrTwoParameters           uint64
    28  	HelloworldAddrFuncWithAbstractOrigin  uint64 // any function which corresponding DIE has the DW_AT_abstract_origin attribute.
    29  	HelloworldAddrTwoReturns              uint64
    30  	HelloworldAddrErrorsNew               uint64
    31  	HelloworldAddrGoBuildID               uint64
    32  	HelloworldAddrFirstModuleData         uint64
    33  
    34  	ProgramInfloop             string
    35  	InfloopAddrMain            uint64
    36  	InfloopAddrFirstModuleData uint64
    37  
    38  	ProgramGoRoutines             string
    39  	ProgramGoRoutinesNoDwarf      string
    40  	GoRoutinesAddrMain            uint64
    41  	GoRoutinesAddrInc             uint64
    42  	GoRoutinesAddrFirstModuleData uint64
    43  
    44  	ProgramRecursive             string
    45  	RecursiveAddrMain            uint64
    46  	RecursiveAddrFirstModuleData uint64
    47  
    48  	ProgramPanic             string
    49  	ProgramPanicNoDwarf      string
    50  	PanicAddrMain            uint64
    51  	PanicAddrThrow           uint64
    52  	PanicAddrInsideThrough   uint64
    53  	PanicAddrCatch           uint64
    54  	PanicAddrFirstModuleData uint64
    55  
    56  	ProgramTypePrint                    string
    57  	TypePrintAddrFirstModuleData        uint64
    58  	TypePrintAddrPrintBool              uint64
    59  	TypePrintAddrPrintInt8              uint64
    60  	TypePrintAddrPrintInt16             uint64
    61  	TypePrintAddrPrintInt32             uint64
    62  	TypePrintAddrPrintInt64             uint64
    63  	TypePrintAddrPrintUint8             uint64
    64  	TypePrintAddrPrintUint16            uint64
    65  	TypePrintAddrPrintUint32            uint64
    66  	TypePrintAddrPrintUint64            uint64
    67  	TypePrintAddrPrintFloat32           uint64
    68  	TypePrintAddrPrintFloat64           uint64
    69  	TypePrintAddrPrintComplex64         uint64
    70  	TypePrintAddrPrintComplex128        uint64
    71  	TypePrintAddrPrintString            uint64
    72  	TypePrintAddrPrintArray             uint64
    73  	TypePrintAddrPrintSlice             uint64
    74  	TypePrintAddrPrintNilSlice          uint64
    75  	TypePrintAddrPrintStruct            uint64
    76  	TypePrintAddrPrintPtr               uint64
    77  	TypePrintAddrPrintFunc              uint64
    78  	TypePrintAddrPrintInterface         uint64
    79  	TypePrintAddrPrintPtrInterface      uint64
    80  	TypePrintAddrPrintNilInterface      uint64
    81  	TypePrintAddrPrintEmptyInterface    uint64
    82  	TypePrintAddrPrintNilEmptyInterface uint64
    83  	TypePrintAddrPrintMap               uint64
    84  	TypePrintAddrPrintNilMap            uint64
    85  	TypePrintAddrPrintChan              uint64
    86  
    87  	ProgramStartStop             string
    88  	StartStopAddrTracedFunc      uint64
    89  	StartStopAddrTracerOff       uint64
    90  	StartStopAddrFirstModuleData uint64
    91  
    92  	ProgramStartOnly string
    93  
    94  	ProgramRecursiveStartStop string
    95  
    96  	ProgramSpecialFuncs             string
    97  	SpecialFuncsAddrMain            uint64
    98  	SpecialFuncsAddrFirstModuleData uint64
    99  )
   100  
   101  func init() {
   102  	_, srcFilename, _, _ := runtime.Caller(0)
   103  	srcDirname := filepath.Dir(srcFilename)
   104  
   105  	if err := buildProgramHelloworld(srcDirname); err != nil {
   106  		panic(err)
   107  	}
   108  	if err := buildProgramInfloop(srcDirname); err != nil {
   109  		panic(err)
   110  	}
   111  	if err := buildProgramGoRoutines(srcDirname); err != nil {
   112  		panic(err)
   113  	}
   114  	if err := buildProgramRecursive(srcDirname); err != nil {
   115  		panic(err)
   116  	}
   117  	if err := buildProgramPanic(srcDirname); err != nil {
   118  		panic(err)
   119  	}
   120  	if err := buildProgramTypePrint(srcDirname); err != nil {
   121  		panic(err)
   122  	}
   123  	if err := buildProgramStartStop(srcDirname); err != nil {
   124  		panic(err)
   125  	}
   126  	if err := buildProgramStartOnly(srcDirname); err != nil {
   127  		panic(err)
   128  	}
   129  	if err := buildProgramRecursiveStartStop(srcDirname); err != nil {
   130  		panic(err)
   131  	}
   132  	if err := buildProgramSpecialFuncs(srcDirname); err != nil {
   133  		panic(err)
   134  	}
   135  
   136  	log.EnableDebugLog = true
   137  }
   138  
   139  func buildProgramHelloworld(srcDirname string) error {
   140  	ProgramHelloworld = filepath.Join(srcDirname, "testdata", "helloworld")
   141  	if err := buildProgram(ProgramHelloworld); err != nil {
   142  		return err
   143  	}
   144  
   145  	ProgramHelloworldNoDwarf = ProgramHelloworld + ".nodwarf"
   146  	if err := buildProgramWithoutDWARF(ProgramHelloworld+".go", ProgramHelloworldNoDwarf); err != nil {
   147  		return err
   148  	}
   149  
   150  	updateAddressIfMatched := func(name string, value uint64) error {
   151  		switch name {
   152  		case "main.main":
   153  			HelloworldAddrMain = value
   154  		case "main.oneParameter":
   155  			HelloworldAddrOneParameter = value
   156  		case "main.oneParameterAndOneVariable":
   157  			HelloworldAddrOneParameterAndVariable = value
   158  		case "main.noParameter":
   159  			HelloworldAddrNoParameter = value
   160  		case "main.twoParameters":
   161  			HelloworldAddrTwoParameters = value
   162  		case "main.twoReturns":
   163  			HelloworldAddrTwoReturns = value
   164  		case "errors.New":
   165  			HelloworldAddrErrorsNew = value
   166  		case "reflect.Value.Kind":
   167  			HelloworldAddrFuncWithAbstractOrigin = value
   168  		case "go.buildid":
   169  			HelloworldAddrGoBuildID = value
   170  		case "runtime.firstmoduledata":
   171  			HelloworldAddrFirstModuleData = value
   172  		}
   173  		return nil
   174  	}
   175  
   176  	return walkSymbols(ProgramHelloworld, updateAddressIfMatched)
   177  }
   178  
   179  func buildProgramInfloop(srcDirname string) error {
   180  	ProgramInfloop = srcDirname + "/testdata/infloop"
   181  
   182  	if err := buildProgram(ProgramInfloop); err != nil {
   183  		return err
   184  	}
   185  
   186  	updateAddressIfMatched := func(name string, value uint64) error {
   187  		switch name {
   188  		case "main.main":
   189  			InfloopAddrMain = value
   190  		case "runtime.firstmoduledata":
   191  			InfloopAddrFirstModuleData = value
   192  		}
   193  		return nil
   194  	}
   195  
   196  	return walkSymbols(ProgramInfloop, updateAddressIfMatched)
   197  }
   198  
   199  func buildProgramGoRoutines(srcDirname string) error {
   200  	ProgramGoRoutines = srcDirname + "/testdata/goroutines"
   201  	if err := buildProgram(ProgramGoRoutines); err != nil {
   202  		return err
   203  	}
   204  
   205  	ProgramGoRoutinesNoDwarf = ProgramGoRoutines + ".nodwarf"
   206  	if err := buildProgramWithoutDWARF(ProgramGoRoutines+".go", ProgramGoRoutinesNoDwarf); err != nil {
   207  		return err
   208  	}
   209  
   210  	updateAddressIfMatched := func(name string, value uint64) error {
   211  		switch name {
   212  		case "main.main":
   213  			GoRoutinesAddrMain = value
   214  		case "main.inc":
   215  			GoRoutinesAddrInc = value
   216  		case "runtime.firstmoduledata":
   217  			GoRoutinesAddrFirstModuleData = value
   218  		}
   219  		return nil
   220  	}
   221  
   222  	return walkSymbols(ProgramGoRoutines, updateAddressIfMatched)
   223  }
   224  
   225  func buildProgramRecursive(srcDirname string) error {
   226  	ProgramRecursive = srcDirname + "/testdata/recursive"
   227  
   228  	if err := buildProgram(ProgramRecursive); err != nil {
   229  		return err
   230  	}
   231  
   232  	updateAddressIfMatched := func(name string, value uint64) error {
   233  		switch name {
   234  		case "main.main":
   235  			RecursiveAddrMain = value
   236  		case "runtime.firstmoduledata":
   237  			RecursiveAddrFirstModuleData = value
   238  		}
   239  		return nil
   240  	}
   241  
   242  	return walkSymbols(ProgramRecursive, updateAddressIfMatched)
   243  }
   244  
   245  func buildProgramPanic(srcDirname string) error {
   246  	ProgramPanic = srcDirname + "/testdata/panic"
   247  	if err := buildProgram(ProgramPanic); err != nil {
   248  		return err
   249  	}
   250  
   251  	ProgramPanicNoDwarf = ProgramPanic + ".nodwarf"
   252  	if err := buildProgramWithoutDWARF(ProgramPanic+".go", ProgramPanicNoDwarf); err != nil {
   253  		return err
   254  	}
   255  
   256  	updateAddressIfMatched := func(name string, value uint64) error {
   257  		switch name {
   258  		case "main.main":
   259  			PanicAddrMain = value
   260  		case "main.throw":
   261  			PanicAddrThrow = value
   262  		case "main.through.func1":
   263  			PanicAddrInsideThrough = value
   264  		case "main.catch":
   265  			PanicAddrCatch = value
   266  		case "runtime.firstmoduledata":
   267  			PanicAddrFirstModuleData = value
   268  		}
   269  		return nil
   270  	}
   271  
   272  	return walkSymbols(ProgramPanic, updateAddressIfMatched)
   273  }
   274  
   275  func buildProgramTypePrint(srcDirname string) error {
   276  	ProgramTypePrint = srcDirname + "/testdata/typeprint"
   277  
   278  	if err := buildProgram(ProgramTypePrint); err != nil {
   279  		return err
   280  	}
   281  
   282  	updateAddressIfMatched := func(name string, value uint64) error {
   283  		switch name {
   284  		case "runtime.firstmoduledata":
   285  			TypePrintAddrFirstModuleData = value
   286  		case "main.printBool":
   287  			TypePrintAddrPrintBool = value
   288  		case "main.printInt8":
   289  			TypePrintAddrPrintInt8 = value
   290  		case "main.printInt16":
   291  			TypePrintAddrPrintInt16 = value
   292  		case "main.printInt32":
   293  			TypePrintAddrPrintInt32 = value
   294  		case "main.printInt64":
   295  			TypePrintAddrPrintInt64 = value
   296  		case "main.printUint8":
   297  			TypePrintAddrPrintUint8 = value
   298  		case "main.printUint16":
   299  			TypePrintAddrPrintUint16 = value
   300  		case "main.printUint32":
   301  			TypePrintAddrPrintUint32 = value
   302  		case "main.printUint64":
   303  			TypePrintAddrPrintUint64 = value
   304  		case "main.printFloat32":
   305  			TypePrintAddrPrintFloat32 = value
   306  		case "main.printFloat64":
   307  			TypePrintAddrPrintFloat64 = value
   308  		case "main.printComplex64":
   309  			TypePrintAddrPrintComplex64 = value
   310  		case "main.printComplex128":
   311  			TypePrintAddrPrintComplex128 = value
   312  		case "main.printString":
   313  			TypePrintAddrPrintString = value
   314  		case "main.printArray":
   315  			TypePrintAddrPrintArray = value
   316  		case "main.printSlice":
   317  			TypePrintAddrPrintSlice = value
   318  		case "main.printNilSlice":
   319  			TypePrintAddrPrintNilSlice = value
   320  		case "main.printStruct":
   321  			TypePrintAddrPrintStruct = value
   322  		case "main.printPtr":
   323  			TypePrintAddrPrintPtr = value
   324  		case "main.printFunc":
   325  			TypePrintAddrPrintFunc = value
   326  		case "main.printInterface":
   327  			TypePrintAddrPrintInterface = value
   328  		case "main.printPtrInterface":
   329  			TypePrintAddrPrintPtrInterface = value
   330  		case "main.printNilInterface":
   331  			TypePrintAddrPrintNilInterface = value
   332  		case "main.printEmptyInterface":
   333  			TypePrintAddrPrintEmptyInterface = value
   334  		case "main.printNilEmptyInterface":
   335  			TypePrintAddrPrintNilEmptyInterface = value
   336  		case "main.printMap":
   337  			TypePrintAddrPrintMap = value
   338  		case "main.printNilMap":
   339  			TypePrintAddrPrintNilMap = value
   340  		case "main.printChan":
   341  			TypePrintAddrPrintChan = value
   342  		}
   343  		return nil
   344  	}
   345  
   346  	return walkSymbols(ProgramTypePrint, updateAddressIfMatched)
   347  }
   348  
   349  func buildProgramStartStop(srcDirname string) error {
   350  	ProgramStartStop = srcDirname + "/testdata/startStop"
   351  
   352  	if err := buildProgram(ProgramStartStop); err != nil {
   353  		return err
   354  	}
   355  
   356  	updateAddressIfMatched := func(name string, value uint64) error {
   357  		switch name {
   358  		case "main.tracedFunc":
   359  			StartStopAddrTracedFunc = value
   360  		case "github.com/ks888/tgo/lib/tracer.Off":
   361  			StartStopAddrTracerOff = value
   362  		case "runtime.firstmoduledata":
   363  			StartStopAddrFirstModuleData = value
   364  		}
   365  		return nil
   366  	}
   367  
   368  	return walkSymbols(ProgramStartStop, updateAddressIfMatched)
   369  }
   370  
   371  func buildProgramStartOnly(srcDirname string) error {
   372  	ProgramStartOnly = srcDirname + "/testdata/startOnly"
   373  
   374  	return buildProgram(ProgramStartOnly)
   375  }
   376  
   377  func buildProgramRecursiveStartStop(srcDirname string) error {
   378  	ProgramRecursiveStartStop = srcDirname + "/testdata/recursiveStartStop"
   379  
   380  	return buildProgram(ProgramRecursiveStartStop)
   381  }
   382  
   383  func buildProgramSpecialFuncs(srcDirname string) error {
   384  	ProgramSpecialFuncs = srcDirname + "/testdata/specialFuncs"
   385  
   386  	if err := buildProgram(ProgramSpecialFuncs); err != nil {
   387  		return err
   388  	}
   389  
   390  	updateAddressIfMatched := func(name string, value uint64) error {
   391  		switch name {
   392  		case "main.main":
   393  			SpecialFuncsAddrMain = value
   394  		case "runtime.firstmoduledata":
   395  			SpecialFuncsAddrFirstModuleData = value
   396  		}
   397  		return nil
   398  	}
   399  
   400  	return walkSymbols(ProgramSpecialFuncs, updateAddressIfMatched)
   401  }
   402  
   403  func buildProgram(programName string) error {
   404  	// Optimization is enabled, because the tool aims to work well even if the binary is optimized.
   405  	linkOptions := ""
   406  	if strings.HasPrefix(runtime.Version(), "go1.11") || strings.HasPrefix(runtime.Version(), "go1.12") || strings.Contains(runtime.Version(), "devel") {
   407  		linkOptions = "-compressdwarf=false" // not required, but useful for debugging.
   408  	}
   409  	src := programName + ".go"
   410  	if out, err := exec.Command(goBinaryPath, "build", "-ldflags", linkOptions, "-o", programName, src).CombinedOutput(); err != nil {
   411  		return fmt.Errorf("failed to build %s: %v\n%v", src, err, string(out))
   412  	}
   413  	return nil
   414  }
   415  
   416  func buildProgramWithoutDWARF(srcName, programName string) error {
   417  	if out, err := exec.Command(goBinaryPath, "build", "-ldflags", "-w -s", "-o", programName, srcName).CombinedOutput(); err != nil {
   418  		return fmt.Errorf("failed to build %s: %v\n%v", srcName, err, string(out))
   419  	}
   420  	return nil
   421  }
   422  
   423  func walkSymbols(programName string, walkFunc func(name string, value uint64) error) error {
   424  	switch runtime.GOOS {
   425  	case "darwin":
   426  		machoFile, err := macho.Open(programName)
   427  		if err != nil {
   428  			return fmt.Errorf("failed to open binary: %v", err)
   429  		}
   430  		for _, sym := range machoFile.Symtab.Syms {
   431  			if err := walkFunc(sym.Name, sym.Value); err != nil {
   432  				return err
   433  			}
   434  		}
   435  
   436  	case "linux":
   437  		elfFile, err := elf.Open(programName)
   438  		if err != nil {
   439  			return fmt.Errorf("failed to open binary: %v", err)
   440  		}
   441  
   442  		syms, err := elfFile.Symbols()
   443  		if err != nil {
   444  			return fmt.Errorf("failed to find symbols: %v", err)
   445  		}
   446  		for _, sym := range syms {
   447  			if err := walkFunc(sym.Name, sym.Value); err != nil {
   448  				return err
   449  			}
   450  		}
   451  	default:
   452  		return fmt.Errorf("unsupported os: %s", runtime.GOOS)
   453  	}
   454  
   455  	return nil
   456  }