//===--- Dwarf.swift - DWARF support for 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 various DWARF structures and provides types for working with
// DWARF data on disk and in memory.
//
//===----------------------------------------------------------------------===//

import Swift

@_implementationOnly import BacktracingImpl.ImageFormats.Dwarf

// .. Use *our* Dwarf definitions ..............................................

// To avoid confusion with other similar sets of definitions, we've put ours
// into a C++ wrapper, which means we need aliases here.

typealias Dwarf_Byte   = swift.runtime.Dwarf_Byte
typealias Dwarf_Half   = swift.runtime.Dwarf_Half
typealias Dwarf_Word   = swift.runtime.Dwarf_Word
typealias Dwarf_Xword  = swift.runtime.Dwarf_Xword
typealias Dwarf_Sbyte  = swift.runtime.Dwarf_Sbyte
typealias Dwarf_Sword  = swift.runtime.Dwarf_Sword
typealias Dwarf_Sxword = swift.runtime.Dwarf_Sxword

typealias Dwarf32_Offset = swift.runtime.Dwarf32_Offset
typealias Dwarf32_Size   = swift.runtime.Dwarf32_Size
typealias Dwarf32_Length = swift.runtime.Dwarf32_Length

typealias Dwarf64_Offset = swift.runtime.Dwarf64_Offset
typealias Dwarf64_Size   = swift.runtime.Dwarf64_Size
typealias Dwarf64_Length = swift.runtime.Dwarf64_Length

typealias Dwarf_UnitType           = swift.runtime.Dwarf_UnitType
typealias Dwarf_Tag                = swift.runtime.Dwarf_Tag
typealias Dwarf_ChildDetermination = swift.runtime.Dwarf_ChildDetermination
typealias Dwarf_Attribute          = swift.runtime.Dwarf_Attribute
typealias Dwarf_Form               = swift.runtime.Dwarf_Form

let DW_OP_addr                = swift.runtime.DW_OP_addr
let DW_OP_deref               = swift.runtime.DW_OP_deref
let DW_OP_const1u             = swift.runtime.DW_OP_const1u
let DW_OP_const1s             = swift.runtime.DW_OP_const1s
let DW_OP_const2u             = swift.runtime.DW_OP_const2u
let DW_OP_const2s             = swift.runtime.DW_OP_const2s
let DW_OP_const4u             = swift.runtime.DW_OP_const4u
let DW_OP_const4s             = swift.runtime.DW_OP_const4s
let DW_OP_const8u             = swift.runtime.DW_OP_const8u
let DW_OP_const8s             = swift.runtime.DW_OP_const8s
let DW_OP_constu              = swift.runtime.DW_OP_constu
let DW_OP_consts              = swift.runtime.DW_OP_consts
let DW_OP_dup                 = swift.runtime.DW_OP_dup
let DW_OP_drop                = swift.runtime.DW_OP_drop
let DW_OP_over                = swift.runtime.DW_OP_over
let DW_OP_pick                = swift.runtime.DW_OP_pick
let DW_OP_swap                = swift.runtime.DW_OP_swap
let DW_OP_rot                 = swift.runtime.DW_OP_rot
let DW_OP_xderef              = swift.runtime.DW_OP_xderef
let DW_OP_abs                 = swift.runtime.DW_OP_abs
let DW_OP_and                 = swift.runtime.DW_OP_and
let DW_OP_div                 = swift.runtime.DW_OP_div
let DW_OP_minus               = swift.runtime.DW_OP_minus
let DW_OP_mod                 = swift.runtime.DW_OP_mod
let DW_OP_mul                 = swift.runtime.DW_OP_mul
let DW_OP_neg                 = swift.runtime.DW_OP_neg
let DW_OP_not                 = swift.runtime.DW_OP_not
let DW_OP_or                  = swift.runtime.DW_OP_or
let DW_OP_plus                = swift.runtime.DW_OP_plus
let DW_OP_plus_uconst         = swift.runtime.DW_OP_plus_uconst
let DW_OP_shl                 = swift.runtime.DW_OP_shl
let DW_OP_shr                 = swift.runtime.DW_OP_shr
let DW_OP_shra                = swift.runtime.DW_OP_shra
let DW_OP_xor                 = swift.runtime.DW_OP_xor
let DW_OP_bra                 = swift.runtime.DW_OP_bra
let DW_OP_eq                  = swift.runtime.DW_OP_eq
let DW_OP_ge                  = swift.runtime.DW_OP_ge
let DW_OP_gt                  = swift.runtime.DW_OP_gt
let DW_OP_le                  = swift.runtime.DW_OP_le
let DW_OP_lt                  = swift.runtime.DW_OP_lt
let DW_OP_ne                  = swift.runtime.DW_OP_ne
let DW_OP_skip                = swift.runtime.DW_OP_skip
let DW_OP_lit0                = swift.runtime.DW_OP_lit0
let DW_OP_lit1                = swift.runtime.DW_OP_lit1
let DW_OP_lit2                = swift.runtime.DW_OP_lit2
let DW_OP_lit3                = swift.runtime.DW_OP_lit3
let DW_OP_lit4                = swift.runtime.DW_OP_lit4
let DW_OP_lit5                = swift.runtime.DW_OP_lit5
let DW_OP_lit6                = swift.runtime.DW_OP_lit6
let DW_OP_lit7                = swift.runtime.DW_OP_lit7
let DW_OP_lit8                = swift.runtime.DW_OP_lit8
let DW_OP_lit9                = swift.runtime.DW_OP_lit9
let DW_OP_lit10               = swift.runtime.DW_OP_lit10
let DW_OP_lit11               = swift.runtime.DW_OP_lit11
let DW_OP_lit12               = swift.runtime.DW_OP_lit12
let DW_OP_lit13               = swift.runtime.DW_OP_lit13
let DW_OP_lit14               = swift.runtime.DW_OP_lit14
let DW_OP_lit15               = swift.runtime.DW_OP_lit15
let DW_OP_lit16               = swift.runtime.DW_OP_lit16
let DW_OP_lit17               = swift.runtime.DW_OP_lit17
let DW_OP_lit18               = swift.runtime.DW_OP_lit18
let DW_OP_lit19               = swift.runtime.DW_OP_lit19
let DW_OP_lit20               = swift.runtime.DW_OP_lit20
let DW_OP_lit21               = swift.runtime.DW_OP_lit21
let DW_OP_lit22               = swift.runtime.DW_OP_lit22
let DW_OP_lit23               = swift.runtime.DW_OP_lit23
let DW_OP_lit24               = swift.runtime.DW_OP_lit24
let DW_OP_lit25               = swift.runtime.DW_OP_lit25
let DW_OP_lit26               = swift.runtime.DW_OP_lit26
let DW_OP_lit27               = swift.runtime.DW_OP_lit27
let DW_OP_lit28               = swift.runtime.DW_OP_lit28
let DW_OP_lit29               = swift.runtime.DW_OP_lit29
let DW_OP_lit30               = swift.runtime.DW_OP_lit30
let DW_OP_lit31               = swift.runtime.DW_OP_lit31

let DW_OP_reg0                = swift.runtime.DW_OP_reg0
let DW_OP_reg1                = swift.runtime.DW_OP_reg1
let DW_OP_reg2                = swift.runtime.DW_OP_reg2
let DW_OP_reg3                = swift.runtime.DW_OP_reg3
let DW_OP_reg4                = swift.runtime.DW_OP_reg4
let DW_OP_reg5                = swift.runtime.DW_OP_reg5
let DW_OP_reg6                = swift.runtime.DW_OP_reg6
let DW_OP_reg7                = swift.runtime.DW_OP_reg7
let DW_OP_reg8                = swift.runtime.DW_OP_reg8
let DW_OP_reg9                = swift.runtime.DW_OP_reg9
let DW_OP_reg10               = swift.runtime.DW_OP_reg10
let DW_OP_reg11               = swift.runtime.DW_OP_reg11
let DW_OP_reg12               = swift.runtime.DW_OP_reg12
let DW_OP_reg13               = swift.runtime.DW_OP_reg13
let DW_OP_reg14               = swift.runtime.DW_OP_reg14
let DW_OP_reg15               = swift.runtime.DW_OP_reg15
let DW_OP_reg16               = swift.runtime.DW_OP_reg16
let DW_OP_reg17               = swift.runtime.DW_OP_reg17
let DW_OP_reg18               = swift.runtime.DW_OP_reg18
let DW_OP_reg19               = swift.runtime.DW_OP_reg19
let DW_OP_reg20               = swift.runtime.DW_OP_reg20
let DW_OP_reg21               = swift.runtime.DW_OP_reg21
let DW_OP_reg22               = swift.runtime.DW_OP_reg22
let DW_OP_reg23               = swift.runtime.DW_OP_reg23
let DW_OP_reg24               = swift.runtime.DW_OP_reg24
let DW_OP_reg25               = swift.runtime.DW_OP_reg25
let DW_OP_reg26               = swift.runtime.DW_OP_reg26
let DW_OP_reg27               = swift.runtime.DW_OP_reg27
let DW_OP_reg28               = swift.runtime.DW_OP_reg28
let DW_OP_reg29               = swift.runtime.DW_OP_reg29
let DW_OP_reg30               = swift.runtime.DW_OP_reg30
let DW_OP_reg31               = swift.runtime.DW_OP_reg31

