diff --git a/stdlib/public/Backtracing/Backtrace.swift b/stdlib/public/Backtracing/Backtrace.swift index a337e855d7778..025179f975887 100644 --- a/stdlib/public/Backtracing/Backtrace.swift +++ b/stdlib/public/Backtracing/Backtrace.swift @@ -16,8 +16,6 @@ import Swift -@_implementationOnly import _StringProcessing - @_implementationOnly import OS.Libc #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @@ -476,107 +474,93 @@ public struct Backtrace: CustomStringConvertible, Sendable { return [] } - let mapRegex = #/ - ^(?[A-Fa-f0-9]+)-(?[A-Fa-f0-9]+)\s+ - (?[-rwxsp]{4})\s+ - (?[A-Fa-f0-9]+)\s+ - (?[A-Fa-f0-9]+):(?[A-Fa-f0-9]+)\s+ - (?\d+)\s+ - (?.*)\s*$ - /# - let lines = procMaps.split(separator: "\n") - // Find all the mapped files and get high/low ranges var mappedFiles: [Substring:AddressRange] = [:] - for line in lines { - if let match = try? mapRegex.wholeMatch(in: line) { - let path = stripWhitespace(match.pathname) - if match.inode == "0" || path == "" { - continue - } - guard let start = Address(match.start, radix: 16), - let end = Address(match.end, radix: 16) else { - continue - } + for match in ProcMapsScanner(procMaps) { + let path = stripWhitespace(match.pathname) + if match.inode == "0" || path == "" { + continue + } + guard let start = Address(match.start, radix: 16), + let end = Address(match.end, radix: 16) else { + continue + } - if let range = mappedFiles[path] { - mappedFiles[path] = AddressRange(low: min(start, range.low), - high: max(end, range.high)) - } else { - mappedFiles[path] = AddressRange(low: start, - high: end) - } + if let range = mappedFiles[path] { + mappedFiles[path] = AddressRange(low: min(start, range.low), + high: max(end, range.high)) + } else { + mappedFiles[path] = AddressRange(low: start, + high: end) } } // Look for ELF headers in the process' memory typealias Source = MemoryImageSource let source = Source(with: reader) - for line in lines { - if let match = try? mapRegex.wholeMatch(in: line) { - let path = stripWhitespace(match.pathname) - if match.inode == "0" || path == "" { - continue - } + for match in ProcMapsScanner(procMaps) { + let path = stripWhitespace(match.pathname) + if match.inode == "0" || path == "" { + continue + } - guard let start = Address(match.start, radix: 16), - let end = Address(match.end, radix: 16), - let offset = Address(match.offset, radix: 16) else { - continue - } + guard let start = Address(match.start, radix: 16), + let end = Address(match.end, radix: 16), + let offset = Address(match.offset, radix: 16) else { + continue + } - if offset != 0 || end - start < EI_NIDENT { - continue - } + if offset != 0 || end - start < EI_NIDENT { + continue + } - // Extract the filename from path - let name: Substring - if let slashIndex = path.lastIndex(of: "/") { - name = path.suffix(from: path.index(after: slashIndex)) - } else { - name = path - } + // Extract the filename from path + let name: Substring + if let slashIndex = path.lastIndex(of: "/") { + name = path.suffix(from: path.index(after: slashIndex)) + } else { + name = path + } - // Inspect the image and extract the UUID and end of text - let range = mappedFiles[path]! - let subSource = SubImageSource(parent: source, - baseAddress: Source.Address(range.low), - length: Source.Size(range.high - - range.low)) - var theUUID: [UInt8]? = nil - var endOfText: Address = range.low - - if let image = try? Elf32Image(source: subSource) { - theUUID = image.uuid - - for hdr in image.programHeaders { - if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { - endOfText = max(endOfText, range.low + Address(hdr.p_vaddr - + hdr.p_memsz)) - } + // Inspect the image and extract the UUID and end of text + let range = mappedFiles[path]! + let subSource = SubImageSource(parent: source, + baseAddress: Source.Address(range.low), + length: Source.Size(range.high + - range.low)) + var theUUID: [UInt8]? = nil + var endOfText: Address = range.low + + if let image = try? Elf32Image(source: subSource) { + theUUID = image.uuid + + for hdr in image.programHeaders { + if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { + endOfText = max(endOfText, range.low + Address(hdr.p_vaddr + + hdr.p_memsz)) } - } else if let image = try? Elf64Image(source: subSource) { - theUUID = image.uuid + } + } else if let image = try? Elf64Image(source: subSource) { + theUUID = image.uuid - for hdr in image.programHeaders { - if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { - endOfText = max(endOfText, range.low + Address(hdr.p_vaddr - + hdr.p_memsz)) - } + for hdr in image.programHeaders { + if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { + endOfText = max(endOfText, range.low + Address(hdr.p_vaddr + + hdr.p_memsz)) } - } else { - // Not a valid ELF image - continue } + } else { + // Not a valid ELF image + continue + } - let image = Image(name: String(name), - path: String(path), - buildID: theUUID, - baseAddress: range.low, - endOfText: endOfText) + let image = Image(name: String(name), + path: String(path), + buildID: theUUID, + baseAddress: range.low, + endOfText: endOfText) - images.append(image) - } + images.append(image) } #endif diff --git a/stdlib/public/Backtracing/CMakeLists.txt b/stdlib/public/Backtracing/CMakeLists.txt index 13591a9b54731..de22ae86f0dd2 100644 --- a/stdlib/public/Backtracing/CMakeLists.txt +++ b/stdlib/public/Backtracing/CMakeLists.txt @@ -36,6 +36,7 @@ set(BACKTRACING_SOURCES ImageSource.swift MemoryImageSource.swift MemoryReader.swift + ProcMapsScanner.swift Registers.swift SymbolicatedBacktrace.swift Utils.swift @@ -75,7 +76,7 @@ set(LLVM_OPTIONAL_SOURCES add_swift_target_library(swift_Backtracing ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB ${BACKTRACING_SOURCES} - SWIFT_MODULE_DEPENDS ${concurrency} _StringProcessing + SWIFT_MODULE_DEPENDS ${concurrency} LINK_LIBRARIES ${swift_backtracing_link_libraries} diff --git a/stdlib/public/Backtracing/ProcMapsScanner.swift b/stdlib/public/Backtracing/ProcMapsScanner.swift new file mode 100644 index 0000000000000..fdf87613d5515 --- /dev/null +++ b/stdlib/public/Backtracing/ProcMapsScanner.swift @@ -0,0 +1,256 @@ +//===--- ProcMapsScanner.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// Defines ProcMapsScanner, which is for scanning the /proc//maps +// pseudofiles on Linux. +// +//===----------------------------------------------------------------------===// + +#if os(Linux) + +import Swift + +// Lines in /proc/pid/maps files match the following regex: +// +// ^(?[A-Fa-f0-9]+)-(?[A-Fa-f0-9]+)\s+ +// (?[-rwxsp]{4})\s+ +// (?[A-Fa-f0-9]+)\s+ +// (?[A-Fa-f0-9]+):(?[A-Fa-f0-9]+)\s+ +// (?\d+)\s+ +// (?.*)\s*$ + +struct ProcMapsScanner: Sequence, IteratorProtocol { + typealias SS = S.SubSequence + + struct Match { + var start: SS + var end: SS + var perms: SS + var offset: SS + var major: SS + var minor: SS + var inode: SS + var pathname: SS + } + + private enum State { + case start + case scanningStart + case scanningEnd + case afterEnd + case scanningPerms(Int) + case afterPerms + case scanningOffset + case afterOffset + case scanningMajor + case scanningMinor + case afterMinor + case scanningInode + case afterInode + case scanningPathname + case scanningPathnameWhitespace + } + + private var procMaps: S + private var procMapsUTF8: S.UTF8View + private var ndx: S.UTF8View.Index + + init(_ string: S) { + procMaps = string + procMapsUTF8 = string.utf8 + ndx = procMapsUTF8.startIndex + } + + mutating func scanMatch() -> Match? { + var match: Match = Match(start: "", + end: "", + perms: "", + offset: "", + major: "", + minor: "", + inode: "", + pathname: "") + var currentChunk = ndx + var state: State = .start + + func isPerm(_ ch: UInt8) -> Bool { + return ch == UInt8(ascii: "-") || ch == UInt8(ascii: "r") + || ch == UInt8(ascii: "w") || ch == UInt8(ascii: "x") + || ch == UInt8(ascii: "s") || ch == UInt8(ascii: "p") + } + + func isDecimal(_ ch: UInt8) -> Bool { + return ch >= UInt8(ascii: "0") && ch <= UInt8(ascii: "9") + } + + func isHex(_ ch: UInt8) -> Bool { + return ch >= UInt8(ascii: "A") && ch <= UInt8(ascii: "F") + || ch >= UInt8(ascii: "a") && ch <= UInt8(ascii: "f") + || ch >= UInt8(ascii: "0") && ch <= UInt8(ascii: "9") + } + + func isWhitespace(_ ch: UInt8) -> Bool { + return ch == UInt8(ascii: " ") || ch == UInt8(ascii: "\t") + } + + while ndx < procMapsUTF8.endIndex { + let ch = procMapsUTF8[ndx] + let next = procMapsUTF8.index(after: ndx) + + switch state { + case .start: + if !isHex(ch) { + return nil + } + state = .scanningStart + case .scanningStart: + if ch == UInt8(ascii: "-") { + match.start = procMaps[currentChunk.. Match? { + while ndx != procMapsUTF8.endIndex { + if let match = scanMatch() { + return match + } + + // If we failed to match, skip to end of line and retry + while ndx != procMapsUTF8.endIndex { + let ch = procMapsUTF8[ndx] + ndx = procMapsUTF8.index(after: ndx) + if ch == 0x0a { + break + } + } + } + + return nil + } +} + +#endif // os(Linux)