Skip to content

Commit 2ee5d15

Browse files
committed
[linux] swift-inspect support for Linux
1 parent 9e7fa1a commit 2ee5d15

File tree

19 files changed

+1129
-0
lines changed

19 files changed

+1129
-0
lines changed

tools/swift-inspect/Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,18 @@ let package = Package(
1919
.product(name: "ArgumentParser", package: "swift-argument-parser"),
2020
.target(name: "SwiftInspectClient", condition: .when(platforms: [.windows])),
2121
.target(name: "SwiftInspectClientInterface", condition: .when(platforms: [.windows])),
22+
.target(name: "SwiftInspectLinux", condition: .when(platforms: [.linux])),
2223
],
2324
swiftSettings: [.unsafeFlags(["-parse-as-library"])]),
2425
.target(name: "SwiftInspectClient"),
26+
.target(
27+
name: "SwiftInspectLinux",
28+
dependencies: ["LinuxSystemHeaders"],
29+
path: "Sources/SwiftInspectLinux",
30+
exclude: ["SystemHeaders"]),
31+
.systemLibrary(
32+
name: "LinuxSystemHeaders",
33+
path: "Sources/SwiftInspectLinux/SystemHeaders"),
2534
.systemLibrary(
2635
name: "SwiftInspectClientInterface"),
2736
.testTarget(
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import LinuxSystemHeaders
15+
16+
internal class AuxVec {
17+
enum Error: Swift.Error { case FileReadFailure(_ filePath: String) }
18+
19+
// enum values must match the constants defined in usr/include/linux/auxv.h
20+
enum Tag: UInt64 {
21+
case AT_NULL = 0
22+
case AT_IGNORE = 1
23+
case AT_EXECFD = 2
24+
case AT_PHDR = 3
25+
case AT_PHENT = 4
26+
case AT_PHNUM = 5
27+
case AT_PAGESZ = 6
28+
case AT_BASE = 7
29+
case AT_FLAGS = 8
30+
case AT_ENTRY = 9
31+
case AT_NOTELF = 10
32+
case AT_UID = 11
33+
case AT_EUID = 12
34+
case AT_GID = 13
35+
case AT_EGID = 14
36+
case AT_PLATFORM = 15
37+
case AT_HWCAP = 16
38+
case AT_CLKTCK = 17
39+
case AT_SECURE = 23
40+
case AT_BASE_PLATFORM = 24
41+
case AT_RANDOM = 25
42+
case AT_HWCAP2 = 26
43+
case AT_RSEQ_FEATURE_SIZE = 27
44+
case AT_RSEQ_ALIGN = 28
45+
case AT_EXECFN = 31
46+
case AT_SYSINFO_EHDR = 33
47+
case AT_MINSIGSTKSZ = 51
48+
}
49+
50+
static func load(for process: Process) throws -> [Tag: UInt64] {
51+
let filePath = "/proc/\(process.pid)/auxv"
52+
53+
let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: filePath))
54+
defer { fileHandle.closeFile() }
55+
56+
guard let data = try fileHandle.readToEnd() else { throw Error.FileReadFailure(filePath) }
57+
58+
// aux vector is an array of 8-byte pairs in a 64-bit process
59+
assert(process.elfFile.isElf64, "only 64-bit processes are supported")
60+
let auxVec: [(UInt64, UInt64)] = data.withUnsafeBytes {
61+
let count = $0.count / MemoryLayout<(UInt64, UInt64)>.stride
62+
return Array($0.bindMemory(to: (UInt64, UInt64).self)[..<count])
63+
}
64+
65+
var entries: [Tag: UInt64] = [:]
66+
for (rawTag, value) in auxVec {
67+
guard let tag = Tag(rawValue: rawTag) else { continue }
68+
entries[tag] = value
69+
}
70+
71+
return entries
72+
}
73+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import LinuxSystemHeaders
15+
16+
protocol ElfDyn {
17+
static var symbolSize: Int { get }
18+
var tag: Int64 { get }
19+
var val: UInt64 { get }
20+
}
21+
22+
extension Elf64_Dyn: ElfDyn {
23+
static var symbolSize: Int { MemoryLayout<Elf64_Dyn>.size }
24+
var tag: Int64 { self.d_tag }
25+
var val: UInt64 { self.d_un.d_val }
26+
}
27+
28+
extension Elf32_Dyn: ElfDyn {
29+
static var symbolSize: Int { MemoryLayout<Elf32_Dyn>.size }
30+
var tag: Int64 { Int64(self.d_tag) }
31+
var val: UInt64 { UInt64(self.d_un.d_val) }
32+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import LinuxSystemHeaders
15+
16+
internal protocol ElfEhdr {
17+
static var symbolSize: Int { get }
18+
var ident: [UInt8] { get }
19+
var type: UInt16 { get }
20+
var machine: UInt16 { get }
21+
var version: UInt32 { get }
22+
var entry: UInt64 { get }
23+
var phoff: UInt64 { get }
24+
var shoff: UInt64 { get }
25+
var flags: UInt32 { get }
26+
var ehsize: UInt16 { get }
27+
var phentsize: UInt16 { get }
28+
var phnum: UInt16 { get }
29+
var shentsize: UInt16 { get }
30+
var shnum: UInt16 { get }
31+
var shstrndx: UInt16 { get }
32+
}
33+
34+
extension Elf64_Ehdr: ElfEhdr {
35+
static var symbolSize: Int { MemoryLayout<Elf64_Ehdr>.size }
36+
var ident: [UInt8] { return withUnsafeBytes(of: self.e_ident) { Array($0) } }
37+
var type: UInt16 { self.e_type }
38+
var machine: UInt16 { self.e_machine }
39+
var version: UInt32 { self.e_version }
40+
var entry: UInt64 { self.e_entry }
41+
var phoff: UInt64 { self.e_phoff }
42+
var shoff: UInt64 { self.e_shoff }
43+
var flags: UInt32 { self.e_flags }
44+
var ehsize: UInt16 { self.e_ehsize }
45+
var phentsize: UInt16 { self.e_phentsize }
46+
var phnum: UInt16 { self.e_phnum }
47+
var shentsize: UInt16 { self.e_shentsize }
48+
var shnum: UInt16 { self.e_shnum }
49+
var shstrndx: UInt16 { self.e_shstrndx }
50+
}
51+
52+
extension Elf32_Ehdr: ElfEhdr {
53+
static var symbolSize: Int { MemoryLayout<Elf32_Ehdr>.size }
54+
var ident: [UInt8] { return withUnsafeBytes(of: self.e_ident) { Array($0) } }
55+
var type: UInt16 { self.e_type }
56+
var machine: UInt16 { self.e_machine }
57+
var version: UInt32 { self.e_version }
58+
var entry: UInt64 { UInt64(self.e_entry) }
59+
var phoff: UInt64 { UInt64(self.e_phoff) }
60+
var shoff: UInt64 { UInt64(self.e_shoff) }
61+
var flags: UInt32 { self.e_flags }
62+
var ehsize: UInt16 { self.e_ehsize }
63+
var phentsize: UInt16 { self.e_phentsize }
64+
var phnum: UInt16 { self.e_phnum }
65+
var shentsize: UInt16 { self.e_shentsize }
66+
var shnum: UInt16 { self.e_shnum }
67+
var shstrndx: UInt16 { self.e_shstrndx }
68+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import LinuxSystemHeaders
15+
16+
class ElfFile {
17+
public enum Error: Swift.Error {
18+
case FileOpenFailure(_ filePath: String)
19+
case FileReadFailure(_ filePath: String, offset: UInt64, size: UInt64)
20+
case FileNotElfFormat(_ filePath: String)
21+
case MalformedElfFile(_ filePath: String, description: String = "")
22+
}
23+
24+
public typealias SymbolMap = [String: (start: UInt64, end: UInt64)]
25+
26+
let filePath: String
27+
let file: FileHandle
28+
let ehdr: ElfEhdr
29+
let isElf64: Bool
30+
31+
public init(filePath: String) throws {
32+
self.filePath = filePath
33+
34+
guard let file = try? FileHandle(forReadingFrom: URL(fileURLWithPath: filePath)) else {
35+
throw Error.FileOpenFailure(filePath)
36+
}
37+
self.file = file
38+
39+
let identLen = Int(EI_NIDENT)
40+
file.seek(toFileOffset: 0)
41+
guard let identData = try file.read(upToCount: identLen), identData.count == identLen else {
42+
file.closeFile()
43+
throw Error.FileReadFailure(filePath, offset: 0, size: UInt64(identLen))
44+
}
45+
46+
let identMagic = String(bytes: identData.prefix(Int(SELFMAG)), encoding: .utf8)
47+
guard identMagic == ELFMAG else {
48+
file.closeFile()
49+
throw Error.FileNotElfFormat(filePath)
50+
}
51+
52+
let identClass = identData[Int(EI_CLASS)]
53+
let isElf64 = identClass == ELFCLASS64
54+
guard isElf64 || identClass == ELFCLASS32 else {
55+
file.closeFile()
56+
throw Error.MalformedElfFile(filePath, description: "unsupported ELFCLASS: \(identClass)")
57+
}
58+
self.isElf64 = isElf64
59+
60+
let ehdrSize = isElf64 ? Elf64_Ehdr.symbolSize : Elf32_Ehdr.symbolSize
61+
file.seek(toFileOffset: 0)
62+
guard let ehdrData = try file.read(upToCount: ehdrSize), ehdrData.count == ehdrSize else {
63+
file.closeFile()
64+
throw Error.FileReadFailure(filePath, offset: 0, size: UInt64(ehdrSize))
65+
}
66+
67+
if isElf64 {
68+
self.ehdr = ehdrData.withUnsafeBytes { $0.load(as: Elf64_Ehdr.self) as ElfEhdr }
69+
} else {
70+
self.ehdr = ehdrData.withUnsafeBytes { $0.load(as: Elf32_Ehdr.self) as ElfEhdr }
71+
}
72+
}
73+
74+
deinit { file.closeFile() }
75+
76+
// returns a map of symbol names to their offset range in file (+ baseAddress)
77+
public func loadSymbols(baseAddress: UInt64 = 0) throws -> SymbolMap {
78+
guard let sectionCount = UInt(exactly: self.ehdr.shnum) else {
79+
throw Error.MalformedElfFile(
80+
self.filePath, description: "invalid ehdr.shnum: \(self.ehdr.shnum)")
81+
}
82+
83+
var symbols: SymbolMap = [:]
84+
for sectionIndex in 0..<sectionCount {
85+
let shdr: ElfShdr =
86+
isElf64
87+
? try self.readShdr(index: sectionIndex) as Elf64_Shdr
88+
: try self.readShdr(index: sectionIndex) as Elf32_Shdr
89+
90+
guard shdr.type == SHT_SYMTAB || shdr.type == SHT_DYNSYM else { continue }
91+
92+
let sectionData: Data = try self.readSection(shdr: shdr)
93+
let symTable: [ElfSym] =
94+
self.isElf64
95+
? sectionData.withUnsafeBytes { Array($0.bindMemory(to: Elf64_Sym.self)) }
96+
: sectionData.withUnsafeBytes { Array($0.bindMemory(to: Elf32_Sym.self)) }
97+
98+
guard shdr.entsize == (self.isElf64 ? Elf64_Sym.symbolSize : Elf32_Sym.symbolSize) else {
99+
throw Error.MalformedElfFile(self.filePath, description: "invalid ElfShdr.sh_entsize")
100+
}
101+
102+
// the link field in the section header for a symbol table section refers
103+
// to the index of the string table section containing the symbol names
104+
guard let linkIndex = UInt(exactly: shdr.link) else {
105+
throw Error.MalformedElfFile(
106+
self.filePath, description: "invalid ElfShdr.sh_link: \(shdr.link)")
107+
}
108+
109+
let shdrLink: ElfShdr =
110+
isElf64
111+
? try self.readShdr(index: UInt(linkIndex)) as Elf64_Shdr
112+
: try self.readShdr(index: UInt(linkIndex)) as Elf32_Shdr
113+
114+
guard shdrLink.type == SHT_STRTAB else {
115+
throw Error.MalformedElfFile(self.filePath, description: "linked section not SHT_STRTAB")
116+
}
117+
118+
// load the entire contents of the string table into memory
119+
let strTable: Data = try self.readSection(shdr: shdrLink)
120+
121+
let symCount = Int(shdr.size / shdr.entsize)
122+
for symIndex in 0..<symCount {
123+
let sym = symTable[symIndex]
124+
guard sym.shndx != SHN_UNDEF, sym.value != 0, sym.size != 0 else { continue }
125+
126+
// sym.name is a byte offset into the string table
127+
guard let strStart = Int(exactly: sym.name), strStart < strTable.count else {
128+
throw Error.MalformedElfFile(
129+
self.filePath, description: "invalid string table offset: \(sym.name)")
130+
}
131+
132+
guard let strEnd = strTable[strStart...].firstIndex(of: 0),
133+
let symName = String(data: strTable[strStart..<strEnd], encoding: .utf8)
134+
else {
135+
throw Error.MalformedElfFile(
136+
self.filePath, description: "invalid string @ offset \(strStart)")
137+
}
138+
139+
// rebase the symbol value on the base address provided by the caller
140+
let symStart = sym.value + baseAddress
141+
symbols[symName] = (start: symStart, end: symStart + sym.size)
142+
}
143+
}
144+
145+
return symbols
146+
}
147+
148+
// reads and returns the Elf32_Shdr or Elf64_Shdr at the specified index
149+
internal func readShdr<T: ElfShdr>(index: UInt) throws -> T {
150+
guard index < self.ehdr.shnum else {
151+
throw Error.MalformedElfFile(
152+
self.filePath, description: "section index \(index) >= ElfEhdr.e_shnum \(self.ehdr.shnum))")
153+
}
154+
155+
let shdrSize = T.symbolSize
156+
guard shdrSize == self.ehdr.shentsize else {
157+
throw Error.MalformedElfFile(self.filePath, description: "ElfEhdr.e_shentsize != \(shdrSize)")
158+
}
159+
160+
let shdrOffset: UInt64 = self.ehdr.shoff + UInt64(index) * UInt64(shdrSize)
161+
self.file.seek(toFileOffset: shdrOffset)
162+
guard let shdrData = try self.file.read(upToCount: shdrSize), shdrData.count == shdrSize else {
163+
throw Error.FileReadFailure(self.filePath, offset: shdrOffset, size: UInt64(shdrSize))
164+
}
165+
166+
return shdrData.withUnsafeBytes { $0.load(as: T.self) as T }
167+
}
168+
169+
// reads and returns all data in the specified section
170+
internal func readSection(shdr: ElfShdr) throws -> Data {
171+
guard let sectionSize = Int(exactly: shdr.size) else {
172+
throw Error.MalformedElfFile(
173+
self.filePath, description: "ElfShdr.sh_size too large \(shdr.size)")
174+
}
175+
176+
let fileOffset = shdr.offset
177+
self.file.seek(toFileOffset: fileOffset)
178+
guard let data = try self.file.read(upToCount: sectionSize), data.count == sectionSize else {
179+
throw Error.FileReadFailure(self.filePath, offset: fileOffset, size: UInt64(sectionSize))
180+
}
181+
182+
return data
183+
}
184+
}

0 commit comments

Comments
 (0)