├── .gitignore ├── .travis.yml ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── GlibcDlfcnShim │ ├── include │ │ └── GlibcDlfcnShim.h │ └── src │ │ └── GlibcDlfcnShim.c └── Symbolic │ └── Symbolic.swift └── Tests ├── LinuxMain.swift ├── Resources ├── foo.c ├── libfoo.dylib └── libfoo.so └── SymbolicTests └── SymbolicTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - LC_CTYPE=en_US.UTF-8 4 | matrix: 5 | include: 6 | - os: osx 7 | language: objective-c 8 | osx_image: xcode9 9 | script: 10 | - swift test 11 | - os: linux 12 | language: generic 13 | sudo: required 14 | dist: trusty 15 | before_install: 16 | - sudo apt-get update -qq 17 | - sudo apt-get install -qq clang-3.5 curl git libblocksruntime0 libcurl4-openssl-dev libedit2 libicu52 libkqueue0 libpython2.7-dev libxml2 python2.7 uuid-dev 18 | - git submodule update --init --recursive 19 | - wget -q -O - https://swift.org/keys/all-keys.asc | gpg --import - 20 | - wget https://swift.org/builds/swift-4.0-release/ubuntu1404/swift-4.0-RELEASE/swift-4.0-RELEASE-ubuntu14.04.tar.gz 21 | - tar xzf swift-4.0-RELEASE-ubuntu14.04.tar.gz 22 | script: 23 | - ${PWD}/swift-4.0-RELEASE-ubuntu14.04/usr/bin/swift test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 silt-lang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Symbolic", 6 | products: [ 7 | .library( 8 | name: "Symbolic", 9 | targets: ["Symbolic"]), 10 | ], 11 | dependencies: [], 12 | targets: [ 13 | .target( 14 | name: "GlibcDlfcnShim", 15 | dependencies: []), 16 | .target( 17 | name: "Symbolic", 18 | dependencies: ["GlibcDlfcnShim"]), 19 | .testTarget( 20 | name: "SymbolicTests", 21 | dependencies: ["Symbolic"]), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symbolic 2 | 3 | Symbolic makes it easy to get information about an executable, shared object, 4 | or static library. 5 | 6 | ## Usage 7 | 8 | ### Note 9 | 10 | ⚠️ This library is incredibly unsafe. Use it with caution! ⚠️ 11 | 12 | To dynamically load a shared object file, create a `SharedObject`, passing in 13 | the file URL where the object resides. 14 | 15 | You can also use `SharedObject.current()` to get ahold of the object in which 16 | your code will reside once compiled. 17 | 18 | ```swift 19 | let libFoo = SharedObject(object: URL(fileURLWithPath: "/usr/lib/libfoo.dylib")) 20 | let myExe = SharedObject.current() 21 | ``` 22 | 23 | From there, you can ask the object for the addresses of symbols in the object 24 | and (if you're adventurous) cast function addresses to `@convention(c)` 25 | function pointers. 26 | 27 | ```swift 28 | let libcURL = URL(fileURLWithPath: "/usr/lib/libc.dylib") 29 | let libc = SharedObject(object: libcURL) 30 | 31 | let addr = libc.address(forSymbol: "sin") // will not be `nil` 32 | 33 | typealias SinFn = @convention(c) (Double) -> Double 34 | 35 | // Will perform an unsafeBitCast on your behalf! 36 | let sinFn = libc.function(forSymbol: "sin", ofType: SinFn.self) 37 | 38 | sinFn?(0.5) // 0.4794255386 39 | ``` 40 | 41 | Additionally, if you have an address in mind that you've already linked, you 42 | can ask for it directly in your current address space: 43 | 44 | ```swift 45 | let addrInfo = SymbolInfo(address: addr) 46 | addrInfo.symbolName // "sin" 47 | ``` 48 | 49 | ## Author 50 | 51 | Harlan Haskins ([@harlanhaskins](https://github.com/harlanhaskins)) 52 | 53 | ## License 54 | 55 | This project is released under the MIT license, a copy of which is avaialable 56 | in this repository. 57 | -------------------------------------------------------------------------------- /Sources/GlibcDlfcnShim/include/GlibcDlfcnShim.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The contents of this file are copied directly from /usr/include/dlfcn.h 3 | */ 4 | 5 | #ifndef SHIM_H 6 | #define SHIM_H 7 | 8 | #ifdef __linux__ 9 | /* Structure containing information about object searched using 10 | `dladdr'. */ 11 | typedef struct 12 | { 13 | const char *dli_fname; /* File name of defining object. */ 14 | void *dli_fbase; /* Load address of that object. */ 15 | const char *dli_sname; /* Name of nearest symbol. */ 16 | void *dli_saddr; /* Exact value of nearest symbol. */ 17 | } Dl_info; 18 | 19 | typedef Dl_info dl_info; 20 | 21 | /* Fill in *INFO with the following information about ADDRESS. 22 | Returns 0 iff no shared object's segments contain that address. */ 23 | int dladdr (const void *__address, Dl_info *__info); 24 | #endif // defined(__linux__) 25 | 26 | #endif // defined(SHIM_H) 27 | -------------------------------------------------------------------------------- /Sources/GlibcDlfcnShim/src/GlibcDlfcnShim.c: -------------------------------------------------------------------------------- 1 | #include "GlibcDlfcnShim.h" 2 | -------------------------------------------------------------------------------- /Sources/Symbolic/Symbolic.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(macOS) 4 | import Darwin 5 | #elseif os(Linux) 6 | import Glibc 7 | import GlibcDlfcnShim 8 | #endif 9 | 10 | enum LoadError: Error { 11 | case dlerror(String) 12 | } 13 | 14 | public class SharedObject { 15 | public enum LoadBehavior { 16 | /// Perform lazy binding. Only resolve symbols as the code that references 17 | /// them is executed. If the symbol is never referenced, then it is never 18 | /// resolved. (Lazy binding is only performed for function references; 19 | /// references to variables are always immediately bound when the library 20 | /// is loaded.) 21 | case lazy 22 | 23 | /// If this value is specified, or the environment variable LD_BIND_NOW is 24 | /// set to a nonempty string, all undefined symbols in the library are 25 | /// resolved before dlopen() returns. If this cannot be done, an error is 26 | /// returned. 27 | case now 28 | } 29 | 30 | public struct LoadFlags: OptionSet { 31 | public let rawValue: Int32 32 | 33 | /// This is the converse of `.global`, and the default if neither flag is 34 | /// specified. Symbols defined in this library are not made available to 35 | /// resolve references in subsequently loaded libraries. 36 | public static let local = LoadFlags(rawValue: RTLD_LOCAL) 37 | 38 | /// The symbols defined by this library will be made available for symbol 39 | /// resolution of subsequently loaded libraries. 40 | public static let global = LoadFlags(rawValue: RTLD_GLOBAL) 41 | 42 | /// Don't load the library. This can be used to test if the library is 43 | /// already resident (dlopen() returns NULL if it is not, or the library's 44 | /// handle if it is resident). This flag can also be used to promote the 45 | /// flags on a library that is already loaded. For example, a library that 46 | /// was previously loaded with RTLD_LOCAL can be reopened with 47 | /// `[.noLoad, .global]`. This flag is not specified in POSIX.1-2001. 48 | public static let noLoad = LoadFlags(rawValue: RTLD_NOLOAD) 49 | 50 | /// Do not unload the library during dlclose(). Consequently, the library's 51 | /// static variables are not reinitialized if the library is reloaded with 52 | /// dlopen() at a later time. This flag is not specified in POSIX.1-2001. 53 | public static let noDelete = LoadFlags(rawValue: RTLD_NODELETE) 54 | 55 | /// Initalizes a set of flags with a given raw integer value. 56 | public init(rawValue: Int32) { 57 | self.rawValue = rawValue 58 | } 59 | } 60 | 61 | /// The path to the provided object. 62 | public let path: URL 63 | 64 | /// Whether this handle is owned and must be closed on `deinit`. 65 | private let ownsHandle: Bool 66 | 67 | /// The handle opened by `dlopen`. 68 | private let handle: UnsafeRawPointer 69 | 70 | public init(path: URL, behavior: LoadBehavior = .lazy, 71 | flags: LoadFlags = [.local]) throws { 72 | var rawFlags = flags.rawValue 73 | 74 | switch behavior { 75 | case .lazy: 76 | rawFlags |= RTLD_LAZY 77 | case .now: 78 | rawFlags |= RTLD_NOW 79 | } 80 | 81 | self.path = path 82 | guard let handle = dlopen(path.path, rawFlags) else { 83 | throw LoadError.dlerror(String(cString: dlerror())) 84 | } 85 | self.handle = UnsafeRawPointer(handle) 86 | self.ownsHandle = !flags.contains(.noLoad) 87 | } 88 | 89 | /// The symbol info for this object. 90 | public var symbolInfo: SymbolInfo { 91 | return SymbolInfo(path: path, fileBaseAddress: handle) 92 | } 93 | 94 | /// Returns the SharedObject for the current object file. 95 | /// - parameter dsohandle: This is defaulted to the `#dsohandle` of the 96 | /// caller, which is what enables this method to 97 | /// reliably get access to the shared object that the 98 | /// caller resides in. You should not pass any argument 99 | /// to this method, as that will not guarantee you will 100 | /// get the shared object you actually reside in. 101 | public static func current( 102 | _ dsohandle: UnsafeRawPointer = #dsohandle) -> SharedObject { 103 | do { 104 | guard let info = SymbolInfo(address: dsohandle) else { 105 | fatalError("dladdr failed for current object") 106 | } 107 | guard let filename = info.path else { 108 | fatalError("could not get filename for current object") 109 | } 110 | return try SharedObject(path: filename) 111 | } catch let error as LoadError { 112 | switch error { 113 | case .dlerror(let dlerror): 114 | fatalError(dlerror) 115 | } 116 | } catch { 117 | fatalError("unknown error loading current object: \(error)") 118 | } 119 | } 120 | 121 | /// Unsafely the address of the provided symbol in the shared object to the 122 | /// provided type. 123 | /// - note: This is incredibly unsafe. You must pass in a type that is 124 | /// some `@convention(c)` function type. 125 | public func function(forSymbol symbol: String, ofType type: T.Type) -> T? { 126 | guard let addr = address(ofSymbol: symbol) else { return nil } 127 | 128 | // HACK: Make sure they pass in an @convention(c) function. 129 | guard "\(type)".hasPrefix("@convention(c)") else { 130 | return nil 131 | } 132 | 133 | return unsafeBitCast(addr, to: type) 134 | } 135 | 136 | public func address(ofSymbol symbol: String) -> UnsafeRawPointer? { 137 | let mut = UnsafeMutableRawPointer(mutating: handle) 138 | guard let addr = dlsym(mut, symbol) else { return nil } 139 | return UnsafeRawPointer(addr) 140 | } 141 | 142 | public static func isLoaded(_ library: URL) -> Bool { 143 | return dlopen(library.path, RTLD_LAZY | RTLD_NOLOAD) != nil 144 | } 145 | 146 | deinit { 147 | if ownsHandle { 148 | dlclose(UnsafeMutableRawPointer(mutating: handle)) 149 | } 150 | } 151 | } 152 | 153 | public struct SymbolInfo { 154 | /// The path of the shared object this symbol resides in, if present. 155 | public let path: URL? 156 | 157 | /// The base address at which the shared object is mapped into the address 158 | /// space of the calling process. 159 | public let fileBaseAddress: UnsafeRawPointer? 160 | 161 | /// The address of the symbol. 162 | public let symbolAddress: UnsafeRawPointer? 163 | 164 | /// The symbol name, if present. 165 | public let symbolName: String? 166 | 167 | /// Creates a new SymbolInfo with the provided path and file base 168 | /// address. 169 | internal init(path: URL, fileBaseAddress: UnsafeRawPointer) { 170 | self.path = path 171 | self.fileBaseAddress = fileBaseAddress 172 | self.symbolName = nil 173 | self.symbolAddress = nil 174 | } 175 | 176 | /// Gets the symbol information for the symbol with the provided address. 177 | /// - parameter address: The address of the symbol you intend to get. 178 | public init?(address: UnsafeRawPointer) { 179 | var info = Dl_info() 180 | guard dladdr(address, &info) != 0 else { return nil } 181 | self.init(info) 182 | } 183 | 184 | /// Creates a SymbolInfo from the corresponding dl_info. 185 | private init(_ dlinfo: Dl_info) { 186 | self.fileBaseAddress = UnsafeRawPointer(dlinfo.dli_fbase) 187 | self.symbolAddress = UnsafeRawPointer(dlinfo.dli_saddr) 188 | self.symbolName = (dlinfo.dli_sname as Optional).map(String.init(cString:)) 189 | let pathStr = (dlinfo.dli_fname as Optional).map(String.init(cString:)) 190 | self.path = pathStr.map(URL.init(fileURLWithPath:)) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SymbolicTests 3 | 4 | XCTMain([ 5 | testCase(SymbolicTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/Resources/foo.c: -------------------------------------------------------------------------------- 1 | int test_ret_1() { 2 | return 1; 3 | } 4 | 5 | const char *test_ret_hello() { 6 | return "hello"; 7 | } 8 | -------------------------------------------------------------------------------- /Tests/Resources/libfoo.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llvm-swift/Symbolic/076fbd317c880af5ee622633c0b1a9386e2e16bc/Tests/Resources/libfoo.dylib -------------------------------------------------------------------------------- /Tests/Resources/libfoo.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llvm-swift/Symbolic/076fbd317c880af5ee622633c0b1a9386e2e16bc/Tests/Resources/libfoo.so -------------------------------------------------------------------------------- /Tests/SymbolicTests/SymbolicTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Symbolic 3 | 4 | #if os(macOS) 5 | let testSharedLibraryExtension = "dylib" 6 | #elseif os(Linux) 7 | let testSharedLibraryExtension = "so" 8 | #endif 9 | 10 | func testLibrary(_ name: String) -> URL { 11 | let currentURL = URL(fileURLWithPath: #file) 12 | return currentURL 13 | .deletingLastPathComponent() 14 | .deletingLastPathComponent() 15 | .appendingPathComponent("Resources") 16 | .appendingPathComponent("lib\(name).\(testSharedLibraryExtension)") 17 | } 18 | 19 | @_cdecl("symbolic_test_hook") 20 | public func symbolicTestHook() -> Int32 { 21 | return 1 22 | } 23 | 24 | class SymbolicTests: XCTestCase { 25 | func testCurrentLibrary() { 26 | #if os(macOS) 27 | let currentObj = SharedObject.current() 28 | XCTAssert(currentObj.path.path.contains("Symbolic")) 29 | XCTAssertNotNil(currentObj.symbolInfo.path) 30 | if let file = currentObj.symbolInfo.path { 31 | XCTAssertEqual(currentObj.path, file) 32 | } 33 | XCTAssertNil(currentObj.symbolInfo.symbolName) 34 | XCTAssertNil(currentObj.symbolInfo.symbolAddress) 35 | 36 | typealias TestHookFn = @convention(c) () -> Int32 37 | 38 | let testHook1Fn = currentObj.function(forSymbol: "symbolic_test_hook", 39 | ofType: TestHookFn.self) 40 | XCTAssertNotNil(testHook1Fn) 41 | XCTAssertEqual(testHook1Fn?(), 1) 42 | #endif 43 | } 44 | 45 | func testLoadLibrary() { 46 | do { 47 | let obj = try SharedObject(path: testLibrary("foo")) 48 | XCTAssertNotNil(obj.address(ofSymbol: "test_ret_1")) 49 | XCTAssertNotNil(obj.address(ofSymbol: "test_ret_hello")) 50 | 51 | typealias Ret1Fn = @convention(c) () -> Int32 52 | let ret1 = obj.function(forSymbol: "test_ret_1", ofType: Ret1Fn.self) 53 | XCTAssertNotNil(ret1) 54 | XCTAssertEqual(ret1?(), 1) 55 | 56 | typealias RetHelloFn = @convention(c) () -> UnsafeMutablePointer 57 | let retHello = obj.function(forSymbol: "test_ret_hello", 58 | ofType: RetHelloFn.self) 59 | XCTAssertNotNil(retHello) 60 | if let result = retHello?() { 61 | XCTAssertEqual(String(cString: result), "hello") 62 | } 63 | } catch { 64 | XCTFail("could not load object: \(error)") 65 | } 66 | } 67 | 68 | static var allTests = [ 69 | ("testCurrentLibrary", testCurrentLibrary), 70 | ("testLoadLibrary", testLoadLibrary), 71 | ] 72 | } 73 | --------------------------------------------------------------------------------