//===--- Elf.swift - ELF 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 ELF structures and provides types for working with ELF
// images on disk and in memory.
//
//===----------------------------------------------------------------------===//

// ###FIXME: We shouldn't really use String for paths.

import Swift

#if os(anyAppleOS)
internal import Darwin
#elseif os(Windows)
internal import WinSDK
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
@_implementationOnly import BacktracingImpl.ImageFormats.Elf

// .. Use *our* Elf definitions ................................................

// On Linux there is an <elf.h> header that can be dragged in via Glibc, which
// contains slightly different definitions that don't work so well with Swift.
// We don't want those, so we're using C++ interop and putting ours in the
// swift::runtime namespace.

// This means we need a lot of typealiases, and also aliases for untyped
// constants.

typealias Elf_Byte   = swift.runtime.Elf_Byte
typealias Elf_Half   = swift.runtime.Elf_Half
typealias Elf_Word   = swift.runtime.Elf_Word
typealias Elf_Xword  = swift.runtime.Elf_Xword
typealias Elf_Sword  = swift.runtime.Elf_Sword
typealias Elf_Sxword = swift.runtime.Elf_Sxword

typealias Elf32_Byte   = swift.runtime.Elf32_Byte
typealias Elf32_Half   = swift.runtime.Elf32_Half
typealias Elf32_Word   = swift.runtime.Elf32_Word
typealias Elf32_Sword  = swift.runtime.Elf32_Sword

typealias Elf64_Byte   = swift.runtime.Elf64_Byte
typealias Elf64_Half   = swift.runtime.Elf64_Half
typealias Elf64_Word   = swift.runtime.Elf64_Word
typealias Elf64_Xword  = swift.runtime.Elf64_Xword
typealias Elf64_Sword  = swift.runtime.Elf64_Sword
typealias Elf64_Sxword = swift.runtime.Elf64_Sxword

typealias Elf_Ehdr_Type    = swift.runtime.Elf_Ehdr_Type
typealias Elf_Ehdr_Machine = swift.runtime.Elf_Ehdr_Machine
typealias Elf_Ehdr_Version = swift.runtime.Elf_Ehdr_Version

let EI_MAG0       = swift.runtime.EI_MAG0
let EI_MAG1       = swift.runtime.EI_MAG1
let EI_MAG2       = swift.runtime.EI_MAG2
let EI_MAG3       = swift.runtime.EI_MAG3
let EI_CLASS      = swift.runtime.EI_CLASS
let EI_DATA       = swift.runtime.EI_DATA
let EI_VERSION    = swift.runtime.EI_VERSION
let EI_OSABI      = swift.runtime.EI_OSABI
let EI_ABIVERSION = swift.runtime.EI_ABIVERSION
let EI_PAD        = swift.runtime.EI_PAD

let ELFMAG0 = swift.runtime.ELFMAG0
let ELFMAG1 = swift.runtime.ELFMAG1
let ELFMAG2 = swift.runtime.ELFMAG2
let ELFMAG3 = swift.runtime.ELFMAG3

typealias Elf_Ehdr_Class = swift.runtime.Elf_Ehdr_Class
typealias Elf_Ehdr_Data  = swift.runtime.Elf_Ehdr_Data
typealias Elf_Ehdr_OsAbi = swift.runtime.Elf_Ehdr_OsAbi

let SHN_UNDEF     = swift.runtime.SHN_UNDEF
let SHN_LORESERVE = swift.runtime.SHN_LORESERVE
let SHN_LOPROC    = swift.runtime.SHN_LOPROC
let SHN_HIPROC    = swift.runtime.SHN_HIPROC
let SHN_LOOS      = swift.runtime.SHN_LOOS
let SHN_HIOS      = swift.runtime.SHN_HIOS
let SHN_ABS       = swift.runtime.SHN_ABS
let SHN_COMMON    = swift.runtime.SHN_COMMON
let SHN_XINDEX    = swift.runtime.SHN_XINDEX
let SHN_HIRESERVE = swift.runtime.SHN_HIRESERVE

typealias Elf_Shdr_Type = swift.runtime.Elf_Shdr_Type

let SHF_WRITE            = swift.runtime.SHF_WRITE
let SHF_ALLOC            = swift.runtime.SHF_ALLOC
let SHF_EXECINSTR        = swift.runtime.SHF_EXECINSTR
let SHF_MERGE            = swift.runtime.SHF_MERGE
let SHF_STRINGS          = swift.runtime.SHF_STRINGS
let SHF_INFO_LINK        = swift.runtime.SHF_INFO_LINK
let SHF_LINK_ORDER       = swift.runtime.SHF_LINK_ORDER
let SHF_OS_NONCONFORMING = swift.runtime.SHF_OS_NONCONFORMING
let SHF_GROUP            = swift.runtime.SHF_GROUP
let SHF_TLS              = swift.runtime.SHF_TLS
let SHF_COMPRESSED       = swift.runtime.SHF_COMPRESSED
let SHF_MASKOS           = swift.runtime.SHF_MASKOS
let SHF_MASKPROC         = swift.runtime.SHF_MASKPROC

let GRP_COMDAT   = swift.runtime.GRP_COMDAT
let GRP_MASKOS   = swift.runtime.GRP_MASKOS
let GRP_MASKPROC = swift.runtime.GRP_MASKPROC

typealias Elf_Chdr_Type = swift.runtime.Elf_Chdr_Type

typealias Elf_Sym_Binding    = swift.runtime.Elf_Sym_Binding
typealias Elf_Sym_Type       = swift.runtime.Elf_Sym_Type
typealias Elf_Sym_Visibility = swift.runtime.Elf_Sym_Visibility

typealias Elf_Phdr_Type  = swift.runtime.Elf_Phdr_Type
typealias Elf_Phdr_Flags = swift.runtime.Elf_Phdr_Flags

let PF_X = swift.runtime.PF_X
let PF_W = swift.runtime.PF_W
let PF_R = swift.runtime.PF_R

let PF_MASKOS   = swift.runtime.PF_MASKOS
let PF_MASKPROC = swift.runtime.PF_MASKPROC

let DT_NULL            = swift.runtime.DT_NULL
let DT_NEEDED          = swift.runtime.DT_NEEDED
let DT_PLTRELSZ        = swift.runtime.DT_PLTRELSZ
let DT_PLTGOT          = swift.runtime.DT_PLTGOT
let DT_HASH            = swift.runtime.DT_HASH
let DT_STRTAB          = swift.runtime.DT_STRTAB
let DT_SYMTAB          = swift.runtime.DT_SYMTAB
let DT_RELA            = swift.runtime.DT_RELA
let DT_RELASZ          = swift.runtime.DT_RELASZ
let DT_RELAENT         = swift.runtime.DT_RELAENT
let DT_STRSZ           = swift.runtime.DT_STRSZ
let DT_SYMENT          = swift.runtime.DT_SYMENT
let DT_INIT            = swift.runtime.DT_INIT
let DT_FINI            = swift.runtime.DT_FINI
let DT_SONAME          = swift.runtime.DT_SONAME
let DT_RPATH           = swift.runtime.DT_RPATH
let DT_SYMBOLIC        = swift.runtime.DT_SYMBOLIC
let DT_REL             = swift.runtime.DT_REL
let DT_RELSZ           = swift.runtime.DT_RELSZ
let DT_RELENT          = swift.runtime.DT_RELENT
let DT_PLTREL          = swift.runtime.DT_PLTREL
let DT_DEBUG           = swift.runtime.DT_DEBUG
let DT_TEXTREL         = swift.runtime.DT_TEXTREL
let DT_JMPREL          = swift.runtime.DT_JMPREL
let DT_BIND_NOW        = swift.runtime.DT_BIND_NOW
let DT_INIT_ARRAY      = swift.runtime.DT_INIT_ARRAY
let DT_FINI_ARRAY      = swift.runtime.DT_FINI_ARRAY
let DT_INIT_ARRAYSZ    = swift.runtime.DT_INIT_ARRAYSZ
let DT_FINI_ARRAYSZ    = swift.runtime.DT_FINI_ARRAYSZ
let DT_RUNPATH         = swift.runtime.DT_RUNPATH
let DT_FLAGS           = swift.runtime.DT_FLAGS
let DT_ENCODING        = swift.runtime.DT_ENCODING
let DT_PREINIT_ARRAY   = swift.runtime.DT_PREINIT_ARRAY
let DT_PREINIT_ARRAYSZ = swift.runtime.DT_PREINIT_ARRAYSZ
let DT_LOOS            = swift.runtime.DT_LOOS
let DT_HIOS            = swift.runtime.DT_HIOS
let DT_LOPROC          = swift.runtime.DT_LOPROC
let DT_HIPROC          = swift.runtime.DT_HIPROC

