github.com/hlts2/go@v0.0.0-20170904000733-812b34efaed8/misc/cgo/testcshared/cshared_test.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cshared_test 6 7 import ( 8 "debug/elf" 9 "fmt" 10 "log" 11 "os" 12 "os/exec" 13 "path" 14 "path/filepath" 15 "strings" 16 "testing" 17 "unicode" 18 ) 19 20 // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)). 21 var cc []string 22 23 // An environment with GOPATH=$(pwd). 24 var gopathEnv []string 25 26 // ".exe" on Windows. 27 var exeSuffix string 28 29 var GOOS, GOARCH, GOROOT string 30 var installdir, androiddir string 31 var libSuffix, libgoname string 32 33 func init() { 34 GOOS = goEnv("GOOS") 35 GOARCH = goEnv("GOARCH") 36 GOROOT = goEnv("GOROOT") 37 38 if _, err := os.Stat(GOROOT); os.IsNotExist(err) { 39 log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT) 40 } 41 42 // Directory where cgo headers and outputs will be installed. 43 // The installation directory format varies depending on the platform. 44 installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared_shared", GOOS, GOARCH)) 45 switch GOOS { 46 case "darwin": 47 libSuffix = "dylib" 48 installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared", GOOS, GOARCH)) 49 case "windows": 50 libSuffix = "dll" 51 default: 52 libSuffix = "so" 53 } 54 55 androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid()) 56 libgoname = "libgo." + libSuffix 57 58 ccOut := goEnv("CC") 59 cc = []string{string(ccOut)} 60 61 out := goEnv("GOGCCFLAGS") 62 quote := '\000' 63 start := 0 64 lastSpace := true 65 backslash := false 66 s := string(out) 67 for i, c := range s { 68 if quote == '\000' && unicode.IsSpace(c) { 69 if !lastSpace { 70 cc = append(cc, s[start:i]) 71 lastSpace = true 72 } 73 } else { 74 if lastSpace { 75 start = i 76 lastSpace = false 77 } 78 if quote == '\000' && !backslash && (c == '"' || c == '\'') { 79 quote = c 80 backslash = false 81 } else if !backslash && quote == c { 82 quote = '\000' 83 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { 84 backslash = true 85 } else { 86 backslash = false 87 } 88 } 89 } 90 if !lastSpace { 91 cc = append(cc, s[start:]) 92 } 93 94 switch GOOS { 95 case "darwin": 96 // For Darwin/ARM. 97 // TODO(crawshaw): can we do better? 98 cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...) 99 case "android": 100 cc = append(cc, "-pie", "-fuse-ld=gold") 101 } 102 libgodir := GOOS + "_" + GOARCH 103 switch GOOS { 104 case "darwin": 105 if GOARCH == "arm" || GOARCH == "arm64" { 106 libgodir += "_shared" 107 } 108 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": 109 libgodir += "_shared" 110 } 111 cc = append(cc, "-I", filepath.Join("pkg", libgodir)) 112 113 // Build an environment with GOPATH=$(pwd) 114 dir, err := os.Getwd() 115 if err != nil { 116 fmt.Fprintln(os.Stderr, err) 117 os.Exit(2) 118 } 119 gopathEnv = append(os.Environ(), "GOPATH="+dir) 120 121 if GOOS == "windows" { 122 exeSuffix = ".exe" 123 } 124 } 125 126 func goEnv(key string) string { 127 out, err := exec.Command("go", "env", key).Output() 128 if err != nil { 129 fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err) 130 fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr) 131 os.Exit(2) 132 } 133 return strings.TrimSpace(string(out)) 134 } 135 136 func cmdToRun(name string) []string { 137 return []string{"./" + name + exeSuffix} 138 } 139 140 func adbPush(t *testing.T, filename string) { 141 if GOOS != "android" { 142 return 143 } 144 args := []string{"adb", "push", filename, fmt.Sprintf("%s/%s", androiddir, filename)} 145 cmd := exec.Command(args[0], args[1:]...) 146 if out, err := cmd.CombinedOutput(); err != nil { 147 t.Fatalf("adb command failed: %v\n%s\n", err, out) 148 } 149 } 150 151 func adbRun(t *testing.T, env []string, adbargs ...string) string { 152 if GOOS != "android" { 153 t.Fatalf("trying to run adb command when operating system is not android.") 154 } 155 args := []string{"adb", "shell"} 156 // Propagate LD_LIBRARY_PATH to the adb shell invocation. 157 for _, e := range env { 158 if strings.Index(e, "LD_LIBRARY_PATH=") != -1 { 159 adbargs = append([]string{e}, adbargs...) 160 break 161 } 162 } 163 shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " ")) 164 args = append(args, shellcmd) 165 cmd := exec.Command(args[0], args[1:]...) 166 out, err := cmd.CombinedOutput() 167 if err != nil { 168 t.Fatalf("adb command failed: %v\n%s\n", err, out) 169 } 170 171 return strings.Replace(string(out), "\r", "", -1) 172 } 173 174 func runwithenv(t *testing.T, env []string, args ...string) string { 175 cmd := exec.Command(args[0], args[1:]...) 176 cmd.Env = env 177 out, err := cmd.CombinedOutput() 178 if err != nil { 179 t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out) 180 } else { 181 t.Logf("run: %v", args) 182 } 183 184 return string(out) 185 } 186 187 func run(t *testing.T, args ...string) string { 188 cmd := exec.Command(args[0], args[1:]...) 189 out, err := cmd.CombinedOutput() 190 if err != nil { 191 t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out) 192 } else { 193 t.Logf("run: %v", args) 194 } 195 196 return string(out) 197 } 198 199 func runExe(t *testing.T, env []string, args ...string) string { 200 if GOOS == "android" { 201 return adbRun(t, env, args...) 202 } 203 204 return runwithenv(t, env, args...) 205 } 206 207 func runwithldlibrarypath(t *testing.T, args ...string) string { 208 return runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), args...) 209 } 210 211 func rungocmd(t *testing.T, args ...string) string { 212 return runwithenv(t, gopathEnv, args...) 213 } 214 215 func createHeaders(t *testing.T) { 216 rungocmd(t, 217 "go", "install", 218 "-buildmode=c-shared", "-installsuffix", 219 "testcshared", "libgo", 220 ) 221 222 rungocmd(t, 223 "go", "build", 224 "-buildmode=c-shared", "-installsuffix", 225 "testcshared", "-o", libgoname, 226 filepath.Join("src", "libgo", "libgo.go"), 227 ) 228 adbPush(t, libgoname) 229 230 if GOOS == "linux" || GOOS == "android" { 231 f, err := elf.Open(libgoname) 232 if err != nil { 233 t.Fatal("elf.Open failed: ", err) 234 } 235 defer f.Close() 236 if hasDynTag(t, f, elf.DT_TEXTREL) { 237 t.Fatalf("%s has DT_TEXTREL flag", libgoname) 238 } 239 } 240 } 241 242 func cleanupHeaders() { 243 os.Remove("libgo.h") 244 } 245 246 func setupAndroid(t *testing.T) { 247 if GOOS != "android" { 248 return 249 } 250 adbRun(t, nil, "mkdir", "-p", androiddir) 251 } 252 253 func cleanupAndroid(t *testing.T) { 254 if GOOS != "android" { 255 return 256 } 257 adbRun(t, nil, "rm", "-rf", androiddir) 258 } 259 260 // test0: exported symbols in shared lib are accessible. 261 func TestExportedSymbols(t *testing.T) { 262 cmd := "testp" 263 bin := cmdToRun(cmd) 264 265 setupAndroid(t) 266 defer cleanupAndroid(t) 267 createHeaders(t) 268 defer cleanupHeaders() 269 270 run(t, append(cc, "-I", installdir, "-o", cmd, "main0.c", libgoname)...) 271 adbPush(t, cmd) 272 273 defer os.Remove(libgoname) 274 defer os.Remove("testp") 275 276 out := runwithldlibrarypath(t, bin...) 277 if strings.TrimSpace(out) != "PASS" { 278 t.Error(out) 279 } 280 } 281 282 // test1: shared library can be dynamically loaded and exported symbols are accessible. 283 func TestExportedSymbolsWithDynamicLoad(t *testing.T) { 284 cmd := "testp" 285 bin := cmdToRun(cmd) 286 287 setupAndroid(t) 288 defer cleanupAndroid(t) 289 createHeaders(t) 290 defer cleanupHeaders() 291 292 run(t, append(cc, "-o", cmd, "main1.c", "-ldl")...) 293 adbPush(t, cmd) 294 295 defer os.Remove(libgoname) 296 defer os.Remove(cmd) 297 298 out := runExe(t, nil, append(bin, "./"+libgoname)...) 299 if strings.TrimSpace(out) != "PASS" { 300 t.Error(out) 301 } 302 } 303 304 // test2: tests libgo2 which does not export any functions. 305 func TestUnexportedSymbols(t *testing.T) { 306 cmd := "testp2" 307 libname := "libgo2." + libSuffix 308 bin := cmdToRun(cmd) 309 310 setupAndroid(t) 311 defer cleanupAndroid(t) 312 313 rungocmd(t, 314 "go", "build", 315 "-buildmode=c-shared", 316 "-installsuffix", "testcshared", 317 "-o", libname, "libgo2", 318 ) 319 adbPush(t, libname) 320 321 linkFlags := "-Wl,--no-as-needed" 322 if GOOS == "darwin" { 323 linkFlags = "" 324 } 325 326 run(t, append( 327 cc, "-o", cmd, 328 "main2.c", linkFlags, 329 libname, 330 )...) 331 adbPush(t, cmd) 332 333 defer os.Remove(libname) 334 defer os.Remove(cmd) 335 336 out := runwithldlibrarypath(t, bin...) 337 338 if strings.TrimSpace(out) != "PASS" { 339 t.Error(out) 340 } 341 } 342 343 // test3: tests main.main is exported on android. 344 func TestMainExportedOnAndroid(t *testing.T) { 345 if GOOS != "android" { 346 return 347 } 348 349 cmd := "testp3" 350 bin := cmdToRun(cmd) 351 352 setupAndroid(t) 353 defer cleanupAndroid(t) 354 createHeaders(t) 355 defer cleanupHeaders() 356 357 run(t, append(cc, "-o", cmd, "main3.c", "-ldl")...) 358 adbPush(t, cmd) 359 360 defer os.Remove(libgoname) 361 defer os.Remove(cmd) 362 363 out := runExe(t, nil, append(bin, "./"+libgoname)...) 364 if strings.TrimSpace(out) != "PASS" { 365 t.Error(out) 366 } 367 } 368 369 // test4: test signal handlers 370 func TestSignalHandlers(t *testing.T) { 371 cmd := "testp4" 372 libname := "libgo4." + libSuffix 373 bin := cmdToRun(cmd) 374 375 setupAndroid(t) 376 defer cleanupAndroid(t) 377 378 rungocmd(t, 379 "go", "build", 380 "-buildmode=c-shared", 381 "-installsuffix", "testcshared", 382 "-o", libname, "libgo4", 383 ) 384 adbPush(t, libname) 385 run(t, append( 386 cc, "-pthread", "-o", cmd, 387 "main4.c", "-ldl", 388 )...) 389 adbPush(t, cmd) 390 391 defer os.Remove(libname) 392 defer os.Remove(cmd) 393 defer os.Remove("libgo4.h") 394 395 out := runExe(t, nil, append(bin, "./"+libname)...) 396 397 if strings.TrimSpace(out) != "PASS" { 398 t.Error(run(t, append(bin, libname, "verbose")...)) 399 } 400 } 401 402 // test5: test signal handlers with os/signal.Notify 403 func TestSignalHandlersWithNotify(t *testing.T) { 404 cmd := "testp5" 405 libname := "libgo5." + libSuffix 406 bin := cmdToRun(cmd) 407 408 setupAndroid(t) 409 defer cleanupAndroid(t) 410 411 rungocmd(t, 412 "go", "build", 413 "-buildmode=c-shared", 414 "-installsuffix", "testcshared", 415 "-o", libname, "libgo5", 416 ) 417 adbPush(t, libname) 418 run(t, append( 419 cc, "-pthread", "-o", cmd, 420 "main5.c", "-ldl", 421 )...) 422 adbPush(t, cmd) 423 424 defer os.Remove(libname) 425 defer os.Remove(cmd) 426 defer os.Remove("libgo5.h") 427 428 out := runExe(t, nil, append(bin, "./"+libname)...) 429 430 if strings.TrimSpace(out) != "PASS" { 431 t.Error(run(t, append(bin, libname, "verbose")...)) 432 } 433 } 434 435 func TestPIE(t *testing.T) { 436 switch GOOS { 437 case "linux", "android": 438 break 439 default: 440 t.Logf("Skipping TestPIE on %s", GOOS) 441 return 442 } 443 444 defer func() { 445 os.RemoveAll("pkg") 446 }() 447 448 createHeaders(t) 449 defer cleanupHeaders() 450 451 f, err := elf.Open(libgoname) 452 if err != nil { 453 t.Fatal("elf.Open failed: ", err) 454 } 455 defer f.Close() 456 if hasDynTag(t, f, elf.DT_TEXTREL) { 457 t.Errorf("%s has DT_TEXTREL flag", libgoname) 458 } 459 } 460 461 func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool { 462 ds := f.SectionByType(elf.SHT_DYNAMIC) 463 if ds == nil { 464 t.Error("no SHT_DYNAMIC section") 465 return false 466 } 467 d, err := ds.Data() 468 if err != nil { 469 t.Errorf("can't read SHT_DYNAMIC contents: %v", err) 470 return false 471 } 472 for len(d) > 0 { 473 var t elf.DynTag 474 switch f.Class { 475 case elf.ELFCLASS32: 476 t = elf.DynTag(f.ByteOrder.Uint32(d[:4])) 477 d = d[8:] 478 case elf.ELFCLASS64: 479 t = elf.DynTag(f.ByteOrder.Uint64(d[:8])) 480 d = d[16:] 481 } 482 if t == tag { 483 return true 484 } 485 } 486 return false 487 }