let DW_OP_breg0               = swift.runtime.DW_OP_breg0
let DW_OP_breg1               = swift.runtime.DW_OP_breg1
let DW_OP_breg2               = swift.runtime.DW_OP_breg2
let DW_OP_breg3               = swift.runtime.DW_OP_breg3
let DW_OP_breg4               = swift.runtime.DW_OP_breg4
let DW_OP_breg5               = swift.runtime.DW_OP_breg5
let DW_OP_breg6               = swift.runtime.DW_OP_breg6
let DW_OP_breg7               = swift.runtime.DW_OP_breg7
let DW_OP_breg8               = swift.runtime.DW_OP_breg8
let DW_OP_breg9               = swift.runtime.DW_OP_breg9
let DW_OP_breg10              = swift.runtime.DW_OP_breg10
let DW_OP_breg11              = swift.runtime.DW_OP_breg11
let DW_OP_breg12              = swift.runtime.DW_OP_breg12
let DW_OP_breg13              = swift.runtime.DW_OP_breg13
let DW_OP_breg14              = swift.runtime.DW_OP_breg14
let DW_OP_breg15              = swift.runtime.DW_OP_breg15
let DW_OP_breg16              = swift.runtime.DW_OP_breg16
let DW_OP_breg17              = swift.runtime.DW_OP_breg17
let DW_OP_breg18              = swift.runtime.DW_OP_breg18
let DW_OP_breg19              = swift.runtime.DW_OP_breg19
let DW_OP_breg20              = swift.runtime.DW_OP_breg20
let DW_OP_breg21              = swift.runtime.DW_OP_breg21
let DW_OP_breg22              = swift.runtime.DW_OP_breg22
let DW_OP_breg23              = swift.runtime.DW_OP_breg23
let DW_OP_breg24              = swift.runtime.DW_OP_breg24
let DW_OP_breg25              = swift.runtime.DW_OP_breg25
let DW_OP_breg26              = swift.runtime.DW_OP_breg26
let DW_OP_breg27              = swift.runtime.DW_OP_breg27
let DW_OP_breg28              = swift.runtime.DW_OP_breg28
let DW_OP_breg29              = swift.runtime.DW_OP_breg29
let DW_OP_breg30              = swift.runtime.DW_OP_breg30
let DW_OP_breg31              = swift.runtime.DW_OP_breg31
let DW_OP_regx                = swift.runtime.DW_OP_regx
let DW_OP_fbreg               = swift.runtime.DW_OP_fbreg
let DW_OP_bregx               = swift.runtime.DW_OP_bregx
let DW_OP_piece               = swift.runtime.DW_OP_piece
let DW_OP_deref_size          = swift.runtime.DW_OP_deref_size
let DW_OP_xderef_size         = swift.runtime.DW_OP_xderef_size
let DW_OP_nop                 = swift.runtime.DW_OP_nop
let DW_OP_push_object_address = swift.runtime.DW_OP_push_object_address
let DW_OP_call2               = swift.runtime.DW_OP_call2
let DW_OP_call4               = swift.runtime.DW_OP_call4
let DW_OP_call_ref            = swift.runtime.DW_OP_call_ref
let DW_OP_form_tls_address    = swift.runtime.DW_OP_form_tls_address
let DW_OP_call_frame_cfa      = swift.runtime.DW_OP_call_frame_cfa
let DW_OP_bit_piece           = swift.runtime.DW_OP_bit_piece
let DW_OP_implicit_value      = swift.runtime.DW_OP_implicit_value
let DW_OP_stack_value         = swift.runtime.DW_OP_stack_value
let DW_OP_implicit_pointer    = swift.runtime.DW_OP_implicit_pointer
let DW_OP_addrx               = swift.runtime.DW_OP_addrx
let DW_OP_constx              = swift.runtime.DW_OP_constx
let DW_OP_entry_value         = swift.runtime.DW_OP_entry_value
let DW_OP_const_type          = swift.runtime.DW_OP_const_type
let DW_OP_regval_type         = swift.runtime.DW_OP_regval_type
let DW_OP_deref_type          = swift.runtime.DW_OP_deref_type
let DW_OP_xderef_type         = swift.runtime.DW_OP_xderef_type
let DW_OP_convert             = swift.runtime.DW_OP_convert
let DW_OP_reinterpret         = swift.runtime.DW_OP_reinterpret
let DW_OP_lo_user             = swift.runtime.DW_OP_lo_user
let DW_OP_hi_user             = swift.runtime.DW_OP_hi_user

typealias Dwarf_LNS_Opcode = swift.runtime.Dwarf_LNS_Opcode
typealias Dwarf_LNE_Opcode = swift.runtime.Dwarf_LNE_Opcode
typealias Dwarf_Lhdr_Format = swift.runtime.Dwarf_Lhdr_Format

typealias DWARF32_Lhdr = swift.runtime.DWARF32_Lhdr
typealias DWARF64_Lhdr = swift.runtime.DWARF64_Lhdr

let DW_CFA_advance_loc        = swift.runtime.DW_CFA_advance_loc
let DW_CFA_offset             = swift.runtime.DW_CFA_offset
let DW_CFA_restore            = swift.runtime.DW_CFA_restore
let DW_CFA_nop                = swift.runtime.DW_CFA_nop
let DW_CFA_set_loc            = swift.runtime.DW_CFA_set_loc
let DW_CFA_advance_loc1       = swift.runtime.DW_CFA_advance_loc1
let DW_CFA_advance_loc2       = swift.runtime.DW_CFA_advance_loc2
let DW_CFA_advance_loc4       = swift.runtime.DW_CFA_advance_loc4
let DW_CFA_offset_extended    = swift.runtime.DW_CFA_offset_extended
let DW_CFA_restore_extended   = swift.runtime.DW_CFA_restore_extended
let DW_CFA_undefined          = swift.runtime.DW_CFA_undefined
let DW_CFA_same_value         = swift.runtime.DW_CFA_same_value
let DW_CFA_register           = swift.runtime.DW_CFA_register
let DW_CFA_remember_state     = swift.runtime.DW_CFA_remember_state
let DW_CFA_restore_state      = swift.runtime.DW_CFA_restore_state
let DW_CFA_def_cfa            = swift.runtime.DW_CFA_def_cfa
let DW_CFA_def_cfa_register   = swift.runtime.DW_CFA_def_cfa_register
let DW_CFA_def_cfa_offset     = swift.runtime.DW_CFA_def_cfa_offset
let DW_CFA_def_cfa_expression = swift.runtime.DW_CFA_def_cfa_expression
let DW_CFA_expression         = swift.runtime.DW_CFA_expression
let DW_CFA_offset_extended_sf = swift.runtime.DW_CFA_offset_extended_sf
let DW_CFA_def_cfa_sf         = swift.runtime.DW_CFA_def_cfa_sf
let DW_CFA_def_cfa_offset_sf  = swift.runtime.DW_CFA_def_cfa_offset_sf
let DW_CFA_val_offset         = swift.runtime.DW_CFA_val_offset
let DW_CFA_val_offset_sf      = swift.runtime.DW_CFA_val_offset_sf
let DW_CFA_val_expression     = swift.runtime.DW_CFA_val_expression
let DW_CFA_lo_user            = swift.runtime.DW_CFA_lo_user
let DW_CFA_hi_user            = swift.runtime.DW_CFA_hi_user

typealias Dwarf_RLE_Entry = swift.runtime.Dwarf_RLE_Entry
typealias Dwarf32_CIEHdr  = swift.runtime.Dwarf32_CIEHdr
typealias Dwarf64_CIEHdr  = swift.runtime.Dwarf64_CIEHdr
typealias Dwarf32_FDEHdr  = swift.runtime.Dwarf32_FDEHdr

typealias EHFrameHdr      = swift.runtime.EHFrameHdr
typealias EHFrameEncoding = swift.runtime.EHFrameEncoding

let DW_EH_PE_omit    = swift.runtime.DW_EH_PE_omit
let DW_EH_PE_uleb128 = swift.runtime.DW_EH_PE_uleb128
let DW_EH_PE_udata2  = swift.runtime.DW_EH_PE_udata2
let DW_EH_PE_udata4  = swift.runtime.DW_EH_PE_udata4
let DW_EH_PE_udata8  = swift.runtime.DW_EH_PE_udata8
let DW_EH_PE_sleb128 = swift.runtime.DW_EH_PE_sleb128
let DW_EH_PE_sdata2  = swift.runtime.DW_EH_PE_sdata2
let DW_EH_PE_sdata4  = swift.runtime.DW_EH_PE_sdata4
let DW_EH_PE_sdata8  = swift.runtime.DW_EH_PE_sdata8
let DW_EH_PE_absptr  = swift.runtime.DW_EH_PE_absptr
let DW_EH_PE_pcrel   = swift.runtime.DW_EH_PE_pcrel
let DW_EH_PE_datarel = swift.runtime.DW_EH_PE_datarel

// .. Dwarf specific errors ....................................................

private enum DwarfError: Error {
  case noDebugInformation
  case unsupportedVersion(Int)
  case unknownEHValueEncoding
  case unknownEHOffsetEncoding
  case badAttribute(UInt64)
  case badForm(UInt64)
  case badTag(UInt64)
  case badLength(UInt32)
  case badAddressSize(Int)
  case badLineContentType(UInt64)
  case badString
  case missingAbbrev(UInt64)
  case doubleIndirectForm
  case unknownForm(Dwarf_Form)
  case missingBaseOffset
  case missingAddrSection
  case missingStrSection
  case missingLineStrSection
  case missingStrOffsetsSection
  case missingAddrBase
  case missingStrOffsetsBase
  case missingLocListsBase
  case unspecifiedAddressSize
}

// .. Dwarf utilities for ImageSource ..........................................

