aboutsummaryrefslogtreecommitdiff
path: root/externals/breakpad/src/tools/mac/upload_system_symbols
diff options
context:
space:
mode:
Diffstat (limited to 'externals/breakpad/src/tools/mac/upload_system_symbols')
-rw-r--r--externals/breakpad/src/tools/mac/upload_system_symbols/arch_constants.h66
-rw-r--r--externals/breakpad/src/tools/mac/upload_system_symbols/arch_reader.go68
-rw-r--r--externals/breakpad/src/tools/mac/upload_system_symbols/go.mod3
-rw-r--r--externals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.go484
-rwxr-xr-xexternals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.sh114
5 files changed, 735 insertions, 0 deletions
diff --git a/externals/breakpad/src/tools/mac/upload_system_symbols/arch_constants.h b/externals/breakpad/src/tools/mac/upload_system_symbols/arch_constants.h
new file mode 100644
index 0000000000..b6dbc89a05
--- /dev/null
+++ b/externals/breakpad/src/tools/mac/upload_system_symbols/arch_constants.h
@@ -0,0 +1,66 @@
+/* Copyright 2014 Google LLC
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google LLC nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <mach-o/arch.h>
+#include <mach-o/loader.h>
+#include <mach/machine.h>
+
+// Go/Cgo does not support #define constants, so turn them into symbols
+// that are reachable from Go.
+
+#ifndef CPU_TYPE_ARM64
+#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
+#endif
+
+#ifndef CPU_SUBTYPE_ARM64_ALL
+#define CPU_SUBTYPE_ARM64_ALL 0
+#endif
+
+#ifndef CPU_SUBTYPE_ARM64_E
+#define CPU_SUBTYPE_ARM64_E 2
+#endif
+
+const cpu_type_t kCPU_TYPE_ARM = CPU_TYPE_ARM;
+const cpu_type_t kCPU_TYPE_ARM64 = CPU_TYPE_ARM64;
+
+const cpu_subtype_t kCPU_SUBTYPE_ARM64_ALL = CPU_SUBTYPE_ARM64_ALL;
+const cpu_subtype_t kCPU_SUBTYPE_ARM64_E = CPU_SUBTYPE_ARM64_E;
+const cpu_subtype_t kCPU_SUBTYPE_ARM_V7S = CPU_SUBTYPE_ARM_V7S;
+
+const char* GetNXArchInfoName(cpu_type_t cpu_type, cpu_subtype_t cpu_subtype) {
+ const NXArchInfo* arch_info = NXGetArchInfoFromCpuType(cpu_type, cpu_subtype);
+ if (!arch_info)
+ return 0;
+ return arch_info->name;
+}
+
+const uint32_t kMachHeaderFtypeDylib = MH_DYLIB;
+const uint32_t kMachHeaderFtypeBundle = MH_BUNDLE;
+const uint32_t kMachHeaderFtypeExe = MH_EXECUTE;
+const uint32_t kMachHeaderFtypeDylinker = MH_DYLINKER;
diff --git a/externals/breakpad/src/tools/mac/upload_system_symbols/arch_reader.go b/externals/breakpad/src/tools/mac/upload_system_symbols/arch_reader.go
new file mode 100644
index 0000000000..03a764215a
--- /dev/null
+++ b/externals/breakpad/src/tools/mac/upload_system_symbols/arch_reader.go
@@ -0,0 +1,68 @@
+/* Copyright 2014 Google LLC
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google LLC nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package main
+
+import (
+ "debug/macho"
+)
+
+/*
+#include "arch_constants.h"
+*/
+import "C"
+
+// getArchStringFromHeader takes a MachO FileHeader and returns a string that
+// represents the CPU type and subtype.
+// This function is a Go version of src/common/mac/arch_utilities.cc:BreakpadGetArchInfoFromCpuType().
+func getArchStringFromHeader(header macho.FileHeader) string {
+ // TODO(rsesek): As of 10.9.4, OS X doesn't list these in /usr/include/mach/machine.h.
+ if header.Cpu == C.kCPU_TYPE_ARM64 && header.SubCpu == C.kCPU_SUBTYPE_ARM64_ALL {
+ return "arm64"
+ }
+ if header.Cpu == C.kCPU_TYPE_ARM64 && header.SubCpu == C.kCPU_SUBTYPE_ARM64_E {
+ return "arm64e"
+ }
+ if header.Cpu == C.kCPU_TYPE_ARM && header.SubCpu == C.kCPU_SUBTYPE_ARM_V7S {
+ return "armv7s"
+ }
+
+ cstr := C.GetNXArchInfoName(C.cpu_type_t(header.Cpu), C.cpu_subtype_t(header.SubCpu))
+ if cstr == nil {
+ return ""
+ }
+ return C.GoString(cstr)
+}
+
+const (
+ MachODylib macho.Type = C.kMachHeaderFtypeDylib
+ MachOBundle = C.kMachHeaderFtypeBundle
+ MachOExe = C.kMachHeaderFtypeExe
+ MachODylinker = C.kMachHeaderFtypeDylinker
+)
diff --git a/externals/breakpad/src/tools/mac/upload_system_symbols/go.mod b/externals/breakpad/src/tools/mac/upload_system_symbols/go.mod
new file mode 100644
index 0000000000..ff21bba9fd
--- /dev/null
+++ b/externals/breakpad/src/tools/mac/upload_system_symbols/go.mod
@@ -0,0 +1,3 @@
+module upload_system_symbols
+
+go 1.17
diff --git a/externals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.go b/externals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.go
new file mode 100644
index 0000000000..f34c288ae9
--- /dev/null
+++ b/externals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.go
@@ -0,0 +1,484 @@
+/* Copyright 2014 Google LLC
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google LLC nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+Tool upload_system_symbols generates and uploads Breakpad symbol files for OS X system libraries.
+
+This tool shells out to the dump_syms and symupload Breakpad tools. In its default mode, this
+will find all dynamic libraries on the system, run dump_syms to create the Breakpad symbol files,
+and then upload them to Google's crash infrastructure.
+
+The tool can also be used to only dump libraries or upload from a directory. See -help for more
+information.
+
+Both i386 and x86_64 architectures will be dumped and uploaded.
+*/
+package main
+
+import (
+ "debug/macho"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path"
+ "regexp"
+ "strings"
+ "sync"
+ "time"
+)
+
+var (
+ breakpadTools = flag.String("breakpad-tools", "out/Release/", "Path to the Breakpad tools directory, containing dump_syms and symupload.")
+ uploadOnlyPath = flag.String("upload-from", "", "Upload a directory of symbol files that has been dumped independently.")
+ dumpOnlyPath = flag.String("dump-to", "", "Dump the symbols to the specified directory, but do not upload them.")
+ systemRoot = flag.String("system-root", "", "Path to the root of the Mac OS X system whose symbols will be dumped.")
+ dumpArchitecture = flag.String("arch", "", "The CPU architecture for which symbols should be dumped. If not specified, dumps all architectures.")
+ apiKey = flag.String("api-key", "", "API key to use. If this is present, the `sym-upload-v2` protocol is used.\nSee https://chromium.googlesource.com/breakpad/breakpad/+/HEAD/docs/sym_upload_v2_protocol.md or\n`symupload`'s help for more information.")
+)
+
+var (
+ // pathsToScan are the subpaths in the systemRoot that should be scanned for shared libraries.
+ pathsToScan = []string{
+ "/System/Library/Frameworks",
+ "/System/Library/PrivateFrameworks",
+ "/usr/lib",
+ }
+
+ // optionalPathsToScan is just like pathsToScan, but the paths are permitted to be absent.
+ optionalPathsToScan = []string{
+ // Gone in 10.15.
+ "/Library/QuickTime",
+ // Not present in dumped dyld_shared_caches
+ "/System/Library/Components",
+ }
+
+ // uploadServersV1 are the list of servers to which symbols should be
+ // uploaded when using the V1 protocol.
+ uploadServersV1 = []string{
+ "https://clients2.google.com/cr/symbol",
+ "https://clients2.google.com/cr/staging_symbol",
+ }
+ // uploadServersV2 are the list of servers to which symbols should be
+ // uploaded when using the V2 protocol.
+ uploadServersV2 = []string{
+ "https://staging-crashsymbolcollector-pa.googleapis.com",
+ "https://prod-crashsymbolcollector-pa.googleapis.com",
+ }
+
+ // uploadServers are the list of servers that should be used, accounting
+ // for whether v1 or v2 protocol is used.
+ uploadServers = uploadServersV1
+
+ // blacklistRegexps match paths that should be excluded from dumping.
+ blacklistRegexps = []*regexp.Regexp{
+ regexp.MustCompile(`/System/Library/Frameworks/Python\.framework/`),
+ regexp.MustCompile(`/System/Library/Frameworks/Ruby\.framework/`),
+ regexp.MustCompile(`_profile\.dylib$`),
+ regexp.MustCompile(`_debug\.dylib$`),
+ regexp.MustCompile(`\.a$`),
+ regexp.MustCompile(`\.dat$`),
+ }
+)
+
+func main() {
+ flag.Parse()
+ log.SetFlags(0)
+
+ // If `apiKey` is set, we're using the v2 protocol.
+ if len(*apiKey) > 0 {
+ uploadServers = uploadServersV2
+ }
+
+ var uq *UploadQueue
+
+ if *uploadOnlyPath != "" {
+ // -upload-from specified, so handle that case early.
+ uq = StartUploadQueue()
+ uploadFromDirectory(*uploadOnlyPath, uq)
+ uq.Wait()
+ return
+ }
+
+ if *systemRoot == "" {
+ log.Fatal("Need a -system-root to dump symbols for")
+ }
+
+ if *dumpOnlyPath != "" {
+ // -dump-to specified, so make sure that the path is a directory.
+ if fi, err := os.Stat(*dumpOnlyPath); err != nil {
+ log.Fatalf("-dump-to location: %v", err)
+ } else if !fi.IsDir() {
+ log.Fatal("-dump-to location is not a directory")
+ }
+ }
+
+ dumpPath := *dumpOnlyPath
+ if *dumpOnlyPath == "" {
+ // If -dump-to was not specified, then run the upload pipeline and create
+ // a temporary dump output directory.
+ uq = StartUploadQueue()
+
+ if p, err := ioutil.TempDir("", "upload_system_symbols"); err != nil {
+ log.Fatalf("Failed to create temporary directory: %v", err)
+ } else {
+ dumpPath = p
+ defer os.RemoveAll(p)
+ }
+ }
+
+ dq := StartDumpQueue(*systemRoot, dumpPath, uq)
+ dq.Wait()
+ if uq != nil {
+ uq.Wait()
+ }
+}
+
+// manglePath reduces an absolute filesystem path to a string suitable as the
+// base for a file name which encodes some of the original path. The result
+// concatenates the leading initial from each path component except the last to
+// the last path component; for example /System/Library/Frameworks/AppKit
+// becomes SLFAppKit.
+// Assumes ASCII.
+func manglePath(path string) string {
+ components := strings.Split(path, "/")
+ n := len(components)
+ builder := strings.Builder{}
+ for i, component := range components {
+ if len(component) == 0 {
+ continue
+ }
+ if i < n-1 {
+ builder.WriteString(component[:1])
+ } else {
+ builder.WriteString(component)
+ }
+ }
+ return builder.String()
+}
+
+type WorkerPool struct {
+ wg sync.WaitGroup
+}
+
+// StartWorkerPool will launch numWorkers goroutines all running workerFunc.
+// When workerFunc exits, the goroutine will terminate.
+func StartWorkerPool(numWorkers int, workerFunc func()) *WorkerPool {
+ p := new(WorkerPool)
+ for i := 0; i < numWorkers; i++ {
+ p.wg.Add(1)
+ go func() {
+ workerFunc()
+ p.wg.Done()
+ }()
+ }
+ return p
+}
+
+// Wait for all the workers in the pool to complete the workerFunc.
+func (p *WorkerPool) Wait() {
+ p.wg.Wait()
+}
+
+type UploadQueue struct {
+ *WorkerPool
+ queue chan string
+}
+
+// StartUploadQueue creates a new worker pool and queue, to which paths to
+// Breakpad symbol files may be sent for uploading.
+func StartUploadQueue() *UploadQueue {
+ uq := &UploadQueue{
+ queue: make(chan string, 10),
+ }
+ uq.WorkerPool = StartWorkerPool(5, uq.worker)
+ return uq
+}
+
+// Upload enqueues the contents of filepath to be uploaded.
+func (uq *UploadQueue) Upload(filepath string) {
+ uq.queue <- filepath
+}
+
+// Done tells the queue that no more files need to be uploaded. This must be
+// called before WorkerPool.Wait.
+func (uq *UploadQueue) Done() {
+ close(uq.queue)
+}
+
+func (uq *UploadQueue) runSymUpload(symfile, server string) *exec.Cmd {
+ symUpload := path.Join(*breakpadTools, "symupload")
+ args := []string{symfile, server}
+ if len(*apiKey) > 0 {
+ args = append([]string{"-p", "sym-upload-v2", "-k", *apiKey}, args...)
+ }
+ return exec.Command(symUpload, args...)
+}
+
+func (uq *UploadQueue) worker() {
+ for symfile := range uq.queue {
+ for _, server := range uploadServers {
+ for i := 0; i < 3; i++ { // Give each upload 3 attempts to succeed.
+ cmd := uq.runSymUpload(symfile, server)
+ if output, err := cmd.Output(); err == nil {
+ // Success. No retry needed.
+ fmt.Printf("Uploaded %s to %s\n", symfile, server)
+ break
+ } else if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 2 && *apiKey != "" {
+ // Exit code 2 in protocol v2 means the file already exists on the server.
+ // No point retrying.
+ fmt.Printf("File %s already exists on %s\n", symfile, server)
+ break
+ } else {
+ log.Printf("Error running symupload(%s, %s), attempt %d: %v: %s\n", symfile, server, i, err, output)
+ time.Sleep(1 * time.Second)
+ }
+ }
+ }
+ }
+}
+
+type DumpQueue struct {
+ *WorkerPool
+ dumpPath string
+ queue chan dumpRequest
+ uq *UploadQueue
+}
+
+type dumpRequest struct {
+ path string
+ arch string
+}
+
+// StartDumpQueue creates a new worker pool to find all the Mach-O libraries in
+// root and dump their symbols to dumpPath. If an UploadQueue is passed, the
+// path to the symbol file will be enqueued there, too.
+func StartDumpQueue(root, dumpPath string, uq *UploadQueue) *DumpQueue {
+ dq := &DumpQueue{
+ dumpPath: dumpPath,
+ queue: make(chan dumpRequest),
+ uq: uq,
+ }
+ dq.WorkerPool = StartWorkerPool(12, dq.worker)
+
+ findLibsInRoot(root, dq)
+
+ return dq
+}
+
+// DumpSymbols enqueues the filepath to have its symbols dumped in the specified
+// architecture.
+func (dq *DumpQueue) DumpSymbols(filepath string, arch string) {
+ dq.queue <- dumpRequest{
+ path: filepath,
+ arch: arch,
+ }
+}
+
+func (dq *DumpQueue) Wait() {
+ dq.WorkerPool.Wait()
+ if dq.uq != nil {
+ dq.uq.Done()
+ }
+}
+
+func (dq *DumpQueue) done() {
+ close(dq.queue)
+}
+
+func (dq *DumpQueue) worker() {
+ dumpSyms := path.Join(*breakpadTools, "dump_syms")
+
+ for req := range dq.queue {
+ filebase := path.Join(dq.dumpPath, manglePath(req.path))
+ symfile := fmt.Sprintf("%s_%s.sym", filebase, req.arch)
+ f, err := os.Create(symfile)
+ if err != nil {
+ log.Fatalf("Error creating symbol file: %v", err)
+ }
+
+ cmd := exec.Command(dumpSyms, "-a", req.arch, req.path)
+ cmd.Stdout = f
+ err = cmd.Run()
+ f.Close()
+
+ if err != nil {
+ os.Remove(symfile)
+ log.Printf("Error running dump_syms(%s, %s): %v\n", req.arch, req.path, err)
+ } else if dq.uq != nil {
+ dq.uq.Upload(symfile)
+ }
+ }
+}
+
+// uploadFromDirectory handles the upload-only case and merely uploads all files in
+// a directory.
+func uploadFromDirectory(directory string, uq *UploadQueue) {
+ d, err := os.Open(directory)
+ if err != nil {
+ log.Fatalf("Could not open directory to upload: %v", err)
+ }
+ defer d.Close()
+
+ entries, err := d.Readdirnames(0)
+ if err != nil {
+ log.Fatalf("Could not read directory: %v", err)
+ }
+
+ for _, entry := range entries {
+ uq.Upload(path.Join(directory, entry))
+ }
+
+ uq.Done()
+}
+
+// findQueue is an implementation detail of the DumpQueue that finds all the
+// Mach-O files and their architectures.
+type findQueue struct {
+ *WorkerPool
+ queue chan string
+ dq *DumpQueue
+}
+
+// findLibsInRoot looks in all the pathsToScan in the root and manages the
+// interaction between findQueue and DumpQueue.
+func findLibsInRoot(root string, dq *DumpQueue) {
+ fq := &findQueue{
+ queue: make(chan string, 10),
+ dq: dq,
+ }
+ fq.WorkerPool = StartWorkerPool(12, fq.worker)
+
+ for _, p := range pathsToScan {
+ fq.findLibsInPath(path.Join(root, p), true)
+ }
+
+ for _, p := range optionalPathsToScan {
+ fq.findLibsInPath(path.Join(root, p), false)
+ }
+
+ close(fq.queue)
+ fq.Wait()
+ dq.done()
+}
+
+// findLibsInPath recursively walks the directory tree, sending file paths to
+// test for being Mach-O to the findQueue.
+func (fq *findQueue) findLibsInPath(loc string, mustExist bool) {
+ d, err := os.Open(loc)
+ if err != nil {
+ if !mustExist && os.IsNotExist(err) {
+ return
+ }
+ log.Fatalf("Could not open %s: %v", loc, err)
+ }
+ defer d.Close()
+
+ for {
+ fis, err := d.Readdir(100)
+ if err != nil && err != io.EOF {
+ log.Fatalf("Error reading directory %s: %v", loc, err)
+ }
+
+ for _, fi := range fis {
+ fp := path.Join(loc, fi.Name())
+ if fi.IsDir() {
+ fq.findLibsInPath(fp, true)
+ continue
+ } else if fi.Mode()&os.ModeSymlink != 0 {
+ continue
+ }
+
+ // Test the blacklist in the worker to not slow down this main loop.
+
+ fq.queue <- fp
+ }
+
+ if err == io.EOF {
+ break
+ }
+ }
+}
+
+func (fq *findQueue) worker() {
+ for fp := range fq.queue {
+ blacklisted := false
+ for _, re := range blacklistRegexps {
+ blacklisted = blacklisted || re.MatchString(fp)
+ }
+ if blacklisted {
+ continue
+ }
+
+ f, err := os.Open(fp)
+ if err != nil {
+ log.Printf("%s: %v", fp, err)
+ continue
+ }
+
+ fatFile, err := macho.NewFatFile(f)
+ if err == nil {
+ // The file is fat, so dump its architectures.
+ for _, fatArch := range fatFile.Arches {
+ fq.dumpMachOFile(fp, fatArch.File)
+ }
+ fatFile.Close()
+ } else if err == macho.ErrNotFat {
+ // The file isn't fat but may still be MachO.
+ thinFile, err := macho.NewFile(f)
+ if err != nil {
+ log.Printf("%s: %v", fp, err)
+ continue
+ }
+ fq.dumpMachOFile(fp, thinFile)
+ thinFile.Close()
+ } else {
+ f.Close()
+ }
+ }
+}
+
+func (fq *findQueue) dumpMachOFile(fp string, image *macho.File) {
+ if image.Type != MachODylib && image.Type != MachOBundle && image.Type != MachODylinker {
+ return
+ }
+
+ arch := getArchStringFromHeader(image.FileHeader)
+ if arch == "" {
+ // Don't know about this architecture type.
+ return
+ }
+
+ if (*dumpArchitecture != "" && *dumpArchitecture == arch) || *dumpArchitecture == "" {
+ fq.dq.DumpSymbols(fp, arch)
+ }
+}
diff --git a/externals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.sh b/externals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.sh
new file mode 100755
index 0000000000..43fd98ed52
--- /dev/null
+++ b/externals/breakpad/src/tools/mac/upload_system_symbols/upload_system_symbols.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+
+# Copyright 2023 Google LLC
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google LLC nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Finds the dyld_shared_cache on a system, extracts it, and dumps the symbols
+# in Breakpad format to the directory passed as the first argument
+# The script must be in the same directory as `dump_syms`,
+# `upload_system_symbols` and `dsc_extractor` binaries.
+# Exits with 0 if all supported architectures for this OS version were found and
+# dumped, and nonzero otherwise.
+
+set -ex
+
+if [[ $# -ne 1 ]]; then
+ echo "usage: $0 <destination_directory>" >& 2
+ exit 1
+fi
+
+destination_dir="$1"
+
+dir="$(dirname "$0")"
+dir="$(cd "${dir}"; pwd)"
+major_version=$(sw_vers -productVersion | cut -d . -f 1)
+if [[ "${major_version}" -lt 13 ]]; then
+ dsc_directory="/System/Library/dyld"
+else
+ dsc_directory="/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld"
+fi
+
+working_dir=$(mktemp -d)
+mkdir "${destination_dir}"
+trap 'rm -rf "${working_dir}" "${destination_dir}"' EXIT
+
+architectures=(x86_64h)
+missing_architectures=()
+# macOS >= 13 on arm64 still has a x86_64 cache for Rosetta.
+if [[ "${major_version}" -lt 13 ]] || [[ $(uname -p) == "arm" ]]; then
+ architectures+=( x86_64 )
+fi
+if [[ "${major_version}" -ge 11 ]]; then
+ architectures+=( arm64e )
+fi
+
+for arch in "${architectures[@]}"; do
+ cache="${dsc_directory}/dyld_shared_cache_${arch}"
+ if [[ ! -f "${cache}" ]]; then
+ missing_architectures+=("${arch}")
+ continue
+ fi
+ "${dir}/dsc_extractor" \
+ "${cache}" \
+ "${working_dir}/${arch}"
+ "${dir}/upload_system_symbols" \
+ --breakpad-tools="${dir}" \
+ --system-root="${working_dir}/${arch}" \
+ --dump-to="${destination_dir}"
+done
+if [[ "${#missing_architectures[@]}" -eq "${#architectures[@]}" ]]; then
+ echo "Couldn't locate dyld_shared_cache for any architectures" >& 2
+ echo "in ${dsc_directory}. Exiting." >& 2
+ exit 1
+fi
+
+rm -rf "${working_dir}"
+# We have results now, so let's keep `destination_dir`.
+trap '' EXIT
+
+"${dir}/upload_system_symbols" \
+ --breakpad-tools="${dir}" \
+ --system-root=/ \
+ --dump-to="${destination_dir}"
+
+set +x
+echo
+echo "Dumped!"
+echo "To upload, run:"
+echo
+echo "'${dir}/upload_system_symbols'" \\
+echo " --breakpad-tools='${dir}'" \\
+echo " --api-key=<YOUR API KEY>" \\
+echo " --upload-from='${destination_dir}'"
+
+if [[ "${#missing_architectures[@]}" -gt 0 ]]; then
+ echo "dyld_shared_cache not found for architecture(s):" >& 2
+ echo " " "${missing_architectures[@]}" >& 2
+ echo "You'll need to get symbols for them elsewhere." >& 2
+ exit 1
+fi