]> Cypherpunks.ru repositories - gostls13.git/blob - misc/go_android_exec/main.go
debug/pe: return error on reading from section with uninitialized data
[gostls13.git] / misc / go_android_exec / main.go
1 // Copyright 2014 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 // This program can be used as go_android_GOARCH_exec by the Go tool.
6 // It executes binaries on an android device using adb.
7 package main
8
9 import (
10         "bytes"
11         "errors"
12         "fmt"
13         "io"
14         "log"
15         "os"
16         "os/exec"
17         "os/signal"
18         "path"
19         "path/filepath"
20         "runtime"
21         "strconv"
22         "strings"
23         "sync"
24         "syscall"
25 )
26
27 func run(args ...string) (string, error) {
28         cmd := adbCmd(args...)
29         buf := new(strings.Builder)
30         cmd.Stdout = io.MultiWriter(os.Stdout, buf)
31         // If the adb subprocess somehow hangs, go test will kill this wrapper
32         // and wait for our os.Stderr (and os.Stdout) to close as a result.
33         // However, if the os.Stderr (or os.Stdout) file descriptors are
34         // passed on, the hanging adb subprocess will hold them open and
35         // go test will hang forever.
36         //
37         // Avoid that by wrapping stderr, breaking the short circuit and
38         // forcing cmd.Run to use another pipe and goroutine to pass
39         // along stderr from adb.
40         cmd.Stderr = struct{ io.Writer }{os.Stderr}
41         err := cmd.Run()
42         if err != nil {
43                 return "", fmt.Errorf("adb %s: %v", strings.Join(args, " "), err)
44         }
45         return buf.String(), nil
46 }
47
48 func adb(args ...string) error {
49         if out, err := adbCmd(args...).CombinedOutput(); err != nil {
50                 fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
51                 return err
52         }
53         return nil
54 }
55
56 func adbCmd(args ...string) *exec.Cmd {
57         if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
58                 args = append(strings.Split(flags, " "), args...)
59         }
60         return exec.Command("adb", args...)
61 }
62
63 const (
64         deviceRoot   = "/data/local/tmp/go_android_exec"
65         deviceGoroot = deviceRoot + "/goroot"
66 )
67
68 func main() {
69         log.SetFlags(0)
70         log.SetPrefix("go_android_exec: ")
71         exitCode, err := runMain()
72         if err != nil {
73                 log.Fatal(err)
74         }
75         os.Exit(exitCode)
76 }
77
78 func runMain() (int, error) {
79         // Concurrent use of adb is flaky, so serialize adb commands.
80         // See https://github.com/golang/go/issues/23795 or
81         // https://issuetracker.google.com/issues/73230216.
82         lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
83         lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
84         if err != nil {
85                 return 0, err
86         }
87         defer lock.Close()
88         if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
89                 return 0, err
90         }
91
92         // In case we're booting a device or emulator alongside all.bash, wait for
93         // it to be ready. adb wait-for-device is not enough, we have to
94         // wait for sys.boot_completed.
95         if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
96                 return 0, err
97         }
98
99         // Done once per make.bash.
100         if err := adbCopyGoroot(); err != nil {
101                 return 0, err
102         }
103
104         // Prepare a temporary directory that will be cleaned up at the end.
105         // Binary names can conflict.
106         // E.g. template.test from the {html,text}/template packages.
107         binName := filepath.Base(os.Args[1])
108         deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
109         deviceGopath := deviceGotmp + "/gopath"
110         defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
111
112         // Determine the package by examining the current working
113         // directory, which will look something like
114         // "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
115         // We extract everything after the $GOROOT or $GOPATH to run on the
116         // same relative directory on the target device.
117         importPath, isStd, err := pkgPath()
118         if err != nil {
119                 return 0, err
120         }
121         var deviceCwd string
122         if isStd {
123                 // Note that we use path.Join here instead of filepath.Join:
124                 // The device paths should be slash-separated even if the go_android_exec
125                 // wrapper itself is compiled for Windows.
126                 deviceCwd = path.Join(deviceGoroot, "src", importPath)
127         } else {
128                 deviceCwd = path.Join(deviceGopath, "src", importPath)
129                 if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
130                         return 0, err
131                 }
132                 if err := adbCopyTree(deviceCwd, importPath); err != nil {
133                         return 0, err
134                 }
135
136                 // Copy .go files from the package.
137                 goFiles, err := filepath.Glob("*.go")
138                 if err != nil {
139                         return 0, err
140                 }
141                 if len(goFiles) > 0 {
142                         args := append(append([]string{"push"}, goFiles...), deviceCwd)
143                         if err := adb(args...); err != nil {
144                                 return 0, err
145                         }
146                 }
147         }
148
149         deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
150         if err := adb("push", os.Args[1], deviceBin); err != nil {
151                 return 0, err
152         }
153
154         // Forward SIGQUIT from the go command to show backtraces from
155         // the binary instead of from this wrapper.
156         quit := make(chan os.Signal, 1)
157         signal.Notify(quit, syscall.SIGQUIT)
158         go func() {
159                 for range quit {
160                         // We don't have the PID of the running process; use the
161                         // binary name instead.
162                         adb("exec-out", "killall -QUIT "+binName)
163                 }
164         }()
165         // In light of
166         // https://code.google.com/p/android/issues/detail?id=3254
167         // dont trust the exitcode of adb. Instead, append the exitcode to
168         // the output and parse it from there.
169         const exitstr = "exitcode="
170         cmd := `export TMPDIR="` + deviceGotmp + `"` +
171                 `; export GOROOT="` + deviceGoroot + `"` +
172                 `; export GOPATH="` + deviceGopath + `"` +
173                 `; export CGO_ENABLED=0` +
174                 `; export GOPROXY=` + os.Getenv("GOPROXY") +
175                 `; export GOCACHE="` + deviceRoot + `/gocache"` +
176                 `; export PATH="` + deviceGoroot + `/bin":$PATH` +
177                 `; cd "` + deviceCwd + `"` +
178                 "; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
179                 "; echo -n " + exitstr + "$?"
180         output, err := run("exec-out", cmd)
181         signal.Reset(syscall.SIGQUIT)
182         close(quit)
183         if err != nil {
184                 return 0, err
185         }
186
187         exitIdx := strings.LastIndex(output, exitstr)
188         if exitIdx == -1 {
189                 return 0, fmt.Errorf("no exit code: %q", output)
190         }
191         code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
192         if err != nil {
193                 return 0, fmt.Errorf("bad exit code: %v", err)
194         }
195         return code, nil
196 }
197
198 // pkgPath determines the package import path of the current working directory,
199 // and indicates whether it is
200 // and returns the path to the package source relative to $GOROOT (or $GOPATH).
201 func pkgPath() (importPath string, isStd bool, err error) {
202         goTool, err := goTool()
203         if err != nil {
204                 return "", false, err
205         }
206         cmd := exec.Command(goTool, "list", "-e", "-f", "{{.ImportPath}}:{{.Standard}}", ".")
207         out, err := cmd.Output()
208         if err != nil {
209                 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
210                         return "", false, fmt.Errorf("%v: %s", cmd, ee.Stderr)
211                 }
212                 return "", false, fmt.Errorf("%v: %w", cmd, err)
213         }
214
215         s := string(bytes.TrimSpace(out))
216         importPath, isStdStr, ok := strings.Cut(s, ":")
217         if !ok {
218                 return "", false, fmt.Errorf("%v: missing ':' in output: %q", cmd, out)
219         }
220         if importPath == "" || importPath == "." {
221                 return "", false, fmt.Errorf("current directory does not have a Go import path")
222         }
223         isStd, err = strconv.ParseBool(isStdStr)
224         if err != nil {
225                 return "", false, fmt.Errorf("%v: non-boolean .Standard in output: %q", cmd, out)
226         }
227
228         return importPath, isStd, nil
229 }
230
231 // adbCopyTree copies testdata, go.mod, go.sum files from subdir
232 // and from parent directories all the way up to the root of subdir.
233 // go.mod and go.sum files are needed for the go tool modules queries,
234 // and the testdata directories for tests.  It is common for tests to
235 // reach out into testdata from parent packages.
236 func adbCopyTree(deviceCwd, subdir string) error {
237         dir := ""
238         for {
239                 for _, name := range []string{"testdata", "go.mod", "go.sum"} {
240                         hostPath := filepath.Join(dir, name)
241                         if _, err := os.Stat(hostPath); err != nil {
242                                 continue
243                         }
244                         devicePath := path.Join(deviceCwd, dir)
245                         if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
246                                 return err
247                         }
248                         if err := adb("push", hostPath, devicePath); err != nil {
249                                 return err
250                         }
251                 }
252                 if subdir == "." {
253                         break
254                 }
255                 subdir = filepath.Dir(subdir)
256                 dir = path.Join(dir, "..")
257         }
258         return nil
259 }
260
261 // adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
262 // and temporary data. Then, it copies relevant parts of GOROOT to the device,
263 // including the go tool built for android.
264 // A lock file ensures this only happens once, even with concurrent exec
265 // wrappers.
266 func adbCopyGoroot() error {
267         goTool, err := goTool()
268         if err != nil {
269                 return err
270         }
271         cmd := exec.Command(goTool, "version")
272         cmd.Stderr = os.Stderr
273         out, err := cmd.Output()
274         if err != nil {
275                 return fmt.Errorf("%v: %w", cmd, err)
276         }
277         goVersion := string(out)
278
279         // Also known by cmd/dist. The bootstrap command deletes the file.
280         statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
281         stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
282         if err != nil {
283                 return err
284         }
285         defer stat.Close()
286         // Serialize check and copying.
287         if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
288                 return err
289         }
290         s, err := io.ReadAll(stat)
291         if err != nil {
292                 return err
293         }
294         if string(s) == goVersion {
295                 return nil
296         }
297
298         goroot, err := findGoroot()
299         if err != nil {
300                 return err
301         }
302
303         // Delete the device's GOROOT, GOPATH and any leftover test data,
304         // and recreate GOROOT.
305         if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
306                 return err
307         }
308
309         // Build Go for Android.
310         cmd = exec.Command(goTool, "install", "cmd")
311         out, err = cmd.CombinedOutput()
312         if err != nil {
313                 if len(bytes.TrimSpace(out)) > 0 {
314                         log.Printf("\n%s", out)
315                 }
316                 return fmt.Errorf("%v: %w", cmd, err)
317         }
318         if err := adb("exec-out", "mkdir", "-p", deviceGoroot); err != nil {
319                 return err
320         }
321
322         // Copy the Android tools from the relevant bin subdirectory to GOROOT/bin.
323         cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/go")
324         cmd.Stderr = os.Stderr
325         out, err = cmd.Output()
326         if err != nil {
327                 return fmt.Errorf("%v: %w", cmd, err)
328         }
329         platformBin := filepath.Dir(string(bytes.TrimSpace(out)))
330         if platformBin == "." {
331                 return errors.New("failed to locate cmd/go for target platform")
332         }
333         if err := adb("push", platformBin, path.Join(deviceGoroot, "bin")); err != nil {
334                 return err
335         }
336
337         // Copy only the relevant subdirectories from pkg: pkg/include and the
338         // platform-native binaries in pkg/tool.
339         if err := adb("exec-out", "mkdir", "-p", path.Join(deviceGoroot, "pkg", "tool")); err != nil {
340                 return err
341         }
342         if err := adb("push", filepath.Join(goroot, "pkg", "include"), path.Join(deviceGoroot, "pkg", "include")); err != nil {
343                 return err
344         }
345
346         cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/compile")
347         cmd.Stderr = os.Stderr
348         out, err = cmd.Output()
349         if err != nil {
350                 return fmt.Errorf("%v: %w", cmd, err)
351         }
352         platformToolDir := filepath.Dir(string(bytes.TrimSpace(out)))
353         if platformToolDir == "." {
354                 return errors.New("failed to locate cmd/compile for target platform")
355         }
356         relToolDir, err := filepath.Rel(filepath.Join(goroot), platformToolDir)
357         if err != nil {
358                 return err
359         }
360         if err := adb("push", platformToolDir, path.Join(deviceGoroot, relToolDir)); err != nil {
361                 return err
362         }
363
364         // Copy all other files from GOROOT.
365         dirents, err := os.ReadDir(goroot)
366         if err != nil {
367                 return err
368         }
369         for _, de := range dirents {
370                 switch de.Name() {
371                 case "bin", "pkg":
372                         // We already created GOROOT/bin and GOROOT/pkg above; skip those.
373                         continue
374                 }
375                 if err := adb("push", filepath.Join(goroot, de.Name()), path.Join(deviceGoroot, de.Name())); err != nil {
376                         return err
377                 }
378         }
379
380         if _, err := stat.WriteString(goVersion); err != nil {
381                 return err
382         }
383         return nil
384 }
385
386 func findGoroot() (string, error) {
387         gorootOnce.Do(func() {
388                 // If runtime.GOROOT reports a non-empty path, assume that it is valid.
389                 // (It may be empty if this binary was built with -trimpath.)
390                 gorootPath = runtime.GOROOT()
391                 if gorootPath != "" {
392                         return
393                 }
394
395                 // runtime.GOROOT is empty — perhaps go_android_exec was built with
396                 // -trimpath and GOROOT is unset. Try 'go env GOROOT' as a fallback,
397                 // assuming that the 'go' command in $PATH is the correct one.
398
399                 cmd := exec.Command("go", "env", "GOROOT")
400                 cmd.Stderr = os.Stderr
401                 out, err := cmd.Output()
402                 if err != nil {
403                         gorootErr = fmt.Errorf("%v: %w", cmd, err)
404                 }
405
406                 gorootPath = string(bytes.TrimSpace(out))
407                 if gorootPath == "" {
408                         gorootErr = errors.New("GOROOT not found")
409                 }
410         })
411
412         return gorootPath, gorootErr
413 }
414
415 func goTool() (string, error) {
416         goroot, err := findGoroot()
417         if err != nil {
418                 return "", err
419         }
420         return filepath.Join(goroot, "bin", "go"), nil
421 }
422
423 var (
424         gorootOnce sync.Once
425         gorootPath string
426         gorootErr  error
427 )