@available(BacktracingDT 6.2, *)
extension ImageSource {

  func fetchULEB128(from a: Address) throws -> (Address, UInt64) {
    var addr = a
    var shift = 0
    var value: UInt64 = 0
    while true {
      let byte = try fetch(from: addr, as: UInt8.self)
      addr += 1
      value |= UInt64(byte & 0x7f) << shift
      if (byte & 0x80) == 0 {
        break
      }
      shift += 7
    }

    return (addr, value)
  }

  func fetchSLEB128(from a: Address) throws -> (Address, Int64) {
    var addr = a
    var shift = 0
    var sign: UInt8 = 0
    var value: Int64 = 0

    while true {
      let byte = try fetch(from: addr, as: UInt8.self)
      addr += 1
      value |= Int64(byte & 0x7f) << shift
      shift += 7
      sign = byte & 0x40
      if (byte & 0x80) == 0 {
        break
      }
    }

    if shift < 64 && sign != 0 {
      value |= -(1 << shift)
    }

    return (addr, value)
  }

  func fetchEHValue(from a: Address, with encoding: EHFrameEncoding,
    pc: Address = 0, data: Address = 0, shouldSwap: Bool = false) throws
    -> (Address, UInt64)? {

    func maybeSwap<T: FixedWidthInteger>(_ x: T) -> T {
      if shouldSwap {
        return x.byteSwapped
      }
      return x
    }

    let valueEnc = EHFrameEncoding(encoding & 0x0f)
    var value: UInt64 = 0
    var addr = a

    switch valueEnc {
    case DW_EH_PE_omit:
      return nil
    case DW_EH_PE_uleb128:
      (addr, value) = try fetchULEB128(from: addr)
    case DW_EH_PE_udata2:
      let u2 = maybeSwap(try fetch(from: addr, as: UInt16.self))
      value = UInt64(u2)
      addr += 2
    case DW_EH_PE_udata4:
      let u4 = maybeSwap(try fetch(from: addr, as: UInt32.self))
      value = UInt64(u4)
      addr += 4
    case DW_EH_PE_udata8:
      let u8 = maybeSwap(try fetch(from: addr, as: UInt64.self))
      value = u8
      addr += 8
    case DW_EH_PE_sleb128:
      let (newAddr, newValue) = try fetchSLEB128(from: addr)
      value = UInt64(bitPattern: newValue)
      addr = newAddr
    case DW_EH_PE_sdata2:
      let s2 = maybeSwap(try fetch(from: addr, as: Int16.self))
      value = UInt64(bitPattern: Int64(s2))
      addr += 2
    case DW_EH_PE_sdata4:
      let s4 = maybeSwap(try fetch(from: addr, as: Int32.self))
      value = UInt64(bitPattern: Int64(s4))
      addr += 4
    case DW_EH_PE_sdata8:
      let s8 = maybeSwap(try fetch(from: addr, as: Int64.self))
      value = UInt64(bitPattern: s8)
      addr += 8
    default:
      throw DwarfError.unknownEHValueEncoding
    }

    let offsetEnc = EHFrameEncoding(encoding & 0xf0)

    switch offsetEnc {
    case DW_EH_PE_absptr:
      return (addr, value)
    case DW_EH_PE_pcrel:
      return (addr, UInt64(pc) &+ value)
    case DW_EH_PE_datarel:
      return (addr, UInt64(data) &+ value)
    default:
      throw DwarfError.unknownEHOffsetEncoding
    }
  }

  func fetchDwarfLength(from addr: Address) throws
    -> (length: UInt64, isDwarf64: Bool) {

    let len32 = try fetch(from: addr, as: UInt32.self)
    if len32 < 0xfffffff0 {
      return (length: UInt64(len32), isDwarf64: false)
    } else if len32 < 0xffffffff {
      throw DwarfError.badLength(len32)
    } else {
      let len64 = try fetch(from: addr + 4, as: UInt64.self)
      return (length: len64, isDwarf64: true)
    }
  }
}

// .. Dwarf utilities for ImageSourceCursor .....................................

@available(BacktracingDT 6.2, *)
extension ImageSourceCursor {

  mutating func readULEB128() throws -> UInt64 {
    let (next, result) = try source.fetchULEB128(from: pos)
    pos = next
    return result
  }

  mutating func readSLEB128() throws -> Int64 {
    let (next, result) = try source.fetchSLEB128(from: pos)
    pos = next
    return result
  }

  mutating func readEHValue(
    with encoding: EHFrameEncoding,
    pc: Address = 0,
    data: Address = 0,
    shouldSwap: Bool = false
  ) throws -> UInt64? {
    guard let (next, result)
            = try source.fetchEHValue(from: pos,
                                      with: encoding,
                                      pc: pc,
                                      data: data,
                                      shouldSwap: shouldSwap) else {
      return nil
    }

    pos = next
    return result
  }

  mutating func readDwarfLength() throws -> (length: UInt64, isDwarf64: Bool) {
    let result = try source.fetchDwarfLength(from: pos)
    pos += result.isDwarf64 ? 12 : 4
    return result
  }

}

// .. DwarfReader ...............................................................

enum DwarfSection {
  case debugAbbrev
  case debugAddr
  case debugARanges
  case debugFrame
  case debugInfo
  case debugLine
  case debugLineStr
  case debugLoc
  case debugLocLists
  case debugMacInfo
  case debugMacro
  case debugNames
  case debugPubNames
  case debugPubTypes
  case debugRanges
  case debugRngLists
  case debugStr
  case debugStrOffsets
  case debugSup
  case debugTypes
  case debugCuIndex
  case debugTuIndex
}

@available(BacktracingDT 6.2, *)
protocol DwarfSource {

  static var pathSeparator: String { get }

  func getDwarfSection(_ section: DwarfSection) -> ImageSource?

}

@available(BacktracingDT 6.2, *)
class DwarfReader<S: DwarfSource & AnyObject> {
  typealias Source = S
  typealias Address = UInt64
  typealias Size = UInt64
  struct Bounds {
    var base: Address
    var size: Size
    var end: Address { return base + size }
  }

  unowned var source: Source

  struct AbbrevInfo {
    var tag: Dwarf_Tag
    var hasChildren: Bool
    var attributes: [(Dwarf_Attribute, Dwarf_Form, Int64?)]
  }

  var infoSection: ImageSource
  var abbrevSection: ImageSource
  var lineSection: ImageSource?
  var addrSection: ImageSource?
  var strSection: ImageSource?
  var lineStrSection: ImageSource?
  var strOffsetsSection: ImageSource?
  var rangesSection: ImageSource?
  var shouldSwap: Bool

  typealias DwarfAbbrev = UInt64

  struct Unit {
    var baseOffset: Address
    var version: Int
    var isDwarf64: Bool
    var unitType: Dwarf_UnitType
    var addressSize: Int
    var abbrevOffset: Address
    var dieBounds: Bounds

    var lowPC: Address?

    var lineBase: UInt64?
    var addrBase: UInt64?
    var strOffsetsBase: UInt64?
    var loclistsBase: UInt64?

    var abbrevs: [DwarfAbbrev: AbbrevInfo]

    var tag: Dwarf_Tag
    var attributes: [Dwarf_Attribute:DwarfValue] = [:]
  }

  var units: [Unit] = []

  var lineNumberInfo: [DwarfLineNumberInfo] = []

  struct RangeListInfo {
    var length: UInt64
    var isDwarf64: Bool
    var version: Int
    var addressSize: Int
    var segmentSelectorSize: Int
    var offsetEntryCount: Int
    var offsetEntryBase: Address
  }

  var rangeListInfo: RangeListInfo?

  @_specialize(kind: full, where S == Elf32Image)
  @_specialize(kind: full, where S == Elf64Image)
  init(source: Source, shouldSwap: Bool = false) throws {
    // ###TODO: This should be optional, because we can have just line number
    //          information.  We should test that, too.
    guard let abbrevSection = source.getDwarfSection(.debugAbbrev),
          let infoSection = source.getDwarfSection(.debugInfo) else {
      throw DwarfError.noDebugInformation
    }

    self.infoSection = infoSection
    self.abbrevSection = abbrevSection

    addrSection = source.getDwarfSection(.debugAddr)
    strSection = source.getDwarfSection(.debugStr)
    lineSection = source.getDwarfSection(.debugLine)
    lineStrSection = source.getDwarfSection(.debugLineStr)
    strOffsetsSection = source.getDwarfSection(.debugStrOffsets)
    rangesSection = source.getDwarfSection(.debugRanges)

    self.source = source
    self.shouldSwap = shouldSwap
    self.lineNumberInfo = try readLineNumberInfo()
    self.units = try readUnits()

    // On DWARF 4 and earlier, we need to fix up a couple of things in the
    // line number info; these are explicitly included in DWARF 5 so that
    // we can strip everything except line number information.
    for n in 0..<lineNumberInfo.count {
      if lineNumberInfo[n].version >= 5 {
        continue
      }

      for unit in self.units {
        if let lineBase = unit.lineBase,
           lineNumberInfo[n].baseOffset == lineBase {
          var filename = "<unknown>"
          if let nameVal = unit.attributes[.DW_AT_name],
             case let .string(theName) = nameVal {
            filename = theName
          }
          var dirname = "."
          if let dirVal = unit.attributes[.DW_AT_comp_dir],
             case let .string(theDir) = dirVal {
            dirname = theDir
          }

          lineNumberInfo[n].directories[0] = dirname
          lineNumberInfo[n].files[0] = DwarfFileInfo(
            path: filename,
            directoryIndex: 0,
            timestamp: nil,
            size: nil,
            md5sum: nil
          )
          lineNumberInfo[n].addressSize = unit.addressSize
          break
        }
      }
    }
  }