let DF_ORIGIN     = swift.runtime.DF_ORIGIN
let DF_SYMBOLIC   = swift.runtime.DF_SYMBOLIC
let DF_TEXTREL    = swift.runtime.DF_TEXTREL
let DF_BIND_NOW   = swift.runtime.DF_BIND_NOW
let DF_STATIC_TLS = swift.runtime.DF_STATIC_TLS

let NT_GNU_ABI_TAG         = swift.runtime.NT_GNU_ABI_TAG
let NT_GNU_HWCAP           = swift.runtime.NT_GNU_HWCAP
let NT_GNU_BUILD_ID        = swift.runtime.NT_GNU_BUILD_ID
let NT_GNU_GOLD_VERSION    = swift.runtime.NT_GNU_GOLD_VERSION
let NT_GNU_PROPERTY_TYPE_0 = swift.runtime.NT_GNU_PROPERTY_TYPE_0

typealias Elf32_Ehdr = swift.runtime.Elf32_Ehdr
typealias Elf64_Ehdr = swift.runtime.Elf64_Ehdr

typealias Elf32_Shdr = swift.runtime.Elf32_Shdr
typealias Elf64_Shdr = swift.runtime.Elf64_Shdr

typealias Elf32_Chdr = swift.runtime.Elf32_Chdr
typealias Elf64_Chdr = swift.runtime.Elf64_Chdr

typealias Elf32_Sym = swift.runtime.Elf32_Sym
typealias Elf64_Sym = swift.runtime.Elf64_Sym

let ELF32_ST_BIND       = swift.runtime.ELF32_ST_BIND
let ELF32_ST_TYPE       = swift.runtime.ELF32_ST_TYPE
let ELF32_ST_INFO       = swift.runtime.ELF32_ST_INFO
let ELF32_ST_VISIBILITY = swift.runtime.ELF32_ST_VISIBILITY

let ELF64_ST_BIND       = swift.runtime.ELF64_ST_BIND
let ELF64_ST_TYPE       = swift.runtime.ELF64_ST_TYPE
let ELF64_ST_INFO       = swift.runtime.ELF64_ST_INFO
let ELF64_ST_VISIBILITY = swift.runtime.ELF64_ST_VISIBILITY

typealias Elf32_Rel  = swift.runtime.Elf32_Rel
typealias Elf32_Rela = swift.runtime.Elf32_Rela
typealias Elf64_Rel  = swift.runtime.Elf64_Rel
typealias Elf64_Rela = swift.runtime.Elf64_Rela

let ELF32_R_SYM  = swift.runtime.ELF32_R_SYM
let ELF32_R_TYPE = swift.runtime.ELF32_R_TYPE
let ELF32_R_INFO = swift.runtime.ELF32_R_INFO

let ELF64_R_SYM  = swift.runtime.ELF64_R_SYM
let ELF64_R_TYPE = swift.runtime.ELF64_R_TYPE
let ELF64_R_INFO = swift.runtime.ELF64_R_INFO

typealias Elf32_Phdr = swift.runtime.Elf32_Phdr
typealias Elf64_Phdr = swift.runtime.Elf64_Phdr

typealias Elf32_Nhdr = swift.runtime.Elf32_Nhdr
typealias Elf64_Nhdr = swift.runtime.Elf64_Nhdr

typealias Elf32_Dyn = swift.runtime.Elf32_Dyn
typealias Elf64_Dyn = swift.runtime.Elf64_Dyn

typealias Elf32_Hash = swift.runtime.Elf32_Hash
typealias Elf64_Hash = swift.runtime.Elf64_Hash

let elf_hash = swift.runtime.elf_hash

// .. Utilities ................................................................

private let crc32Table: [UInt32] = [
  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
  0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
  0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
  0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
  0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
  0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
  0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
  0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
  0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
  0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
  0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
  0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
  0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
  0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
  0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
  0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
  0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
  0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
  0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
  0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
  0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
  0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
  0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
  0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
  0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
  0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
  0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
  0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
  0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
  0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
  0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
]

private func updateCrc(_ crc: UInt32,
                       _ bytes: UnsafeRawBufferPointer) -> UInt32 {
  var theCrc = ~crc
  for byte in bytes {
    theCrc = crc32Table[Int(UInt8(truncatingIfNeeded: theCrc)
                              ^ byte)] ^ (theCrc >> 8)
  }
  return ~theCrc
}

// .. Byte swapping ............................................................

extension Elf32_Ehdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Ehdr(
      e_ident: e_ident,
      e_type: Elf_Ehdr_Type(rawValue: e_type.rawValue.byteSwapped)!,
      e_machine: Elf_Ehdr_Machine(rawValue: e_machine.rawValue.byteSwapped)!,
      e_version: Elf_Ehdr_Version(rawValue: e_version.rawValue.byteSwapped)!,
      e_entry: e_entry.byteSwapped,
      e_phoff: e_phoff.byteSwapped,
      e_shoff: e_shoff.byteSwapped,
      e_flags: e_flags.byteSwapped,
      e_ehsize: e_ehsize.byteSwapped,
      e_phentsize: e_phentsize.byteSwapped,
      e_phnum: e_phnum.byteSwapped,
      e_shentsize: e_shentsize.byteSwapped,
      e_shnum: e_shnum.byteSwapped,
      e_shstrndx: e_shstrndx.byteSwapped
    )
  }
}

extension Elf64_Ehdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Ehdr(
      e_ident: e_ident,
      e_type: Elf_Ehdr_Type(rawValue: e_type.rawValue.byteSwapped)!,
      e_machine: Elf_Ehdr_Machine(rawValue: e_machine.rawValue.byteSwapped)!,
      e_version: Elf_Ehdr_Version(rawValue: e_version.rawValue.byteSwapped)!,
      e_entry: e_entry.byteSwapped,
      e_phoff: e_phoff.byteSwapped,
      e_shoff: e_shoff.byteSwapped,
      e_flags: e_flags.byteSwapped,
      e_ehsize: e_ehsize.byteSwapped,
      e_phentsize: e_phentsize.byteSwapped,
      e_phnum: e_phnum.byteSwapped,
      e_shentsize: e_shentsize.byteSwapped,
      e_shnum: e_shnum.byteSwapped,
      e_shstrndx: e_shstrndx.byteSwapped
    )
  }
}

extension Elf32_Shdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Shdr(
      sh_name: sh_name.byteSwapped,
      sh_type: Elf_Shdr_Type(rawValue: sh_type.rawValue.byteSwapped)!,
      sh_flags: sh_flags.byteSwapped,
      sh_addr: sh_addr.byteSwapped,
      sh_offset: sh_offset.byteSwapped,
      sh_size: sh_size.byteSwapped,
      sh_link: sh_link.byteSwapped,
      sh_info: sh_info.byteSwapped,
      sh_addralign: sh_addralign.byteSwapped,
      sh_entsize: sh_entsize.byteSwapped
    )
  }
}

