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