  private func maybeSwap<T: FixedWidthInteger>(_ x: T) -> T {
    if shouldSwap {
      return x.byteSwapped
    } else {
      return x
    }
  }

  private func readUnits() throws -> [Unit] {
    let end = infoSection.bytes.count
    var units: [Unit] = []
    var cursor = ImageSourceCursor(source: infoSection)

    while cursor.pos < end {
      // See 7.5.1.1 Full and Partial Compilation Unit Headers
      let base = cursor.pos

      // .1 unit_length
      let (length, dwarf64) = try cursor.readDwarfLength()
      let next = cursor.pos + length

      // .2 version
      let version = Int(maybeSwap(try cursor.read(as: Dwarf_Half.self)))

      if version < 2 || version > 5 {
        throw DwarfError.unsupportedVersion(version)
      }

      var unitType: Dwarf_UnitType = .DW_UT_unknown
      let addressSize: Int
      let abbrevOffset: Address
      let dieBounds: Bounds

      if dwarf64 {
        if version >= 3 && version <= 4 {
          // .3 debug_abbrev_offset
          abbrevOffset = Address(maybeSwap(try cursor.read(as: Dwarf_Xword.self)))

          // .4 address_size
          addressSize = Int(try cursor.read(as: Dwarf_Byte.self))
        } else if version == 5 {
          // .3 unit_type
          unitType = try cursor.read(as: Dwarf_UnitType.self)

          // .4 address_size
          addressSize = Int(try cursor.read(as: Dwarf_Byte.self))

          // .5 debug_abbrev_offset
          abbrevOffset = Address(maybeSwap(try cursor.read(as: Dwarf_Xword.self)))
        } else {
          throw DwarfError.unsupportedVersion(version)
        }
      } else {
        if version >= 2 && version <= 4 {
          // .3 debug_abbrev_offset
          abbrevOffset = Address(maybeSwap(try cursor.read(as: Dwarf_Word.self)))

          // .4 address_size
          addressSize = Int(try cursor.read(as: Dwarf_Byte.self))
        } else if version == 5 {
          // .3 unit_type
          unitType = try cursor.read(as: Dwarf_UnitType.self)

          // .4 address_size
          addressSize = Int(try cursor.read(as: Dwarf_Byte.self))

          // .5 debug_abbrev_offset
          abbrevOffset = Address(maybeSwap(try cursor.read(as: Dwarf_Word.self)))
        } else {
          throw DwarfError.unsupportedVersion(version)
        }
      }

      if unitType == .DW_UT_skeleton || unitType == .DW_UT_split_compile {
        // .6 dwo_id
        let _ = try cursor.read(as: UInt64.self)
      } else if unitType == .DW_UT_type || unitType == .DW_UT_split_type {
        // .6 type_signature
        let _ = try cursor.read(as: UInt64.self)

        // .7 type_offset
        if dwarf64 {
          let _ = try cursor.read(as: UInt64.self)
        } else {
          let _ = try cursor.read(as: UInt32.self)
        }
      }

      dieBounds = Bounds(base: cursor.pos, size: next - cursor.pos)

      let abbrevs = try readAbbrevs(at: abbrevOffset)

      let abbrev = try cursor.readULEB128()

      guard let abbrevInfo = abbrevs[abbrev] else {
        throw DwarfError.missingAbbrev(abbrev)
      }
      let tag = abbrevInfo.tag

      var unit = Unit(baseOffset: base,
                           version: Int(version),
                           isDwarf64: dwarf64,
                           unitType: unitType,
                           addressSize: Int(addressSize),
                           abbrevOffset: abbrevOffset,
                           dieBounds: dieBounds,
                           abbrevs: abbrevs,
                           tag: tag)

      let attrPos = cursor.pos
      let firstPass = try readDieAttributes(
        at: &cursor,
        unit: unit,
        abbrevInfo: abbrevInfo,
        shouldFetchIndirect: false
      )

      if let value = firstPass[.DW_AT_addr_base],
         case let .sectionOffset(offset) = value {
        unit.addrBase = offset
      }
      if let value = firstPass[.DW_AT_str_offsets_base],
         case let .sectionOffset(offset) = value {
        unit.strOffsetsBase = offset
      }
      if let value = firstPass[.DW_AT_loclists_base],
         case let .sectionOffset(offset) = value {
        unit.loclistsBase = offset
      }
      if let value = firstPass[.DW_AT_stmt_list],
         case let .sectionOffset(offset) = value {
        unit.lineBase = offset
      }
      if let value = firstPass[.DW_AT_low_pc],
         case let .address(lowPC) = value {
        unit.lowPC = lowPC
      }

      // Re-read the attributes, with indirect fetching enabled;
      // we can't do this in one step because attributes might be using
      // indirections based on the base attributes, and those can come
      // after the data needed to decode them.
      cursor.pos = attrPos

      let attributes = try readDieAttributes(
        at: &cursor,
        unit: unit,
        abbrevInfo: abbrevInfo,
        shouldFetchIndirect: true
      )

      unit.attributes = attributes

      units.append(unit)

      cursor.pos = next
    }

    return units
  }

  private func readLineNumberInfo() throws -> [DwarfLineNumberInfo] {
    guard let lineSection = lineSection else {
      return []
    }

    let end = lineSection.bytes.count
    var result: [DwarfLineNumberInfo] = []
    var cursor = ImageSourceCursor(source: lineSection, offset: 0)

    while cursor.pos < end {
      // 6.2.4 The Line Number Program Header

      // .1 unit_length
      let baseOffset = cursor.pos
      let (length, dwarf64) = try cursor.readDwarfLength()
      if length == 0 {
        break
      }

      let nextOffset = cursor.pos + length

      // .2 version
      let version = Int(maybeSwap(try cursor.read(as: Dwarf_Half.self)))

      if version < 2 || version > 5 {
        cursor.pos = nextOffset
        continue
      }

      var addressSize: Int? = nil
      var segmentSelectorSize: Int? = nil

      if version == 5 {
        // .3 address_size
        addressSize = Int(try cursor.read(as: Dwarf_Byte.self))

        // .4 segment_selector_size
        segmentSelectorSize = Int(try cursor.read(as: Dwarf_Byte.self))
      }

      // .5 header_length
      let headerLength: UInt64
      if dwarf64 {
        headerLength = maybeSwap(try cursor.read(as: Dwarf_Xword.self))
      } else {
        headerLength = UInt64(maybeSwap(try cursor.read(as: Dwarf_Word.self)))
      }

      // .6 minimum_instruction_length
      let minimumInstructionLength = UInt(try cursor.read(as: Dwarf_Byte.self))

      let maximumOpsPerInstruction: UInt
      if version >= 4 {
        // .7 maximum_operations_per_instruction
        maximumOpsPerInstruction = UInt(try cursor.read(as: Dwarf_Byte.self))
      } else {
        maximumOpsPerInstruction = 1
      }

      // .8 default_is_stmt
      let defaultIsStmt = try cursor.read(as: Dwarf_Byte.self) != 0

      // .9 line_base
      let lineBase = try cursor.read(as: Dwarf_Sbyte.self)

      // .10 line_range
      let lineRange = try cursor.read(as: Dwarf_Byte.self)

      // .11 opcode_base
      let opcodeBase = try cursor.read(as: Dwarf_Byte.self)

      // .12 standard_opcode_lengths
      var standardOpcodeLengths: [UInt64] = [0]
      for _ in 1..<Int(opcodeBase) {
        let length = try cursor.readULEB128()
        standardOpcodeLengths.append(length)
      }

      var dirNames: [String] = []
      var fileInfo: [DwarfFileInfo] = []

      if version >= 2 && version <= 4 {
        // .11 include_directories

        // Prior to version 5, the compilation directory is not included; put
        // a placeholder here for now, which we'll fix later.
        dirNames.append(".")

        while true {
          guard let path = try cursor.readString() else {
            throw DwarfError.badString
          }

          if path == "" {
            break
          }

          dirNames.append(path)
        }

        // .12 file_names

        // Prior to version 5, the compilation unit's filename is not included;
        // put a placeholder here for now, which we'll fix up later.
        fileInfo.append(DwarfFileInfo(
                          path: "<unknown>",
                          directoryIndex: 0,
                          timestamp: nil,
                          size: nil,
                          md5sum: nil))

        while true {
          guard let path = try cursor.readString() else {
            throw DwarfError.badString
          }

          if path == "" {
            break
          }

          let dirIndex = try cursor.readULEB128()
          let timestamp = try cursor.readULEB128()
          let size = try cursor.readULEB128()

          fileInfo.append(DwarfFileInfo(
                            path: path,
                            directoryIndex: Int(dirIndex),
                            timestamp: timestamp != 0 ? Int(timestamp) : nil,
                            size: size != 0 ? size : nil,
                            md5sum: nil))
        }
      } else if version == 5 {
        // .13/.14 directory_entry_format
        var dirEntryFormat: [(Dwarf_Lhdr_Format, Dwarf_Form)] = []
        let dirEntryFormatCount = Int(try cursor.read(as: Dwarf_Byte.self))
        for _ in 0..<dirEntryFormatCount {
          let rawType = try cursor.readULEB128()
          let rawForm = try cursor.readULEB128()

          guard let halfType = Dwarf_Half(exactly: rawType),
                let type = Dwarf_Lhdr_Format(rawValue: halfType) else {
            throw DwarfError.badLineContentType(rawType)
          }
          guard let byteForm = Dwarf_Byte(exactly: rawForm),
                let form = Dwarf_Form(rawValue: byteForm) else {
            throw DwarfError.badForm(rawForm)
          }

          dirEntryFormat.append((type, form))
        }

        // .15 directories_count
        let dirCount = Int(try cursor.readULEB128())

        // .16 directories
        for _ in 0..<dirCount {
          var attributes: [Dwarf_Lhdr_Format: DwarfValue] = [:]
          for (type, form) in dirEntryFormat {
            attributes[type] = try read(form: form,
                                        at: &cursor,
                                        addressSize: addressSize ?? 4,
                                        isDwarf64: dwarf64,
                                        unit: nil,
                                        shouldFetchIndirect: true)
          }

          if let pathVal = attributes[.DW_LNCT_path],
             case let .string(path) = pathVal {
            dirNames.append(path)
          } else {
            dirNames.append("<unknown>")
          }
        }

        // .17/.18 file_name_entry_format
        var fileEntryFormat: [(Dwarf_Lhdr_Format, Dwarf_Form)] = []
        let fileEntryFormatCount = Int(try cursor.read(as: Dwarf_Byte.self))
        for _ in 0..<fileEntryFormatCount {
          let rawType = try cursor.readULEB128()
          let rawForm = try cursor.readULEB128()

          guard let halfType = Dwarf_Half(exactly: rawType),
                let type = Dwarf_Lhdr_Format(rawValue: halfType) else {
            throw DwarfError.badLineContentType(rawType)
          }
          guard let byteForm = Dwarf_Byte(exactly: rawForm),
                let form = Dwarf_Form(rawValue: byteForm) else {
            throw DwarfError.badForm(rawForm)
          }

          fileEntryFormat.append((type, form))
        }

        // .19 file_names_count
        let fileCount = Int(try cursor.readULEB128())

        // .20 file_names
        for _ in 0..<fileCount {
          var attributes: [Dwarf_Lhdr_Format: DwarfValue] = [:]
          for (type, form) in fileEntryFormat {
            attributes[type] = try read(form: form,
                                        at: &cursor,
                                        addressSize: addressSize ?? 4,
                                        isDwarf64: dwarf64,
                                        unit: nil,
                                        shouldFetchIndirect: true)
          }

          let path: String
          if let pathVal = attributes[.DW_LNCT_path],
             case let .string(thePath) = pathVal {
            path = thePath
          } else {
            path = "<unknown>"
          }

          let dirIndex = attributes[.DW_LNCT_directory_index]?.intValue()
          let timestamp = attributes[.DW_LNCT_timestamp]?.intValue()
          let size = attributes[.DW_LNCT_size]?.uint64Value()
          let md5sum: [UInt8]?
          if let md5sumVal = attributes[.DW_LNCT_MD5],
             case let .data(theSum) = md5sumVal {
            md5sum = theSum
          } else {
            md5sum = nil
          }

          fileInfo.append(DwarfFileInfo(
                            path: path,
                            directoryIndex: dirIndex,
                            timestamp: timestamp,
                            size: size,
                            md5sum: md5sum))
        }
      }

      // The actual program comes next
      let program = cursor.source[cursor.pos..<nextOffset]
      cursor.pos = nextOffset

      result.append(DwarfLineNumberInfo(
                      pathSeparator: Source.pathSeparator,
                      baseOffset: baseOffset,
                      version: version,
                      addressSize: addressSize,
                      selectorSize: segmentSelectorSize,
                      headerLength: headerLength,
                      minimumInstructionLength: minimumInstructionLength,
                      maximumOpsPerInstruction: maximumOpsPerInstruction,
                      defaultIsStmt: defaultIsStmt,
                      lineBase: lineBase,
                      lineRange: lineRange,
                      opcodeBase: opcodeBase,
                      standardOpcodeLengths: standardOpcodeLengths,
                      directories: dirNames,
                      files: fileInfo,
                      program: program,
                      shouldSwap: shouldSwap
                    ))
    }

    return result
  }