extension Elf64_Shdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Shdr(
      sh_name: sh_name.byteSwapped,
      sh_type: Elf_Shdr_Type(rawValue: sh_type.rawValue.byteSwapped)!,
      sh_flags: sh_flags.byteSwapped,
      sh_addr: sh_addr.byteSwapped,
      sh_offset: sh_offset.byteSwapped,
      sh_size: sh_size.byteSwapped,
      sh_link: sh_link.byteSwapped,
      sh_info: sh_info.byteSwapped,
      sh_addralign: sh_addralign.byteSwapped,
      sh_entsize: sh_entsize.byteSwapped
    )
  }
}

protocol Elf_Chdr: ByteSwappable {
  associatedtype Size: FixedWidthInteger

  init()

  var ch_type: Elf_Chdr_Type { get set }
  var ch_size: Size { get set }
  var ch_addralign: Size { get set }
}

extension Elf32_Chdr: Elf_Chdr {
  var byteSwapped: Self {
    return Elf32_Chdr(
      ch_type: Elf_Chdr_Type(rawValue: ch_type.rawValue.byteSwapped)!,
      ch_size: ch_size.byteSwapped,
      ch_addralign: ch_addralign.byteSwapped
    )
  }
}

extension Elf64_Chdr: Elf_Chdr {
  var byteSwapped: Self {
    return Elf64_Chdr(
      ch_type: Elf_Chdr_Type(rawValue: ch_type.rawValue.byteSwapped)!,
      ch_reserved: ch_reserved.byteSwapped,
      ch_size: ch_size.byteSwapped,
      ch_addralign: ch_addralign.byteSwapped
    )
  }
}

extension Elf_Chdr_Type: ByteSwappable {
  var byteSwapped: Self {
    return Elf_Chdr_Type(rawValue: rawValue.byteSwapped)!
  }
}

extension Elf32_Sym: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Sym(
      st_name: st_name.byteSwapped,
      st_value: st_value.byteSwapped,
      st_size: st_size.byteSwapped,
      st_info: st_info.byteSwapped,
      st_other: st_other.byteSwapped,
      st_shndx: st_shndx.byteSwapped
    )
  }
}

extension Elf64_Sym: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Sym(
      st_name: st_name.byteSwapped,
      st_info: st_info.byteSwapped,
      st_other: st_other.byteSwapped,
      st_shndx: st_shndx.byteSwapped,
      st_value: st_value.byteSwapped,
      st_size: st_size.byteSwapped
    )
  }
}

extension Elf32_Rel: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Rel(
      r_offset: r_offset.byteSwapped,
      r_info: r_info.byteSwapped
    )
  }
}

extension Elf32_Rela: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Rela(
      r_offset: r_offset.byteSwapped,
      r_info: r_info.byteSwapped,
      r_addend: r_addend.byteSwapped
    )
  }
}

extension Elf64_Rel: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Rel(
      r_offset: r_offset.byteSwapped,
      r_info: r_info.byteSwapped
    )
  }
}

extension Elf64_Rela: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Rela(
      r_offset: r_offset.byteSwapped,
      r_info: r_info.byteSwapped,
      r_addend: r_addend.byteSwapped
    )
  }
}

extension Elf32_Phdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Phdr(
      p_type: Elf_Phdr_Type(rawValue: p_type.rawValue.byteSwapped)!,
      p_offset: p_offset.byteSwapped,
      p_vaddr: p_vaddr.byteSwapped,
      p_paddr: p_paddr.byteSwapped,
      p_filesz: p_filesz.byteSwapped,
      p_memsz: p_memsz.byteSwapped,
      p_flags: p_flags.byteSwapped,
      p_align: p_align.byteSwapped
    )
  }
}

extension Elf64_Phdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Phdr(
      p_type: Elf_Phdr_Type(rawValue: p_type.rawValue.byteSwapped)!,
      p_flags: p_flags.byteSwapped,
      p_offset: p_offset.byteSwapped,
      p_vaddr: p_vaddr.byteSwapped,
      p_paddr: p_paddr.byteSwapped,
      p_filesz: p_filesz.byteSwapped,
      p_memsz: p_memsz.byteSwapped,
      p_align: p_align.byteSwapped
    )
  }
}

extension Elf32_Nhdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Nhdr(
      n_namesz: n_namesz.byteSwapped,
      n_descsz: n_descsz.byteSwapped,
      n_type: n_type.byteSwapped
    )
  }
}

extension Elf64_Nhdr: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Nhdr(
      n_namesz: n_namesz.byteSwapped,
      n_descsz: n_descsz.byteSwapped,
      n_type: n_type.byteSwapped
    )
  }
}

extension Elf32_Dyn: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Dyn(
      d_tag: d_tag.byteSwapped,
      d_un: .init(d_val: d_un.d_val.byteSwapped)
    )
  }
}

extension Elf64_Dyn: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Dyn(
      d_tag: d_tag.byteSwapped,
      d_un: .init(d_val: d_un.d_val.byteSwapped)
    )
  }
}

extension Elf32_Hash: ByteSwappable {
  var byteSwapped: Self {
    return Elf32_Hash(
      h_nbucket: h_nbucket.byteSwapped,
      h_nchain: h_nchain.byteSwapped
    )
  }
}

extension Elf64_Hash: ByteSwappable {
  var byteSwapped: Self {
    return Elf64_Hash(
      h_nbucket: h_nbucket.byteSwapped,
      h_nchain: h_nchain.byteSwapped
    )
  }
}

// .. Protocols ................................................................

typealias Elf_Magic = (UInt8, UInt8, UInt8, UInt8)

typealias Elf_Ident = (
  UInt8, UInt8, UInt8, UInt8,
  UInt8, UInt8, UInt8, UInt8,
  UInt8, UInt8, UInt8, UInt8,
  UInt8, UInt8, UInt8, UInt8
)

let ElfMagic: Elf_Magic = (0x7f, 0x45, 0x4c, 0x46)

protocol Elf_Ehdr : ByteSwappable {
  associatedtype Address: FixedWidthInteger
  associatedtype Offset: FixedWidthInteger

  init()

  var e_ident: Elf_Ident { get set }
  var ei_magic: Elf_Magic { get set }
  var ei_class: Elf_Ehdr_Class { get set }
  var ei_data: Elf_Ehdr_Data { get set }
  var ei_version: Elf_Byte { get set }
  var ei_osabi: Elf_Ehdr_OsAbi { get set }
  var ei_abiversion: Elf_Byte { get set }

  var e_type: Elf_Ehdr_Type { get set }
  var e_machine: Elf_Ehdr_Machine { get set }
  var e_version: Elf_Ehdr_Version { get set }
  var e_entry: Address { get set }
  var e_phoff: Offset { get set }
  var e_shoff: Offset { get set }
  var e_flags: Elf_Word { get set }
  var e_ehsize: Elf_Half { get set }
  var e_phentsize: Elf_Half { get set }
  var e_phnum: Elf_Half { get set }
  var e_shentsize: Elf_Half { get set }
  var e_shnum: Elf_Half { get set }
  var e_shstrndx: Elf_Half { get set }

  var shouldByteSwap: Bool { get }
}

