| Index: build/mac/strip_save_dsym
|
| diff --git a/build/mac/strip_save_dsym b/build/mac/strip_save_dsym
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..c9cf22663757912c16895c55abd7ce346355079d
|
| --- /dev/null
|
| +++ b/build/mac/strip_save_dsym
|
| @@ -0,0 +1,335 @@
|
| +#!/usr/bin/env python
|
| +
|
| +# Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +# Usage: strip_save_dsym <whatever-arguments-you-would-pass-to-strip>
|
| +#
|
| +# strip_save_dsym is a wrapper around the standard strip utility. Given an
|
| +# input Mach-O file, strip_save_dsym will save a copy of the file in a "fake"
|
| +# .dSYM bundle for debugging, and then call strip to strip the Mach-O file.
|
| +# Note that the .dSYM file is a "fake" in that it's not a self-contained
|
| +# .dSYM bundle, it just contains a copy of the original (unstripped) Mach-O
|
| +# file, and therefore contains references to object files on the filesystem.
|
| +# The generated .dSYM bundle is therefore unsuitable for debugging in the
|
| +# absence of these .o files.
|
| +#
|
| +# If a .dSYM already exists and has a newer timestamp than the Mach-O file,
|
| +# this utility does nothing. That allows strip_save_dsym to be run on a file
|
| +# that has already been stripped without trashing the .dSYM.
|
| +#
|
| +# Rationale: the "right" way to generate dSYM bundles, dsymutil, is incredibly
|
| +# slow. On the other hand, doing a file copy (which is really all that
|
| +# dsymutil does) is comparatively fast. Since we usually just want to strip
|
| +# a release-mode executable but still be able to debug it, and we don't care
|
| +# so much about generating a hermetic dSYM bundle, we'll prefer the file copy.
|
| +# If a real dSYM is ever needed, it's still possible to create one by running
|
| +# dsymutil and pointing it at the original Mach-O file inside the "fake"
|
| +# bundle, provided that the object files are available.
|
| +
|
| +import errno
|
| +import os
|
| +import re
|
| +import shutil
|
| +import subprocess
|
| +import sys
|
| +import time
|
| +
|
| +# Returns a list of architectures contained in a Mach-O file. The file can be
|
| +# a universal (fat) file, in which case there will be one list element for
|
| +# each contained architecture, or it can be a thin single-architecture Mach-O
|
| +# file, in which case the list will contain a single element identifying the
|
| +# architecture. On error, returns an empty list. Determines the architecture
|
| +# list by calling file.
|
| +def macho_archs(macho):
|
| + macho_types = ["executable",
|
| + "dynamically linked shared library",
|
| + "bundle"]
|
| + macho_types_re = "Mach-O (?:64-bit )?(?:" + "|".join(macho_types) + ")"
|
| +
|
| + file_cmd = subprocess.Popen(["/usr/bin/file", "-b", "--", macho],
|
| + stdout=subprocess.PIPE)
|
| +
|
| + archs = []
|
| +
|
| + type_line = file_cmd.stdout.readline()
|
| + type_match = re.match("^%s (.*)$" % macho_types_re, type_line)
|
| + if type_match:
|
| + archs.append(type_match.group(1))
|
| + return [type_match.group(1)]
|
| + else:
|
| + type_match = re.match("^Mach-O universal binary with (.*) architectures$",
|
| + type_line)
|
| + if type_match:
|
| + for i in range(0, int(type_match.group(1))):
|
| + arch_line = file_cmd.stdout.readline()
|
| + arch_match = re.match(
|
| + "^.* \(for architecture (.*)\):\t%s .*$" % macho_types_re,
|
| + arch_line)
|
| + if arch_match:
|
| + archs.append(arch_match.group(1))
|
| +
|
| + if file_cmd.wait() != 0:
|
| + archs = []
|
| +
|
| + if len(archs) == 0:
|
| + print >> sys.stderr, "No architectures in %s" % macho
|
| +
|
| + return archs
|
| +
|
| +# Returns a dictionary mapping architectures contained in the file as returned
|
| +# by macho_archs to the LC_UUID load command for that architecture.
|
| +# Architectures with no LC_UUID load command are omitted from the dictionary.
|
| +# Determines the UUID value by calling otool.
|
| +def macho_uuids(macho):
|
| + uuids = {}
|
| +
|
| + archs = macho_archs(macho)
|
| + if len(archs) == 0:
|
| + return uuids
|
| +
|
| + for arch in archs:
|
| + if arch == "":
|
| + continue
|
| +
|
| + otool_cmd = subprocess.Popen(["/usr/bin/otool", "-arch", arch, "-l", "-",
|
| + macho],
|
| + stdout=subprocess.PIPE)
|
| + # state 0 is when nothing UUID-related has been seen yet. State 1 is
|
| + # entered after a load command begins, but it may not be an LC_UUID load
|
| + # command. States 2, 3, and 4 are intermediate states while reading an
|
| + # LC_UUID command. State 5 is the terminal state for a successful LC_UUID
|
| + # read. State 6 is the error state.
|
| + state = 0
|
| + uuid = ""
|
| + for otool_line in otool_cmd.stdout:
|
| + if state == 0:
|
| + if re.match("^Load command .*$", otool_line):
|
| + state = 1
|
| + elif state == 1:
|
| + if re.match("^ cmd LC_UUID$", otool_line):
|
| + state = 2
|
| + else:
|
| + state = 0
|
| + elif state == 2:
|
| + if re.match("^ cmdsize 24$", otool_line):
|
| + state = 3
|
| + else:
|
| + state = 6
|
| + elif state == 3:
|
| + # The UUID display format changed in the version of otool shipping
|
| + # with the Xcode 3.2.2 prerelease. The new format is traditional:
|
| + # uuid 4D7135B2-9C56-C5F5-5F49-A994258E0955
|
| + # and with Xcode 3.2.6, then line is indented one more space:
|
| + # uuid 4D7135B2-9C56-C5F5-5F49-A994258E0955
|
| + # The old format, from cctools-750 and older's otool, breaks the UUID
|
| + # up into a sequence of bytes:
|
| + # uuid 0x4d 0x71 0x35 0xb2 0x9c 0x56 0xc5 0xf5
|
| + # 0x5f 0x49 0xa9 0x94 0x25 0x8e 0x09 0x55
|
| + new_uuid_match = re.match("^ {3,4}uuid (.{8}-.{4}-.{4}-.{4}-.{12})$",
|
| + otool_line)
|
| + if new_uuid_match:
|
| + uuid = new_uuid_match.group(1)
|
| +
|
| + # Skip state 4, there is no second line to read.
|
| + state = 5
|
| + else:
|
| + old_uuid_match = re.match("^ uuid 0x(..) 0x(..) 0x(..) 0x(..) "
|
| + "0x(..) 0x(..) 0x(..) 0x(..)$",
|
| + otool_line)
|
| + if old_uuid_match:
|
| + state = 4
|
| + uuid = old_uuid_match.group(1) + old_uuid_match.group(2) + \
|
| + old_uuid_match.group(3) + old_uuid_match.group(4) + "-" + \
|
| + old_uuid_match.group(5) + old_uuid_match.group(6) + "-" + \
|
| + old_uuid_match.group(7) + old_uuid_match.group(8) + "-"
|
| + else:
|
| + state = 6
|
| + elif state == 4:
|
| + old_uuid_match = re.match("^ 0x(..) 0x(..) 0x(..) 0x(..) "
|
| + "0x(..) 0x(..) 0x(..) 0x(..)$",
|
| + otool_line)
|
| + if old_uuid_match:
|
| + state = 5
|
| + uuid += old_uuid_match.group(1) + old_uuid_match.group(2) + "-" + \
|
| + old_uuid_match.group(3) + old_uuid_match.group(4) + \
|
| + old_uuid_match.group(5) + old_uuid_match.group(6) + \
|
| + old_uuid_match.group(7) + old_uuid_match.group(8)
|
| + else:
|
| + state = 6
|
| +
|
| + if otool_cmd.wait() != 0:
|
| + state = 6
|
| +
|
| + if state == 5:
|
| + uuids[arch] = uuid.upper()
|
| +
|
| + if len(uuids) == 0:
|
| + print >> sys.stderr, "No UUIDs in %s" % macho
|
| +
|
| + return uuids
|
| +
|
| +# Given a path to a Mach-O file and possible information from the environment,
|
| +# determines the desired path to the .dSYM.
|
| +def dsym_path(macho):
|
| + # If building a bundle, the .dSYM should be placed next to the bundle. Use
|
| + # WRAPPER_NAME to make this determination. If called from xcodebuild,
|
| + # WRAPPER_NAME will be set to the name of the bundle.
|
| + dsym = ""
|
| + if "WRAPPER_NAME" in os.environ:
|
| + if "BUILT_PRODUCTS_DIR" in os.environ:
|
| + dsym = os.path.join(os.environ["BUILT_PRODUCTS_DIR"],
|
| + os.environ["WRAPPER_NAME"])
|
| + else:
|
| + dsym = os.environ["WRAPPER_NAME"]
|
| + else:
|
| + dsym = macho
|
| +
|
| + dsym += ".dSYM"
|
| +
|
| + return dsym
|
| +
|
| +# Creates a fake .dSYM bundle at dsym for macho, a Mach-O image with the
|
| +# architectures and UUIDs specified by the uuids map.
|
| +def make_fake_dsym(macho, dsym):
|
| + uuids = macho_uuids(macho)
|
| + if len(uuids) == 0:
|
| + return False
|
| +
|
| + dwarf_dir = os.path.join(dsym, "Contents", "Resources", "DWARF")
|
| + dwarf_file = os.path.join(dwarf_dir, os.path.basename(macho))
|
| + try:
|
| + os.makedirs(dwarf_dir)
|
| + except OSError, (err, error_string):
|
| + if err != errno.EEXIST:
|
| + raise
|
| + shutil.copyfile(macho, dwarf_file)
|
| +
|
| + # info_template is the same as what dsymutil would have written, with the
|
| + # addition of the fake_dsym key.
|
| + info_template = \
|
| +'''<?xml version="1.0" encoding="UTF-8"?>
|
| +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| +<plist version="1.0">
|
| + <dict>
|
| + <key>CFBundleDevelopmentRegion</key>
|
| + <string>English</string>
|
| + <key>CFBundleIdentifier</key>
|
| + <string>com.apple.xcode.dsym.%(root_name)s</string>
|
| + <key>CFBundleInfoDictionaryVersion</key>
|
| + <string>6.0</string>
|
| + <key>CFBundlePackageType</key>
|
| + <string>dSYM</string>
|
| + <key>CFBundleSignature</key>
|
| + <string>????</string>
|
| + <key>CFBundleShortVersionString</key>
|
| + <string>1.0</string>
|
| + <key>CFBundleVersion</key>
|
| + <string>1</string>
|
| + <key>dSYM_UUID</key>
|
| + <dict>
|
| +%(uuid_dict)s </dict>
|
| + <key>fake_dsym</key>
|
| + <true/>
|
| + </dict>
|
| +</plist>
|
| +'''
|
| +
|
| + root_name = os.path.basename(dsym)[:-5] # whatever.dSYM without .dSYM
|
| + uuid_dict = ""
|
| + for arch in sorted(uuids):
|
| + uuid_dict += "\t\t\t<key>" + arch + "</key>\n"\
|
| + "\t\t\t<string>" + uuids[arch] + "</string>\n"
|
| + info_dict = {
|
| + "root_name": root_name,
|
| + "uuid_dict": uuid_dict,
|
| + }
|
| + info_contents = info_template % info_dict
|
| + info_file = os.path.join(dsym, "Contents", "Info.plist")
|
| + info_fd = open(info_file, "w")
|
| + info_fd.write(info_contents)
|
| + info_fd.close()
|
| +
|
| + return True
|
| +
|
| +# For a Mach-O file, determines where the .dSYM bundle should be located. If
|
| +# the bundle does not exist or has a modification time older than the Mach-O
|
| +# file, calls make_fake_dsym to create a fake .dSYM bundle there, then strips
|
| +# the Mach-O file and sets the modification time on the .dSYM bundle and Mach-O
|
| +# file to be identical.
|
| +def strip_and_make_fake_dsym(macho):
|
| + dsym = dsym_path(macho)
|
| + macho_stat = os.stat(macho)
|
| + dsym_stat = None
|
| + try:
|
| + dsym_stat = os.stat(dsym)
|
| + except OSError, (err, error_string):
|
| + if err != errno.ENOENT:
|
| + raise
|
| +
|
| + if dsym_stat is None or dsym_stat.st_mtime < macho_stat.st_mtime:
|
| + # Make a .dSYM bundle
|
| + if not make_fake_dsym(macho, dsym):
|
| + return False
|
| +
|
| + # Strip the Mach-O file
|
| + remove_dsym = True
|
| + try:
|
| + strip_cmdline = ['xcrun', 'strip'] + sys.argv[1:]
|
| + strip_cmd = subprocess.Popen(strip_cmdline)
|
| + if strip_cmd.wait() == 0:
|
| + remove_dsym = False
|
| + finally:
|
| + if remove_dsym:
|
| + shutil.rmtree(dsym)
|
| +
|
| + # Update modification time on the Mach-O file and .dSYM bundle
|
| + now = time.time()
|
| + os.utime(macho, (now, now))
|
| + os.utime(dsym, (now, now))
|
| +
|
| + return True
|
| +
|
| +def main(argv=None):
|
| + if argv is None:
|
| + argv = sys.argv
|
| +
|
| + # This only supports operating on one file at a time. Look at the arguments
|
| + # to strip to figure out what the source to be stripped is. Arguments are
|
| + # processed in the same way that strip does, although to reduce complexity,
|
| + # this doesn't do all of the same checking as strip. For example, strip
|
| + # has no -Z switch and would treat -Z on the command line as an error. For
|
| + # the purposes this is needed for, that's fine.
|
| + macho = None
|
| + process_switches = True
|
| + ignore_argument = False
|
| + for arg in argv[1:]:
|
| + if ignore_argument:
|
| + ignore_argument = False
|
| + continue
|
| + if process_switches:
|
| + if arg == "-":
|
| + process_switches = False
|
| + # strip has these switches accept an argument:
|
| + if arg in ["-s", "-R", "-d", "-o", "-arch"]:
|
| + ignore_argument = True
|
| + if arg[0] == "-":
|
| + continue
|
| + if macho is None:
|
| + macho = arg
|
| + else:
|
| + print >> sys.stderr, "Too many things to strip"
|
| + return 1
|
| +
|
| + if macho is None:
|
| + print >> sys.stderr, "Nothing to strip"
|
| + return 1
|
| +
|
| + if not strip_and_make_fake_dsym(macho):
|
| + return 1
|
| +
|
| + return 0
|
| +
|
| +if __name__ == "__main__":
|
| + sys.exit(main(sys.argv))
|
|
|