  private func readAbbrevs(
    at offset: UInt64
  ) throws -> [DwarfAbbrev: AbbrevInfo] {
    var abbrevs: [DwarfAbbrev: AbbrevInfo] = [:]
    var cursor = ImageSourceCursor(source: abbrevSection, offset: offset)
    while true {
      let abbrev = try cursor.readULEB128()

      if abbrev == 0 {
        break
      }

      let rawTag = try cursor.readULEB128()

      guard let tag = Dwarf_Tag(rawValue: rawTag) else {
        throw DwarfError.badTag(rawTag)
      }

      let children = try cursor.read(as: Dwarf_ChildDetermination.self)

      // Fetch attributes
      var attributes: [(Dwarf_Attribute, Dwarf_Form, Int64?)] = []
      while true {
        let rawAttr = try cursor.readULEB128()
        let rawForm = try cursor.readULEB128()

        if rawAttr == 0 && rawForm == 0 {
          break
        }

        guard let attr = Dwarf_Attribute(rawValue: UInt32(rawAttr)) else {
          throw DwarfError.badAttribute(rawAttr)
        }
        guard let form = Dwarf_Form(rawValue: Dwarf_Byte(rawForm)) else {
          throw DwarfError.badForm(rawForm)
        }

        if form == .DW_FORM_implicit_const {
          let value = try cursor.readSLEB128()
          attributes.append((attr, form, value))
        } else {
          attributes.append((attr, form, nil))
        }
      }

      abbrevs[abbrev] = AbbrevInfo(tag: tag,
                                     hasChildren: children != .DW_CHILDREN_no,
                                     attributes: attributes)
    }

    return abbrevs
  }

  enum DwarfValue {
    case flag(Bool)
    case string(String)
    case address(UInt64)
    case integer(Int)
    case unsignedInt8(UInt8)
    case unsignedInt16(UInt16)
    case unsignedInt32(UInt32)
    case signedInt64(Int64)
    case unsignedInt64(UInt64)
    case dieOffset(UInt64)
    case data([UInt8])
    case expression([UInt8])
    case locationList(UInt64)
    case rangeList(UInt64)
    case sectionOffset(UInt64)
    case reference(UInt64)
    case signature([UInt8])
    case supplementaryReference(UInt64)
    case supplementaryString(UInt64)
    case indirectAddress(UInt64)
    case stringFromStrTab(UInt64)
    case stringFromLineStrTab(UInt64)
    case stringViaStrOffsets(UInt64)

    func uint64Value() -> UInt64? {
      switch self {
        case let .unsignedInt8(value): return UInt64(value)
        case let .unsignedInt16(value): return UInt64(value)
        case let .unsignedInt32(value): return UInt64(value)
        case let .unsignedInt64(value): return value
        default:
          return nil
      }
    }

    func intValue() -> Int? {
      switch self {
        case let .unsignedInt8(value): return Int(value)
        case let .unsignedInt16(value): return Int(value)
        case let .unsignedInt32(value): return Int(value)
        case let .unsignedInt64(value): return Int(value)
        default:
          return nil
      }
    }
  }

  private func threeByteToOffset(_ bytes: (UInt8, UInt8, UInt8)) -> UInt64 {
    let offset: UInt64
    #if _endian(big)
    if shouldSwap {
      offset = UInt64(bytes.0) | UInt64(bytes.1) << 8 | UInt64(bytes.2) << 16
    } else {
      offset = UInt64(bytes.2) | UInt64(bytes.1) << 8 | UInt64(bytes.0) << 16
    }
    #else
    if shouldSwap {
      offset = UInt64(bytes.2) | UInt64(bytes.1) << 8 | UInt64(bytes.0) << 16
    } else {
      offset = UInt64(bytes.0) | UInt64(bytes.1) << 8 | UInt64(bytes.2) << 16
    }
    #endif
    return offset
  }