extension Elf_Ehdr {
  var ei_magic: Elf_Magic {
    get {
      return (e_ident.0, e_ident.1, e_ident.2, e_ident.3)
    }
    set {
      e_ident.0 = newValue.0
      e_ident.1 = newValue.1
      e_ident.2 = newValue.2
      e_ident.3 = newValue.3
    }
  }
  var ei_class: Elf_Ehdr_Class {
    get {
      return Elf_Ehdr_Class(rawValue: e_ident.4)!
    }
    set {
      e_ident.4 = newValue.rawValue
    }
  }
  var ei_data: Elf_Ehdr_Data {
    get {
      return Elf_Ehdr_Data(rawValue: e_ident.5)!
    }
    set {
      e_ident.5 = newValue.rawValue
    }
  }
  var ei_version: UInt8 {
    get {
      return e_ident.6
    }
    set {
      e_ident.6 = newValue
    }
  }
  var ei_osabi: Elf_Ehdr_OsAbi {
    get {
      return Elf_Ehdr_OsAbi(rawValue: e_ident.7)!
    }
    set {
      e_ident.7 = newValue.rawValue
    }
  }
  var ei_abiversion: UInt8 {
    get {
      return e_ident.8
    }
    set {
      e_ident.8 = newValue
    }
  }
  var ei_pad: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) {
    get {
      return (e_ident.9, e_ident.10, e_ident.11,
              e_ident.12, e_ident.13, e_ident.14,
              e_ident.15)
    }
    set {
      e_ident.9 = newValue.0
      e_ident.10 = newValue.1
      e_ident.11 = newValue.2
      e_ident.12 = newValue.3
      e_ident.13 = newValue.4
      e_ident.14 = newValue.5
      e_ident.15 = newValue.6
    }
  }

  var shouldByteSwap: Bool {
    #if _endian(big)
    return ei_data == .ELFDATA2LSB
    #else
    return ei_data == .ELFDATA2MSB
    #endif
  }
}

extension Elf32_Ehdr : Elf_Ehdr {
}

extension Elf64_Ehdr : Elf_Ehdr {
}

protocol Elf_Shdr : ByteSwappable {
  associatedtype Flags: FixedWidthInteger
  associatedtype Address: FixedWidthInteger
  associatedtype Offset: FixedWidthInteger
  associatedtype Size: FixedWidthInteger

  init()

  var sh_name: Elf_Word { get set }
  var sh_type: Elf_Shdr_Type { get set }
  var sh_flags: Flags { get set }
  var sh_addr: Address { get set }
  var sh_offset: Offset { get set }
  var sh_size: Size { get set }
  var sh_link: Elf_Word { get set }
  var sh_info: Elf_Word { get set }
  var sh_addralign: Size { get set }
  var sh_entsize: Size { get set }
}

extension Elf32_Shdr : Elf_Shdr {
}

extension Elf64_Shdr : Elf_Shdr {
}

protocol Elf_Phdr : ByteSwappable {
  associatedtype Address: FixedWidthInteger
  associatedtype Offset: FixedWidthInteger
  associatedtype Size: FixedWidthInteger

  init()

  var p_type: Elf_Phdr_Type { get set }
  var p_flags: Elf_Phdr_Flags { get set }
  var p_offset: Offset { get set }
  var p_vaddr: Address { get set }
  var p_paddr: Address { get set }
  var p_filesz: Size { get set }
  var p_memsz: Size { get set }
  var p_align: Size { get set }
}

extension Elf32_Phdr : Elf_Phdr {
}

extension Elf64_Phdr : Elf_Phdr {
}

protocol Elf_Nhdr : ByteSwappable {
  init()

  var n_namesz: Elf_Word { get set }
  var n_descsz: Elf_Word { get set }
  var n_type: Elf_Word { get set }
}

extension Elf32_Nhdr : Elf_Nhdr {
}

extension Elf64_Nhdr : Elf_Nhdr {
}

protocol Elf_Sym {
  associatedtype Address: FixedWidthInteger
  associatedtype Size: FixedWidthInteger

  var st_name: Elf_Word { get set }
  var st_value: Address { get set }
  var st_size: Size { get set }
  var st_info: Elf_Byte { get set }
  var st_other: Elf_Byte { get set }
  var st_shndx: Elf_Half { get set }

  var st_binding: Elf_Sym_Binding { get set }
  var st_type: Elf_Sym_Type { get set }
  var st_visibility: Elf_Sym_Visibility { get set }
}

extension Elf32_Sym: Elf_Sym {
  var st_binding: Elf_Sym_Binding {
    get {
      return ELF32_ST_BIND(st_info)
    }
    set {
      st_info = ELF32_ST_INFO(newValue, ELF32_ST_TYPE(st_info))
    }
  }

  var st_type: Elf_Sym_Type {
    get {
      return ELF32_ST_TYPE(st_info)
    }
    set {
      st_info = ELF32_ST_INFO(ELF32_ST_BIND(st_info), newValue)
    }
  }

  var st_visibility: Elf_Sym_Visibility {
    get {
      return ELF32_ST_VISIBILITY(st_other)
    }
    set {
      st_other = (st_other & ~3) | newValue.rawValue
    }
  }
}

extension Elf64_Sym: Elf_Sym {
  var st_binding: Elf_Sym_Binding {
    get {
      return ELF64_ST_BIND(st_info)
    }
    set {
      st_info = ELF64_ST_INFO(newValue, ELF64_ST_TYPE(st_info))
    }
  }

  var st_type: Elf_Sym_Type {
    get {
      return ELF64_ST_TYPE(st_info)
    }
    set {
      st_info = ELF64_ST_INFO(ELF64_ST_BIND(st_info), newValue)
    }
  }

  var st_visibility: Elf_Sym_Visibility {
    get {
      return ELF64_ST_VISIBILITY(st_other)
    }
    set {
      st_other = (st_other & ~3) | newValue.rawValue
    }
  }
}

extension Elf32_Rel {
  var r_sym: Elf32_Byte {
    get {
      return ELF32_R_SYM(r_info)
    }
    set {
      r_info = ELF32_R_INFO(newValue, ELF32_R_TYPE(r_info))
    }
  }

  var r_type: Elf32_Byte {
    get {
      return ELF32_R_TYPE(r_info)
    }
    set {
      r_info = ELF32_R_INFO(ELF32_R_SYM(r_info), newValue)
    }
  }
}

extension Elf32_Rela {
  var r_sym: Elf32_Byte {
    get {
      return ELF32_R_SYM(r_info)
    }
    set {
      r_info = ELF32_R_INFO(newValue, ELF32_R_TYPE(r_info))
    }
  }

  var r_type: Elf32_Byte {
    get {
      return ELF32_R_TYPE(r_info)
    }
    set {
      r_info = ELF32_R_INFO(ELF32_R_SYM(r_info), newValue)
    }
  }
}

extension Elf64_Rel {
  var r_sym: Elf64_Word {
    get {
      return ELF64_R_SYM(r_info)
    }
    set {
      r_info = ELF64_R_INFO(newValue, ELF64_R_TYPE(r_info))
    }
  }

  var r_type: Elf64_Word {
    get {
      return ELF64_R_TYPE(r_info)
    }
    set {
      r_info = ELF64_R_INFO(ELF64_R_SYM(r_info), newValue)
    }
  }
}

extension Elf64_Rela {
  var r_sym: Elf64_Word {
    get {
      return ELF64_R_SYM(r_info)
    }
    set {
      r_info = ELF64_R_INFO(newValue, ELF64_R_TYPE(r_info))
    }
  }

  var r_type: Elf64_Word {
    get {
      return ELF64_R_TYPE(r_info)
    }
    set {
      r_info = ELF64_R_INFO(ELF64_R_SYM(r_info), newValue)
    }
  }
}

// .. Traits ...................................................................

protocol ElfTraits {
  associatedtype Address: FixedWidthInteger
  associatedtype Offset: FixedWidthInteger
  associatedtype Size: FixedWidthInteger

  associatedtype Ehdr: Elf_Ehdr where Ehdr.Address == Address,
                                      Ehdr.Offset == Offset
  associatedtype Phdr: Elf_Phdr where Phdr.Address == Address,
                                      Phdr.Offset == Offset,
                                      Phdr.Size == Size
  associatedtype Shdr: Elf_Shdr where Shdr.Address == Address,
                                      Shdr.Offset == Offset,
                                      Shdr.Size == Size
  associatedtype Nhdr: Elf_Nhdr
  associatedtype Chdr: Elf_Chdr where Chdr.Size == Size
  associatedtype Sym: Elf_Sym where Sym.Address == Address, Sym.Size == Size

  static var elfClass: Elf_Ehdr_Class { get }
}

