mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[Backtracing] Add support for repeats to Compact Backtrace Format.
This lets us repeat a frame over and over; it only works for a single frame, but that's the common case for infinite recursions. rdar://124913332
This commit is contained in:
@@ -48,6 +48,9 @@ enum CompactBacktraceFormat {
|
||||
static let omit_first = Instruction(rawValue: 0b01000000)!
|
||||
static let omit_last = Instruction(rawValue: 0b01111111)!
|
||||
|
||||
static let rep_first = Instruction(rawValue: 0b10000000)!
|
||||
static let rep_last = Instruction(rawValue: 0b10001111)!
|
||||
|
||||
private static func addressInstr(
|
||||
_ code: UInt8, _ absolute: Bool, _ count: Int
|
||||
) -> Instruction {
|
||||
@@ -71,6 +74,12 @@ enum CompactBacktraceFormat {
|
||||
| (external ? 0b00100000 : 0)
|
||||
| UInt8(count - 1))!
|
||||
}
|
||||
|
||||
static func rep(external: Bool, count: Int) -> Instruction {
|
||||
return Instruction(rawValue: 0b10000000
|
||||
| (external ? 0b00001000 : 0)
|
||||
| UInt8(count - 1))!
|
||||
}
|
||||
}
|
||||
|
||||
// Represents a decoded instruction
|
||||
@@ -81,9 +90,119 @@ enum CompactBacktraceFormat {
|
||||
case ra(absolute: Bool, count: Int)
|
||||
case `async`(absolute: Bool, count: Int)
|
||||
case omit(external: Bool, count: Int)
|
||||
case rep(external: Bool, count: Int)
|
||||
}
|
||||
|
||||
|
||||
/// Accumulates bytes until the end of a Compact Backtrace Format
|
||||
/// sequence is detected.
|
||||
public struct Accumulator<S: Sequence<UInt8>>: Sequence {
|
||||
public typealias Element = UInt8
|
||||
typealias Source = S
|
||||
|
||||
private var source: S
|
||||
|
||||
public init(_ source: S) {
|
||||
self.source = source
|
||||
}
|
||||
|
||||
public func makeIterator() -> Iterator {
|
||||
return Iterator(source.makeIterator())
|
||||
}
|
||||
|
||||
public struct Iterator: IteratorProtocol {
|
||||
var iterator: Source.Iterator?
|
||||
|
||||
enum State {
|
||||
case infoByte
|
||||
case instruction
|
||||
case argumentData(Int)
|
||||
}
|
||||
|
||||
var state: State
|
||||
|
||||
init(_ iterator: Source.Iterator?) {
|
||||
self.iterator = iterator
|
||||
self.state = .infoByte
|
||||
}
|
||||
|
||||
private mutating func finished() {
|
||||
iterator = nil
|
||||
}
|
||||
|
||||
private mutating func fail() {
|
||||
iterator = nil
|
||||
}
|
||||
|
||||
public mutating func next() -> UInt8? {
|
||||
if iterator == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch state {
|
||||
case .infoByte:
|
||||
guard let infoByte = iterator!.next() else {
|
||||
fail()
|
||||
return nil
|
||||
}
|
||||
let version = infoByte >> 2
|
||||
guard let _ = WordSize(rawValue: infoByte & 0x3) else {
|
||||
fail()
|
||||
return nil
|
||||
}
|
||||
guard version == 0 else {
|
||||
fail()
|
||||
return nil
|
||||
}
|
||||
|
||||
state = .instruction
|
||||
|
||||
return infoByte
|
||||
|
||||
case .instruction:
|
||||
guard let instr = iterator!.next() else {
|
||||
finished()
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let decoded = Instruction(rawValue: instr)?.decoded() else {
|
||||
fail()
|
||||
return nil
|
||||
}
|
||||
|
||||
switch decoded {
|
||||
case .end, .trunc:
|
||||
finished()
|
||||
return instr
|
||||
case let .pc(_, count), let .ra(_, count), let .async(_, count):
|
||||
state = .argumentData(count)
|
||||
return instr
|
||||
case let .omit(external, count), let .rep(external, count):
|
||||
if external {
|
||||
state = .argumentData(count)
|
||||
}
|
||||
return instr
|
||||
}
|
||||
|
||||
case let .argumentData(count):
|
||||
guard let byte = iterator!.next() else {
|
||||
fail()
|
||||
return nil
|
||||
}
|
||||
|
||||
let newCount = count - 1
|
||||
if newCount == 0 {
|
||||
state = .instruction
|
||||
} else {
|
||||
state = .argumentData(newCount)
|
||||
}
|
||||
|
||||
return byte
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapts a Sequence containing Compact Backtrace Format data into a
|
||||
/// Sequence of `Backtrace.Frame`s.
|
||||
struct Decoder<S: Sequence<UInt8>>: Sequence {
|
||||
@@ -117,6 +236,8 @@ enum CompactBacktraceFormat {
|
||||
let wordSize: WordSize
|
||||
let wordMask: UInt64
|
||||
var lastAddress: UInt64
|
||||
var lastFrame: Backtrace.Frame?
|
||||
var repeatCount: Int = 0
|
||||
|
||||
init(_ iterator: Storage.Iterator?, _ size: WordSize) {
|
||||
self.iterator = iterator
|
||||
@@ -169,6 +290,19 @@ enum CompactBacktraceFormat {
|
||||
}
|
||||
}
|
||||
|
||||
private mutating func decodeWord(
|
||||
_ count: Int
|
||||
) -> Int? {
|
||||
var word: Int = 0
|
||||
for _ in 0..<count {
|
||||
guard let byte = iterator!.next() else {
|
||||
return nil
|
||||
}
|
||||
word = (word << 8) | Int(byte)
|
||||
}
|
||||
return word
|
||||
}
|
||||
|
||||
private mutating func finished() {
|
||||
iterator = nil
|
||||
}
|
||||
@@ -177,9 +311,14 @@ enum CompactBacktraceFormat {
|
||||
iterator = nil
|
||||
}
|
||||
|
||||
// Note: If we hit an error while decoding, we will return .trucnated.
|
||||
// Note: If we hit an error while decoding, we will return .truncated.
|
||||
|
||||
public mutating func next() -> Backtrace.Frame? {
|
||||
if repeatCount > 0 {
|
||||
repeatCount -= 1
|
||||
return lastFrame
|
||||
}
|
||||
|
||||
public mutating func next() -> Frame? {
|
||||
if iterator == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -194,6 +333,7 @@ enum CompactBacktraceFormat {
|
||||
return .truncated
|
||||
}
|
||||
|
||||
let result: Backtrace.Frame
|
||||
switch decoded {
|
||||
case .end:
|
||||
finished()
|
||||
@@ -206,34 +346,49 @@ enum CompactBacktraceFormat {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
return .programCounter(addr)
|
||||
result = .programCounter(addr)
|
||||
case let .ra(absolute, count):
|
||||
guard let addr = decodeAddress(absolute, count) else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
return .returnAddress(addr)
|
||||
result = .returnAddress(addr)
|
||||
case let .async(absolute, count):
|
||||
guard let addr = decodeAddress(absolute, count) else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
return .asyncResumePoint(addr)
|
||||
result = .asyncResumePoint(addr)
|
||||
case let .omit(external, count):
|
||||
if !external {
|
||||
return .omittedFrames(count)
|
||||
result = .omittedFrames(count)
|
||||
} else {
|
||||
var word: Int = 0
|
||||
for _ in 0..<count {
|
||||
guard let byte = iterator!.next() else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
word = (word << 8) | Int(byte)
|
||||
guard let word = decodeWord(count) else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
return .omittedFrames(word)
|
||||
result = .omittedFrames(word)
|
||||
}
|
||||
case let .rep(external, count):
|
||||
if lastFrame == nil {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
if !external {
|
||||
repeatCount = count - 1
|
||||
} else {
|
||||
guard let word = decodeWord(count) else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
repeatCount = word - 1
|
||||
}
|
||||
result = lastFrame!
|
||||
}
|
||||
|
||||
lastFrame = result
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +399,7 @@ enum CompactBacktraceFormat {
|
||||
struct Encoder<A: FixedWidthInteger, S: Sequence<RichFrame<A>>>: Sequence {
|
||||
typealias Element = UInt8
|
||||
typealias Frame = Backtrace.Frame
|
||||
typealias SourceFrame = RichFrame<A>
|
||||
typealias Address = A
|
||||
typealias Source = S
|
||||
|
||||
@@ -264,11 +420,13 @@ enum CompactBacktraceFormat {
|
||||
enum State {
|
||||
case start
|
||||
case ready
|
||||
case emittingBytes(Int)
|
||||
case emittingBytes(Int, SourceFrame?)
|
||||
case stashedFrame(SourceFrame)
|
||||
case done
|
||||
}
|
||||
var bytes = EightByteBuffer()
|
||||
var state: State = .start
|
||||
var lastFrame: SourceFrame? = nil
|
||||
|
||||
init(_ iterator: Source.Iterator) {
|
||||
self.iterator = iterator
|
||||
@@ -276,8 +434,8 @@ enum CompactBacktraceFormat {
|
||||
|
||||
/// Set up to emit the bytes of `address`, returning the number of bytes
|
||||
/// we will need to emit
|
||||
private mutating func emitAddressNext(
|
||||
_ address: Address
|
||||
private mutating func emitNext(
|
||||
address: Address
|
||||
) -> (absolute: Bool, count: Int) {
|
||||
let delta = address &- lastAddress
|
||||
|
||||
@@ -303,28 +461,77 @@ enum CompactBacktraceFormat {
|
||||
|
||||
if absCount < deltaCount {
|
||||
bytes = EightByteBuffer(address)
|
||||
state = .emittingBytes(8 - absCount)
|
||||
state = .emittingBytes(8 - absCount, nil)
|
||||
return (absolute: true, count: absCount)
|
||||
} else {
|
||||
bytes = EightByteBuffer(delta)
|
||||
state = .emittingBytes(8 - deltaCount)
|
||||
state = .emittingBytes(8 - deltaCount, nil)
|
||||
return (absolute: false, count: deltaCount)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up to emit the bytes of `count`, returning the number of bytes
|
||||
/// we will need to emit
|
||||
private mutating func emitExternalCountNext(
|
||||
_ count: Int
|
||||
private mutating func emitNext(
|
||||
externalCount count: Int
|
||||
) -> Int {
|
||||
let ucount = UInt64(count)
|
||||
let zeroes = ucount.leadingZeroBitCount >> 3
|
||||
let byteCount = 8 - zeroes
|
||||
bytes = EightByteBuffer(ucount)
|
||||
state = .emittingBytes(zeroes)
|
||||
state = .emittingBytes(zeroes, nil)
|
||||
return byteCount
|
||||
}
|
||||
|
||||
private mutating func emitNext(
|
||||
frame: SourceFrame?,
|
||||
externalCount count: Int? = nil
|
||||
) -> Int {
|
||||
if let count {
|
||||
let ucount = UInt64(count)
|
||||
let zeroes = ucount.leadingZeroBitCount >> 3
|
||||
let byteCount = 8 - zeroes
|
||||
bytes = EightByteBuffer(ucount)
|
||||
state = .emittingBytes(zeroes, frame)
|
||||
return byteCount
|
||||
} else if let frame {
|
||||
state = .stashedFrame(frame)
|
||||
} else {
|
||||
state = .ready
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private mutating func emit(frame: SourceFrame) -> UInt8 {
|
||||
lastFrame = frame
|
||||
|
||||
switch frame {
|
||||
case let .programCounter(addr):
|
||||
let (absolute, count) = emitNext(address: addr)
|
||||
return Instruction.pc(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .returnAddress(addr):
|
||||
let (absolute, count) = emitNext(address: addr)
|
||||
return Instruction.ra(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .asyncResumePoint(addr):
|
||||
let (absolute, count) = emitNext(address: addr)
|
||||
return Instruction.async(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .omittedFrames(count):
|
||||
if count <= 0x1f {
|
||||
return Instruction.omit(external: false,
|
||||
count: count).rawValue
|
||||
}
|
||||
let countCount = emitNext(externalCount: count)
|
||||
return Instruction.omit(external: true,
|
||||
count: countCount).rawValue
|
||||
case .truncated:
|
||||
self.state = .done
|
||||
return Instruction.trunc.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func next() -> UInt8? {
|
||||
switch state {
|
||||
case .done:
|
||||
@@ -351,13 +558,17 @@ enum CompactBacktraceFormat {
|
||||
let infoByte = (version << 2) | size.rawValue
|
||||
return infoByte
|
||||
|
||||
case let .emittingBytes(ndx):
|
||||
case let .emittingBytes(ndx, frame):
|
||||
|
||||
let byte = bytes[ndx]
|
||||
if ndx + 1 == 8 {
|
||||
state = .ready
|
||||
if let frame {
|
||||
state = .stashedFrame(frame)
|
||||
} else {
|
||||
state = .ready
|
||||
}
|
||||
} else {
|
||||
state = .emittingBytes(ndx + 1)
|
||||
state = .emittingBytes(ndx + 1, frame)
|
||||
}
|
||||
return byte
|
||||
|
||||
@@ -369,31 +580,37 @@ enum CompactBacktraceFormat {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch frame {
|
||||
case let .programCounter(addr):
|
||||
let (absolute, count) = emitAddressNext(addr)
|
||||
return Instruction.pc(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .returnAddress(addr):
|
||||
let (absolute, count) = emitAddressNext(addr)
|
||||
return Instruction.ra(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .asyncResumePoint(addr):
|
||||
let (absolute, count) = emitAddressNext(addr)
|
||||
return Instruction.async(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .omittedFrames(count):
|
||||
if count <= 0x1f {
|
||||
return Instruction.omit(external: false,
|
||||
count: count).rawValue
|
||||
if let lastFrame, lastFrame == frame {
|
||||
var count = 1
|
||||
var nextFrame: SourceFrame? = nil
|
||||
while let frame = iterator.next() {
|
||||
if frame != lastFrame {
|
||||
nextFrame = frame
|
||||
break
|
||||
} else {
|
||||
count += 1
|
||||
}
|
||||
let countCount = emitExternalCountNext(count)
|
||||
return Instruction.omit(external: true,
|
||||
count: countCount).rawValue
|
||||
case .truncated:
|
||||
self.state = .done
|
||||
return Instruction.trunc.rawValue
|
||||
}
|
||||
|
||||
if count <= 8 {
|
||||
_ = emitNext(frame: nextFrame)
|
||||
return Instruction.rep(external: false,
|
||||
count: count).rawValue
|
||||
} else {
|
||||
let countCount = emitNext(frame: nextFrame,
|
||||
externalCount: count)
|
||||
return Instruction.rep(external: true,
|
||||
count: countCount).rawValue
|
||||
}
|
||||
}
|
||||
|
||||
return emit(frame: frame)
|
||||
|
||||
case let .stashedFrame(frame):
|
||||
|
||||
state = .ready
|
||||
|
||||
return emit(frame: frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,6 +649,10 @@ extension CompactBacktraceFormat.Instruction {
|
||||
let count = Int((self.rawValue & 0x1f) + 1)
|
||||
let external = (self.rawValue & 0x20) != 0
|
||||
return .omit(external: external, count: count)
|
||||
case .rep_first ... .rep_last:
|
||||
let count = Int((self.rawValue & 0x7) + 1)
|
||||
let external = (self.rawValue & 0x8) != 0
|
||||
return .rep(external: external, count: count)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user