  private func read(form theForm: Dwarf_Form,
                    at cursor: inout ImageSourceCursor,
                    addressSize: Int, isDwarf64: Bool,
                    unit: Unit?,
                    shouldFetchIndirect: Bool,
                    constantValue: Int64? = nil) throws -> DwarfValue {
    let form: Dwarf_Form
    if theForm == .DW_FORM_indirect {
      let rawForm = try cursor.readULEB128()
      guard let theForm = Dwarf_Form(rawValue: Dwarf_Byte(rawForm)) else {
        throw DwarfError.badForm(rawForm)
      }
      form = theForm
    } else {
      form = theForm
    }

    switch form {
      case .DW_FORM_implicit_const:
        return .signedInt64(constantValue!)

      case .DW_FORM_addr:
        let address: UInt64
        switch addressSize {
          case 4:
            address = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
          case 8:
            address = maybeSwap(try cursor.read(as: UInt64.self))
          default:
            throw DwarfError.badAddressSize(addressSize)
        }
        return .address(address)
      case .DW_FORM_addrx, .DW_FORM_addrx1, .DW_FORM_addrx2,
           .DW_FORM_addrx3, .DW_FORM_addrx4:
        guard let addrSection = addrSection else {
          throw DwarfError.missingAddrSection
        }

        let ndx: UInt64
        switch form {
          case .DW_FORM_addrx:
            ndx = try cursor.readULEB128()
          case .DW_FORM_addrx1:
            ndx = UInt64(try cursor.read(as: UInt8.self))
          case .DW_FORM_addrx2:
            ndx = UInt64(maybeSwap(try cursor.read(as: UInt16.self)))
          case .DW_FORM_addrx3:
            let bytes = try cursor.read(as: (UInt8, UInt8, UInt8).self)
            ndx = threeByteToOffset(bytes)
          case .DW_FORM_addrx4:
            ndx = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
          default:
            fatalError("unreachable")
        }

        if !shouldFetchIndirect {
          return .indirectAddress(ndx)
        } else {
          guard let addrBase = unit?.addrBase else {
            throw DwarfError.missingAddrBase
          }

          let address: UInt64
          switch addressSize {
            case 4:
              address = UInt64(maybeSwap(
                                 try addrSection.fetch(from: ndx * 4 + addrBase,
                                                       as: UInt32.self)))
            case 8:
              address = maybeSwap(try addrSection.fetch(from: ndx * 8 + addrBase,
                                                        as: UInt64.self))
            default:
              throw DwarfError.badAddressSize(addressSize)
          }
          return .address(address)
        }
      case .DW_FORM_block:
        let length = try cursor.readULEB128()
        let bytes = try cursor.read(count: Int(length), as: UInt8.self)
        return .data(bytes)
      case .DW_FORM_block1:
        let length = try cursor.read(as: UInt8.self)
        let bytes = try cursor.read(count: Int(length), as: UInt8.self)
        return .data(bytes)
      case .DW_FORM_block2:
        let length = maybeSwap(try cursor.read(as: UInt16.self))
        let bytes = try cursor.read(count: Int(length), as: UInt8.self)
        return .data(bytes)
      case .DW_FORM_block4:
        let length = maybeSwap(try cursor.read(as: UInt32.self))
        let bytes = try cursor.read(count: Int(length), as: UInt8.self)
        return .data(bytes)

      case .DW_FORM_sdata:
        let data = try cursor.readSLEB128()
        return .signedInt64(data)

      case .DW_FORM_udata:
        let data = try cursor.readULEB128()
        return .unsignedInt64(data)

      case .DW_FORM_data1:
        let data = try cursor.read(as: UInt8.self)
        return .unsignedInt8(data)

      case .DW_FORM_data2:
        let data = maybeSwap(try cursor.read(as: UInt16.self))
        return .unsignedInt16(data)

      case .DW_FORM_data4:
        let data = maybeSwap(try cursor.read(as: UInt32.self))
        return .unsignedInt32(data)

      case .DW_FORM_data8:
        let data = maybeSwap(try cursor.read(as: UInt64.self))
        return .unsignedInt64(data)

      case .DW_FORM_data16:
        let data = try cursor.read(count: 16, as: UInt8.self)
        return .data(data)

      case .DW_FORM_exprloc:
        let length = try cursor.readULEB128()
        let bytes = try cursor.read(count: Int(length), as: UInt8.self)
        return .expression(bytes)

      case .DW_FORM_flag:
        let flag = try cursor.read(as: UInt8.self)
        return .flag(flag != 0)

      case .DW_FORM_flag_present:
        return .flag(true)

      case .DW_FORM_loclistx:
        let offset = try cursor.readULEB128()
        return .locationList(offset)

      case .DW_FORM_sec_offset:
        let offset: UInt64
        if isDwarf64 {
          offset = maybeSwap(try cursor.read(as: UInt64.self))
        } else {
          offset = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
        }
        return .sectionOffset(offset)

      case .DW_FORM_rnglistx:
        let offset = try cursor.readULEB128()
        return .rangeList(offset)

      case .DW_FORM_ref1, .DW_FORM_ref2, .DW_FORM_ref4, .DW_FORM_ref8,
           .DW_FORM_ref_udata:
        guard let baseOffset = unit?.baseOffset else {
          throw DwarfError.missingBaseOffset
        }

        let offset: Address
        switch form {
          case .DW_FORM_ref1:
            offset = UInt64(try cursor.read(as: UInt8.self))
          case .DW_FORM_ref2:
            offset = UInt64(maybeSwap(try cursor.read(as: UInt16.self)))
          case .DW_FORM_ref4:
            offset = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
          case .DW_FORM_ref8:
            offset = maybeSwap(try cursor.read(as: UInt64.self))
          case .DW_FORM_ref_udata:
            offset = try cursor.readULEB128()
          default:
            fatalError("unreachable")
        }
        return .reference(offset + baseOffset)

      case .DW_FORM_ref_addr:
        let offset: UInt64
        if isDwarf64 {
          offset = maybeSwap(try cursor.read(as: UInt64.self))
        } else {
          offset = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
        }
        return .reference(offset)

      case .DW_FORM_ref_sig8:
        let signature = try cursor.read(count: 8, as: UInt8.self)
        return .signature(signature)

      case .DW_FORM_ref_sup4:
        let offset = maybeSwap(try cursor.read(as: UInt32.self))
        return .supplementaryReference(Address(offset))

      case .DW_FORM_ref_sup8:
        let offset = maybeSwap(try cursor.read(as: UInt64.self))
        return .supplementaryReference(Address(offset))

      case .DW_FORM_string:
        guard let string = try cursor.readString() else {
          throw DwarfError.badString
        }
        return .string(string)

      case .DW_FORM_strp:
        guard let strSection = strSection else {
          throw DwarfError.missingStrSection
        }

        let offset: UInt64
        if isDwarf64 {
          offset = maybeSwap(try cursor.read(as: UInt64.self))
        } else {
          offset = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
        }

        if !shouldFetchIndirect {
          return .stringFromStrTab(offset)
        } else {
          guard let string = try strSection.fetchString(from: offset) else {
            throw DwarfError.badString
          }
          return .string(string)
        }

      case .DW_FORM_strp_sup:
        let offset: UInt64
        if isDwarf64 {
          offset = maybeSwap(try cursor.read(as: UInt64.self))
        } else {
          offset = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
        }
        return .supplementaryString(offset)

      case .DW_FORM_line_strp:
        guard let lineStrSection = lineStrSection else {
          throw DwarfError.missingLineStrSection
        }

        let offset: UInt64
        if isDwarf64 {
          offset = maybeSwap(try cursor.read(as: UInt64.self))
        } else {
          offset = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
        }

        if !shouldFetchIndirect {
          return .stringFromLineStrTab(offset)
        } else {
          guard let string = try lineStrSection.fetchString(from: offset) else {
            throw DwarfError.badString
          }
          return .string(string)
        }

      case .DW_FORM_strx,
           .DW_FORM_strx1, .DW_FORM_strx2, .DW_FORM_strx3,.DW_FORM_strx4:
        guard let strOffsetsSection = strOffsetsSection else {
          throw DwarfError.missingStrOffsetsSection
        }
        guard let strSection = strSection else {
          throw DwarfError.missingStrSection
        }

        let offset: UInt64
        switch form {
          case .DW_FORM_strx:
            offset = try cursor.readULEB128()
          case .DW_FORM_strx1:
            offset = UInt64(try cursor.read(as: UInt8.self))
          case .DW_FORM_strx2:
            offset = UInt64(maybeSwap(try cursor.read(as: UInt16.self)))
          case .DW_FORM_strx3:
            let bytes = try cursor.read(as: (UInt8, UInt8, UInt8).self)
            offset = threeByteToOffset(bytes)
          case .DW_FORM_strx4:
            offset = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
          default:
            fatalError("unreachable")
        }

        if !shouldFetchIndirect {
          return .stringViaStrOffsets(offset)
        } else {
          guard let strBase = unit?.strOffsetsBase else {
            throw DwarfError.missingStrOffsetsBase
          }

          let actualOffset: UInt64
          if isDwarf64 {
            actualOffset = maybeSwap(try strOffsetsSection.fetch(
                                       from: offset * 8 + strBase,
                                       as: UInt64.self))
          } else {
            actualOffset = UInt64(maybeSwap(try strOffsetsSection.fetch(
                                              from: offset * 4 + strBase,
                                              as: UInt32.self)))
          }

          guard let string = try strSection.fetchString(from: actualOffset)
          else {
            throw DwarfError.badString
          }
          return .string(string)
        }

      case .DW_FORM_indirect:
        // We should have handled this already
        throw DwarfError.doubleIndirectForm
      default:
        throw DwarfError.unknownForm(theForm)
    }
  }

  private func readDieAttributes(
    at cursor: inout ImageSourceCursor,
    unit: Unit,
    abbrevInfo: AbbrevInfo,
    shouldFetchIndirect: Bool
  ) throws -> [Dwarf_Attribute:DwarfValue] {
    var attributes: [Dwarf_Attribute:DwarfValue] = [:]

    for (attribute, form, constantValue) in abbrevInfo.attributes {
      attributes[attribute] = try read(form: form,
                                       at: &cursor,
                                       addressSize: unit.addressSize,
                                       isDwarf64: unit.isDwarf64,
                                       unit: unit,
                                       shouldFetchIndirect: shouldFetchIndirect,
                                       constantValue: constantValue)
    }

    return attributes
  }

  struct CallSiteInfo {
    var depth: Int
    var rawName: String?
    var name: String?
    var lowPC: Address
    var highPC: Address
    var filename: String
    var line: Int
    var column: Int
  }