struct Elf32Traits: ElfTraits {
  typealias Address = UInt32
  typealias Offset = UInt32
  typealias Size = UInt32

  typealias Ehdr = Elf32_Ehdr
  typealias Phdr = Elf32_Phdr
  typealias Shdr = Elf32_Shdr
  typealias Nhdr = Elf32_Nhdr
  typealias Chdr = Elf32_Chdr
  typealias Sym = Elf32_Sym

  static let elfClass: Elf_Ehdr_Class = .ELFCLASS32
}

struct Elf64Traits: ElfTraits {
  typealias Address = UInt64
  typealias Offset = UInt64
  typealias Size = UInt64

  typealias Ehdr = Elf64_Ehdr
  typealias Phdr = Elf64_Phdr
  typealias Shdr = Elf64_Shdr
  typealias Nhdr = Elf64_Nhdr
  typealias Chdr = Elf64_Chdr
  typealias Sym = Elf64_Sym

  static let elfClass: Elf_Ehdr_Class = .ELFCLASS64
}

// .. ElfStringSection .........................................................

@available(BacktracingDT 6.2, *)
struct ElfStringSection {
  let source: ImageSource

  func getStringAt(index: Int) -> String? {
    if index < 0 || index >= source.bytes.count {
      return nil
    }

    let slice = UnsafeRawBufferPointer(rebasing: source.bytes[index...])
    var len: Int = 0
    len = strnlen(slice.baseAddress!, slice.count)
    return String(decoding: source.bytes[index..<index+len], as: UTF8.self)
  }
}

// .. ElfImage .................................................................

enum ElfImageError: Error {
  case notAnElfImage
  case wrongClass
  case badNoteName
  case badStringTableSectionIndex
}

protocol ElfSymbolProtocol: Equatable {
  associatedtype Address: FixedWidthInteger
  associatedtype Size: FixedWidthInteger

  var name: String { get set }
  var value: Address { get set }
  var size: Size { get set }
  var sectionIndex: Int { get set }
  var binding: Elf_Sym_Binding { get set }
  var type: Elf_Sym_Type { get set }
  var visibility: Elf_Sym_Visibility { get set }
}

protocol ElfSymbolTableProtocol {
  associatedtype Traits: ElfTraits
  associatedtype Symbol: ElfSymbolProtocol where Symbol.Address == Traits.Address,
                                                 Symbol.Size == Traits.Size

  func lookupSymbol(address: Traits.Address) -> Symbol?
}

@available(BacktracingDT 6.2, *)
struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
  typealias Traits = SomeElfTraits

  struct Symbol: ElfSymbolProtocol {
    typealias Address = Traits.Address
    typealias Size = Traits.Size

    var name: String
    var value: Address
    var size: Size
    var sectionIndex: Int
    var binding: Elf_Sym_Binding
    var type: Elf_Sym_Type
    var visibility: Elf_Sym_Visibility
  }

  private var _symbols: [Symbol] = []

  init() {}

  @_specialize(kind: full, where SomeElfTraits == Elf32Traits)
  @_specialize(kind: full, where SomeElfTraits == Elf64Traits)
  init?(image: ElfImage<Traits>) {
    guard let strtab = image.getSection(".strtab"),
          let symtab = image.getSection(".symtab") else {
      return nil
    }

    let stringSect = ElfStringSection(source: strtab)

    // Extract all the data
    symtab.bytes.withMemoryRebound(to: Traits.Sym.self) { symbols in
      for symbol in symbols {
        // Ignore things that are not functions
        if symbol.st_type != .STT_FUNC {
          continue
        }

        // Ignore anything undefined or absolute
        if symbol.st_shndx == SHN_UNDEF || symbol.st_shndx == SHN_ABS {
          continue
        }

        _symbols.append(
          Symbol(
            name: (stringSect.getStringAt(index: Int(symbol.st_name))
                     ?? "<unknown>"),
            value: symbol.st_value,
            size: symbol.st_size,
            sectionIndex: Int(symbol.st_shndx),
            binding: symbol.st_binding,
            type: symbol.st_type,
            visibility: symbol.st_visibility
          )
        )
      }
    }

    // Now sort by address
    _symbols.sort(by: {
                    $0.value < $1.value || (
                      $0.value == $1.value && $0.size < $1.size
                    )
                  })
  }

  private init(sortedSymbols: [Symbol]) {
    _symbols = sortedSymbols
  }

  @_specialize(kind: full, where SomeElfTraits == Elf32Traits)
  @_specialize(kind: full, where SomeElfTraits == Elf64Traits)
  public func merged(with other: ElfSymbolTable<Traits>) -> ElfSymbolTable<Traits> {
    var merged: [Symbol] = []

    var ourNdx = 0, theirNdx = 0

    while ourNdx < _symbols.count && theirNdx < other._symbols.count {
        let ourSym = _symbols[ourNdx]
        let theirSym = other._symbols[theirNdx]

        if ourSym.value < theirSym.value {
          merged.append(ourSym)
          ourNdx += 1
        } else if ourSym.value > theirSym.value {
          merged.append(theirSym)
          theirNdx += 1
        } else if ourSym == theirSym {
          merged.append(ourSym)
          ourNdx += 1
          theirNdx += 1
        } else {
          if ourSym.size <= theirSym.size {
            merged.append(ourSym)
          }
          merged.append(theirSym)
          if ourSym.size > theirSym.size {
            merged.append(theirSym)
          }
          ourNdx += 1
          theirNdx += 1
        }
      }

      if ourNdx < _symbols.count {
        merged.append(contentsOf:_symbols[ourNdx...])
      }
      if theirNdx < other._symbols.count {
        merged.append(contentsOf:other._symbols[theirNdx...])
      }

      return ElfSymbolTable(sortedSymbols: merged)
  }

  @_specialize(kind: full, where SomeElfTraits == Elf32Traits)
  @_specialize(kind: full, where SomeElfTraits == Elf64Traits)
  public func lookupSymbol(address: Traits.Address) -> Symbol? {
    var min = 0
    var max = _symbols.count

    while min < max {
      let mid = min + (max - min) / 2
      let symbol = _symbols[mid]
      let nextValue: Traits.Address
      if mid == _symbols.count - 1 {
        nextValue = ~Traits.Address(0)
      } else {
        nextValue = _symbols[mid + 1].value
      }

      if symbol.value <= address && nextValue > address {
        var ndx = mid
        while ndx > 0 && _symbols[ndx - 1].value == address {
          ndx -= 1
        }
        return _symbols[ndx]
      } else if symbol.value <= address {
        min = mid + 1
      } else if symbol.value > address {
        max = mid
      }
    }

    return nil
  }
}

@available(BacktracingDT 6.2, *)
final class ElfImage<SomeElfTraits: ElfTraits> : DwarfSource {
  typealias Traits = SomeElfTraits
  typealias SymbolTable = ElfSymbolTable<SomeElfTraits>

  // This is arbitrary and it isn't in the spec
  let maxNoteNameLength = 256

  var baseAddress: ImageSource.Address
  var endAddress: ImageSource.Address

  var source: ImageSource
  var header: Traits.Ehdr
  var programHeaders: [Traits.Phdr]
  var sectionHeaders: [Traits.Shdr]?
  var shouldByteSwap: Bool { return header.shouldByteSwap }

  var imageBase: ImageSource.Address

