[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:
Alastair Houghton
2025-01-01 16:43:19 +00:00
parent 348a325d2a
commit ffda416d36
3 changed files with 292 additions and 49 deletions

View File

@@ -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
}