  private func buildCallSiteInfo(
    depth: Int,
    unit: Unit,
    attributes: [Dwarf_Attribute:DwarfValue],
    _ fn: (CallSiteInfo) -> ()
  ) throws {
    guard let abstractOriginVal = attributes[.DW_AT_abstract_origin],
          let callFile = attributes[.DW_AT_call_file]?.uint64Value(),
          let callLine = attributes[.DW_AT_call_line]?.uint64Value(),
          let callColumn = attributes[.DW_AT_call_column]?.uint64Value(),
          case let .reference(abstractOrigin) = abstractOriginVal else {
      return
    }

    var cursor = ImageSourceCursor(source: infoSection,
                                   offset: abstractOrigin)
    var abbrev = try cursor.readULEB128()
    if abbrev == 0 {
      return
    }

    guard let abbrevInfo = unit.abbrevs[abbrev] else {
      throw DwarfError.missingAbbrev(abbrev)
    }

    let tag = abbrevInfo.tag

    if tag != .DW_TAG_subprogram {
      return
    }

    var refAttrs = try readDieAttributes(
      at: &cursor,
      unit: unit,
      abbrevInfo: abbrevInfo,
      shouldFetchIndirect: true
    )

    if let specificationVal = refAttrs[.DW_AT_specification],
       case let .reference(specification) = specificationVal {
      cursor = ImageSourceCursor(source: infoSection,
                                 offset: specification)
      abbrev = try cursor.readULEB128()
      if abbrev == 0 {
        return
      }

      guard let abbrevInfo = unit.abbrevs[abbrev] else {
        throw DwarfError.missingAbbrev(abbrev)
      }

      let tag = abbrevInfo.tag
      if tag != .DW_TAG_subprogram {
        return
      }

      refAttrs = try readDieAttributes(
        at: &cursor,
        unit: unit,
        abbrevInfo: abbrevInfo,
        shouldFetchIndirect: true
      )
    }

    var name: String? = nil
    var rawName: String? = nil

    if let nameVal = refAttrs[.DW_AT_name],
       case let .string(theName) = nameVal {
      name = theName
    }

    if let linkageNameVal = refAttrs[.DW_AT_linkage_name],
       case let .string(theRawName) = linkageNameVal {
      rawName = theRawName
    } else {
      rawName = name
    }

    var filename: String = "<unknown>"
    for info in lineNumberInfo {
      if info.baseOffset == unit.lineBase {
        filename = info.fullPathForFile(index: Int(callFile))
        break
      }
    }

    if let lowPCVal = attributes[.DW_AT_low_pc],
       let highPCVal = attributes[.DW_AT_high_pc],
       case let .address(lowPC) = lowPCVal {
      let highPC: Address
      if case let .address(highPCAddr) = highPCVal {
        highPC = highPCAddr
      } else if let highPCOffset = highPCVal.uint64Value() {
        highPC = lowPC + highPCOffset
      } else {
        return
      }

      fn(CallSiteInfo(
           depth: depth,
           rawName: rawName,
           name: name,
           lowPC: lowPC,
           highPC: highPC,
           filename: filename,
           line: Int(callLine),
           column: Int(callColumn)))
    } else if let rangeVal = attributes[.DW_AT_ranges],
              let rangesSection = rangesSection,
              case let .sectionOffset(offset) = rangeVal,
              unit.version < 5 {
      // We don't support .debug_rnglists at present (which is what we'd
      // have if unit.version is 5 or higher).
      var rangeCursor = ImageSourceCursor(source: rangesSection,
                                          offset: offset)
      var rangeBase: Address = unit.lowPC ?? 0

      while true {
        let beginning: Address
        let ending: Address

        switch unit.addressSize {
          case 4:
            beginning = UInt64(maybeSwap(try rangeCursor.read(as: UInt32.self)))
            ending = UInt64(maybeSwap(try rangeCursor.read(as: UInt32.self)))
            if beginning == 0xffffffff {
              rangeBase = ending
              continue
            }
          case 8:
            beginning = maybeSwap(try rangeCursor.read(as: UInt64.self))
            ending = maybeSwap(try rangeCursor.read(as: UInt64.self))
            if beginning == 0xffffffffffffffff {
              rangeBase = ending
              continue
            }
          default:
            throw DwarfError.badAddressSize(unit.addressSize)
        }

        if beginning == 0 && ending == 0 {
          break
        }

        fn(CallSiteInfo(
             depth: depth,
             rawName: rawName,
             name: name,
             lowPC: beginning + rangeBase,
             highPC: ending + rangeBase,
             filename: filename,
             line: Int(callLine),
             column: Int(callColumn)))
      }
    }
  }

  struct FunctionInfo {
    var depth: Int
    var rawName: String
    var name: String?
    var lowPC: Address
    var highPC: Address
  }

  private func buildFunctionInfo (
    depth: Int,
    unit: Unit,
    attributes: [Dwarf_Attribute:DwarfValue],
    _ fn: (FunctionInfo) -> ()
  ) throws {
    var name: String? = nil
    var refAttrs = attributes

    if let specificationVal = refAttrs[.DW_AT_specification],
       case let .reference(specification) = specificationVal {
      var cursor = ImageSourceCursor(source: infoSection,
                                     offset: specification)
      let abbrev = try cursor.readULEB128()
      if abbrev == 0 {
        return
      }

      guard let abbrevInfo = unit.abbrevs[abbrev] else {
        throw DwarfError.missingAbbrev(abbrev)
      }

      let tag = abbrevInfo.tag
      if tag != .DW_TAG_subprogram {
        return
      }

      refAttrs = try readDieAttributes(
        at: &cursor,
        unit: unit,
        abbrevInfo: abbrevInfo,
        shouldFetchIndirect: true
      )
    }

    if let nameVal = refAttrs[.DW_AT_name],
       case let .string(theName) = nameVal {
      name = theName
    }

    let rawName: String

    if let linkageNameVal = refAttrs[.DW_AT_linkage_name],
       case let .string(theRawName) = linkageNameVal {
      rawName = theRawName
    } else {
      rawName = name ?? "<unknown>"
    }

    if let lowPCVal = attributes[.DW_AT_low_pc],
       let highPCVal = attributes[.DW_AT_high_pc],
       case let .address(lowPC) = lowPCVal {
      let highPC: Address
      if case let .address(highPCAddr) = highPCVal {
        highPC = highPCAddr
      } else if let highPCOffset = highPCVal.uint64Value() {
        highPC = lowPC + highPCOffset
      } else {
        return
      }

      fn(FunctionInfo(
           depth: depth,
           rawName: rawName,
           name: name,
           lowPC: lowPC,
           highPC: highPC))
    }
  }

  private lazy var _lazyInfo: (callSites: [CallSiteInfo],
                               functions: [FunctionInfo]) = _scanUnits()

  var inlineCallSites: [CallSiteInfo] {
    _lazyInfo.callSites
  }

  var functions: [FunctionInfo] {
    _lazyInfo.functions
  }

  func lookupInlineCallSites(
    at address: Address
  ) -> ArraySlice<CallSiteInfo> {
    var min = 0
    var max = inlineCallSites.count

    while min < max {
      let mid = min + (max - min) / 2
      let callSite = inlineCallSites[mid]

      if callSite.lowPC <= address && callSite.highPC > address {
        var first = mid, last = mid
        while first > 0
                && inlineCallSites[first - 1].lowPC <= address
                && inlineCallSites[first - 1].highPC > address {
          first -= 1
        }
        while last < inlineCallSites.count - 1
                && inlineCallSites[last + 1].lowPC <= address
                && inlineCallSites[last + 1].highPC > address {
          last += 1
        }

        return inlineCallSites[first...last]
      } else if callSite.highPC <= address {
        min = mid + 1
      } else if callSite.lowPC > address {
        max = mid
      }
    }

    return []
  }

  func lookupFunction(
    at address: Address
  ) -> FunctionInfo? {
    var min = 0, max = functions.count
    while min < max {
      let mid = min + (max - min) / 2
      let function = functions[mid]

      if function.lowPC <= address && function.highPC > address {
        return function
      } else if function.highPC <= address {
        min = mid + 1
      } else if function.lowPC > address {
        max = mid
      }
    }
    return nil
  }

  typealias SourceLocation = SymbolicatedBacktrace.SourceLocation

  func sourceLocation(
    for address: Address
  ) throws -> SourceLocation? {
    var result: SourceLocation? = nil
    var prevState: DwarfLineNumberState? = nil

    for ndx in 0..<lineNumberInfo.count {
      var info = lineNumberInfo[ndx]
      try info.executeProgram { (state, done) in
        if let oldState = prevState,
           address >= oldState.address && address < state.address {
          result = SourceLocation(
            path: oldState.path,
            line: oldState.line,
            column: oldState.column
          )
          done = true
        }

        if state.endSequence {
          prevState = nil
        } else {
          prevState = state
        }
      }
    }

    return result
  }

  private func _scanUnits()
    -> (callSites: [CallSiteInfo],
        functions: [FunctionInfo]) {

    var callSites: [CallSiteInfo] = []
    var functions: [FunctionInfo] = []

    for unit in units {
      do {
        var cursor = ImageSourceCursor(source: infoSection,
                                       offset: unit.dieBounds.base)
        var depth = 0

        while cursor.pos < unit.dieBounds.end {
          let abbrev = try cursor.readULEB128()

          if abbrev == 0 {
            depth -= 1
            if depth == 0 {
              break
            }
            continue
          }

          guard let abbrevInfo = unit.abbrevs[abbrev] else {
            throw DwarfError.missingAbbrev(abbrev)
          }

          let tag = abbrevInfo.tag

          let attributes = try readDieAttributes(
            at: &cursor,
            unit: unit,
            abbrevInfo: abbrevInfo,
            shouldFetchIndirect: (tag == .DW_TAG_inlined_subroutine
                                    || tag == .DW_TAG_subprogram)
          )

          if tag == .DW_TAG_inlined_subroutine {
            try buildCallSiteInfo(depth: depth,
                                  unit: unit,
                                  attributes: attributes) {
              callSites.append($0)
            }
          } else if tag == .DW_TAG_subprogram {
            try buildFunctionInfo(depth: depth,
                                  unit: unit,
                                  attributes: attributes) {
              functions.append($0)
            }
          }

          if abbrevInfo.hasChildren {
            depth += 1
          }
        }
      } catch {
        let name: String
        if let value = unit.attributes[.DW_AT_name],
           case let .string(theName) = value {
          name = theName
        } else {
          name = "<unknown at \(hex(unit.baseOffset))>"
        }
        swift_reportWarning(0,
                            """
                              swift-runtime: warning: unable to fetch inline \
                              frame data for DWARF unit \(name): \(error)\n
                              """)
      }
    }

    callSites.sort(
      by: { (a, b) in
        a.lowPC < b.lowPC || (a.lowPC == b.lowPC) && a.depth > b.depth
      })

    functions.sort(
      by: { (a, b) in
        a.lowPC < b.lowPC || (a.lowPC == b.lowPC) && a.depth > b.depth
      })

    return (callSites: callSites, functions: functions)
  }

}