  @_specialize(kind: full, where SomeElfTraits == Elf32Traits)
  @_specialize(kind: full, where SomeElfTraits == Elf64Traits)
  required init(source: ImageSource,
                baseAddress: ImageSource.Address = 0,
                endAddress: ImageSource.Address = 0) throws {
    self.source = source
    self.baseAddress = baseAddress
    self.endAddress = endAddress

    header = try source.fetch(from: 0, as: Traits.Ehdr.self)
    if header.ei_magic != ElfMagic {
      throw ElfImageError.notAnElfImage
    }

    if header.ei_class != Traits.elfClass {
      throw ElfImageError.wrongClass
    }

    if header.shouldByteSwap {
      header = header.byteSwapped
    }

    let byteSwap = header.shouldByteSwap
    func maybeSwap<T: ByteSwappable>(_ x: T) -> T {
      if byteSwap {
        return x.byteSwapped
      }
      return x
    }

    var phdrs: [Traits.Phdr] = []
    var phAddr = ImageSource.Address(header.e_phoff)
    var minAddr: Traits.Address? = nil
    for _ in 0..<header.e_phnum {
      let phdr = maybeSwap(try source.fetch(from: phAddr, as: Traits.Phdr.self))
      phdrs.append(phdr)
      phAddr += ImageSource.Address(header.e_phentsize)

      if phdr.p_type == .PT_LOAD {
        if let oldMinAddr = minAddr {
          minAddr = min(oldMinAddr, phdr.p_vaddr)
        } else {
          minAddr = phdr.p_vaddr
        }
      }
    }
    imageBase = ImageSource.Address(exactly: minAddr ?? 0)!
    programHeaders = phdrs

    if source.isMappedImage {
      sectionHeaders = nil
    } else {
      var shdrs: [Traits.Shdr] = []
      var shAddr = ImageSource.Address(header.e_shoff)
      for _ in 0..<header.e_shnum {
        let shdr = maybeSwap(try source.fetch(from: shAddr, as: Traits.Shdr.self))
        shdrs.append(shdr)
        shAddr += ImageSource.Address(header.e_shentsize)
      }
      sectionHeaders = shdrs
    }

    if header.e_shstrndx >= header.e_shnum {
      throw ElfImageError.badStringTableSectionIndex
    }
  }

  struct Note {
    let name: String
    let type: UInt32
    let desc: [UInt8]
  }

  struct Notes: Sequence {
    var image: ElfImage<Traits>

    struct NoteIterator: IteratorProtocol {
      var image: ElfImage<Traits>

      var hdrNdx = -1
      var noteAddr = ImageSource.Address()
      var noteEnd = ImageSource.Address()

      @_specialize(kind: full, where SomeElfTraits == Elf32Traits)
      @_specialize(kind: full, where SomeElfTraits == Elf64Traits)
      init(image: ElfImage<Traits>) {
        self.image = image
      }

      mutating func startHeader() {
        let ph = image.programHeaders[hdrNdx]

        if image.source.isMappedImage {
          noteAddr = ImageSource.Address(ph.p_vaddr)
          noteEnd = noteAddr + ImageSource.Address(ph.p_memsz)
        } else {
          noteAddr = ImageSource.Address(ph.p_offset)
          noteEnd = noteAddr + ImageSource.Address(ph.p_filesz)
        }
      }

      @_specialize(kind: full, where SomeElfTraits == Elf32Traits)
      @_specialize(kind: full, where SomeElfTraits == Elf64Traits)
      mutating func next() -> Note? {
        let byteSwap = image.shouldByteSwap
        func maybeSwap<T: ByteSwappable>(_ x: T) -> T {
          if byteSwap {
            return x.byteSwapped
          }
          return x
        }

        if hdrNdx >= image.programHeaders.count {
          return nil
        }
        while true {
          while noteAddr >= noteEnd {
            repeat {
              hdrNdx += 1
              if hdrNdx >= image.programHeaders.count {
                return nil
              }
            } while image.programHeaders[hdrNdx].p_type != .PT_NOTE
            startHeader()
          }

          do {
            let nhdr = maybeSwap(try image.source.fetch(from: noteAddr,
                                                        as: Traits.Nhdr.self))

            noteAddr += ImageSource.Address(MemoryLayout<Traits.Nhdr>.size)

            if noteEnd - noteAddr < nhdr.n_namesz {
              // The segment is probably corrupted
              noteAddr = noteEnd
              continue
            }

            let nameLen = nhdr.n_namesz > 0 ? nhdr.n_namesz - 1 : 0
            guard let name = try image.source.fetchString(from: noteAddr,
                                                          length: Int(nameLen))
            else {
              // Bad note name
              noteAddr = noteEnd
              continue
            }

            noteAddr += ImageSource.Address(nhdr.n_namesz)
            if (noteAddr & 3) != 0 {
              noteAddr += 4 - (noteAddr & 3)
            }

            if noteEnd - noteAddr < nhdr.n_descsz {
              // The segment is probably corrupted
              noteAddr = noteEnd
              continue
            }

            let desc = try image.source.fetch(from: noteAddr,
                                              count: Int(nhdr.n_descsz),
                                              as: UInt8.self)

            noteAddr += ImageSource.Address(nhdr.n_descsz)
            if (noteAddr & 3) != 0 {
              noteAddr += 4 - (noteAddr & 3)
            }

            return Note(name: name, type: UInt32(nhdr.n_type), desc: desc)
          } catch {
            hdrNdx = image.programHeaders.count
            return nil
          }
        }
      }
    }

    func makeIterator() -> NoteIterator {
      return NoteIterator(image: image)
    }
  }

  var notes: Notes {
    return Notes(image: self)
  }

  private var _uuid: [UInt8]?
  public var uuid: [UInt8]? {
    if let uuid = _uuid {
      return uuid
    }

    for note in notes {
      if note.name == "GNU" && note.type == NT_GNU_BUILD_ID {
        _uuid = note.desc
        return _uuid
      }
    }

    return nil
  }

  private var _debugLinkCRC: UInt32?
  var debugLinkCRC: UInt32 {
    if let crc = _debugLinkCRC {
      return crc
    }

    let crc = updateCrc(0, source.bytes)
    _debugLinkCRC = crc
    return crc
  }

  struct Range {
    var base: ImageSource.Address
    var size: ImageSource.Size
  }

  struct EHFrameInfo {
    var ehFrameSection: Range?
    var ehFrameHdrSection: Range?
  }

  private var _ehFrameInfo: EHFrameInfo?
  var ehFrameInfo: EHFrameInfo? {
    if let ehFrameInfo = _ehFrameInfo {
      return ehFrameInfo
    }

    var ehFrameInfo = EHFrameInfo()

    for phdr in programHeaders {
      if phdr.p_type == .PT_GNU_EH_FRAME {
        var ehFrameHdrRange: Range
        if source.isMappedImage {
          ehFrameHdrRange = Range(base: ImageSource.Address(phdr.p_vaddr),
                                  size: ImageSource.Size(phdr.p_memsz))
        } else {
          ehFrameHdrRange = Range(base: ImageSource.Address(phdr.p_offset),
                                  size: ImageSource.Size(phdr.p_filesz))
        }

        if (ehFrameHdrRange.size < MemoryLayout<EHFrameHdr>.size) {
          continue
        }

        guard let ehdr = try? source.fetch(
                from: ImageSource.Address(ehFrameHdrRange.base),
                as: EHFrameHdr.self
              ) else {
          continue
        }

        if ehdr.version != 1 {
          continue
        }

        let pc = ehFrameHdrRange.base + ImageSource.Address(MemoryLayout<EHFrameHdr>.size)
        guard let (_, eh_frame_ptr) =
                try? source.fetchEHValue(from: ImageSource.Address(pc),
                                         with: ehdr.eh_frame_ptr_enc,
                                         pc: ImageSource.Address(pc)) else {
          continue
        }

        ehFrameInfo.ehFrameHdrSection = ehFrameHdrRange

        // The .eh_frame_hdr section doesn't specify the size of the
        // .eh_frame section, so we just rely on it being properly
        // terminated.  This does mean that bulk fetching the entire
        // thing isn't a good idea.
        ehFrameInfo.ehFrameSection = Range(base: ImageSource.Address(eh_frame_ptr),
                                           size: ~ImageSource.Size(0))
      }
    }

    if let sectionHeaders = sectionHeaders {
      let stringShdr = sectionHeaders[Int(header.e_shstrndx)]
      let base = ImageSource.Address(stringShdr.sh_offset)
      let end = base + ImageSource.Size(stringShdr.sh_size)
      let stringSource = source[base..<end]
      let stringSect = ElfStringSection(source: stringSource)

      for shdr in sectionHeaders {
        // All other fields are undefined for SHT_NULL
        if shdr.sh_type == .SHT_NULL {
          continue
        }

        guard let name = stringSect.getStringAt(index: Int(shdr.sh_name)) else {
          continue
        }

        if name == ".eh_frame" {
          ehFrameInfo.ehFrameSection = Range(base: ImageSource.Address(shdr.sh_offset),
                                             size: ImageSource.Size(shdr.sh_size))
        }
      }
    }

    return ehFrameInfo
  }

