Skip to content
This repository has been archived by the owner on Apr 2, 2020. It is now read-only.

Commit

Permalink
Merge pull request #53 from tfiala/SR-2637-v2
Browse files Browse the repository at this point in the history
support debugging statically-linked Swift binaries on macOS
  • Loading branch information
tfiala committed Sep 17, 2016
2 parents 169bfb8 + a646342 commit 88e778c
Show file tree
Hide file tree
Showing 14 changed files with 340 additions and 50 deletions.
4 changes: 3 additions & 1 deletion include/lldb/Symbol/SwiftASTContext.h
Expand Up @@ -822,6 +822,8 @@ class SwiftASTContext : public TypeSystem {

bool TargetHasNoSDK();

std::vector<lldb::DataBufferSP> &GetASTVectorForModule(const Module *module);

std::unique_ptr<swift::SourceManager> m_source_manager_ap;
std::unique_ptr<swift::DiagnosticEngine> m_diagnostic_engine_ap;
std::unique_ptr<swift::ASTContext> m_ast_context_ap;
Expand Down Expand Up @@ -850,7 +852,7 @@ class SwiftASTContext : public TypeSystem {
// target's process pointer be filled in
std::string m_platform_sdk_path;
std::string m_resource_dir;
typedef std::map<Module *, lldb::DataBufferSP> ASTFileDataMap;
typedef std::map<Module *, std::vector<lldb::DataBufferSP>> ASTFileDataMap;
ASTFileDataMap m_ast_file_data_map;
/// FIXME: this vector is needed because the LLDBNameLookup debugger clients
/// are being put into
Expand Down
29 changes: 21 additions & 8 deletions include/lldb/Symbol/SymbolFile.h
Expand Up @@ -10,6 +10,8 @@
#ifndef liblldb_SymbolFile_h_
#define liblldb_SymbolFile_h_

#include <vector>

#include "lldb/Core/PluginInterface.h"
#include "lldb/Symbol/CompilerDecl.h"
#include "lldb/Symbol/CompilerDeclContext.h"
Expand Down Expand Up @@ -223,15 +225,26 @@ class SymbolFile : public PluginInterface {
virtual bool ForceInlineSourceFileCheck();

//------------------------------------------------------------------
// Symbol files can store AST data for any language that wants to
// store the native AST format supported by the current compiler.
// This information is often only usable by a compiler that is in
// sync with the compiler sources that were used to build LLDB so
// any data should be versioned appropriately so the compiler can
// try to load the data and know if the data will be able to be
// used.
/// Retrieve all the AST data blobs from the SymbolFile.
///
/// Symbol files can store AST data for any language that wants to
/// store the native AST format supported by the current compiler.
/// This information is often only usable by a compiler that is in
/// sync with the compiler sources that were used to build LLDB so
/// any data should be versioned appropriately so the compiler can
/// try to load the data and know if the data will be able to be
/// used.
///
/// @param[in] language
/// The language for which AST data is being requested.
/// A given file can contain ASTs for more than one language.
///
/// @return
/// Zero or more buffers, each of which contain the raw data
/// of an AST in the requested language.
//------------------------------------------------------------------
virtual lldb::DataBufferSP GetASTData(lldb::LanguageType language);
virtual std::vector<lldb::DataBufferSP>
GetASTData(lldb::LanguageType language);

// Used for the REPL to limit source file ranges that are valid within "file".
// Since
Expand Down
3 changes: 2 additions & 1 deletion include/lldb/Symbol/SymbolVendor.h
Expand Up @@ -163,7 +163,8 @@ class SymbolVendor : public ModuleChild, public PluginInterface {
virtual bool SymbolContextShouldBeExcluded(const SymbolContext &sc,
uint32_t actual_line);

virtual lldb::DataBufferSP GetASTData(lldb::LanguageType language);
virtual std::vector<lldb::DataBufferSP>
GetASTData(lldb::LanguageType language);

virtual bool ForceInlineSourceFileCheck();

Expand Down
@@ -0,0 +1,7 @@
import Foundation

@objc public class A: NSObject {
public func foo() -> Int {
return 4 // Set breakpoint here
}
}
@@ -0,0 +1,7 @@
import Foundation

@objc public class B: NSObject {
public func bar() -> Int {
return 8 // Set breakpoint here
}
}
@@ -0,0 +1,29 @@
# Build swift modules with debug info

LEVEL=../../../../make

# Don't use 'all' target. There is a default build rule that will kick in that
# will be wrong. WE use 'first' so that the normal 'make' command (without
# a target) selects the first (but not 'all') target so we avoid the undesired
# default behavior.
first: main

include $(LEVEL)/Makefile.rules

# To use the path commented out below, which is what we'd really want to do,
# we'd also need to require that the Swift standard library be built along
# with the compiler. I'd like to avoid that requirement.
# SWIFT_LIB_DIR=$(dir $(SWIFTCC))../lib
SWIFT_LIB_DIR="$(shell xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx"

main: A.o B.o objc_main.m
$(CC) objc_main.m -fobjc-arc -o main A.o B.o -L$(SWIFT_LIB_DIR) -Xlinker -add_ast_path -Xlinker A.swiftmodule -Xlinker -add_ast_path -Xlinker B.swiftmodule -Xlinker -rpath -Xlinker $(SWIFT_LIB_DIR)

A.o: A.swift
$(SWIFTCC) -c -g -Onone -sdk "$(SWIFTSDKROOT)" -parse-as-library -module-name A -emit-module-path A.swiftmodule -emit-objc-header-path A-Swift.h -output-file-map output_map A.swift

B.o: B.swift
$(SWIFTCC) -c -g -Onone -sdk "$(SWIFTSDKROOT)" -parse-as-library -module-name B -emit-module-path B.swiftmodule -emit-objc-header-path B-Swift.h -output-file-map output_map B.swift -o B.o

clean::
rm -f *.o main *-Swift.h *.swiftmodule *.swiftdoc
@@ -0,0 +1,114 @@
# TestSwiftStaticLinkingMacOS.py
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
#
# ------------------------------------------------------------------------------
"""
Test that macOS can statically link two separately-compiled Swift modules
with one Objective-C module, link them through the clang driver, and still
access debug info for each of the Swift modules.
"""
from __future__ import print_function


# System imports
import os
import commands

# Third-party imports

# LLDB imports
import lldb
from lldbsuite.test.lldbtest import TestBase
from lldbsuite.test import decorators, lldbtest, lldbutil


class SwiftStaticLinkingMacOSTestCase(TestBase):

mydir = TestBase.compute_mydir(__file__)

NO_DEBUG_INFO_TESTCASE = True

def expect_self_var_available_at_breakpoint(
self, process, breakpoint, module_name):
# Frame #0 should be at the given breakpoint
threads = lldbutil.get_threads_stopped_at_breakpoint(
process, breakpoint)

self.assertEquals(1, len(threads))
self.thread = threads[0]
self.frame = self.thread.frames[0]
self.assertTrue(self.frame, "Frame 0 is valid.")

patterns = [
# Ensure we report a self with an address.
r"self\s*=\s*0x[0-9a-fA-F]+",
# Ensure we think it is an NSObject.
r"ObjectiveC.NSObject"]
substrs = [
"(%s.%s)" % (module_name, module_name)
]
self.expect("frame variable self", patterns=patterns,
substrs=substrs)

@decorators.skipUnlessDarwin
def test_variables_print_from_both_swift_modules(self):
"""Test that variables from two modules can be accessed."""
self.build()

# I could not find a reasonable way to say "skipUnless(archs=[])".
# That would probably be worth adding.
if self.getArchitecture() != 'x86_64':
self.skipTest("This test requires x86_64 as the architecture "
"for the inferior")

exe_name = "main"
src_a = lldb.SBFileSpec("A.swift")
line_a = 5
src_b = lldb.SBFileSpec("B.swift")
line_b = 5
exe = os.path.join(os.getcwd(), exe_name)

# Create the target
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, lldbtest.VALID_TARGET)

# Set the breakpoints
# breakpoint_a = target.BreakpointCreateBySourceRegex(
# 'Set breakpoint here', src_a)
breakpoint_a = target.BreakpointCreateByLocation(
src_a, line_a)
self.assertTrue(breakpoint_a.GetNumLocations() > 0,
lldbtest.VALID_BREAKPOINT)

# breakpoint_b = target.BreakpointCreateBySourceRegex(
# 'Set breakpoint here', src_b)
breakpoint_b = target.BreakpointCreateByLocation(
src_b, line_b)
self.assertTrue(breakpoint_b.GetNumLocations() > 0,
lldbtest.VALID_BREAKPOINT)

# Launch the process, and do not stop at the entry point.
envp = ['DYLD_FRAMEWORK_PATH=.']
process = target.LaunchSimple(None, envp, os.getcwd())

self.assertTrue(process, lldbtest.PROCESS_IS_VALID)

# We should be at breakpoint in module A.
self.expect_self_var_available_at_breakpoint(
process, breakpoint_a, "A")

# Jump to the next breakpoint
process.Continue()

# We should be at breakpoint in module B.
self.expect_self_var_available_at_breakpoint(
process, breakpoint_b, "B")

return
@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>

#import "A-Swift.h"
#import "B-Swift.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
NSLog(@"A = %ld", [[[A alloc] init] foo]);
NSLog(@"B = %ld", [[[B alloc] init] bar]);
}
return 0;
}
@@ -0,0 +1,3 @@
{'A.swift': {'object': 'A.o'},
'B.swift': {'object': 'B.o'},
}
73 changes: 59 additions & 14 deletions source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
Expand Up @@ -1441,20 +1441,65 @@ SymbolFileDWARFDebugMap::AddOSOARanges(SymbolFileDWARF *dwarf2Data,
return num_line_entries_added;
}

DataBufferSP SymbolFileDWARFDebugMap::GetASTData(lldb::LanguageType language) {
if (language == eLanguageTypeSwift) {
Symtab *symtab = m_obj_file->GetSymtab();
if (symtab) {
uint32_t start_idx = 0;
Symbol *symbol =
symtab->FindSymbolWithType(eSymbolTypeASTFile, Symtab::eDebugAny,
Symtab::eVisibilityAny, start_idx);
if (symbol) {
FileSpec file_spec(symbol->GetName().GetCString(), false);
if (file_spec.Exists())
return file_spec.ReadFileContents();
std::vector<DataBufferSP>
SymbolFileDWARFDebugMap::GetASTData(lldb::LanguageType language) {
Log *log(LogChannelDWARF::GetLogIfAll(DWARF_LOG_DEBUG_MAP));

std::vector<DataBufferSP> ast_datas;
if (language != eLanguageTypeSwift) {
if (log)
log->Printf("SymbolFileDWARFDebugMap::%s() - ignoring because not Swift",
__FUNCTION__);
return ast_datas;
}

Symtab *symtab = m_obj_file->GetSymtab();
if (!symtab) {
if (log)
log->Printf("SymbolFileDWARFDebugMap::%s() - ignoring because the obj "
"file has no symbol table",
__FUNCTION__);
return ast_datas;
}

uint32_t next_idx = 0;
bool done = false;
do {
Symbol *symbol =
symtab->FindSymbolWithType(eSymbolTypeASTFile, Symtab::eDebugAny,
Symtab::eVisibilityAny, next_idx);
if (symbol == nullptr) {
// We didn't find any more symbols of type eSymbolTypeASTFile. We are
// done looping for them.
done = true;
} else {
// Try to load the specified file.
FileSpec file_spec(symbol->GetName().GetCString(), false);
if (file_spec.Exists()) {
// We found the source data for the AST data blob.
// Read it in and add it to our return vector.
ast_datas.push_back(file_spec.ReadFileContents());
if (log)
log->Printf("SymbolFileDWARFDebugMap::%s() - found and loaded AST "
"data from file %s",
__FUNCTION__, file_spec.GetPath().c_str());
} else {
if (log)
log->Printf("SymbolFileDWARFDebugMap::%s() - found reference to AST "
"file %s, but could not find the file, ignoring",
__FUNCTION__, file_spec.GetPath().c_str());
}

// Regardless of whether we could find the specified file, start the next
// symbol search at the index past the one we just found.
++next_idx;
}
}
return DataBufferSP();
} while (!done);

// Return the vector of AST data blobs
if (log)
log->Printf("SymbolFileDWARFDebugMap::%s() - returning %d AST data blobs",
__FUNCTION__, (int)ast_datas.size());

return ast_datas;
}
3 changes: 2 additions & 1 deletion source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
Expand Up @@ -122,7 +122,8 @@ class SymbolFileDWARFDebugMap : public lldb_private::SymbolFile {
uint32_t type_mask,
lldb_private::TypeList &type_list) override;

lldb::DataBufferSP GetASTData(lldb::LanguageType language) override;
std::vector<lldb::DataBufferSP>
GetASTData(lldb::LanguageType language) override;

//------------------------------------------------------------------
// PluginInterface protocol
Expand Down

0 comments on commit 88e778c

Please sign in to comment.