struct DwarfFileInfo {
  var path: String
  var directoryIndex: Int?
  var timestamp: Int?
  var size: UInt64?
  var md5sum: [UInt8]?
}

struct DwarfLineNumberState: CustomStringConvertible {
  typealias Address = UInt64

  var address: Address
  var opIndex: UInt
  var file: Int
  var path: String
  var line: Int
  var column: Int
  var isStmt: Bool
  var basicBlock: Bool
  var endSequence: Bool
  var prologueEnd: Bool
  var epilogueBegin: Bool
  var isa: UInt
  var discriminator: UInt

  var description: String {
    var flags: [String] = []
    if isStmt {
      flags.append("is_stmt")
    }
    if basicBlock {
      flags.append("basic_block")
    }
    if endSequence {
      flags.append("end_sequence")
    }
    if prologueEnd {
      flags.append("prologue_end")
    }
    if epilogueBegin {
      flags.append("epilogue_begin")
    }

    let flagsString = flags.joined(separator:" ")

    return """
      \(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \
      \(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString)
      """
  }
}

@available(BacktracingDT 6.2, *)
struct DwarfLineNumberInfo {
  typealias Address = UInt64

  var pathSeparator: String

  var baseOffset: Address
  var version: Int
  var addressSize: Int?
  var selectorSize: Int?
  var headerLength: UInt64
  var minimumInstructionLength: UInt
  var maximumOpsPerInstruction: UInt
  var defaultIsStmt: Bool
  var lineBase: Int8
  var lineRange: UInt8
  var opcodeBase: UInt8
  var standardOpcodeLengths: [UInt64]
  var directories: [String] = []
  var files: [DwarfFileInfo] = []
  var program: ImageSource
  var shouldSwap: Bool

  /// Compute the full path for a file, given its index in the file table.
  func fullPathForFile(index: Int) -> String {
    if index >= files.count {
      return "<unknown>"
    }

    let info = files[index]
    if info.path.hasPrefix(pathSeparator) {
      return info.path
    }

    let dirName: String
    if let dirIndex = info.directoryIndex,
       dirIndex < directories.count {
      dirName = directories[dirIndex]
    } else {
      dirName = "<unknown>"
    }

    return "\(dirName)\(pathSeparator)\(info.path)"
  }

  /// Execute the line number program, calling a closure for every line
  /// table entry.
  mutating func executeProgram(
    line: (DwarfLineNumberState, inout Bool) -> ()
  ) throws {
    let end = program.bytes.count
    var cursor = ImageSourceCursor(source: program)

    func maybeSwap<T: FixedWidthInteger>(_ x: T) -> T {
      if shouldSwap {
        return x.byteSwapped
      }
      return x
    }

    // Table 6.4: Line number program initial state
    let initialState = DwarfLineNumberState(
      address: 0,
      opIndex: 0,
      file: 1,
      path: fullPathForFile(index: 1),
      line: 1,
      column: 0,
      isStmt: defaultIsStmt,
      basicBlock: false,
      endSequence: false,
      prologueEnd: false,
      epilogueBegin: false,
      isa: 0,
      discriminator: 0
    )

    var state = initialState

    // Flag to allow fast exit
    var done = false

    while !done && cursor.pos < end {
      let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self)

      if opcode.rawValue >= opcodeBase {
        // Special opcode
        let adjustedOpcode = UInt(opcode.rawValue - opcodeBase)
        let advance = adjustedOpcode / UInt(lineRange)
        let lineAdvance = adjustedOpcode % UInt(lineRange)
        let instrAdvance
          = (state.opIndex + advance) / maximumOpsPerInstruction
        let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
        state.address += Address(instrAdvance)
        state.opIndex = newOp
        state.line += Int(lineBase) + Int(lineAdvance)

        line(state, &done)

        state.discriminator = 0
        state.basicBlock = false
        state.prologueEnd = false
        state.epilogueBegin = false
      } else if opcode == .DW_LNS_extended {
        // Extended opcode
        let length = try cursor.readULEB128()
        let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self)

        switch opcode {
          case .DW_LNE_end_sequence:
            state.endSequence = true
            line(state, &done)
            state = initialState
          case .DW_LNE_set_address:
            let address: UInt64
            guard let addressSize = addressSize else {
              throw DwarfError.unspecifiedAddressSize
            }
            switch addressSize {
              case 4:
                address = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
              case 8:
                address = maybeSwap(try cursor.read(as: UInt64.self))
              default:
                throw DwarfError.badAddressSize(addressSize)
            }
            state.address = Address(address)
              case .DW_LNE_define_file:
                guard let path = try cursor.readString() else {
                  throw DwarfError.badString
                }
                let directoryIndex = try cursor.readULEB128()
                let timestamp = try cursor.readULEB128()
                let size = try cursor.readULEB128()
                files.append(DwarfFileInfo(
                               path: path,
                               directoryIndex: Int(directoryIndex),
                               timestamp: timestamp != 0 ? Int(timestamp) : nil,
                               size: size != 0 ? size : nil,
                               md5sum: nil
                             ))
              case .DW_LNE_set_discriminator:
                let discriminator = try cursor.readULEB128()
                state.discriminator = UInt(discriminator)
              default:
                cursor.pos += length - 1
        }
      } else {
        // Standard opcode
        switch opcode {
          case .DW_LNS_copy:
            line(state, &done)
            state.discriminator = 0
            state.basicBlock = false
            state.prologueEnd = false
            state.epilogueBegin = false
          case .DW_LNS_advance_pc:
            let advance = UInt(try cursor.readULEB128())
            let instrAdvance
              = (state.opIndex + advance) / maximumOpsPerInstruction
            let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
            state.address += Address(instrAdvance)
            state.opIndex = newOp
          case .DW_LNS_advance_line:
            let advance = try cursor.readSLEB128()
            state.line += Int(advance)
          case .DW_LNS_set_file:
            let file = Int(try cursor.readULEB128())
            state.file = file
            state.path = fullPathForFile(index: state.file)
          case .DW_LNS_set_column:
            let column = Int(try cursor.readULEB128())
            state.column = column
          case .DW_LNS_negate_stmt:
            state.isStmt = !state.isStmt
          case .DW_LNS_set_basic_block:
            state.basicBlock = true
          case .DW_LNS_const_add_pc:
            let adjustedOpcode = UInt(255 - opcodeBase)
            let advance = adjustedOpcode / UInt(lineRange)
            let instrAdvance
              = (state.opIndex + advance) / maximumOpsPerInstruction
            let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
            state.address += Address(instrAdvance)
            state.opIndex = newOp
          case .DW_LNS_fixed_advance_pc:
            let advance = try cursor.read(as: Dwarf_Half.self)
            state.address += Address(advance)
            state.opIndex = 0
          case .DW_LNS_set_prologue_end:
            state.prologueEnd = true
          case .DW_LNS_set_epilogue_begin:
            state.epilogueBegin = true
          case .DW_LNS_set_isa:
            let isa = UInt(try cursor.readULEB128())
            state.isa = isa
          default:
            // Skip this unknown opcode
            let length = standardOpcodeLengths[Int(opcode.rawValue)]
            for _ in 0..<length {
              _ = try cursor.readULEB128()
            }
        }
      }
    }
  }
}

// .. Testing ..................................................................

@_spi(DwarfTest)
@available(BacktracingDT 6.2, *)
public func testDwarfReaderFor(path: String) -> Bool {
  guard let source = try? ImageSource(path: path) else {
    print("\(path) was not accessible")
    return false
  }

  if let elfImage = try? Elf32Image(source: source) {
    print("\(path) is a 32-bit ELF image")

    var reader: DwarfReader<Elf32Image>
    do {
      reader = try DwarfReader(source: elfImage)
    } catch {
      print("Unable to create reader - \(error)")
      return false
    }

    print("Units:")
    print(reader.units)

    print("Call Sites:")
    print(reader.inlineCallSites)
    return true
  } else if let elfImage = try? Elf64Image(source: source) {
    print("\(path) is a 64-bit ELF image")

    var reader: DwarfReader<Elf64Image>
    do {
      reader = try DwarfReader(source: elfImage)
    } catch {
      print("Unable to create reader - \(error)")
      return false
    }

    print("Units:")
    for unit in reader.units {
      if let value = unit.attributes[.DW_AT_name],
         case let .string(name) = value {
        print("  \(name)")
      } else {
        print("  <unnamed>")
      }
    }

    print("Call Sites:")
    print(reader.inlineCallSites)
    return true
  } else {
    print("\(path) is not an ELF image")
    return false
  }
}