  // Image name
  private var _imageName: String?
  var imageName: String {
    if let imageName = _imageName {
      return imageName
    }

    let name: String
    if let path = source.path {
      name = path
    } else if let uuid = uuid {
      name = "image \(hex(uuid))"
    } else {
      name = "<unknown image>"
    }

    _imageName = name
    return name
  }

  /// Find the named section and return an ImageSource pointing at it.
  ///
  /// In general, the section may be compressed or even in a different image;
  /// this is particularly the case for debug sections.
  @_specialize(kind: full, where SomeElfTraits == Elf32Traits)
  @_specialize(kind: full, where SomeElfTraits == Elf64Traits)
  func getSection(_ name: String) -> ImageSource? {
    if let sectionHeaders = sectionHeaders {
      let zname = ".z" + name.dropFirst()
      let stringShdr = sectionHeaders[Int(header.e_shstrndx)]
      do {
        let base = ImageSource.Address(stringShdr.sh_offset)
        let end = base + ImageSource.Size(stringShdr.sh_size)
        let stringSource = source[base..<end]
        let stringSect = ElfStringSection(source: stringSource)

        for shdr in sectionHeaders {
          // All other fields are undefined for SHT_NULL
          if shdr.sh_type == .SHT_NULL {
            continue
          }

          guard let sname
                  = stringSect.getStringAt(index: Int(shdr.sh_name)) else {
            continue
          }

          if name == sname {
            let base = ImageSource.Address(shdr.sh_offset)
            let end = base + ImageSource.Size(shdr.sh_size)
            let subSource = source[base..<end]

            if (shdr.sh_flags & Traits.Shdr.Flags(SHF_COMPRESSED)) != 0 {
              return try ImageSource(elfCompressedImageSource: subSource,
                                     traits: Traits.self)
            } else {
              return subSource
            }
          }

          if zname == sname {
            let base = ImageSource.Address(shdr.sh_offset)
            let end = base + ImageSource.Size(shdr.sh_size)
            let subSource = source[base..<end]

            return try ImageSource(gnuCompressedImageSource: subSource)
          }
        }
      } catch let CompressedImageSourceError.libraryNotFound(library) {
        swift_reportWarning(0,
                            """
                              swift-runtime: warning: \(library) not found, \
                              unable to decode the \(name) section in \
                              \(imageName)
                              """)
      } catch {
      }
    }

    return nil
  }

  struct DebugLinkInfo {
    var link: String
    var crc: UInt32
  }

  struct DebugAltLinkInfo {
    var link: String
    var uuid: [UInt8]
  }

  /// Get and decode a .gnu_debuglink section
  func getDebugLink() -> DebugLinkInfo? {
    guard let section = getSection(".gnu_debuglink") else {
      return nil
    }

    guard let link = try? section.fetchString(from: 0) else {
      return nil
    }

    let nullIndex = ImageSource.Address(link.utf8.count)
    let crcIndex = (nullIndex + 4) & ~3

    guard let unswappedCrc = try? section.fetch(
            from: crcIndex, as: UInt32.self
          ) else {
      return nil
    }

    let crc: UInt32
    if shouldByteSwap {
      crc = unswappedCrc.byteSwapped
    } else {
      crc = unswappedCrc
    }

    return DebugLinkInfo(link: link, crc: crc)
  }

  /// Get and decode a .gnu_debugaltlink section
  func getDebugAltLink() -> DebugAltLinkInfo? {
    guard let section = getSection(".gnu_debugaltlink") else {
      return nil
    }

    guard let link = try? section.fetchString(from: 0) else {
      return nil
    }

    let nullIndex = link.utf8.count

    let uuid = [UInt8](section.bytes[(nullIndex + 1)...])

    return DebugAltLinkInfo(link: link, uuid: uuid)
  }

  /// Find the named section and read a string out of it.
  func getSectionAsString(_ name: String) -> String? {
    guard let sectionSource = getSection(name) else {
      return nil
    }

    return String(decoding: sectionSource.bytes, as: UTF8.self)
  }

  struct ElfSymbol {
    var name: String
    var value: Traits.Address
    var size: Traits.Size
    var sectionIndex: Int
    var binding: Elf_Sym_Binding
    var type: Elf_Sym_Type
    var visibility: Elf_Sym_Visibility
  }

  var _symbolTable: SymbolTable? = nil
  var symbolTable: SymbolTable { return _getSymbolTable() }

  func _getSymbolTable() -> SymbolTable {
    if let table = _symbolTable {
      return table
    }

    guard let localTable = SymbolTable(image: self) else {
      return SymbolTable()
    }

    _symbolTable = localTable
    return localTable
  }

  static var pathSeparator: String { "/" }

  func getDwarfSection(_ section: DwarfSection) -> ImageSource? {
    switch section {
      case .debugAbbrev: return getSection(".debug_abbrev")
      case .debugAddr: return getSection(".debug_addr")
      case .debugARanges: return getSection(".debug_aranges")
      case .debugFrame: return getSection(".debug_frame")
      case .debugInfo: return getSection(".debug_info")
      case .debugLine: return getSection(".debug_line")
      case .debugLineStr: return getSection(".debug_line_str")
      case .debugLoc: return getSection(".debug_loc")
      case .debugLocLists: return getSection(".debug_loclists")
      case .debugMacInfo: return getSection(".debug_macinfo")
      case .debugMacro: return getSection(".debug_macro")
      case .debugNames: return getSection(".debug_names")
      case .debugPubNames: return getSection(".debug_pubnames")
      case .debugPubTypes: return getSection(".debug_pubtypes")
      case .debugRanges: return getSection(".debug_ranges")
      case .debugRngLists: return getSection(".debug_rnglists")
      case .debugStr: return getSection(".debug_str")
      case .debugStrOffsets: return getSection(".debug_str_offsets")
      case .debugSup: return getSection(".debug_sup")
      case .debugTypes: return getSection(".debug_types")
      case .debugCuIndex: return getSection(".debug_cu_index")
      case .debugTuIndex: return getSection(".debug_tu_index")
    }
  }

  private lazy var dwarfReader = { [unowned self] in
    try? DwarfReader(source: self, shouldSwap: header.shouldByteSwap)
  }()

  typealias CallSiteInfo = DwarfReader<ElfImage>.CallSiteInfo

  func inlineCallSites(
    at address: Traits.Address
  ) -> ArraySlice<CallSiteInfo> {
    guard let dwarfReader else {
      return [][0..<0]
    }

    return dwarfReader.lookupInlineCallSites(
      at: DwarfReader<ElfImage>.Address(address)
    )
  }

  typealias SourceLocation = SymbolicatedBacktrace.SourceLocation

  func sourceLocation(
    for address: Traits.Address
  ) throws -> SourceLocation? {
    guard let dwarfReader else {
      return nil
    }
    return try dwarfReader.sourceLocation(
      for: DwarfReader<ElfImage>.Address(address)
    )
  }
}

@available(BacktracingDT 6.2, *)
typealias Elf32Image = ElfImage<Elf32Traits>

@available(BacktracingDT 6.2, *)
typealias Elf64Image = ElfImage<Elf64Traits>

// .. Checking for ELF images ..................................................

