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 }