|
13 | 13 | import Foundation
|
14 | 14 | import LinuxSystemHeaders
|
15 | 15 |
|
| 16 | +// TODO: replace this implementation with general purpose ELF parsing support |
| 17 | +// currently private to swift/stdlib/public/Backtrace. |
16 | 18 | 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 = "") |
| 19 | + public enum ELFError: Error { |
| 20 | + case readFailure(_ filePath: String, offset: UInt64, size: UInt64) |
| 21 | + case notELF64(_ filePath: String, _ description: String = "") |
| 22 | + case malformedFile(_ filePath: String, _ description: String = "") |
22 | 23 | }
|
23 | 24 |
|
24 | 25 | public typealias SymbolMap = [String: (start: UInt64, end: UInt64)]
|
25 | 26 |
|
26 | 27 | let filePath: String
|
27 | 28 | let file: FileHandle
|
28 |
| - let ehdr: ElfEhdr |
29 |
| - let isElf64: Bool |
| 29 | + let ehdr: Elf64_Ehdr |
30 | 30 |
|
31 | 31 | public init(filePath: String) throws {
|
32 | 32 | self.filePath = filePath
|
33 | 33 |
|
34 |
| - guard let file = try? FileHandle(forReadingFrom: URL(fileURLWithPath: filePath)) else { |
35 |
| - throw Error.FileOpenFailure(filePath) |
36 |
| - } |
| 34 | + let file = try FileHandle(forReadingFrom: URL(fileURLWithPath: filePath)) |
37 | 35 | self.file = file
|
38 | 36 |
|
39 | 37 | let identLen = Int(EI_NIDENT)
|
40 | 38 | file.seek(toFileOffset: 0)
|
41 | 39 | guard let identData = try file.read(upToCount: identLen), identData.count == identLen else {
|
42 | 40 | file.closeFile()
|
43 |
| - throw Error.FileReadFailure(filePath, offset: 0, size: UInt64(identLen)) |
| 41 | + throw ELFError.readFailure(filePath, offset: 0, size: UInt64(identLen)) |
44 | 42 | }
|
45 | 43 |
|
46 | 44 | let identMagic = String(bytes: identData.prefix(Int(SELFMAG)), encoding: .utf8)
|
47 | 45 | guard identMagic == ELFMAG else {
|
48 | 46 | file.closeFile()
|
49 |
| - throw Error.FileNotElfFormat(filePath) |
| 47 | + throw ELFError.notELF64(filePath) |
50 | 48 | }
|
51 | 49 |
|
52 | 50 | 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)") |
| 51 | + guard identClass == ELFCLASS64 else { |
| 52 | + throw ELFError.notELF64(filePath, "\(identClass) != ELFCLASS64") |
57 | 53 | }
|
58 |
| - self.isElf64 = isElf64 |
59 | 54 |
|
60 |
| - let ehdrSize = isElf64 ? Elf64_Ehdr.symbolSize : Elf32_Ehdr.symbolSize |
| 55 | + let ehdrSize = MemoryLayout<Elf64_Ehdr>.size |
61 | 56 | file.seek(toFileOffset: 0)
|
62 | 57 | guard let ehdrData = try file.read(upToCount: ehdrSize), ehdrData.count == ehdrSize else {
|
63 | 58 | file.closeFile()
|
64 |
| - throw Error.FileReadFailure(filePath, offset: 0, size: UInt64(ehdrSize)) |
| 59 | + throw ELFError.readFailure(filePath, offset: 0, size: UInt64(ehdrSize)) |
65 | 60 | }
|
66 | 61 |
|
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 |
| - } |
| 62 | + self.ehdr = ehdrData.withUnsafeBytes { $0.load(as: Elf64_Ehdr.self) } |
72 | 63 | }
|
73 | 64 |
|
74 |
| - deinit { file.closeFile() } |
| 65 | + deinit { self.file.closeFile() } |
75 | 66 |
|
76 | 67 | // returns a map of symbol names to their offset range in file (+ baseAddress)
|
77 | 68 | 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 ElfEhdr.e_shnum: \(self.ehdr.shnum)") |
| 69 | + guard let sectionCount = UInt(exactly: self.ehdr.e_shnum) else { |
| 70 | + throw ELFError.malformedFile( |
| 71 | + self.filePath, "invalid Elf64_Ehdr.e_shnum: \(self.ehdr.e_shnum)") |
81 | 72 | }
|
82 | 73 |
|
83 | 74 | var symbols: SymbolMap = [:]
|
84 | 75 | 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 |
| 76 | + let shdr: Elf64_Shdr = try self.readShdr(index: sectionIndex) |
| 77 | + guard shdr.sh_type == SHT_SYMTAB || shdr.sh_type == SHT_DYNSYM else { continue } |
89 | 78 |
|
90 |
| - guard shdr.type == SHT_SYMTAB || shdr.type == SHT_DYNSYM else { continue } |
| 79 | + let sectionData: Data = try self.readSection(shdr) |
| 80 | + let symTable: [Elf64_Sym] = sectionData.withUnsafeBytes { Array($0.bindMemory(to: Elf64_Sym.self)) } |
91 | 81 |
|
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") |
| 82 | + guard shdr.sh_entsize == MemoryLayout<Elf64_Sym>.size else { |
| 83 | + throw ELFError.malformedFile(self.filePath, "invalid Elf64_Shdr.sh_entsize") |
100 | 84 | }
|
101 | 85 |
|
102 | 86 | // the link field in the section header for a symbol table section refers
|
103 | 87 | // 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)") |
| 88 | + guard let linkIndex = UInt(exactly: shdr.sh_link) else { |
| 89 | + throw ELFError.malformedFile(self.filePath, "invalid Elf64_Shdr.sh_link: \(shdr.sh_link)") |
107 | 90 | }
|
108 | 91 |
|
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") |
| 92 | + let shdrLink: Elf64_Shdr = try self.readShdr(index: UInt(linkIndex)) |
| 93 | + guard shdrLink.sh_type == SHT_STRTAB else { |
| 94 | + throw ELFError.malformedFile(self.filePath, "linked section not SHT_STRTAB") |
116 | 95 | }
|
117 | 96 |
|
118 | 97 | // load the entire contents of the string table into memory
|
119 |
| - let strTable: Data = try self.readSection(shdr: shdrLink) |
| 98 | + let strTable: Data = try self.readSection(shdrLink) |
120 | 99 |
|
121 |
| - let symCount = Int(shdr.size / shdr.entsize) |
| 100 | + let symCount = Int(shdr.sh_size / shdr.sh_entsize) |
122 | 101 | for symIndex in 0..<symCount {
|
123 | 102 | let sym = symTable[symIndex]
|
124 |
| - guard sym.shndx != SHN_UNDEF, sym.value != 0, sym.size != 0 else { continue } |
| 103 | + guard sym.st_shndx != SHN_UNDEF, sym.st_value != 0, sym.st_size != 0 else { continue } |
125 | 104 |
|
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)") |
| 105 | + // sym.st_name is a byte offset into the string table |
| 106 | + guard let strStart = Int(exactly: sym.st_name), strStart < strTable.count else { |
| 107 | + throw ELFError.malformedFile(self.filePath, "invalid string table offset: \(sym.st_name)") |
130 | 108 | }
|
131 | 109 |
|
132 | 110 | guard let strEnd = strTable[strStart...].firstIndex(of: 0),
|
133 | 111 | let symName = String(data: strTable[strStart..<strEnd], encoding: .utf8)
|
134 | 112 | else {
|
135 |
| - throw Error.MalformedElfFile( |
136 |
| - self.filePath, description: "invalid string @ offset \(strStart)") |
| 113 | + throw ELFError.malformedFile(self.filePath, "invalid string @ offset \(strStart)") |
137 | 114 | }
|
138 | 115 |
|
139 | 116 | // 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) |
| 117 | + let symStart = sym.st_value + baseAddress |
| 118 | + symbols[symName] = (start: symStart, end: symStart + sym.st_size) |
142 | 119 | }
|
143 | 120 | }
|
144 | 121 |
|
145 | 122 | return symbols
|
146 | 123 | }
|
147 | 124 |
|
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))") |
| 125 | + // reads and returns the Elf64_Shdr at the specified index |
| 126 | + internal func readShdr(index: UInt) throws -> Elf64_Shdr { |
| 127 | + guard index < self.ehdr.e_shnum else { |
| 128 | + throw ELFError.malformedFile( |
| 129 | + self.filePath, "section index \(index) >= Elf64_Ehdr.e_shnum \(self.ehdr.e_shnum))") |
153 | 130 | }
|
154 | 131 |
|
155 |
| - let shdrSize = T.symbolSize |
156 |
| - guard shdrSize == self.ehdr.shentsize else { |
157 |
| - throw Error.MalformedElfFile(self.filePath, description: "ElfEhdr.e_shentsize != \(shdrSize)") |
| 132 | + let shdrSize = MemoryLayout<Elf64_Shdr>.size |
| 133 | + guard shdrSize == self.ehdr.e_shentsize else { |
| 134 | + throw ELFError.malformedFile(self.filePath, "Elf64_Ehdr.e_shentsize != \(shdrSize)") |
158 | 135 | }
|
159 | 136 |
|
160 |
| - let shdrOffset: UInt64 = self.ehdr.shoff + UInt64(index) * UInt64(shdrSize) |
| 137 | + let shdrOffset: UInt64 = self.ehdr.e_shoff + UInt64(index) * UInt64(shdrSize) |
161 | 138 | self.file.seek(toFileOffset: shdrOffset)
|
162 | 139 | guard let shdrData = try self.file.read(upToCount: shdrSize), shdrData.count == shdrSize else {
|
163 |
| - throw Error.FileReadFailure(self.filePath, offset: shdrOffset, size: UInt64(shdrSize)) |
| 140 | + throw ELFError.readFailure(self.filePath, offset: shdrOffset, size: UInt64(shdrSize)) |
164 | 141 | }
|
165 | 142 |
|
166 |
| - return shdrData.withUnsafeBytes { $0.load(as: T.self) as T } |
| 143 | + return shdrData.withUnsafeBytes { $0.load(as: Elf64_Shdr.self) } |
167 | 144 | }
|
168 | 145 |
|
169 | 146 | // 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)") |
| 147 | + internal func readSection(_ shdr: Elf64_Shdr) throws -> Data { |
| 148 | + guard let sectionSize = Int(exactly: shdr.sh_size) else { |
| 149 | + throw ELFError.malformedFile(self.filePath, "Elf64_Shdr.sh_size too large \(shdr.sh_size)") |
174 | 150 | }
|
175 | 151 |
|
176 |
| - let fileOffset = shdr.offset |
| 152 | + let fileOffset = shdr.sh_offset |
177 | 153 | self.file.seek(toFileOffset: fileOffset)
|
178 | 154 | guard let data = try self.file.read(upToCount: sectionSize), data.count == sectionSize else {
|
179 |
| - throw Error.FileReadFailure(self.filePath, offset: fileOffset, size: UInt64(sectionSize)) |
| 155 | + throw ELFError.readFailure(self.filePath, offset: fileOffset, size: UInt64(sectionSize)) |
180 | 156 | }
|
181 | 157 |
|
182 | 158 | return data
|
|
0 commit comments