/// Test if there is a valid ELF image at the specified address; if there is,
/// extract the address range for the text segment and the UUID, if any.
@_specialize(kind: full, where R == UnsafeLocalMemoryReader)
#if os(macOS) || os(Linux)
@_specialize(kind: full, where R == RemoteMemoryReader)
#endif
#if os(Linux)
@_specialize(kind: full, where R == MemserverMemoryReader)
#endif
@available(BacktracingDT 6.2, *)
func getElfImageInfo<R: MemoryReader>(at address: R.Address,
                                      using reader: R)
  -> (endOfText: R.Address, uuid: [UInt8]?)?
{
  do {
    // Check the magic number first
    let magic = try reader.fetch(from: address, as: Elf_Magic.self)

    if magic != ElfMagic {
      return nil
    }

    // Read the class from the next byte
    let elfClass = Elf_Ehdr_Class(rawValue: try reader.fetch(from: address + 4,
                                                             as: UInt8.self))

    if elfClass == .ELFCLASS32 {
      return try getElfImageInfo(at: address, using: reader,
                                 traits: Elf32Traits.self)
    } else if elfClass == .ELFCLASS64 {
      return try getElfImageInfo(at: address, using: reader,
                                 traits: Elf64Traits.self)
    } else {
      return nil
    }
  } catch {
    return nil
  }
}

@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf32Traits)
@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf64Traits)
#if os(macOS) || os(Linux)
@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf32Traits)
@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf64Traits)
#endif
#if os(Linux)
@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf32Traits)
@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf64Traits)
#endif
@available(BacktracingDT 6.2, *)
func getElfImageInfo<R: MemoryReader, Traits: ElfTraits>(
  at address: R.Address,
  using reader: R,
  traits: Traits.Type
) throws -> (endOfText: R.Address, uuid: [UInt8]?)? {
  // Grab the whole 32-bit header
  let unswappedHeader = try reader.fetch(from: address, as: Traits.Ehdr.self)

  let header: Traits.Ehdr
  if unswappedHeader.shouldByteSwap {
    header = unswappedHeader.byteSwapped
  } else {
    header = unswappedHeader
  }

  let byteSwap = header.shouldByteSwap
  func maybeSwap<T: ByteSwappable>(_ x: T) -> T {
    if byteSwap {
      return x.byteSwapped
    }
    return x
  }

  var endOfText = address
  var uuid: [UInt8]? = nil

  // Find the last loadable executable segment, and scan for PT_NOTE
  // segments that contain the UUID
  var phAddr = ImageSource.Address(address) + ImageSource.Size(header.e_phoff)
  for _ in 0..<header.e_phnum {
    let phdr = maybeSwap(try reader.fetch(from: phAddr, as: Traits.Phdr.self))
    if phdr.p_type == .PT_LOAD && (phdr.p_flags & PF_X) != 0 {
      endOfText = max(endOfText, address + ImageSource.Address(phdr.p_vaddr)
                                   + ImageSource.Size(phdr.p_memsz))
    }
    if phdr.p_type == .PT_NOTE {
      var noteAddr = address + ImageSource.Address(phdr.p_vaddr)
      let noteEnd = noteAddr + ImageSource.Size(phdr.p_memsz)

      while noteAddr < noteEnd {
        let nhdr = maybeSwap(try reader.fetch(
                               from: noteAddr, as: Traits.Nhdr.self))

        noteAddr += ImageSource.Size(MemoryLayout<Traits.Nhdr>.size)

        if noteEnd - noteAddr < nhdr.n_namesz {
          // This segment is probably corrupted, so skip it
          noteAddr = noteEnd
          continue
        }

        var isBuildId = false
        let nameLen = nhdr.n_namesz > 0 ? nhdr.n_namesz - 1 : 0

        // Test if this is a "GNU" NT_GNU_BUILD_ID note
        if nameLen == 3 {
          let byte0 = try reader.fetch(from: noteAddr, as: UInt8.self)
          let byte1 = try reader.fetch(from: noteAddr + 1, as: UInt8.self)
          let byte2 = try reader.fetch(from: noteAddr + 2, as: UInt8.self)

          if byte0 == 0x47 && byte1 == 0x4e && byte2 == 0x55 &&
               UInt32(nhdr.n_type) == NT_GNU_BUILD_ID {
            isBuildId = true
          }
        }

        noteAddr += ImageSource.Size(nhdr.n_namesz)
        if (noteAddr & 3) != 0 {
          noteAddr += 4 - (noteAddr & 3)
        }

        if noteEnd - noteAddr < nhdr.n_descsz {
          // Corrupted segment, skip
          noteAddr = noteEnd
          continue
        }

        if isBuildId {
          uuid = try reader.fetch(from: noteAddr,
                                  count: Int(nhdr.n_descsz),
                                  as: UInt8.self)
        }

        noteAddr += ImageSource.Size(nhdr.n_descsz)
        if (noteAddr & 3) != 0 {
          noteAddr += 4 - (noteAddr & 3)
        }
      }
    }

    phAddr += ImageSource.Address(header.e_phentsize)
  }

  return (endOfText: endOfText, uuid: uuid)
}

// .. SymbolSource .............................................................

extension ElfImage: SymbolSource {
  func lookupSymbol(address: SymbolSource.Address) -> SymbolSource.Symbol? {
    let relativeAddress = Traits.Address(address - baseAddress)
    guard let symbol = symbolTable.lookupSymbol(address: relativeAddress) else {
      return nil
    }

    return SymbolSource.Symbol(
      name: symbol.name,
      offset: Int(relativeAddress - symbol.value),
      size: Int(symbol.size)
    )
  }

  func sourceLocation(
    for address: SymbolSource.Address
  ) -> SymbolSource.SourceLocation? {
    guard let dwarfReader else {
      return nil
    }
    return try? dwarfReader.sourceLocation(
      for: DwarfReader<ElfImage>.Address(address)
    )
  }

  func inlineCallSites(
    at address: SymbolSource.Address
  ) -> Array<SymbolSource.CallSiteInfo> {
    guard let dwarfReader else {
      return []
    }

    var result: [SymbolSource.CallSiteInfo] = []
    for site in dwarfReader.lookupInlineCallSites(
          at: DwarfReader<ElfImage>.Address(address)
        ) {
      result.append(SymbolSource.CallSiteInfo(rawName: site.rawName,
                                              name: site.name,
                                              location: SourceLocation(
                                                path: site.filename,
                                                line: site.line,
                                                column: site.column
                                              )))
    }

    return result
  }
}

// .. SymbolLocator.Image ......................................................

@_spi(SymbolLocation)
extension ElfImage: SymbolLocator.Image {

  public var name: String? {
    guard let path = self.path else {
      return nil
    }
    let (_, filename) = splitpath(path)
    return String(filename)
  }

  public var path: String? {
    return source.path
  }

  public var age: UInt32? {
    return nil
  }

}

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

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

  let debugSections: [String] = [
    ".debug_info",
    ".debug_line",
    ".debug_abbrev",
    ".debug_ranges",
    ".debug_str",
    ".debug_addr",
    ".debug_str_offsets",
    ".debug_line_str",
    ".debug_rnglists"
  ]

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

    if let uuid = elfImage.uuid {
      print("  uuid: \(hex(uuid))")
    } else {
      print("  uuid: <no uuid>")
    }

    for section in debugSections {
      if let _ = elfImage.getSection(section) {
        print("  \(section): found")
      } else {
        print("  \(section): not found")
      }
    }

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

    if let uuid = elfImage.uuid {
      print("  uuid: \(hex(uuid))")
    } else {
      print("  uuid: <no uuid>")
    }

    for section in debugSections {
      if let _ = elfImage.getSection(section) {
        print("  \(section): found")
      } else {
        print("  \(section): not found")
      }
    }

    return true
  } else {
    print("\(path) is not an ELF image")
    return false
  }
}
