Skip to content

Instantly share code, notes, and snippets.

@norio-nomura
Last active March 24, 2017 14:59
Show Gist options
  • Save norio-nomura/10d9790e7b5e414e20ae5ac6e621ef20 to your computer and use it in GitHub Desktop.
Save norio-nomura/10d9790e7b5e414e20ae5ac6e621ef20 to your computer and use it in GitHub Desktop.
`swift_oss_helper.py` helps debugging Swift Standard Library using source code with Swift Toolchain distributed at swift.org.

swift_oss_helper.py

swift_oss_helper.py helps debugging Swift Standard Library using source code with Swift Toolchain distributed at swift.org.

This helps:

  • Configuring target.source-map setting in lldb for adjusting symbol path lookup to distributed Swift Toolchain snapshots
  • Processing *.swift.gyb without actual building Swift toolchain
  • Generate Symbol Map at ~/Library/Developer/SymbolMap/uuids

Usage

  1. install Swift Toolchain snapshot and symbols from https://swift.org/download/#snapshots
    e.g. swift-3.1-DEVELOPMENT-SNAPSHOT-2017-03-12-a Xcode and Symbol
  2. Check out the same tag as the snapshot. e.g.swift-3.1-DEVELOPMENT-SNAPSHOT-2017-03-12-a
  3. clone Swift sources to SWIFT_SOURCE_ROOT:
mkdir swift-source
cd swift-source
git clone https://github.com/apple/swift.git
  1. copy swift_oss_helper.py to SWIFT_SOURCE_ROOT
  2. process *.swift.gyb. See next section
  3. add following to ~/.lldbinit
command script import ~/swift-source/swift_oss_helper.py
swift_oss_helper source-map 3.1
  1. confirm target.source-map is configured in lldb:
$  lldb
swift_oss_helper command enabled.
(lldb) settings show target.source-map
target.source-map (path-map) =
[0] "/Users/buildnode/jenkins/workspace/oss-swift-package-osx/build/buildbot_osx" -> "/Users/norio/swift-source/build/swift_gyb_processed"
[1] "/Users/buildnode/jenkins/workspace/oss-swift-package-osx/swift" -> "/Users/norio/swift-source/swift"
[2] "/Users/buildnode/jenkins/workspace/oss-swift-3.1-package-osx/build/buildbot_osx" -> "/Users/norio/swift-source/build/swift_gyb_processed"
[3] "/Users/buildnode/jenkins/workspace/oss-swift-3.1-package-osx/swift" -> "/Users/norio/swift-source/swift"
  1. generate symbol map and enable them if needed:
$  swift-source/swift_oss_helper.py update-symbol-map
create map: ~/Library/Developer/SymbolMap/uuids/6C24/2369/8EFD/3E83/B9DB/EE1B0D55C8C7 -> /Library/Developer/Toolchains/swift-3.0.2-RELEASE.xctoolchain/Developer/Platforms/AppleTVSimulator.platform/Developer/Library/Frameworks/PlaygroundSupport.framework/PlaygroundSupport.dSYM/Contents/Resources/DWARF/PlaygroundSupport
create map: ~/Library/Developer/SymbolMap/uuids/41D9/1DF7/6BE6/3BCF/BBBD/5FD323D699B4 -> /Library/Developer/Toolchains/swift-3.0.2-RELEASE.xctoolchain/Developer/Platforms/AppleTVSimulator.platform/Developer/Library/Frameworks/XCPlayground.framework/XCPlayground.dSYM/Contents/Resources/DWARF/XCPlayground
…
$ defaults write com.apple.DebugSymbols DBGFileMappedPaths ~/Library/Developer/SymbolMap/uuids

Process *.swift.gyb

Since some source code is generated at build time, some symbols do not point to the source code being distributed.
We can generate them by followings:
in terminal:

swift-source/swift_oss_helper.py process-gyb

in lldb:

(lldb) swift_oss_helper process-gyb

You will need to doing this manualy after updating snapshots.

Symbol Map

lldb looks up .dSYM by using Spotlight. If Spotlight does not index your .dSYM by some reasons(e.g. Swift toolchain directory is excluded from Time Machine Backup.), and lldb does not locate them.
On such case, you need to create "File mapped UUID paths" that explained in Debug Symbols on Mac OS X.
swift_oss_helper.py helps as following:

swift-source/swift_oss_helper.py update-symbol-map
#!/usr/bin/env python
import os
local_base_dir = os.path.dirname(os.path.abspath(__file__))
source_map_path = 'build/swift_gyb_processed'
def _walk(top, sub='', topdown=True, onerror=None, followlinks=False):
'''Tweaked version of os.walk
Changed from os.walk to returning subpath based on top instead top itself.
'''
try:
names = os.listdir(os.path.join(top, sub))
except os.error as err:
if onerror is not None:
onerror(err)
return
dirs, nondirs = [], []
for name in names:
if os.path.isdir(os.path.join(os.path.join(top, sub), name)):
dirs.append(name)
else:
nondirs.append(name)
if topdown:
yield sub, dirs, nondirs
for name in dirs:
if followlinks or not os.path.islink(os.path.join(top, sub, name)):
for x in _walk(top, os.path.join(sub, name), topdown, onerror, followlinks):
yield x
if not topdown:
yield sub, dirs, nondirs
def create_symbol_map_entries(directories, symbol_map_path):
import fnmatch
import re
import subprocess
symbol_map_path_expanded = os.path.expanduser(symbol_map_path)
created = False
prog = re.compile('^UUID: (?P<uuid>[-0-9A-Z]{36}) \(.*\) (?P<path>.*)$')
for directory in directories:
if os.path.islink(directory): continue
for sub_dirpath, dirnames, filenames in _walk(directory, topdown=False):
for dirname in fnmatch.filter(dirnames, '*.dSYM'):
output = subprocess.check_output(['dwarfdump', '-u', os.path.join(directory, sub_dirpath, dirname)])
match = prog.match(output)
if not match: continue
uuid = match.group('uuid').replace('-','')
uuid_components = [uuid[0:4], uuid[4:8], uuid[8:12], uuid[12:16], uuid[16:20], uuid[20:]]
symlink_dir = os.path.join(symbol_map_path_expanded, *uuid_components[:-1])
if not os.path.exists(symlink_dir): os.makedirs(symlink_dir)
dsym_path = match.group('path')
symlink_path = os.path.join(symbol_map_path_expanded, *uuid_components)
should_create = False
if os.path.exists(symlink_path):
if os.path.islink(symlink_path) and os.path.realpath(symlink_path) != dsym_path:
os.remove(symlink_path)
should_create = True
else:
should_create = True
if should_create:
created = True
os.symlink(dsym_path, symlink_path)
print('create map: {} -> {}'.format(os.path.join(symbol_map_path, *uuid_components), dsym_path))
return created
def remove_orphan_symbol_map_entries(symbol_map_path):
symbol_map_path_expanded = os.path.expanduser(symbol_map_path)
for sub_dirpath, dirnames, filenames in _walk(symbol_map_path_expanded, topdown=False):
for filename in filenames:
target = os.path.join(symbol_map_path_expanded, sub_dirpath, filename)
if os.path.islink(target) and not os.path.exists(target):
print('remove deadlink: {} -> {}'.format(os.path.join(symbol_map_path, sub_dirpath, filename), os.path.realpath(target)))
os.remove(target)
def cleanup_symbol_map(symbol_map_path):
symbol_map_path_expanded = os.path.expanduser(symbol_map_path)
for sub_dirpath, dirnames, filenames in _walk(symbol_map_path_expanded, topdown=False):
if len(dirnames) == 0 and len(filenames) == 0:
try: os.removedirs(os.path.join(symbol_map_path_expanded, sub_dirpath))
except: continue
def update_symbol_map(args):
import string
directories = ['/Library/Developer/Toolchains', '~/Library/Developer/Toolchains']
symbol_map_path = '~/Library/Developer/SymbolMap/uuids'
remove_orphan_symbol_map_entries(symbol_map_path)
created = create_symbol_map_entries(directories, symbol_map_path)
cleanup_symbol_map(symbol_map_path)
if created:
defaults_write_args = ['defaults', 'write', 'com.apple.DebugSymbols', 'DBGFileMappedPaths', symbol_map_path]
print('\nYou need to enable symbol map by following command:\n```\n{}\n```'.format(string.join(defaults_write_args, ' ')) )
def source_map(args):
import lldb
remote_base_dirs = ['/Users/buildnode/jenkins/workspace/oss-swift-package-osx']
for version in args.versions:
remote_base_dirs.append('/Users/buildnode/jenkins/workspace/oss-swift-{}-package-osx'.format(version))
remote_local_subdir_map = [
('build/buildbot_osx', source_map_path),
('clang', 'clang'),
('cmark', 'cmark'),
('compiler-rt', 'compiler-rt'),
('llbuild', 'llbuild'),
('lldb', 'lldb'),
('llvm', 'llvm'),
('swift', 'swift'),
('swift-corelibs-foundation', 'swift-corelibs-foundation'),
('swift-corelibs-libdispatch', 'swift-corelibs-libdispatch'),
('swift-corelibs-xctest', 'swift-corelibs-xctest'),
('swift-integration-tests', 'swift-integration-tests'),
('swift-xcode-playground-support', 'swift-xcode-playground-support'),
('swiftpm', 'swiftpm')
]
for remote_base_dir in remote_base_dirs:
for remote_sub_dir, local_sub_dir in remote_local_subdir_map:
remote_path = os.path.join(remote_base_dir, remote_sub_dir)
local_path = os.path.join(local_base_dir, local_sub_dir)
if os.path.exists(local_path):
args.debugger.HandleCommand('settings append target.source-map {} {}'.format(remote_path, local_path))
def process_gyb(args):
import fnmatch
import subprocess
gyb = 'swift/utils/gyb'
stdlib_path = 'swift/stdlib'
output_base_path = os.path.join(source_map_path, 'swift-macosx-x86_64/stdlib')
if os.getcwd() != args.swift_source_root:
gyb = os.path.join(args.swift_source_root, gyb)
stdlib_path = os.path.join(args.swift_source_root, stdlib_path)
output_base_path = os.path.join(args.swift_source_root, output_base_path)
for sub_dirpath, dirnames, filenames in _walk(stdlib_path, topdown=False):
for filename in fnmatch.filter(filenames, '*.swift.gyb'):
for size in ['4', '8']:
gyb_path = os.path.join(stdlib_path, sub_dirpath, filename)
target_dir = os.path.join(output_base_path, sub_dirpath, size)
if not os.path.exists(target_dir): os.makedirs(target_dir)
target_path = os.path.join(target_dir, os.path.splitext(filename)[0])
print('{} -> {}'.format(gyb_path, target_path))
subprocess.call(
[gyb, '-D', 'CMAKE_SIZEOF_VOID_P={}'.format(size), '-o', target_path, gyb_path]
)
def create_options():
import argparse
import sys
parser = argparse.ArgumentParser(prog='swift_oss_helper')
subparsers = parser.add_subparsers(dest='cmd')
parser_update_symbol_map = subparsers.add_parser(
'update-symbol-map',
help ='update symbol map from paths containing `.dSYMs`')
parser_update_symbol_map.set_defaults(func=update_symbol_map)
parser_process_gyb = subparsers.add_parser(
'process-gyb',
help='process `*.swift.gyb` files in `swift/stdlib`')
parser_process_gyb.add_argument(
'-s',
default=local_base_dir,
help='(default: {})'.format(local_base_dir),
dest='swift_source_root')
parser_process_gyb.set_defaults(func=process_gyb)
if sys.modules.has_key('lldb'):
parser_source_map = subparsers.add_parser(
'source-map',
help='set `target.source-map` setting for using source files of Swift')
parser_source_map.add_argument('versions', default=[], nargs='*')
parser_source_map.set_defaults(func=source_map)
parser.set_defaults(cmd='process-gyb')
return parser
def lldbmain(debugger, command, result, internal_dict):
import shlex
parser = create_options()
args = parser.parse_args(shlex.split(command))
args.debugger=debugger
args.func(args)
def __lldb_init_module(debugger,internal_dict):
parser = create_options()
lldbmain.__doc__ = parser.format_help()
debugger.HandleCommand('command script add -f swift_oss_helper.lldbmain swift_oss_helper')
print('swift_oss_helper command enabled.')
def main():
parser = create_options()
args = parser.parse_args()
args.func(args)
if __name__ == '__main__':
main()
$ cat ~/.lldbinit
command script import ~/github/swift-dev/swift_oss_helper.py
swift_oss_helper source-map 3.0 3.1
$ lldb
swift_oss_helper command enabled.
(lldb) settings show target.source-map
target.source-map (path-map) =
[0] "/Users/buildnode/jenkins/workspace/oss-swift-package-osx/build/buildbot_osx" -> "/Users/norio/github/swift-dev/build/swift_gyb_processed"
[1] "/Users/buildnode/jenkins/workspace/oss-swift-package-osx/clang" -> "/Users/norio/github/swift-dev/clang"
[40] "/Users/buildnode/jenkins/workspace/oss-swift-3.1-package-osx/swift-xcode-playground-support" -> "/Users/norio/github/swift-dev/swift-xcode-playground-support"
[41] "/Users/buildnode/jenkins/workspace/oss-swift-3.1-package-osx/swiftpm" -> "/Users/norio/github/swift-dev/swiftpm"
(lldb) swift_oss_helper -h
usage: swift_oss_helper [-h] {source-map,process-gyb} ...
positional arguments:
{update-symbol-map,process-gyb,source-map}
update-symbol-map update symbol map from paths containing `.dSYMs`
source-map set `target.source-map` setting for using source files
of Swift
process-gyb process `*.swift.gyb` files in `swift/stdlib`
optional arguments:
-h, --help show this help message and exit
(lldb) swift_oss_helper process-gyb
swift/stdlib/private/StdlibCollectionUnittest/CheckCollectionInstance.swift.gyb -> build/swift_gyb_processed/swift-macosx-x86_64/stdlib/private/StdlibCollectionUnittest/4/CheckCollectionInstance.swift
swift/stdlib/private/StdlibCollectionUnittest/CheckCollectionInstance.swift.gyb -> build/swift_gyb_processed/swift-macosx-x86_64/stdlib/private/StdlibCollectionUnittest/8/CheckCollectionInstance.swift
swift/stdlib/public/SDK/UIKit/UIKit_FoundationExtensions.swift.gyb -> build/swift_gyb_processed/swift-macosx-x86_64/stdlib/public/SDK/UIKit/4/UIKit_FoundationExtensions.swift
swift/stdlib/public/SDK/UIKit/UIKit_FoundationExtensions.swift.gyb -> build/swift_gyb_processed/swift-macosx-x86_64/stdlib/public/SDK/UIKit/8/UIKit_FoundationExtensions.swift
(lldb) ^D
$ ~/github/swift-dev/swift_oss_helper.py update-symbol-map
create map: ~/Library/Developer/SymbolMap/uuids/6C24/2369/8EFD/3E83/B9DB/EE1B0D55C8C7 -> /Library/Developer/Toolchains/swift-3.0.2-RELEASE.xctoolchain/Developer/Platforms/AppleTVSimulator.platform/Developer/Library/Frameworks/PlaygroundSupport.framework/PlaygroundSupport.dSYM/Contents/Resources/DWARF/PlaygroundSupport
create map: ~/Library/Developer/SymbolMap/uuids/41D9/1DF7/6BE6/3BCF/BBBD/5FD323D699B4 -> /Library/Developer/Toolchains/swift-3.0.2-RELEASE.xctoolchain/Developer/Platforms/AppleTVSimulator.platform/Developer/Library/Frameworks/XCPlayground.framework/XCPlayground.dSYM/Contents/Resources/DWARF/XCPlayground
create map: ~/Library/Developer/SymbolMap/uuids/DB71/848E/F7BA/3BE3/ABAD/016AB7A87C67 -> /Library/Developer/Toolchains/swift-3.1-DEVELOPMENT-SNAPSHOT-2017-03-19-a.xctoolchain/usr/lib/libswiftDemangle.dylib.dSYM/Contents/Resources/DWARF/libswiftDemangle.dylib
create map: ~/Library/Developer/SymbolMap/uuids/C445/55A7/A5B4/3F6F/803A/54B6CC65B174 -> /Library/Developer/Toolchains/swift-3.1-DEVELOPMENT-SNAPSHOT-2017-03-19-a.xctoolchain/usr/libexec/swift/pm/swiftpm-xctest-helper.dSYM/Contents/Resources/DWARF/swiftpm-xctest-helper
You need to enable symbol map by following command:
```
defaults write com.apple.DebugSymbols DBGFileMappedPaths ~/Library/Developer/SymbolMap/uuids
```
$ defaults write com.apple.DebugSymbols DBGFileMappedPaths ~/Library/Developer/SymbolMap/uuids
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment