├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── ELFSage.lean ├── ELFSage ├── Commands │ ├── HexDump.lean │ ├── Patch.lean │ ├── Read.lean │ └── Validate.lean ├── Constants │ ├── Dynamic.lean │ ├── ELFHeader.lean │ └── SectionHeaderTable.lean ├── Macros │ └── Enum.lean ├── Test │ ├── Data │ │ ├── ELFFiles │ │ │ └── true │ │ ├── PrintedHeaders │ │ │ └── true │ │ └── generatePrintedOutputs.sh │ ├── TestRunOutput │ │ └── .gitignore │ └── TestRunner.lean ├── Types │ ├── Dynamic.lean │ ├── ELFHeader.lean │ ├── File.lean │ ├── Note.lean │ ├── ProgramHeaderTable.lean │ ├── Relocation.lean │ ├── Section.lean │ ├── SectionHeaderTable.lean │ ├── Segment.lean │ ├── Sizes.lean │ ├── StringTable.lean │ ├── Symbol.lean │ └── SymbolTable.lean └── Util │ ├── ByteArray.lean │ ├── Cli.lean │ ├── Flags.lean │ ├── Hex.lean │ ├── IO.lean │ ├── List.lean │ └── String.lean ├── LICENCE ├── LICENCE_USG ├── Main.lean ├── Test.lean ├── lake-manifest.json ├── lakefile.lean └── lean-toolchain /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.lean] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # Run using manual triggers from GitHub UI: 5 | # https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow 6 | workflow_dispatch: {} 7 | # Run on every push: 8 | push: {} 9 | # Run on pull request activity: 10 | pull_request: {} 11 | 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest] 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | 23 | - uses: actions/checkout@v4 24 | 25 | - uses: leanprover/lean-action@v1 26 | with: 27 | auto-config: false 28 | use-mathlib-cache: false 29 | use-github-cache: true 30 | 31 | - name: Run test suite 32 | run: | 33 | lake exe test | grep "PASS" 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.lake 2 | Session.vim 3 | -------------------------------------------------------------------------------- /ELFSage.lean: -------------------------------------------------------------------------------- 1 | -- This module serves as the root of the `ELFSage` library. 2 | -- Import modules here that should be built as part of the library. 3 | import ELFSage.Commands.Read 4 | import ELFSage.Commands.HexDump 5 | import ELFSage.Commands.Validate 6 | import ELFSage.Commands.Patch 7 | -------------------------------------------------------------------------------- /ELFSage/Commands/HexDump.lean: -------------------------------------------------------------------------------- 1 | import Cli 2 | import ELFSage.Util.Cli 3 | import ELFSage.Util.IO 4 | 5 | def runHexDumpCmd (p : Cli.Parsed): IO UInt32 := do 6 | let targetBinary : System.FilePath := (p.positionalArg! "targetBinary").as! System.FilePath 7 | IO.FS.readBinFile targetBinary >>= dumpBytesAsHex 8 | IO.print "\n\n" 9 | return 0 10 | -------------------------------------------------------------------------------- /ELFSage/Commands/Patch.lean: -------------------------------------------------------------------------------- 1 | import Cli 2 | import ELFSage.Util.Cli 3 | import ELFSage.Util.IO 4 | import ELFSage.Types.File 5 | import ELFSage.Types.Relocation 6 | import ELFSage.Types.Dynamic 7 | import ELFSage.Constants.SectionHeaderTable 8 | import ELFSage.Commands.Read 9 | 10 | --Get the minimum filesize in bytes required to accommodate ELF data 11 | def RawELFFile.getFileSize (elffile : RawELFFile) : Nat := 12 | 13 | let elfheader := elffile.getRawELFHeader 14 | 15 | let sec_max := elffile.getRawSectionHeaderTableEntries.foldr 16 | (λ(shte,_) acc => Nat.max (SectionHeaderTableEntry.sh_end shte) acc) 0 17 | 18 | let seg_max := elffile.getRawProgramHeaderTableEntries.foldr 19 | (λ(phte,_) acc => Nat.max (ProgramHeaderTableEntry.ph_end phte) acc) 0 20 | 21 | [seg_max, sec_max, ELFHeader.ph_end elfheader, ELFHeader.sh_end elfheader].foldr Nat.max 0 22 | 23 | def RawELFFile.serialize (elffile : RawELFFile) : IO ByteArray := do 24 | let elfheader := elffile.getRawELFHeader 25 | 26 | let elfheader_bytes := ELFHeader.bytes elfheader 27 | 28 | let isBigendian := ELFHeader.isBigendian elfheader 29 | 30 | let section_header_table_entries := elffile.getRawSectionHeaderTableEntries 31 | 32 | let program_header_table_entries := elffile.getRawProgramHeaderTableEntries 33 | 34 | -- We need the full zeroed-out byte array 35 | -- copySlice grows the target bytearray if needed, but it won't insert the 36 | -- source array beyond the final byte of the target array. 37 | 38 | let mut outBytes := ByteArray.mkEmpty elffile.getFileSize 39 | 40 | for _ in [:elffile.getFileSize] do 41 | outBytes := outBytes.push 0 42 | 43 | let mut program_header_base := ELFHeader.e_phoff elfheader 44 | 45 | let mut section_header_base := ELFHeader.e_shoff elfheader 46 | 47 | 48 | for phte in program_header_table_entries do 49 | --copy the header to the header table 50 | let header_bytes := ProgramHeaderTableEntry.bytes phte.fst isBigendian 51 | outBytes := header_bytes.copySlice 0 outBytes program_header_base (header_bytes.size) 52 | program_header_base := program_header_base + ELFHeader.e_phentsize elfheader 53 | 54 | --copy the segment to the appropriate position 55 | let entryOffset := ProgramHeaderTableEntry.p_offset phte.fst 56 | let segment_bytes := phte.snd.segment_body 57 | outBytes := segment_bytes.copySlice 0 outBytes entryOffset segment_bytes.size 58 | 59 | for shte in section_header_table_entries do 60 | --copy the header to the header table 61 | let header_bytes := SectionHeaderTableEntry.bytes shte.fst isBigendian 62 | outBytes := header_bytes.copySlice 0 outBytes section_header_base (header_bytes.size) 63 | section_header_base := section_header_base + ELFHeader.e_shentsize elfheader 64 | 65 | --copy the section to the appropriate position 66 | let entryOffset := SectionHeaderTableEntry.sh_offset shte.fst 67 | let section_bytes := shte.snd.section_body 68 | outBytes := section_bytes.copySlice 0 outBytes entryOffset section_bytes.size 69 | 70 | outBytes := elfheader_bytes.copySlice 0 outBytes 0 (elfheader_bytes.size) 71 | 72 | return outBytes 73 | 74 | def runNoopCmd (p : Cli.Parsed) : IO UInt32 := do 75 | let targetBinary := (p.positionalArg! "targetBinary").as! System.FilePath 76 | let outPath := (p.positionalArg! "outPath").as! System.FilePath 77 | let bytes ← IO.FS.readBinFile targetBinary 78 | 79 | match mkRawELFFile? bytes with 80 | | .error warn => IO.println warn *> return 1 81 | | .ok elffile => do 82 | 83 | let outBytes ← RawELFFile.serialize elffile 84 | 85 | IO.FS.writeBinFile outPath outBytes 86 | 87 | return 0 88 | 89 | 90 | def nullSegment32 : ELF32ProgramHeaderTableEntry := 91 | { 92 | p_type := 0, 93 | p_flags := 0, 94 | p_offset := 0, 95 | p_vaddr := 0, 96 | p_paddr := 0, 97 | p_filesz := 0, 98 | p_memsz := 0, 99 | p_align := 0, 100 | } 101 | 102 | def nullSegment64 : ELF64ProgramHeaderTableEntry := 103 | { 104 | p_type := 0, 105 | p_flags := 0, 106 | p_offset := 0, 107 | p_vaddr := 0, 108 | p_paddr := 0, 109 | p_filesz := 0, 110 | p_memsz := 0, 111 | p_align := 0, 112 | } 113 | 114 | def nullInterpretedSegment : InterpretedSegment := { 115 | segment_type := 0, 116 | segment_size := 0, 117 | segment_memsz := 0, 118 | segment_base := 0, 119 | segment_paddr := 0, 120 | segment_align := 0, 121 | segment_offset := 0, 122 | segment_body := ByteArray.mk #[] 123 | segment_flags := (False,False,False) 124 | } 125 | 126 | def RawELFFile.addDummyProgramHeader (elffile : RawELFFile) : RawELFFile := 127 | match elffile with 128 | | .elf32 elffile => 129 | let new_segments := elffile.interpreted_segments.cons (nullSegment32, nullInterpretedSegment) 130 | let new_header := { elffile.file_header with e_phnum := elffile.file_header.e_phnum + 1 } 131 | .elf32 { elffile with 132 | interpreted_segments := new_segments 133 | file_header := new_header 134 | } 135 | | .elf64 elffile => 136 | let new_segments := elffile.interpreted_segments.cons (nullSegment64, nullInterpretedSegment) 137 | let new_header := { elffile.file_header with e_phnum := elffile.file_header.e_phnum + 1 } 138 | .elf64 { elffile with 139 | interpreted_segments := new_segments 140 | file_header := new_header 141 | } 142 | 143 | def shift_rela_64 (shift : elf64_off → elf64_off) (isBigendian : Bool) (bytes : ByteArray) : ByteArray := 144 | let split_bytes := (List.range (bytes.size / 0x18)).map $ λn ↦ bytes.extract (n * 0x18) ((n + 1) * 0x18) 145 | let make_mreloca (b : ByteArray) := if h: b.size ≥ 0x18 then Option.some $ mkELF64RelocationA isBigendian b 0 h else .none 146 | let mrelocs := split_bytes.map make_mreloca 147 | let mrelocs_shifted := mrelocs.map λmrela ↦ match mrela with 148 | | .none => Option.none 149 | | .some rela => .some { rela with ra_offset := shift rela.ra_offset } 150 | mrelocs_shifted.foldr λmrela acc ↦ match mrela with 151 | | .none => (List.replicate 0x18 0).toByteArray ++ acc 152 | | .some rela => rela.bytes isBigendian ++ acc 153 | ByteArray.empty 154 | 155 | def shift_rel_64 (shift : elf64_off → elf64_off) (isBigendian : Bool) (bytes : ByteArray) : ByteArray := 156 | let split_bytes := (List.range (bytes.size / 0x10)).map $ λn ↦ bytes.extract (n * 0x10) ((n + 1) * 0x10) 157 | let make_mreloca (b : ByteArray) := if h: b.size ≥ 0x10 then Option.some $ mkELF64Relocation isBigendian b 0 h else .none 158 | let mrelocs := split_bytes.map make_mreloca 159 | let mrelocs_shifted := mrelocs.map λmrela ↦ match mrela with 160 | | .none => Option.none 161 | | .some rel => .some { rel with r_offset := shift rel.r_offset } 162 | mrelocs_shifted.foldr λmrela acc ↦ match mrela with 163 | | .none => (List.replicate 0x10 0).toByteArray ++ acc 164 | | .some rel => rel.bytes isBigendian ++ acc 165 | ByteArray.empty 166 | 167 | inductive DynWrapper (A C : Type) 168 | | ok : A → DynWrapper A C 169 | | none : DynWrapper A C 170 | | skip : C → DynWrapper A C 171 | 172 | def shift_dyn_64 (shift : elf64_off → elf64_off) (isBigendian : Bool) (bytes : ByteArray) : ByteArray := 173 | let split_bytes := (List.range (bytes.size / 0x10)).map $ λn ↦ bytes.extract (n * 0x10) ((n + 1) * 0x10) 174 | let make_dyn (b : ByteArray) := if h: b.size ≥ 0x10 then match mkELF64DynamicEntry? isBigendian b 0 h with 175 | | .ok dyn => DynWrapper.ok dyn 176 | | .error _ => DynWrapper.skip b 177 | else DynWrapper.none 178 | let mdyns := split_bytes.map make_dyn 179 | let mdyns_shifted := mdyns.map λmdyn ↦ match mdyn with 180 | | .ok dyn => DynWrapper.ok { dyn with d_un := match dyn.d_un with 181 | | .d_val v => .d_val v 182 | | .d_ptr p => .d_ptr $ shift p 183 | | .d_ignored bs => .d_ignored bs 184 | } 185 | | .none => DynWrapper.none 186 | | .skip (b : ByteArray) => .skip b 187 | mdyns_shifted.foldr λmdyn acc ↦ match mdyn with 188 | | .none => (List.replicate 0x10 0).toByteArray ++ acc 189 | | .ok dyn => dyn.bytes isBigendian ++ acc 190 | | .skip b => b ++ acc 191 | ByteArray.empty 192 | 193 | def RawELFFile.expandPHDRSegment (elffile : RawELFFile) : Option RawELFFile := 194 | let isBigendian := isBigendian elffile 195 | match elffile with 196 | | .elf64 elffile => 197 | match elffile.interpreted_segments.find? λ(phte,_) => phte.p_type == 6 with --PHDR Segment 198 | | .none => .none 199 | | .some (phdr_phte, _) => 200 | let contains_phdr (phte : ELF64ProgramHeaderTableEntry) := 201 | phte.p_offset ≤ phdr_phte.p_offset && 202 | phte.p_offset + phte.p_filesz > phdr_phte.p_offset && 203 | phte.p_type == 1 --PT_LOAD 204 | 205 | let shift_if_after_phdr n := if n > phdr_phte.p_offset then n + 0x1000 else n 206 | 207 | -- we rewrite the ELFSage representation of the program header table 208 | let new_segments := elffile.interpreted_segments.map λ(phte, seg) => 209 | if contains_phdr phte || phte.p_type = 6 210 | then ({ phte with 211 | p_filesz := phte.p_filesz + 0x1000, 212 | p_memsz := phte.p_memsz + 0x1000 213 | }, seg) 214 | else ({ phte with 215 | p_offset := shift_if_after_phdr phte.p_offset, 216 | p_vaddr := shift_if_after_phdr phte.p_vaddr, 217 | p_paddr := shift_if_after_phdr phte.p_paddr, 218 | }, seg) 219 | 220 | -- We generate a new set of contents for the program header table based on 221 | -- our rewritten table 222 | let zeroes := (List.replicate 0x1000 0).toByteArray 223 | let new_segment_bytes := new_segments.foldr (λ(phte,_) acc => phte.bytes isBigendian ++ acc) zeroes 224 | 225 | -- And we rewrite the byte contents of the PHDR segment and the containing 226 | -- load segment based on that. Also the dynamic section 227 | let new_segments := new_segments.map λ(phte, seg) => 228 | if phte.p_type = 6 -- PHDR segment 229 | then (phte, { seg with segment_body := new_segment_bytes }) 230 | else if contains_phdr phte --Containing Load segment 231 | then 232 | let dest_offset := phdr_phte.p_offset - phte.p_offset 233 | -- We zero-extend the original load segment 234 | let expanded_segment := seg.segment_body ++ zeroes 235 | --we then shift the bytes of the extended load segment below the insertion 236 | --point to the right by 0x1000 to make room for the header table 237 | let shifted_body := seg.segment_body.copySlice 238 | dest_offset.toNat 239 | expanded_segment 240 | (dest_offset.toNat + 0x1000) 241 | (seg.segment_body.size - dest_offset.toNat) 242 | --then we insert the header table 243 | let new_body := new_segment_bytes.copySlice 0 shifted_body dest_offset.toNat new_segment_bytes.size 244 | (phte, { seg with segment_body := new_body }) 245 | else (phte, seg) 246 | 247 | -- finally, we're going to want to rewrite the dynamic and relocation sections. 248 | let new_sections := elffile.interpreted_sections.map λ(shte, sec) => 249 | let new_shte := { shte with 250 | sh_addr := shift_if_after_phdr shte.sh_addr 251 | sh_offset := shift_if_after_phdr shte.sh_offset 252 | } 253 | if shte.sh_type.toNat == ELFSectionHeaderTableEntry.Type.SHT_RELA then 254 | (new_shte, { sec with section_body := shift_rela_64 shift_if_after_phdr isBigendian sec.section_body}) 255 | else if shte.sh_type.toNat == ELFSectionHeaderTableEntry.Type.SHT_REL then 256 | (new_shte, { sec with section_body := shift_rel_64 shift_if_after_phdr isBigendian sec.section_body}) 257 | else if shte.sh_type.toNat == ELFSectionHeaderTableEntry.Type.SHT_DYNAMIC then 258 | (new_shte, { sec with section_body := shift_dyn_64 shift_if_after_phdr isBigendian sec.section_body}) 259 | else 260 | (new_shte, sec) 261 | 262 | let new_header := { elffile.file_header with 263 | e_entry := shift_if_after_phdr elffile.file_header.e_entry 264 | e_shoff := shift_if_after_phdr elffile.file_header.e_shoff 265 | } 266 | 267 | .some $ .elf64 { elffile with 268 | interpreted_segments := new_segments 269 | interpreted_sections := new_sections 270 | file_header := new_header 271 | } 272 | | .elf32 _ => .none 273 | 274 | def runAddSpaceCmd (p : Cli.Parsed): IO UInt32 := do 275 | let targetBinary := (p.positionalArg! "targetBinary").as! System.FilePath 276 | let outPath := (p.positionalArg! "outPath").as! System.FilePath 277 | let count := (p.positionalArg! "count").as! Nat 278 | let bytes ← IO.FS.readBinFile targetBinary 279 | 280 | match mkRawELFFile? bytes with 281 | | .error warn => IO.println warn *> return 1 282 | | .ok elffile => do 283 | 284 | let mut elffile := elffile 285 | let mut idx := 0 286 | while idx < count do 287 | idx := idx + 1 288 | elffile := elffile.addDummyProgramHeader 289 | 290 | --XXX: this is maybe not ideally modular. addDummyProgramHeader adds a null 291 | --header to the intepreted segments map in the lean-level ELF data, but it 292 | --doesn't actually rewrite the program header table contents — that happens 293 | --inside of expandPHDRSegment. Could this be resequenced more elegantly? 294 | match elffile.expandPHDRSegment with 295 | | .none => IO.println "target binary appears to not contain a program header table" *> return 1 296 | | .some newFile => do 297 | 298 | let outBytes ← RawELFFile.serialize newFile 299 | 300 | IO.FS.writeBinFile outPath outBytes 301 | 302 | return 0 303 | -------------------------------------------------------------------------------- /ELFSage/Commands/Read.lean: -------------------------------------------------------------------------------- 1 | import Cli 2 | import ELFSage.Util.Cli 3 | import ELFSage.Util.IO 4 | import ELFSage.Types.File 5 | import ELFSage.Types.ELFHeader 6 | import ELFSage.Types.ProgramHeaderTable 7 | import ELFSage.Types.SectionHeaderTable 8 | import ELFSage.Types.SymbolTable 9 | import ELFSage.Types.StringTable 10 | import ELFSage.Types.Dynamic 11 | import ELFSage.Types.Note 12 | import ELFSage.Types.Relocation 13 | import ELFSage.Types.Symbol 14 | 15 | def checkImplemented (p: Cli.Parsed) : Except String Unit := do 16 | let unimplemented := 17 | [ "a", "all" 18 | , "g", "section-groups" 19 | , "t", "section-details" 20 | , "lto-syms" 21 | , "sym-base" 22 | , "C", "demangle" 23 | , "u", "unwind" 24 | , "V", "version-info" 25 | , "A", "arch-specific" 26 | , "c", "archive-index" 27 | , "D", "use-dynamic" 28 | , "L", "lint" 29 | , "R", "relocated-dump" 30 | , "z", "decompress" 31 | ] 32 | for flag in unimplemented do 33 | if p.hasFlag flag 34 | then throw s!"The flag --{flag} isn't implemented yet, sorry!" 35 | 36 | return () 37 | 38 | /- Prints all the symbols in the section with header `sectionHeaderEnt` -/ 39 | def printSymbolsForSection 40 | (elffile : RawELFFile) 41 | (shte: RawSectionHeaderTableEntry) 42 | (sec : InterpretedSection) := 43 | for idx in [:SectionHeaderTableEntry.sh_size shte / SectionHeaderTableEntry.sh_entsize shte] do 44 | IO.print s!"Symbol {idx}: " 45 | let offset := idx * SectionHeaderTableEntry.sh_entsize shte 46 | match mkRawSymbolTableEntry? 47 | sec.section_body elffile.is64Bit elffile.isBigendian 48 | offset 49 | with 50 | | .error warn => IO.println warn 51 | | .ok ste => 52 | match RawELFFile.symbolNameByLinkAndOffset elffile (SectionHeaderTableEntry.sh_link shte) (SymbolTableEntry.st_name ste) with 53 | | .ok name => IO.print s!"{name}\n" 54 | | .error warn => IO.print s!"??? - {warn}\n" 55 | IO.println $ repr ste 56 | 57 | /- gets the symbol in the section with header `sectionHeaderEnt` with index symidx -/ 58 | def getSymbolNameInSection 59 | (elffile : RawELFFile) 60 | (shte : RawSectionHeaderTableEntry) 61 | (sec : InterpretedSection) 62 | (symidx : Nat) 63 | : Except String String := 64 | let offset := symidx * SectionHeaderTableEntry.sh_entsize shte 65 | match mkRawSymbolTableEntry? 66 | sec.section_body 67 | elffile.is64Bit 68 | elffile.isBigendian 69 | offset 70 | with 71 | | .error warn => .error warn 72 | | .ok ste => RawELFFile.symbolNameByLinkAndOffset 73 | elffile (SectionHeaderTableEntry.sh_link shte) (SymbolTableEntry.st_name ste) 74 | 75 | --TODO find? might be more appropriate than filter here. 76 | 77 | def printSymbolsForSectionType (elffile: RawELFFile) (ent_type : Nat) := 78 | let ofType := elffile.getRawSectionHeaderTableEntries.filter $ λ⟨shte, _⟩↦ 79 | SectionHeaderTableEntry.sh_type shte == ent_type 80 | ofType.forM $ λ⟨shte, sec⟩ ↦ printSymbolsForSection elffile shte sec 81 | 82 | def printStringsForSectionIdx (elffile : RawELFFile) (idx : Nat) := 83 | match elffile.getRawSectionHeaderTableEntries[idx]? with 84 | | .none => IO.println s!"There doesn't appear to be a section header {idx}" 85 | | .some ⟨_, sec⟩ => for byte in sec.section_body do 86 | if byte == 0 then IO.print '\n' else IO.print (Char.ofNat byte.toNat) 87 | 88 | def msecByName (elffile : RawELFFile) (name : String) : IO (Option (RawSectionHeaderTableEntry × InterpretedSection)) := 89 | match elffile.getSectionHeaderStringTable? with 90 | | .error err => IO.println err *> return none 91 | | .ok (_,shstrtab_sec) => 92 | let shstrtab : ELFStringTable := ⟨shstrtab_sec.section_body⟩ 93 | let offset := shstrtab.stringToOffset name 94 | let findPred : RawSectionHeaderTableEntry × InterpretedSection → Bool := (λent => SectionHeaderTableEntry.sh_name ent.fst == offset) 95 | return (elffile.getRawSectionHeaderTableEntries.find? findPred) 96 | 97 | def printStringsForSectionName (elffile : RawELFFile) (name : String) := do 98 | match (← msecByName elffile name) with 99 | | .none => IO.println s!"There doesn't appear to be a section header named {name}" 100 | | .some ⟨_, sec⟩ => for byte in sec.section_body do 101 | if byte == 0 then IO.print '\n' else IO.print (Char.ofNat byte.toNat) 102 | 103 | def printHexForSectionIdx (elffile : RawELFFile) (idx : Nat) := 104 | match elffile.getRawSectionHeaderTableEntries[idx]? with 105 | | .none => IO.println s!"There doesn't appear to be a section header {idx}" 106 | | .some ⟨_, sec⟩ => dumpBytesAsHex sec.section_body 107 | 108 | def printHexForSectionName (elffile : RawELFFile) (name : String) := do 109 | match (← msecByName elffile name) with 110 | | .none => IO.println s!"There doesn't appear to be a section header named {name}" 111 | | .some ⟨_, sec⟩ => dumpBytesAsHex sec.section_body 112 | 113 | def printHexForSymbolIdx (elffile : RawELFFile) (idx : Nat) := 114 | match do 115 | let ⟨symshte, symsec⟩ ← elffile.getSymbolTable? 116 | let offset := idx * SectionHeaderTableEntry.sh_entsize symshte 117 | let ste ← mkRawSymbolTableEntry? 118 | symsec.section_body 119 | elffile.is64Bit 120 | elffile.isBigendian 121 | offset 122 | SymbolTableEntry.toBody? ste elffile 123 | with 124 | | .error warn => IO.println warn 125 | | .ok bytes => dumpBytesAsHex bytes 126 | 127 | def printDynamics (elffile : RawELFFile) := 128 | let dynamics := elffile.getRawSectionHeaderTableEntries.filter $ λsec ↦ 129 | SectionHeaderTableEntry.sh_type sec.fst == ELFSectionHeaderTableEntry.Type.SHT_DYNAMIC 130 | for ⟨shte, sec⟩ in dynamics do 131 | for idx in [:SectionHeaderTableEntry.sh_size shte / SectionHeaderTableEntry.sh_entsize shte] do 132 | IO.print s!"Dynamic Entry {idx}: " 133 | let offset := idx * SectionHeaderTableEntry.sh_entsize shte 134 | match mkRawDynamicEntry? 135 | sec.section_body 136 | elffile.is64Bit 137 | elffile.isBigendian 138 | offset 139 | with 140 | | .error e => IO.println s!"warning: {e}" 141 | | .ok dynamicEnt => IO.println $ repr dynamicEnt 142 | 143 | -- should use this for both SHT_NOTE sections and PT_NOTE segments 144 | def printNotes 145 | (elffile: RawELFFile) 146 | (shte: RawSectionHeaderTableEntry) 147 | (sec: InterpretedSection) 148 | : IO Unit := recur (SectionHeaderTableEntry.sh_size shte) 0 149 | where 150 | alignTo4 n := n + (n % 4) 151 | recur space offset := 152 | match space with 153 | | 0 => pure () 154 | | spaceminus + 1 => --we work with space-1 to automatically prove termination 155 | match mkRawNoteEntry? 156 | sec.section_body 157 | elffile.isBigendian 158 | elffile.is64Bit 159 | offset 160 | with 161 | | .error e => IO.println e 162 | | .ok ne => do 163 | IO.println $ repr ne 164 | let notesize := 0xc + alignTo4 (NoteEntry.note_name ne).size + alignTo4 (NoteEntry.note_desc ne).size 165 | if spaceminus - notesize ≥ 0xb 166 | then recur (spaceminus - (notesize - 1)) (offset + notesize) 167 | else pure () 168 | 169 | def printNoteSections (elffile: RawELFFile) := 170 | for ⟨shte, sec⟩ in elffile.getRawSectionHeaderTableEntries do 171 | if SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_NOTE then 172 | match sec.section_name_as_string with 173 | | .some name => IO.print s!"Notes from {name}\n" 174 | | .none => IO.print s!"Notes from unnamed section\n" 175 | printNotes elffile shte sec 176 | 177 | def printRelocationA 178 | (elffile : RawELFFile) 179 | (shte : RawSectionHeaderTableEntry) 180 | (sec : InterpretedSection) := 181 | for idx in [:SectionHeaderTableEntry.sh_size shte / SectionHeaderTableEntry.sh_entsize shte] do 182 | IO.print s!"Relocation {idx}: " 183 | let offset := idx * SectionHeaderTableEntry.sh_entsize shte 184 | match mkRawRelocationA? 185 | sec.section_body 186 | elffile.is64Bit 187 | elffile.isBigendian 188 | offset 189 | with 190 | | .error warn => IO.println warn 191 | | .ok ra => 192 | let r_sym := if elffile.is64Bit 193 | then (RelocationA.ra_info ra) / 2^32 194 | else (RelocationA.ra_info ra) / 2^8 195 | match elffile.getRawSectionHeaderTableEntries[linkedSymbolNames]? with 196 | | .some ⟨shte, sec⟩ => match getSymbolNameInSection elffile shte sec r_sym with 197 | | .ok name => IO.print s!"{name}\n" 198 | | .error warn => IO.print s!"??? - {warn}\n" 199 | | .none => IO.print s!"??? - can't locate string table of symbol names\n" 200 | IO.println $ repr ra 201 | where 202 | linkedSymbolNames := SectionHeaderTableEntry.sh_link shte 203 | 204 | def printRelocationSections (elffile: RawELFFile) := 205 | let relocations := elffile.getRawSectionHeaderTableEntries.filter $ λsec ↦ 206 | SectionHeaderTableEntry.sh_type sec.fst == ELFSectionHeaderTableEntry.Type.SHT_RELA 207 | for ⟨shte, sec⟩ in relocations do 208 | match sec.section_name_as_string with 209 | | .some name => IO.print s!"Relocations from {name}\n" 210 | | .none => IO.print s!"Relocations from unnamed section\n" 211 | printRelocationA elffile shte sec 212 | 213 | private def printFileHeader (eh : RawELFHeader) := do 214 | IO.println $ toString eh 215 | 216 | private def printSectionHeaders (elffile : RawELFFile) := do 217 | IO.println $ RawELFFile.sectionHeadersToString elffile 218 | 219 | private def printProgramHeaders (elffile : RawELFFile) := do 220 | IO.println $ RawELFFile.programHeadersToString elffile 221 | 222 | private def printHeaders (elffile : RawELFFile) := do 223 | IO.println $ RawELFFile.headersToString elffile 224 | 225 | def runReadCmd (p : Cli.Parsed): IO UInt32 := do 226 | 227 | match checkImplemented p with 228 | | .error warn => IO.println warn *> return 1 229 | | .ok _ => do 230 | 231 | if p.flags.size == 0 then do 232 | p.printHelp 233 | return 1 234 | 235 | let targetBinary := (p.positionalArg! "targetBinary").as! System.FilePath 236 | let bytes ← IO.FS.readBinFile targetBinary 237 | 238 | match mkRawELFFile? bytes with 239 | | .error warn => IO.println warn *> return 1 240 | | .ok elffile => do 241 | 242 | let elfheader := elffile.getRawELFHeader 243 | 244 | for flag in p.flags do 245 | match flag.flag.longName with 246 | | "file-header" => printFileHeader elfheader 247 | | "headers" => printHeaders elffile 248 | | "program-headers" => printProgramHeaders elffile 249 | | "segments" => printProgramHeaders elffile 250 | | "section-headers" => printSectionHeaders elffile 251 | | "sections" => printSectionHeaders elffile 252 | | "dynamic" => printDynamics elffile 253 | | "dyn-syms" => 254 | let type := ELFSectionHeaderTableEntry.Type.SHT_DYNSYM; 255 | printSymbolsForSectionType elffile type 256 | | "syms" => 257 | let symtab := ELFSectionHeaderTableEntry.Type.SHT_SYMTAB 258 | printSymbolsForSectionType elffile symtab 259 | --TODO fallback to DYNSYM when SYMTAB isn't present 260 | | "string-dump" => match flag.as? Nat with 261 | | some idx => printStringsForSectionIdx elffile idx 262 | | none => match flag.as? String with 263 | | some name => printStringsForSectionName elffile name 264 | | none => IO.println "couldn't parse section number provided for string dump" 265 | | "hex-dump" => match flag.as? Nat with 266 | | some idx => printHexForSectionIdx elffile idx 267 | | none => match flag.as? String with 268 | | some name => printHexForSectionName elffile name 269 | | none => IO.println "couldn't parse section provided for hex dump" 270 | | "sym-dump" => match flag.as? Nat with 271 | | none => IO.println "couldn't parse symbol number provided for hex dump" 272 | | some idx => printHexForSymbolIdx elffile idx 273 | | "notes" => printNoteSections elffile 274 | | "relocs" => printRelocationSections elffile 275 | | _ => IO.println $ "unrecognized flag: " ++ flag.flag.longName 276 | 277 | return 0 278 | -------------------------------------------------------------------------------- /ELFSage/Commands/Validate.lean: -------------------------------------------------------------------------------- 1 | import Cli 2 | import ELFSage.Util.Cli 3 | 4 | import ELFSage.Types.ELFHeader 5 | import ELFSage.Types.File 6 | import ELFSage.Types.Note 7 | import ELFSage.Types.Dynamic 8 | import ELFSage.Types.Relocation 9 | import ELFSage.Types.SymbolTable 10 | 11 | def mkRawProgramHeaderErrors 12 | [ELFHeader α] 13 | (eh : α) 14 | (bytes : ByteArray) 15 | : List (Except String RawProgramHeaderTableEntry) := 16 | let shoffsets := (List.range (ELFHeader.e_phnum eh)).map λidx ↦ ELFHeader.e_phoff eh + ELFHeader.e_phentsize eh * idx 17 | let isBigendian := ELFHeader.isBigendian eh 18 | let is64Bit := ELFHeader.is64Bit eh 19 | List.map (λoffset ↦ mkRawProgramHeaderTableEntry? bytes is64Bit isBigendian offset) shoffsets 20 | 21 | def mkRawSectionHeaderErrors 22 | [ELFHeader α] 23 | (eh : α) 24 | (bytes : ByteArray) 25 | : List (Except String RawSectionHeaderTableEntry) := 26 | let shoffsets := (List.range (ELFHeader.e_shnum eh)).map λidx ↦ ELFHeader.e_shoff eh + ELFHeader.e_shentsize eh * idx 27 | let isBigendian := ELFHeader.isBigendian eh 28 | let is64Bit := ELFHeader.is64Bit eh 29 | List.map (λoffset ↦ mkRawSectionHeaderTableEntry? bytes is64Bit isBigendian offset) shoffsets 30 | 31 | def getSectionNamesOrErr 32 | [ELFHeader α] (eh : α) 33 | [SectionHeaderTableEntry β] (sht: List (Except String β)) 34 | (bytes : ByteArray) 35 | : Except String ELFStringTable := 36 | let shstrndx := ELFHeader.e_shstrndx eh 37 | if h : shstrndx ≥ sht.length 38 | then .error $ 39 | s!"the shstrndx from the elf header requests section {ELFHeader.e_shstrndx eh}, " ++ 40 | s!"but the there are only {sht.length} section header table entries indicated." 41 | else match sht[shstrndx] with 42 | | .error e => .error 43 | $ s!"the shstrndx from the elf header requests section {ELFHeader.e_shstrndx eh}, " 44 | ++ s!"but that section was unreadable, with the following error: {e}" 45 | | .ok shte => do 46 | let shstr_start := SectionHeaderTableEntry.sh_offset shte 47 | let shstr_end := shstr_start + SectionHeaderTableEntry.sh_size shte 48 | if shstr_end > bytes.size 49 | then .error $ 50 | s!"The section header name string table offset 0x{Hex.toHex shstr_start}, and endpoint 0x{Hex.toHex shstr_end} " ++ 51 | s!"runs off the end of the binary, which contains only 0x{Hex.toHex bytes.size} bytes." 52 | else .ok ⟨bytes.extract shstr_start shstr_end⟩ 53 | 54 | def printNotesErrs 55 | [ELFHeader α] (eh : α) 56 | (shte: RawSectionHeaderTableEntry) 57 | (sec: InterpretedSection) 58 | (idx : Nat) 59 | : IO Unit := recur (SectionHeaderTableEntry.sh_size shte) 0 1 60 | where 61 | alignTo4 n := n + (n % 4) 62 | recur space offset count := 63 | match space with 64 | | 0 => pure () 65 | | spaceminus + 1 => --we work with space-1 to automatically prove termination 66 | match mkRawNoteEntry? 67 | (sec.section_body) 68 | (ELFHeader.isBigendian eh) 69 | (ELFHeader.is64Bit eh) 70 | offset 71 | with 72 | | .error e => IO.println s!"Note {count} from section {idx} was damaged: {e}" 73 | | .ok ne => do 74 | let notesize := 0xc + alignTo4 (NoteEntry.note_name ne).size + alignTo4 (NoteEntry.note_desc ne).size 75 | if spaceminus - notesize ≥ 0xb 76 | then recur (spaceminus - (notesize - 1)) (offset + notesize) (count + 1) 77 | else pure () 78 | 79 | def printDynamicErrs 80 | [ELFHeader α] (eh : α) 81 | (shte: RawSectionHeaderTableEntry) 82 | (sec: InterpretedSection) 83 | (sidx : Nat) 84 | : IO Unit := 85 | for idx in [:SectionHeaderTableEntry.sh_size shte / SectionHeaderTableEntry.sh_entsize shte] do 86 | let offset := idx * SectionHeaderTableEntry.sh_entsize shte 87 | match mkRawDynamicEntry? 88 | sec.section_body 89 | (ELFHeader.is64Bit eh) 90 | (ELFHeader.isBigendian eh) 91 | offset 92 | with 93 | | .error e => IO.println s!"Dynamic Entry {idx} of section {sidx} was damaged: {e}" 94 | | .ok _ => pure () 95 | 96 | def printRelocationAErrs 97 | [ELFHeader α] (eh : α) 98 | (shte : RawSectionHeaderTableEntry) 99 | (sec : InterpretedSection) 100 | (sidx : Nat) := 101 | for idx in [:SectionHeaderTableEntry.sh_size shte / SectionHeaderTableEntry.sh_entsize shte] do 102 | let offset := idx * SectionHeaderTableEntry.sh_entsize shte 103 | match mkRawRelocationA? 104 | (sec.section_body) 105 | (ELFHeader.is64Bit eh) 106 | (ELFHeader.isBigendian eh) 107 | offset 108 | with 109 | | .error e => IO.println s!"Relocation {idx} of section {sidx} was damaged: {e}" 110 | | .ok _ => pure () 111 | 112 | def printSymbolErrs 113 | [ELFHeader α] (eh : α) 114 | (shte: RawSectionHeaderTableEntry) 115 | (sec: InterpretedSection) 116 | (sidx : Nat) := 117 | for idx in [:SectionHeaderTableEntry.sh_size shte / SectionHeaderTableEntry.sh_entsize shte] do 118 | let offset := idx * SectionHeaderTableEntry.sh_entsize shte 119 | match mkRawSymbolTableEntry? 120 | sec.section_body 121 | (ELFHeader.is64Bit eh) 122 | (ELFHeader.isBigendian eh) 123 | offset 124 | with 125 | | .error e => IO.println s!"Symbol {idx} of section {sidx} was damaged: {e}" 126 | | .ok _ => pure () 127 | 128 | 129 | def runValidateCmd (p : Cli.Parsed) : IO UInt32 := do 130 | 131 | let targetBinary := (p.positionalArg! "targetBinary").as! System.FilePath 132 | let bytes ← IO.FS.readBinFile targetBinary 133 | 134 | -- retrieve ELF header 135 | match mkRawELFHeader? bytes with 136 | | .error warn => do 137 | IO.println "The ELF Header can't be recovered, so nothing else is readable:" 138 | IO.println warn *> return 1 139 | | .ok elfheader => do 140 | 141 | -- retrieve other headers 142 | let phtEntries := mkRawProgramHeaderErrors elfheader bytes 143 | let shtEntries := mkRawSectionHeaderErrors elfheader bytes 144 | 145 | -- get section names 146 | let maybeNames := getSectionNamesOrErr elfheader shtEntries bytes 147 | let tryName (n: Nat) := match maybeNames with | .ok s => .some (s.stringAt n) | .error _ => .none 148 | 149 | match maybeNames with 150 | | .ok _ => pure () 151 | | .error e => IO.println s!"The string table listing the section names is unavailable: {e}" 152 | 153 | let mut idx := 0 154 | 155 | for ent in phtEntries do 156 | idx := idx + 1 157 | 158 | match ent with 159 | | .error s => IO.println s!"Program Header Table Entry {idx}: {s}" 160 | | .ok phte => 161 | 162 | match ProgramHeaderTableEntry.checkAlignment phte with 163 | | .error s => IO.println s 164 | | .ok _ => pure () 165 | 166 | match ProgramHeaderTableEntry.toSegment? phte bytes with 167 | | .error s => IO.println s!"Segment Associated with Program Header Table Entry {idx}: {s}" 168 | | .ok _ => pure () 169 | 170 | idx := 0 171 | 172 | for ent in shtEntries do 173 | idx := idx + 1 174 | match ent with 175 | | .error s => IO.println s!"Section Header Table Entry {idx}: {s}" 176 | | .ok shte => 177 | 178 | match SectionHeaderTableEntry.toSection? shte bytes (tryName idx) with 179 | | .error s => IO.println s!"Section Associated with Section Header Table Entry {idx}: {s}" 180 | | .ok sec => 181 | 182 | match SectionHeaderTableEntry.checkAlignment shte with 183 | | .error s => IO.println s 184 | | .ok _ => 185 | 186 | if SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_NOTE 187 | then printNotesErrs elfheader shte sec idx 188 | 189 | if SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_DYNAMIC 190 | then printDynamicErrs elfheader shte sec idx 191 | 192 | if SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_SYMTAB 193 | ∨ SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_DYNSYM 194 | then printDynamicErrs elfheader shte sec idx 195 | 196 | if SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_RELA 197 | then printRelocationAErrs elfheader shte sec idx 198 | 199 | return 0 200 | -------------------------------------------------------------------------------- /ELFSage/Constants/Dynamic.lean: -------------------------------------------------------------------------------- 1 | /-- [dt_null] marks the end of the dynamic array -/ 2 | def Dynamic.Tag.DT_NULL : Nat := 0 3 | /-- [dt_needed] holds the string table offset of a string containing the name of a needed library. -/ 4 | def Dynamic.Tag.DT_NEEDED : Nat := 1 5 | /--[dt_pltrelsz] holds the size in bytes of relocation entries associated with the PLT. -/ 6 | def Dynamic.Tag.DT_PLTRELSZ : Nat := 2 7 | /-- [dt_pltgot] holds an address associated with the PLT or GOT. -/ 8 | def Dynamic.Tag.DT_PLTGOT : Nat := 3 9 | /-- [dt_hash] holds the address of a symbol-table hash. -/ 10 | def Dynamic.Tag.DT_HASH : Nat := 4 11 | /-- [dt_strtab] holds the address of the string table. -/ 12 | def Dynamic.Tag.DT_STRTAB : Nat := 5 13 | /-- [dt_symtab] holds the address of a symbol table. -/ 14 | def Dynamic.Tag.DT_SYMTAB : Nat := 6 15 | /-- [dt_rela] holds the address of a relocation table. -/ 16 | def Dynamic.Tag.DT_RELA : Nat := 7 17 | /-- [dt_relasz] holds the size in bytes of the relocation table. -/ 18 | def Dynamic.Tag.DT_RELASZ : Nat := 8 19 | /-- [dt_relaent] holds the size in bytes of a relocation table entry. -/ 20 | def Dynamic.Tag.DT_RELAENT : Nat := 9 21 | /-- [dt_strsz] holds the size in bytes of the string table. -/ 22 | def Dynamic.Tag.DT_STRSZ : Nat := 10 23 | /-- [dt_syment] holds the size in bytes of a symbol table entry. -/ 24 | def Dynamic.Tag.DT_SYMENT : Nat := 11 25 | /-- [dt_init] holds the address of the initialisation function. -/ 26 | def Dynamic.Tag.DT_INIT : Nat := 12 27 | /-- [dt_fini] holds the address of the finalisation function. -/ 28 | def Dynamic.Tag.DT_FINI : Nat := 13 29 | /-- [dt_soname] holds the string table offset of a string containing the shared object name. -/ 30 | def Dynamic.Tag.DT_SONAME : Nat := 14 31 | /-- [dt_rpath] holds the string table offset of a string containing the library search path. -/ 32 | def Dynamic.Tag.DT_RPATH : Nat := 15 33 | /-- [dt_symbolic] alters the linker's symbol resolution algorithm so that names are resolved first from the shared object file itself, rather than the executable file. -/ 34 | def Dynamic.Tag.DT_SYMBOLIC : Nat := 16 35 | /-- [dt_rel] is similar to [dt_rela] except its table has implicit addends. -/ 36 | def Dynamic.Tag.DT_REL : Nat := 17 37 | /-- [dt_relsz] holds the size in bytes of the [dt_rel] relocation table. -/ 38 | def Dynamic.Tag.DT_RELSZ : Nat := 18 39 | /-- [dt_relent] holds the size in bytes of a [dt_rel] relocation entry. -/ 40 | def Dynamic.Tag.DT_RELENT : Nat := 19 41 | /-- [dt_pltrel] specifies the type of relocation entry to which the PLT refers. -/ 42 | def Dynamic.Tag.DT_PLTREL : Nat := 20 43 | /-- [dt_debug] is used for debugging and its purpose is not specified in the ABI. Programs using this entry are not ABI-conformant. -/ 44 | def Dynamic.Tag.DT_DEBUG : Nat := 21 45 | /-- [dt_textrel] absence of this entry indicates that no relocation entry should 46 | cause a modification to a non-writable segment. Otherwise, if present, one 47 | or more relocation entries may request modifications to a non-writable 48 | segment. -/ 49 | def Dynamic.Tag.DT_TEXTREL : Nat := 22 50 | /-- [dt_jmprel]'s member holds the address of relocation entries associated with the PLT. -/ 51 | def Dynamic.Tag.DT_JMPREL : Nat := 23 52 | /-- [dt_bindnow] instructs the linker to process all relocations for the object containing the entry before transferring control to the program. -/ 53 | def Dynamic.Tag.DT_BINDNOW : Nat := 24 54 | /-- [dt_init_array] holds the address to the array of pointers to initialisation functions. -/ 55 | def Dynamic.Tag.DT_INIT_ARRAY : Nat := 25 56 | /-- [dt_fini_array] holds the address to the array of pointers to finalisation functions. -/ 57 | def Dynamic.Tag.DT_FINI_ARRAY : Nat := 26 58 | /-- [dt_init_arraysz] holds the size in bytes of the array of pointers to initialisation functions. -/ 59 | def Dynamic.Tag.DT_INIT_ARRAYSZ : Nat := 27 60 | /-- [dt_fini_arraysz] holds the size in bytes of the array of pointers to finalisation functions. -/ 61 | def Dynamic.Tag.DT_FINI_ARRAYSZ : Nat := 28 62 | /-- [dt_runpath] holds an offset into the string table holding a string containing the library search path. -/ 63 | def Dynamic.Tag.DT_RUNPATH : Nat := 29 64 | /-- [dt_flags] holds flag values specific to the object being loaded. -/ 65 | def Dynamic.Tag.DT_FLAGS : Nat := 30 66 | /-- [dt_encoding] is a special value, not corresponding to an actual tag (its value is equal to the value of dt_preinit_array). Tags above DT_ENCODING have special sematnics: https://www.sco.com/developers/gabi/2012-12-31/ch5.dynamic.html#dynamic_section -/ 67 | def Dynamic.Tag.DT_ENCODING : Nat := 32 68 | /-- [dt_preinit_array] holds the address to the array of pointers of pre- initialisation functions. -/ 69 | def Dynamic.Tag.DT_PREINIT_ARRAY : Nat := 32 70 | /-- [dt_preinit_arraysz] holds the size in bytes of the array of pointers of pre-initialisation functions. -/ 71 | def Dynamic.Tag.DT_PREINIT_ARRAYSZ : Nat := 33 72 | /-- [dt_loos] and [dt_hios]: this inclusive range is reserved for OS-specific semantics. -/ 73 | def Dynamic.Tag.DT_LOOS : Nat := 0x6000000D 74 | /-- [dt_loos] and [dt_hios]: this inclusive range is reserved for OS-specific semantics. -/ 75 | def Dynamic.Tag.DT_HIOS : Nat := 0x6ffff000 76 | /-- [dt_loproc] and [dt_hiproc]: this inclusive range is reserved for processor specific semantics. -/ 77 | def Dynamic.Tag.DT_LOPROC : Nat := 0x70000000 78 | /-- [dt_loproc] and [dt_hiproc]: this inclusive range is reserved for processor specific semantics. -/ 79 | def Dynamic.Tag.DT_HIPROC : Nat := 0x7fffffff 80 | 81 | def Dynamic.Tag.GNU.DT_GNU_ADDRRNGHI : Nat := 0x6ffffeff 82 | def Dynamic.Tag.GNU.DT_GNU_ADDRRNGLO : Nat := 0x6ffffe00 83 | def Dynamic.Tag.GNU.DT_GNU_AUXILIARY : Nat := 0x7ffffffd 84 | def Dynamic.Tag.GNU.DT_GNU_FILTER : Nat := 0x7fffffff 85 | def Dynamic.Tag.GNU.DT_GNU_NUM : Nat := 32 -- ??? this is from linksem, they're confused by it too 86 | def Dynamic.Tag.GNU.DT_GNU_POSFLAG_1 : Nat := 0x6ffffdfd 87 | def Dynamic.Tag.GNU.DT_GNU_RELCOUNT : Nat := 0x6ffffffa 88 | def Dynamic.Tag.GNU.DT_GNU_RELACOUNT : Nat := 0x6FFFFFF9 89 | def Dynamic.Tag.GNU.DT_GNU_SYMINENT : Nat := 0x6ffffdff 90 | def Dynamic.Tag.GNU.DT_GNU_SYMINFO : Nat := 0x6ffffeff 91 | def Dynamic.Tag.GNU.DT_GNU_SYMINSZ : Nat := 0x6ffffdfe 92 | def Dynamic.Tag.GNU.DT_GNU_VALRNGHI : Nat := 0x6ffffdff 93 | def Dynamic.Tag.GNU.DT_GNU_VALRNGLO : Nat := 0x6ffffd00 94 | def Dynamic.Tag.GNU.DT_GNU_VERDEF : Nat := 0x6ffffffc 95 | def Dynamic.Tag.GNU.DT_GNU_VERDEFNUM : Nat := 0x6ffffffd 96 | def Dynamic.Tag.GNU.DT_GNU_VERNEED : Nat := 0x6ffffffe 97 | def Dynamic.Tag.GNU.DT_GNU_VERNEEDNUM : Nat := 0x6fffffff 98 | def Dynamic.Tag.GNU.DT_GNU_VERSYM : Nat := 0x6ffffff0 99 | def Dynamic.Tag.GNU.DT_GNU_HASH : Nat := 0x6ffffef5 100 | def Dynamic.Tag.GNU.DT_GNU_FLAGS_1 : Nat := 0x6ffffffb 101 | def Dynamic.Tag.GNU.DT_GNU_CHECKSUM : Nat := 0x6FFFFDF8 102 | def Dynamic.Tag.GNU.DT_GNU_PRELINKED : Nat := 0x6FFFFDF5 103 | -------------------------------------------------------------------------------- /ELFSage/Constants/SectionHeaderTable.lean: -------------------------------------------------------------------------------- 1 | /-- Marks the section header as being inactive. -/ 2 | def ELFSectionHeaderTableEntry.Type.SHT_NULL : Nat := 0 3 | /-- Section holds information defined by the program. -/ 4 | def ELFSectionHeaderTableEntry.Type.SHT_PROGBITS : Nat := 1 5 | /-- Section holds a symbol table. The symtab provides a place for link editing. -/ 6 | def ELFSectionHeaderTableEntry.Type.SHT_SYMTAB : Nat := 2 7 | /-- Section holds a string table -/ 8 | def ELFSectionHeaderTableEntry.Type.SHT_STRTAB : Nat := 3 9 | /-- Section holds relocation entries with explicit addends. An object file may 10 | \ have multiple section of this type. -/ 11 | def ELFSectionHeaderTableEntry.Type.SHT_RELA : Nat := 4 12 | /-- Section holds a symbol hash table. An object file may only have a single hash table. -/ 13 | def ELFSectionHeaderTableEntry.Type.SHT_HASH : Nat := 5 14 | /-- Section holds information for dynamic linking. An object file may only 15 | \ have a single dynamic section. -/ 16 | def ELFSectionHeaderTableEntry.Type.SHT_DYNAMIC : Nat := 6 17 | /-- Section holds information that marks the file in some way. -/ 18 | def ELFSectionHeaderTableEntry.Type.SHT_NOTE : Nat := 7 19 | /-- Section occupies no space in the file but otherwise resembles a progbits 20 | section. -/ 21 | def ELFSectionHeaderTableEntry.Type.SHT_NOBITS : Nat := 8 22 | /-- Section holds relocation entries without explicit addends. An object file 23 | \ may have multiple section of this type. -/ 24 | def ELFSectionHeaderTableEntry.Type.SHT_REL : Nat := 9 25 | /-- Section type is reserved but has an unspecified meaning. -/ 26 | def ELFSectionHeaderTableEntry.Type.SHT_SHLIB : Nat := 10 27 | /-- Section holds a symbol table. The dynsym section holds a minimal set of 28 | dynamic linking symbols -/ 29 | def ELFSectionHeaderTableEntry.Type.SHT_DYNSYM : Nat := 11 30 | /-- Section contains an array of pointers to initialisation functions. Each 31 | pointer is taken as a parameterless function with a void return type. -/ 32 | def ELFSectionHeaderTableEntry.Type.SHT_INIT_ARRAY : Nat := 14 33 | /-- Section contains an array of pointers to termination functions. Each 34 | pointer is taken as a parameterless function with a void return type. -/ 35 | def ELFSectionHeaderTableEntry.Type.SHT_FINI_ARRAY : Nat := 15 36 | /-- Section contains an array of pointers to initialisation functions that are 37 | invoked before all other initialisation functions. Each 38 | pointer is taken as a parameterless function with a void return type. -/ 39 | def ELFSectionHeaderTableEntry.Type.SHT_PREINIT_ARRAY : Nat := 16 40 | /-- Section defines a section group, i.e. a set of sections that are related and 41 | must be treated especially by the linker. May only appear in relocatable 42 | objects. -/ 43 | def ELFSectionHeaderTableEntry.Type.SHT_GROUP : Nat := 17 44 | /-- Section is associated with sections of type SHT_SYMTAB and is required if 45 | any of the section header indices referenced by that symbol table contains 46 | the escape value SHN_XINDEX. -/ 47 | def ELFSectionHeaderTableEntry.Type.SHT_SYMTAB_SHNDX : Nat := 18 48 | 49 | /- XXX: This is what the rems-project uses. some other values, like SHT_NUM, SHT_LOOS 50 | seem to be missing -/ 51 | -------------------------------------------------------------------------------- /ELFSage/Macros/Enum.lean: -------------------------------------------------------------------------------- 1 | import Lean 2 | open Lean Parser Command Syntax 3 | 4 | declare_syntax_cat enum_case 5 | syntax atomic((docComment)? " | " ident " => " ident ", " term) : enum_case 6 | syntax (docComment)? " | " ident " => " term : enum_case 7 | 8 | private inductive EnumCaseValue 9 | | literalValue (value : Term) 10 | | parametricValue (value : Ident) (cond : Term) 11 | deriving Inhabited 12 | 13 | private structure EnumCaseParsed where 14 | name : Ident 15 | value : EnumCaseValue 16 | comment : Lean.TSyntax `Lean.Parser.Command.docComment 17 | deriving Inhabited 18 | 19 | private def makeComment (comment: String) : Lean.TSyntax `Lean.Parser.Command.docComment := 20 | let commentLeader : Lean.Syntax := Lean.Syntax.atom SourceInfo.none "/--" 21 | let commentBody : Lean.Syntax := Lean.Syntax.atom SourceInfo.none s!"{comment} -/" 22 | Lean.TSyntax.mk $ Lean.Syntax.node SourceInfo.none `Lean.Parser.Command.docComment #[commentLeader, commentBody] 23 | 24 | private def parseCase (case : TSyntax `enum_case) : EnumCaseParsed := 25 | match case with 26 | | `(enum_case| | $name => $value) => ⟨name, .literalValue value, emptyComment⟩ 27 | | `(enum_case| | $name => $value, $cond) => ⟨name, .parametricValue value cond, emptyComment⟩ 28 | | `(enum_case| $comment:docComment | $name => $value) => ⟨name, .literalValue value, comment⟩ 29 | | `(enum_case| $comment:docComment | $name => $value, $cond) => ⟨name, .parametricValue value cond, comment⟩ 30 | | _ => unreachable! 31 | where emptyComment := makeComment "undocumented" 32 | 33 | private def parseCases (cases : TSyntaxArray `enum_case) : Array EnumCaseParsed := 34 | cases.foldl (init := (#[])) fun (parsedCases) => fun (case) => 35 | parsedCases.push (parseCase case) 36 | 37 | -- TODO: prove termination. 38 | -- Warning: this is not performant if there are many name collisions. 39 | private partial def getUniqueName (base : String) (names : List String) : String := 40 | let rec loop (i : Nat) : String := 41 | let name := base ++ (if i == 0 then "" else s!"_{i}") 42 | if name ∈ names then loop (i + 1) else name 43 | loop 0 44 | 45 | private def addCaseDefaults (n: Ident) (cases : Array EnumCaseParsed) : MacroM (Array EnumCaseParsed) := do 46 | -- Allow the final case to omit the true condition. 47 | let ⟨name, value, comment⟩ := cases.back 48 | let trueValue ← `(true) 49 | let defaultValue := EnumCaseValue.parametricValue n trueValue 50 | let (cases, lastValue) := match value with 51 | | .literalValue v => 52 | if v == n then (cases.pop.push ⟨name, defaultValue, comment⟩, defaultValue) 53 | else (cases, value) 54 | | .parametricValue _ _ => (cases, value) 55 | 56 | -- Append a default case if the final case is not a catch-all. 57 | let names := cases.map (·.name.getId.toString) 58 | let addDefaultCase (n : Ident) (cases : Array EnumCaseParsed) : Array EnumCaseParsed := 59 | let defaultName: Ident := mkIdentFrom n (getUniqueName "default" names.toList) 60 | cases.push ⟨defaultName, defaultValue, makeComment "default case"⟩ 61 | let cases := match lastValue with 62 | | .literalValue _ => addDefaultCase n cases 63 | | .parametricValue _ h => if h == trueValue then cases else addDefaultCase n cases 64 | 65 | pure cases 66 | 67 | private def validateCases (n: Ident) (cases : Array EnumCaseParsed) : MacroM Unit := 68 | -- validate that parametric values use the same name as the enum value 69 | for case in cases do 70 | match case.value with 71 | | .literalValue _ => pure () 72 | | .parametricValue v _ => if v != n then 73 | Macro.throwErrorAt v s!"parametric value {v.getId.toString} must match the enum parameter {n.getId.toString}" 74 | 75 | /- 76 | Make the constructor types for the inductive type that an enum desugars to. 77 | Constructors for non-literal cases bind to a value and a proof that the value is 78 | in the case's range. A case's range is limited by both its local condition and 79 | the conditions of all previous cases (since cases are matched greedily). The net 80 | restriction on a case is the conjunction of its local restriction and the 81 | negations of all prior restrictions. A literal constructor's implied restriction 82 | is that of equality with its literal value. 83 | 84 | To make these constructor types, we fold over the cases, building the 85 | constructor type for each case and the net restriction after that case. 86 | -/ 87 | private def mkCtorTypes (e n : Ident) (ty : Term) (parsedCases : Array EnumCaseParsed) : MacroM (Array Term) := do 88 | let mkCtorTypeAndCarriedRestrictions (value : EnumCaseValue) (priorRestrictions : Term) : MacroM (Term × Term) := do 89 | let thisType ← match value with 90 | | .literalValue _ => `($e) 91 | | .parametricValue _ h => `(($n:ident: $ty) → (h: $h ∧ $priorRestrictions) → $e) 92 | let netRestrictions ← match value with 93 | | .literalValue v => `(¬($n:ident = $v) ∧ $priorRestrictions) 94 | | .parametricValue _ h => `(¬($h) ∧ $priorRestrictions) 95 | pure ⟨thisType, netRestrictions⟩ 96 | 97 | let ⟨ctorTypes, _⟩ ← parsedCases.foldlM (init := (#[], ←`(true))) fun (valuesAcc, restrictions) (case) => do 98 | let (thisType, netRestrictions) ← mkCtorTypeAndCarriedRestrictions case.value restrictions 99 | return (valuesAcc.push thisType, netRestrictions) 100 | 101 | pure ctorTypes 102 | 103 | /-- 104 | The `enum` macro defines an enumeration type over a base type. The enumeration 105 | type defines cases that partition the base type. Each case is associated with a 106 | literal value or a guard condition on values. Cases are matched greedily. 107 | 108 | An enum type defines constructors for each of its cases. Constructors for 109 | guarded types take a value and a proof that the value is in the case's range. An 110 | enum type also defines functions to convert between the enum type and values of 111 | the base type, and defines proofs that a round-trip in either direction between 112 | a value of the base type and its corresponding enum value is the identity. 113 | 114 | An enum always includes a default case that matches any value not matched by 115 | other cases. If a default case is not explicitly defined, it is added. 116 | 117 | Example: 118 | ``` 119 | enum E (n: Nat) 120 | | one => 1 121 | | small => n, 2 ≤ n ∧ n < 4 122 | | four => 4 123 | | other => n 124 | 125 | #eval E.four -- four 126 | #eval E.ofVal 4 -- four 127 | #eval E.four.val -- 4 128 | #eval E.small 3 (by decide) -- small 3 129 | #eval (E.small 3 (by decide)).val -- 3 130 | #eval (E.ofVal 3).val -- 3 131 | #check E.ofVal_val -- ∀ (e : E), E.ofVal (E.val e) = e 132 | #check E.val_ofVal -- ∀ (n : Nat), E.val (E.ofVal n) = n 133 | ``` 134 | -/ 135 | macro "enum" e:ident "(" n:ident " : " ty:term ")" (" where")? cases:enum_case* : command => do 136 | -- Must have at least one case. Don't enforce this in the syntax parser since 137 | -- it's easier to give a good error message here. 138 | if cases.size == 0 then Macro.throwErrorAt e "enum must have at least one case" 139 | 140 | let eIdent (suffix : Name) := mkIdentFrom e (e.getId ++ suffix) 141 | let toStrLit (n : Name) := Lean.Syntax.mkStrLit n.toString 142 | 143 | let rawParsedCases := parseCases cases 144 | let parsedCases ← addCaseDefaults n rawParsedCases 145 | let _ ← validateCases n parsedCases 146 | 147 | let comments := parsedCases.map (·.comment) 148 | let names := parsedCases.map (·.name) 149 | let ctorTypes ← mkCtorTypes e n ty parsedCases 150 | 151 | let reprCases ← parsedCases.mapM fun ⟨name, value, _⟩ => do match value with 152 | | .literalValue _ => `(Term.matchAltExpr| | .$name:ident => $(toStrLit name.getId)) 153 | | .parametricValue v _ => `(Term.matchAltExpr| | .$name:ident $v _ => s!"{$(toStrLit name.getId)} {$v}") 154 | 155 | let valCases ← parsedCases.mapM fun ⟨name, value, _⟩ => do match value with 156 | | .literalValue v => `(Term.matchAltExpr| | .$name:ident => $v) 157 | | .parametricValue v _ => `(Term.matchAltExpr| | .$name:ident $v _ => $v) 158 | 159 | let ofValCases: Term ← parsedCases.reverse.foldlM (init := ←`(unreachable!)) fun res case => do 160 | let ⟨name, value, _⟩ := case 161 | match value with 162 | | .literalValue v => `(if _ : $n = $v then .$name:ident else $res) 163 | | .parametricValue _ h => `(if _ : $h then .$name:ident $n (by simp; omega) else $res) 164 | 165 | `( 166 | inductive $e:ident 167 | $[$comments:docComment | $names:ident : $ctorTypes]* 168 | deriving Inhabited, BEq 169 | instance : Repr $e where reprPrec := fun e _ => match e with $reprCases:matchAlt* 170 | 171 | def $(eIdent `val) : $e → $ty $valCases:matchAlt* 172 | 173 | def $(eIdent `ofVal) : $ty → $e := fun $n => $ofValCases 174 | 175 | theorem $(eIdent `ofVal_val) (e : $e) : $(eIdent `ofVal) ($(eIdent `val) e) = e := by 176 | cases e <;> try rfl 177 | all_goals rename _ => h; simp [$(eIdent `val): ident, $(eIdent `ofVal): ident, h] 178 | 179 | theorem $(eIdent `val_ofVal) (n : $ty) : $(eIdent `val) ($(eIdent `ofVal) n) = n := by 180 | unfold $(eIdent `ofVal) 181 | repeat split; simp only [*, $(eIdent `val): ident] 182 | simp only [*, $(eIdent `val): ident] 183 | contradiction 184 | ) 185 | -------------------------------------------------------------------------------- /ELFSage/Test/Data/ELFFiles/true: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/draperlaboratory/ELFSage/8f3d6e753febf602a07a4495504738b968719520/ELFSage/Test/Data/ELFFiles/true -------------------------------------------------------------------------------- /ELFSage/Test/Data/PrintedHeaders/true: -------------------------------------------------------------------------------- 1 | ElfHeader { 2 | Ident { 3 | Magic: (7F 45 4C 46) 4 | Class: 64-bit (0x2) 5 | DataEncoding: LittleEndian (0x1) 6 | FileVersion: 1 7 | OS/ABI: SystemV (0x0) 8 | ABIVersion: 0 9 | Unused: (00 00 00 00 00 00 00) 10 | } 11 | Type: SharedObject (0x3) 12 | Machine: EM_X86_64 (0x3E) 13 | Version: 1 14 | Entry: 0x15B0 15 | ProgramHeaderOffset: 0x40 16 | SectionHeaderOffset: 0x6148 17 | Flags [ (0x0) 18 | ] 19 | HeaderSize: 64 20 | ProgramHeaderEntrySize: 56 21 | ProgramHeaderCount: 13 22 | SectionHeaderEntrySize: 64 23 | SectionHeaderCount: 26 24 | StringTableSectionIndex: 25 25 | } 26 | Sections [ 27 | Section { 28 | Index: 0 29 | Name: (0) 30 | Type: SHT_NULL (0x0) 31 | Flags [ (0x0) 32 | ] 33 | Address: 0x0 34 | Offset: 0x0 35 | Size: 0 36 | Link: 0 37 | Info: 0 38 | AddressAlignment: 0 39 | EntrySize: 0 40 | } 41 | Section { 42 | Index: 1 43 | Name: .interp (11) 44 | Type: SHT_PROGBITS (0x1) 45 | Flags [ (0x2) 46 | SHF_ALLOC (0x2) 47 | ] 48 | Address: 0x318 49 | Offset: 0x318 50 | Size: 28 51 | Link: 0 52 | Info: 0 53 | AddressAlignment: 1 54 | EntrySize: 0 55 | } 56 | Section { 57 | Index: 2 58 | Name: .note.gnu.property (19) 59 | Type: SHT_NOTE (0x7) 60 | Flags [ (0x2) 61 | SHF_ALLOC (0x2) 62 | ] 63 | Address: 0x338 64 | Offset: 0x338 65 | Size: 80 66 | Link: 0 67 | Info: 0 68 | AddressAlignment: 8 69 | EntrySize: 0 70 | } 71 | Section { 72 | Index: 3 73 | Name: .note.gnu.build-id (38) 74 | Type: SHT_NOTE (0x7) 75 | Flags [ (0x2) 76 | SHF_ALLOC (0x2) 77 | ] 78 | Address: 0x388 79 | Offset: 0x388 80 | Size: 36 81 | Link: 0 82 | Info: 0 83 | AddressAlignment: 4 84 | EntrySize: 0 85 | } 86 | Section { 87 | Index: 4 88 | Name: .note.ABI-tag (57) 89 | Type: SHT_NOTE (0x7) 90 | Flags [ (0x2) 91 | SHF_ALLOC (0x2) 92 | ] 93 | Address: 0x3AC 94 | Offset: 0x3AC 95 | Size: 32 96 | Link: 0 97 | Info: 0 98 | AddressAlignment: 4 99 | EntrySize: 0 100 | } 101 | Section { 102 | Index: 5 103 | Name: .gnu.hash (71) 104 | Type: SHT_GNU_HASH (0x6FFFFFF6) 105 | Flags [ (0x2) 106 | SHF_ALLOC (0x2) 107 | ] 108 | Address: 0x3D0 109 | Offset: 0x3D0 110 | Size: 28 111 | Link: 6 112 | Info: 0 113 | AddressAlignment: 8 114 | EntrySize: 0 115 | } 116 | Section { 117 | Index: 6 118 | Name: .dynsym (81) 119 | Type: SHT_DYNSYM (0xB) 120 | Flags [ (0x2) 121 | SHF_ALLOC (0x2) 122 | ] 123 | Address: 0x3F0 124 | Offset: 0x3F0 125 | Size: 1104 126 | Link: 7 127 | Info: 1 128 | AddressAlignment: 8 129 | EntrySize: 24 130 | } 131 | Section { 132 | Index: 7 133 | Name: .dynstr (89) 134 | Type: SHT_STRTAB (0x3) 135 | Flags [ (0x2) 136 | SHF_ALLOC (0x2) 137 | ] 138 | Address: 0x840 139 | Offset: 0x840 140 | Size: 606 141 | Link: 0 142 | Info: 0 143 | AddressAlignment: 1 144 | EntrySize: 0 145 | } 146 | Section { 147 | Index: 8 148 | Name: .gnu.version (97) 149 | Type: SHT_GNU_versym (0x6FFFFFFF) 150 | Flags [ (0x2) 151 | SHF_ALLOC (0x2) 152 | ] 153 | Address: 0xA9E 154 | Offset: 0xA9E 155 | Size: 92 156 | Link: 6 157 | Info: 0 158 | AddressAlignment: 2 159 | EntrySize: 2 160 | } 161 | Section { 162 | Index: 9 163 | Name: .gnu.version_r (110) 164 | Type: SHT_GNU_verneed (0x6FFFFFFE) 165 | Flags [ (0x2) 166 | SHF_ALLOC (0x2) 167 | ] 168 | Address: 0xB00 169 | Offset: 0xB00 170 | Size: 112 171 | Link: 7 172 | Info: 1 173 | AddressAlignment: 8 174 | EntrySize: 0 175 | } 176 | Section { 177 | Index: 10 178 | Name: .rela.dyn (125) 179 | Type: SHT_RELA (0x4) 180 | Flags [ (0x2) 181 | SHF_ALLOC (0x2) 182 | ] 183 | Address: 0xB70 184 | Offset: 0xB70 185 | Size: 1104 186 | Link: 6 187 | Info: 0 188 | AddressAlignment: 8 189 | EntrySize: 24 190 | } 191 | Section { 192 | Index: 11 193 | Name: .init (135) 194 | Type: SHT_PROGBITS (0x1) 195 | Flags [ (0x6) 196 | SHF_ALLOC (0x2) 197 | SHF_EXECINSTR (0x4) 198 | ] 199 | Address: 0x1000 200 | Offset: 0x1000 201 | Size: 27 202 | Link: 0 203 | Info: 0 204 | AddressAlignment: 4 205 | EntrySize: 0 206 | } 207 | Section { 208 | Index: 12 209 | Name: .text (141) 210 | Type: SHT_PROGBITS (0x1) 211 | Flags [ (0x6) 212 | SHF_ALLOC (0x2) 213 | SHF_EXECINSTR (0x4) 214 | ] 215 | Address: 0x1020 216 | Offset: 0x1020 217 | Size: 10627 218 | Link: 0 219 | Info: 0 220 | AddressAlignment: 16 221 | EntrySize: 0 222 | } 223 | Section { 224 | Index: 13 225 | Name: .fini (147) 226 | Type: SHT_PROGBITS (0x1) 227 | Flags [ (0x6) 228 | SHF_ALLOC (0x2) 229 | SHF_EXECINSTR (0x4) 230 | ] 231 | Address: 0x39A4 232 | Offset: 0x39A4 233 | Size: 13 234 | Link: 0 235 | Info: 0 236 | AddressAlignment: 4 237 | EntrySize: 0 238 | } 239 | Section { 240 | Index: 14 241 | Name: .rodata (153) 242 | Type: SHT_PROGBITS (0x1) 243 | Flags [ (0x2) 244 | SHF_ALLOC (0x2) 245 | ] 246 | Address: 0x4000 247 | Offset: 0x4000 248 | Size: 2507 249 | Link: 0 250 | Info: 0 251 | AddressAlignment: 32 252 | EntrySize: 0 253 | } 254 | Section { 255 | Index: 15 256 | Name: .eh_frame_hdr (161) 257 | Type: SHT_PROGBITS (0x1) 258 | Flags [ (0x2) 259 | SHF_ALLOC (0x2) 260 | ] 261 | Address: 0x49CC 262 | Offset: 0x49CC 263 | Size: 116 264 | Link: 0 265 | Info: 0 266 | AddressAlignment: 4 267 | EntrySize: 0 268 | } 269 | Section { 270 | Index: 16 271 | Name: .eh_frame (175) 272 | Type: SHT_PROGBITS (0x1) 273 | Flags [ (0x2) 274 | SHF_ALLOC (0x2) 275 | ] 276 | Address: 0x4A40 277 | Offset: 0x4A40 278 | Size: 712 279 | Link: 0 280 | Info: 0 281 | AddressAlignment: 8 282 | EntrySize: 0 283 | } 284 | Section { 285 | Index: 17 286 | Name: .init_array (185) 287 | Type: SHT_INIT_ARRAY (0xE) 288 | Flags [ (0x3) 289 | SHF_ALLOC (0x2) 290 | SHF_WRITE (0x1) 291 | ] 292 | Address: 0x5CD0 293 | Offset: 0x5CD0 294 | Size: 8 295 | Link: 0 296 | Info: 0 297 | AddressAlignment: 8 298 | EntrySize: 8 299 | } 300 | Section { 301 | Index: 18 302 | Name: .fini_array (197) 303 | Type: SHT_FINI_ARRAY (0xF) 304 | Flags [ (0x3) 305 | SHF_ALLOC (0x2) 306 | SHF_WRITE (0x1) 307 | ] 308 | Address: 0x5CD8 309 | Offset: 0x5CD8 310 | Size: 8 311 | Link: 0 312 | Info: 0 313 | AddressAlignment: 8 314 | EntrySize: 8 315 | } 316 | Section { 317 | Index: 19 318 | Name: .dynamic (209) 319 | Type: SHT_DYNAMIC (0x6) 320 | Flags [ (0x3) 321 | SHF_ALLOC (0x2) 322 | SHF_WRITE (0x1) 323 | ] 324 | Address: 0x5CE0 325 | Offset: 0x5CE0 326 | Size: 432 327 | Link: 7 328 | Info: 0 329 | AddressAlignment: 8 330 | EntrySize: 16 331 | } 332 | Section { 333 | Index: 20 334 | Name: .got (218) 335 | Type: SHT_PROGBITS (0x1) 336 | Flags [ (0x3) 337 | SHF_ALLOC (0x2) 338 | SHF_WRITE (0x1) 339 | ] 340 | Address: 0x5E90 341 | Offset: 0x5E90 342 | Size: 368 343 | Link: 0 344 | Info: 0 345 | AddressAlignment: 8 346 | EntrySize: 8 347 | } 348 | Section { 349 | Index: 21 350 | Name: .data (223) 351 | Type: SHT_PROGBITS (0x1) 352 | Flags [ (0x3) 353 | SHF_ALLOC (0x2) 354 | SHF_WRITE (0x1) 355 | ] 356 | Address: 0x6000 357 | Offset: 0x6000 358 | Size: 20 359 | Link: 0 360 | Info: 0 361 | AddressAlignment: 8 362 | EntrySize: 0 363 | } 364 | Section { 365 | Index: 22 366 | Name: .bss (229) 367 | Type: SHT_NOBITS (0x8) 368 | Flags [ (0x3) 369 | SHF_ALLOC (0x2) 370 | SHF_WRITE (0x1) 371 | ] 372 | Address: 0x6018 373 | Offset: 0x6014 374 | Size: 16 375 | Link: 0 376 | Info: 0 377 | AddressAlignment: 8 378 | EntrySize: 0 379 | } 380 | Section { 381 | Index: 23 382 | Name: .comment (234) 383 | Type: SHT_PROGBITS (0x1) 384 | Flags [ (0x30) 385 | SHF_MERGE (0x10) 386 | SHF_STRINGS (0x20) 387 | ] 388 | Address: 0x0 389 | Offset: 0x6014 390 | Size: 27 391 | Link: 0 392 | Info: 0 393 | AddressAlignment: 1 394 | EntrySize: 1 395 | } 396 | Section { 397 | Index: 24 398 | Name: .gnu_debuglink (243) 399 | Type: SHT_PROGBITS (0x1) 400 | Flags [ (0x0) 401 | ] 402 | Address: 0x0 403 | Offset: 0x6030 404 | Size: 16 405 | Link: 0 406 | Info: 0 407 | AddressAlignment: 4 408 | EntrySize: 0 409 | } 410 | Section { 411 | Index: 25 412 | Name: .shstrtab (1) 413 | Type: SHT_STRTAB (0x3) 414 | Flags [ (0x0) 415 | ] 416 | Address: 0x0 417 | Offset: 0x6040 418 | Size: 258 419 | Link: 0 420 | Info: 0 421 | AddressAlignment: 1 422 | EntrySize: 0 423 | } 424 | ] 425 | ProgramHeaders [ 426 | ProgramHeader { 427 | Type: PT_PHDR (0x6) 428 | Offset: 0x40 429 | VirtualAddress: 0x40 430 | PhysicalAddress: 0x40 431 | FileSize: 728 432 | MemSize: 728 433 | Flags [ (0x4) 434 | PF_R (0x4) 435 | ] 436 | Alignment: 8 437 | } 438 | ProgramHeader { 439 | Type: PT_INTERP (0x3) 440 | Offset: 0x318 441 | VirtualAddress: 0x318 442 | PhysicalAddress: 0x318 443 | FileSize: 28 444 | MemSize: 28 445 | Flags [ (0x4) 446 | PF_R (0x4) 447 | ] 448 | Alignment: 1 449 | } 450 | ProgramHeader { 451 | Type: PT_LOAD (0x1) 452 | Offset: 0x0 453 | VirtualAddress: 0x0 454 | PhysicalAddress: 0x0 455 | FileSize: 4032 456 | MemSize: 4032 457 | Flags [ (0x4) 458 | PF_R (0x4) 459 | ] 460 | Alignment: 4096 461 | } 462 | ProgramHeader { 463 | Type: PT_LOAD (0x1) 464 | Offset: 0x1000 465 | VirtualAddress: 0x1000 466 | PhysicalAddress: 0x1000 467 | FileSize: 10673 468 | MemSize: 10673 469 | Flags [ (0x5) 470 | PF_R (0x4) 471 | PF_X (0x1) 472 | ] 473 | Alignment: 4096 474 | } 475 | ProgramHeader { 476 | Type: PT_LOAD (0x1) 477 | Offset: 0x4000 478 | VirtualAddress: 0x4000 479 | PhysicalAddress: 0x4000 480 | FileSize: 3336 481 | MemSize: 3336 482 | Flags [ (0x4) 483 | PF_R (0x4) 484 | ] 485 | Alignment: 4096 486 | } 487 | ProgramHeader { 488 | Type: PT_LOAD (0x1) 489 | Offset: 0x5CD0 490 | VirtualAddress: 0x5CD0 491 | PhysicalAddress: 0x5CD0 492 | FileSize: 836 493 | MemSize: 856 494 | Flags [ (0x6) 495 | PF_R (0x4) 496 | PF_W (0x2) 497 | ] 498 | Alignment: 4096 499 | } 500 | ProgramHeader { 501 | Type: PT_DYNAMIC (0x2) 502 | Offset: 0x5CE0 503 | VirtualAddress: 0x5CE0 504 | PhysicalAddress: 0x5CE0 505 | FileSize: 432 506 | MemSize: 432 507 | Flags [ (0x6) 508 | PF_R (0x4) 509 | PF_W (0x2) 510 | ] 511 | Alignment: 8 512 | } 513 | ProgramHeader { 514 | Type: PT_NOTE (0x4) 515 | Offset: 0x338 516 | VirtualAddress: 0x338 517 | PhysicalAddress: 0x338 518 | FileSize: 80 519 | MemSize: 80 520 | Flags [ (0x4) 521 | PF_R (0x4) 522 | ] 523 | Alignment: 8 524 | } 525 | ProgramHeader { 526 | Type: PT_NOTE (0x4) 527 | Offset: 0x388 528 | VirtualAddress: 0x388 529 | PhysicalAddress: 0x388 530 | FileSize: 68 531 | MemSize: 68 532 | Flags [ (0x4) 533 | PF_R (0x4) 534 | ] 535 | Alignment: 4 536 | } 537 | ProgramHeader { 538 | Type: PT_GNU_PROPERTY (0x6474E553) 539 | Offset: 0x338 540 | VirtualAddress: 0x338 541 | PhysicalAddress: 0x338 542 | FileSize: 80 543 | MemSize: 80 544 | Flags [ (0x4) 545 | PF_R (0x4) 546 | ] 547 | Alignment: 8 548 | } 549 | ProgramHeader { 550 | Type: PT_GNU_EH_FRAME (0x6474E550) 551 | Offset: 0x49CC 552 | VirtualAddress: 0x49CC 553 | PhysicalAddress: 0x49CC 554 | FileSize: 116 555 | MemSize: 116 556 | Flags [ (0x4) 557 | PF_R (0x4) 558 | ] 559 | Alignment: 4 560 | } 561 | ProgramHeader { 562 | Type: PT_GNU_STACK (0x6474E551) 563 | Offset: 0x0 564 | VirtualAddress: 0x0 565 | PhysicalAddress: 0x0 566 | FileSize: 0 567 | MemSize: 0 568 | Flags [ (0x6) 569 | PF_R (0x4) 570 | PF_W (0x2) 571 | ] 572 | Alignment: 16 573 | } 574 | ProgramHeader { 575 | Type: PT_GNU_RELRO (0x6474E552) 576 | Offset: 0x5CD0 577 | VirtualAddress: 0x5CD0 578 | PhysicalAddress: 0x5CD0 579 | FileSize: 816 580 | MemSize: 816 581 | Flags [ (0x4) 582 | PF_R (0x4) 583 | ] 584 | Alignment: 1 585 | } 586 | ] 587 | -------------------------------------------------------------------------------- /ELFSage/Test/Data/generatePrintedOutputs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PARENT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 4 | 5 | pushd $PARENT_DIR &> /dev/null 6 | 7 | rm -rf PrintedHeaders 8 | mkdir PrintedHeaders 9 | 10 | for f in ./ELFFiles/*; do 11 | fname=$(basename -- "$f") 12 | # llvm-readobj prepends 6 lines of file metadata before all outputs. Discard this metadata 13 | llvm-readobj --headers ./ELFFiles/$fname | tail -n +7 > PrintedHeaders/$fname 14 | done 15 | 16 | popd &> /dev/null 17 | -------------------------------------------------------------------------------- /ELFSage/Test/TestRunOutput/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /ELFSage/Test/TestRunner.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.File 2 | 3 | private def DATA_DIR_RELATIVE: System.FilePath := System.mkFilePath [ "Data" ] 4 | private def ELF_FILES_DIR_RELATIVE: System.FilePath := DATA_DIR_RELATIVE.join $ System.mkFilePath [ "ELFFiles" ] 5 | private def EXPECTED_OUTPUT_DIR_RELATIVE: System.FilePath := DATA_DIR_RELATIVE.join $ System.mkFilePath [ "PrintedHeaders" ] 6 | private def TEST_OUTPUT_DIR_RELATIVE: System.FilePath := System.mkFilePath [ "TestRunOutput" ] 7 | 8 | private def EXAMPLE_TEST_NAME: System.FilePath := System.mkFilePath [ "true" ] 9 | 10 | def runSingleTest (elfFile: System.FilePath) (expectedOutputFile: System.FilePath) 11 | (testOutputFile: System.FilePath): IO UInt32 := do 12 | 13 | -- delete the existing test output file if it exists to avoid debugging confusion 14 | IO.FS.removeFile testOutputFile <|> pure () 15 | 16 | let bytes ← IO.FS.readBinFile elfFile 17 | 18 | match mkRawELFFile? bytes with 19 | | .error warn => IO.println warn *> return 1 20 | | .ok elffile => do 21 | 22 | let expectedString ← IO.FS.readFile expectedOutputFile 23 | let actualString := RawELFFile.headersToString elffile ++ "\n" 24 | 25 | if expectedString == actualString then 26 | IO.println "PASS" 27 | return 0 28 | else 29 | IO.println "FAIL" 30 | IO.FS.writeFile testOutputFile actualString 31 | IO.println $ s!"Expected {testOutputFile} to equal {expectedOutputFile}." 32 | return 1 33 | 34 | def runTests (testDirRelative : System.FilePath): IO UInt32 := do 35 | let rootDir ← IO.currentDir 36 | let testDir: System.FilePath := rootDir.join testDirRelative 37 | let elfFilesDir := testDir.join ELF_FILES_DIR_RELATIVE 38 | let testOutputDir := testDir.join TEST_OUTPUT_DIR_RELATIVE 39 | let expectedOutputDir := testDir.join EXPECTED_OUTPUT_DIR_RELATIVE 40 | 41 | -- for now just run a single example test 42 | let elfFile := elfFilesDir.join EXAMPLE_TEST_NAME 43 | let expectedOutputFile := expectedOutputDir.join EXAMPLE_TEST_NAME 44 | let testOutputFile := testOutputDir.join EXAMPLE_TEST_NAME 45 | runSingleTest elfFile expectedOutputFile testOutputFile 46 | -------------------------------------------------------------------------------- /ELFSage/Types/Dynamic.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Util.ByteArray 3 | import ELFSage.Constants.Dynamic 4 | 5 | inductive DynamicUnion (α β : Type) where 6 | | d_val : α → DynamicUnion α β 7 | | d_ptr : β → DynamicUnion α β 8 | | d_ignored : ByteArray → DynamicUnion α β 9 | deriving Repr 10 | 11 | inductive Dynamic.FieldInterpretation where 12 | | d_val : FieldInterpretation 13 | | d_ptr : FieldInterpretation 14 | | d_ignored : FieldInterpretation 15 | deriving Repr 16 | 17 | inductive Dynamic.Tag where 18 | /-- [dt_null] marks the end of the dynamic array -/ 19 | | dt_null : Dynamic.Tag 20 | /-- [dt_needed] holds the string table offset of a string containing the name of a needed library. -/ 21 | | dt_needed : Dynamic.Tag 22 | /--[dt_pltrelsz] holds the size in bytes of relocation entries associated with the PLT. -/ 23 | | dt_pltrelsz : Dynamic.Tag 24 | /-- [dt_pltgot] holds an address associated with the PLT or GOT. -/ 25 | | dt_pltgot : Dynamic.Tag 26 | /-- [dt_hash] holds the address of a symbol-table hash. -/ 27 | | dt_hash : Dynamic.Tag 28 | /-- [dt_strtab] holds the address of the string table. -/ 29 | | dt_strtab : Dynamic.Tag 30 | /-- [dt_symtab] holds the address of a symbol table. -/ 31 | | dt_symtab : Dynamic.Tag 32 | /-- [dt_rela] holds the address of a relocation table. -/ 33 | | dt_rela : Dynamic.Tag 34 | /-- [dt_relasz] holds the size in bytes of the relocation table. -/ 35 | | dt_relasz : Dynamic.Tag 36 | /-- [dt_relaent] holds the size in bytes of a relocation table entry. -/ 37 | | dt_relaent : Dynamic.Tag 38 | /-- [dt_strsz] holds the size in bytes of the string table. -/ 39 | | dt_strsz : Dynamic.Tag 40 | /-- [dt_syment] holds the size in bytes of a symbol table entry. -/ 41 | | dt_syment : Dynamic.Tag 42 | /-- [dt_init] holds the address of the initialisation function. -/ 43 | | dt_init : Dynamic.Tag 44 | /-- [dt_fini] holds the address of the finalisation function. -/ 45 | | dt_fini : Dynamic.Tag 46 | /-- [dt_soname] holds the string table offset of a string containing the shared object name. -/ 47 | | dt_soname : Dynamic.Tag 48 | /-- [dt_rpath] holds the string table offset of a string containing the library search path. -/ 49 | | dt_rpath : Dynamic.Tag 50 | /-- [dt_symbolic] alters the linker's symbol resolution algorithm so that names are resolved first from the shared object file itself, rather than the executable file. -/ 51 | | dt_symbolic : Dynamic.Tag 52 | /-- [dt_rel] is similar to [dt_rela] except its table has implicit addends. -/ 53 | | dt_rel : Dynamic.Tag 54 | /-- [dt_relsz] holds the size in bytes of the [dt_rel] relocation table. -/ 55 | | dt_relsz : Dynamic.Tag 56 | /-- [dt_relent] holds the size in bytes of a [dt_rel] relocation entry. -/ 57 | | dt_relent : Dynamic.Tag 58 | /-- [dt_pltrel] specifies the type of relocation entry to which the PLT refers. -/ 59 | | dt_pltrel : Dynamic.Tag 60 | /-- [dt_debug] is used for debugging and its purpose is not specified in the ABI. Programs using this entry are not ABI-conformant. -/ 61 | | dt_debug : Dynamic.Tag 62 | /-- [dt_textrel] absence of this entry indicates that no relocation entry should 63 | a modification to a non-writable segment. Otherwise, if present, one 64 | or more relocation entries may request modifications to a non-writable 65 | segment. -/ 66 | | dt_textrel : Dynamic.Tag 67 | /-- [dt_jmprel]'s member holds the address of relocation entries associated with the PLT. -/ 68 | | dt_jmprel : Dynamic.Tag 69 | /-- [dt_bindnow] instructs the linker to process all relocations for the object containing the entry before transferring control to the program. -/ 70 | | dt_bindnow : Dynamic.Tag 71 | /-- [dt_init_array] holds the address to the array of pointers to initialisation functions. -/ 72 | | dt_init_array : Dynamic.Tag 73 | /-- [dt_fini_array] holds the address to the array of pointers to finalisation functions. -/ 74 | | dt_fini_array : Dynamic.Tag 75 | /-- [dt_init_arraysz] holds the size in bytes of the array of pointers to initialisation functions. -/ 76 | | dt_init_arraysz : Dynamic.Tag 77 | /-- [dt_fini_arraysz] holds the size in bytes of the array of pointers to finalisation functions. -/ 78 | | dt_fini_arraysz : Dynamic.Tag 79 | /-- [dt_runpath] holds an offset into the string table holding a string containing the library search path. -/ 80 | | dt_runpath : Dynamic.Tag 81 | /-- [dt_flags] holds flag values specific to the object being loaded. -/ 82 | | dt_flags : Dynamic.Tag 83 | /-- [dt_preinit_array] holds the address to the array of pointers of pre- initialisation functions. -/ 84 | | dt_preinit_array : Dynamic.Tag 85 | /-- [dt_preinit_arraysz] holds the size in bytes of the array of pointers of pre-initialisation functions. -/ 86 | | dt_preinit_arraysz : Dynamic.Tag 87 | /-- [dt_loos] and [dt_hios]: this inclusive range is reserved for OS-specific semantics. -/ 88 | | dt_loos_hios (n :Nat) : Dynamic.Tag 89 | /-- [dt_loproc] and [dt_hiproc]: this inclusive range is reserved for processor specific semantics. -/ 90 | | dt_loproc_hiproc (n: Nat) : Dynamic.Tag 91 | /-- [dt_loproc] and [dt_hiproc]: this inclusive range is reserved for processor specific semantics. -/ 92 | | dt_unknown (n : Int) : Dynamic.Tag 93 | 94 | /-- GNU Dynamic Tag Extensions -/ 95 | inductive Dynamic.Tag.GNU 96 | | dt_gnu_auxiliary : Dynamic.Tag.GNU 97 | | dt_gnu_filter : Dynamic.Tag.GNU 98 | | dt_gnu_num : Dynamic.Tag.GNU 99 | | dt_gnu_posflag_1 : Dynamic.Tag.GNU 100 | | dt_gnu_relcount : Dynamic.Tag.GNU 101 | | dt_gnu_relacount : Dynamic.Tag.GNU 102 | | dt_gnu_syminent : Dynamic.Tag.GNU 103 | | dt_gnu_syminfo : Dynamic.Tag.GNU 104 | | dt_gnu_syminsz : Dynamic.Tag.GNU 105 | | dt_gnu_verdef : Dynamic.Tag.GNU 106 | | dt_gnu_verdefnum : Dynamic.Tag.GNU 107 | | dt_gnu_verneed : Dynamic.Tag.GNU 108 | | dt_gnu_verneednum : Dynamic.Tag.GNU 109 | | dt_gnu_versym : Dynamic.Tag.GNU 110 | | dt_gnu_hash : Dynamic.Tag.GNU 111 | | dt_gnu_flags_1 : Dynamic.Tag.GNU 112 | | dt_gnu_checksum : Dynamic.Tag.GNU 113 | | dt_gnu_prelinked : Dynamic.Tag.GNU 114 | 115 | section open Dynamic.Tag 116 | 117 | def Dynamic.Tag.mkRawTag (x : Int) : Dynamic.Tag := 118 | if x == DT_NULL then .dt_null 119 | else if x == DT_NEEDED then .dt_needed 120 | else if x == DT_PLTRELSZ then .dt_pltrelsz 121 | else if x == DT_PLTGOT then .dt_pltgot 122 | else if x == DT_HASH then .dt_hash 123 | else if x == DT_STRTAB then .dt_strtab 124 | else if x == DT_SYMTAB then .dt_symtab 125 | else if x == DT_RELA then .dt_rela 126 | else if x == DT_RELASZ then .dt_relasz 127 | else if x == DT_RELAENT then .dt_relaent 128 | else if x == DT_STRSZ then .dt_strsz 129 | else if x == DT_SYMENT then .dt_syment 130 | else if x == DT_INIT then .dt_init 131 | else if x == DT_FINI then .dt_fini 132 | else if x == DT_SONAME then .dt_soname 133 | else if x == DT_RPATH then .dt_rpath 134 | else if x == DT_SYMBOLIC then .dt_symbolic 135 | else if x == DT_REL then .dt_rel 136 | else if x == DT_RELSZ then .dt_relsz 137 | else if x == DT_RELENT then .dt_relent 138 | else if x == DT_PLTREL then .dt_pltrel 139 | else if x == DT_DEBUG then .dt_debug 140 | else if x == DT_TEXTREL then .dt_textrel 141 | else if x == DT_JMPREL then .dt_jmprel 142 | else if x == DT_BINDNOW then .dt_bindnow 143 | else if x == DT_INIT_ARRAY then .dt_init_array 144 | else if x == DT_FINI_ARRAY then .dt_fini_array 145 | else if x == DT_INIT_ARRAYSZ then .dt_init_arraysz 146 | else if x == DT_FINI_ARRAYSZ then .dt_fini_arraysz 147 | else if x == DT_RUNPATH then .dt_runpath 148 | else if x == DT_FLAGS then .dt_flags 149 | else if x == DT_PREINIT_ARRAY then .dt_preinit_array 150 | else if x == DT_PREINIT_ARRAYSZ then .dt_preinit_arraysz 151 | else if x >= DT_LOOS ∧ DT_LOOS <= DT_LOOS then .dt_loos_hios x.toNat 152 | else if x >= DT_LOPROC ∧ x <= DT_LOOS then .dt_loproc_hiproc x.toNat 153 | else .dt_unknown x 154 | 155 | def Dynamic.Tag.GNU.mkRawTag (x : Int) : Except String Dynamic.Tag.GNU := 156 | if x == GNU.DT_GNU_AUXILIARY then .ok .dt_gnu_auxiliary 157 | else if x == GNU.DT_GNU_FILTER then .ok .dt_gnu_filter 158 | else if x == GNU.DT_GNU_NUM then .ok .dt_gnu_num 159 | else if x == GNU.DT_GNU_POSFLAG_1 then .ok .dt_gnu_posflag_1 160 | else if x == GNU.DT_GNU_RELCOUNT then .ok .dt_gnu_relcount 161 | else if x == GNU.DT_GNU_RELACOUNT then .ok .dt_gnu_relacount 162 | else if x == GNU.DT_GNU_SYMINENT then .ok .dt_gnu_syminent 163 | else if x == GNU.DT_GNU_SYMINFO then .ok .dt_gnu_syminfo 164 | else if x == GNU.DT_GNU_SYMINSZ then .ok .dt_gnu_syminsz 165 | else if x == GNU.DT_GNU_VERDEF then .ok .dt_gnu_verdef 166 | else if x == GNU.DT_GNU_VERDEFNUM then .ok .dt_gnu_verdefnum 167 | else if x == GNU.DT_GNU_VERNEED then .ok .dt_gnu_verneed 168 | else if x == GNU.DT_GNU_VERNEEDNUM then .ok .dt_gnu_verneednum 169 | else if x == GNU.DT_GNU_VERSYM then .ok .dt_gnu_versym 170 | else if x == GNU.DT_GNU_HASH then .ok .dt_gnu_hash 171 | else if x == GNU.DT_GNU_FLAGS_1 then .ok .dt_gnu_flags_1 172 | else if x == GNU.DT_GNU_CHECKSUM then .ok .dt_gnu_checksum 173 | else if x == GNU.DT_GNU_PRELINKED then .ok .dt_gnu_prelinked 174 | else .error "No corresponding GNU dynamic tag found" 175 | 176 | end 177 | 178 | def Dynamic.Tag.toFieldInterpretation 179 | (dt : Dynamic.Tag) 180 | (os : Nat → Except String Dynamic.FieldInterpretation) 181 | (proc : Nat → Except String Dynamic.FieldInterpretation) 182 | : Except String Dynamic.FieldInterpretation := 183 | match dt with 184 | | dt_null => .ok .d_ignored 185 | | dt_needed => .ok .d_val 186 | | dt_pltrelsz => .ok .d_val 187 | | dt_pltgot => .ok .d_ptr 188 | | dt_hash => .ok .d_ptr 189 | | dt_strtab => .ok .d_ptr 190 | | dt_symtab => .ok .d_ptr 191 | | dt_rela => .ok .d_ptr 192 | | dt_relasz => .ok .d_val 193 | | dt_relaent => .ok .d_val 194 | | dt_strsz => .ok .d_val 195 | | dt_syment => .ok .d_val 196 | | dt_init => .ok .d_ptr 197 | | dt_fini => .ok .d_ptr 198 | | dt_soname => .ok .d_val 199 | | dt_rpath => .ok .d_val 200 | | dt_symbolic => .ok .d_ignored 201 | | dt_rel => .ok .d_ptr 202 | | dt_relsz => .ok .d_val 203 | | dt_relent => .ok .d_val 204 | | dt_pltrel => .ok .d_val 205 | | dt_debug => .ok .d_ptr 206 | | dt_textrel => .ok .d_ignored 207 | | dt_jmprel => .ok .d_ptr 208 | | dt_bindnow => .ok .d_ignored 209 | | dt_init_array => .ok .d_ptr 210 | | dt_fini_array => .ok .d_ptr 211 | | dt_init_arraysz => .ok .d_val 212 | | dt_fini_arraysz => .ok .d_val 213 | | dt_runpath => .ok .d_val 214 | | dt_flags => .ok .d_val 215 | -- XXX: linksem makes the effect of DT_PREINIT_ARRAY (which is equal to 216 | -- DT_ENCODING, which is equal to 32) conditional on whether you're dealing 217 | -- with a shared object: 218 | -- https://github.com/rems-project/linksem/blob/238f803b18e485eecbc550e0e2292257eaf7029b/src/elf_dynamic.lem#L364 219 | -- But other sources seem to say that it should be d_ptr 220 | -- https://www.sco.com/developers/gabi/2012-12-31/ch5.dynamic.html#dynamic_section 221 | -- https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-42444.html#scrolltoc 222 | | dt_preinit_array => .ok .d_ptr 223 | | dt_preinit_arraysz => .ok .d_val 224 | | dt_loos_hios (n :Nat) => os n 225 | | dt_loproc_hiproc (n: Nat) => proc n 226 | -- The rules for unknowns are mentioned in 227 | -- https://www.sco.com/developers/gabi/2012-12-31/ch5.dynamic.html#dynamic_section 228 | -- https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-42444.html#scrolltoc 229 | -- oracle includes a fair number of tags not mentioned here or at linksem. 230 | | dt_unknown (x : Int) => if (x > DT_HIOS ∨ x < DT_LOPROC) ∧ x % 2 == 0 231 | then .ok .d_ptr 232 | else .error s!"unknown dynamic tag prevents interpretation of dt_un" 233 | 234 | -- based on https://github.com/rems-project/linksem/blob/238f803b18e485eecbc550e0e2292257eaf7029b/src/gnu_extensions/gnu_ext_dynamic.lem#L199 235 | def Dynamic.Tag.GNU.toFieldInterpretation 236 | (dt : Dynamic.Tag.GNU) 237 | : Dynamic.FieldInterpretation := 238 | match dt with 239 | | dt_gnu_auxiliary => .d_val 240 | | dt_gnu_filter => .d_val 241 | | dt_gnu_num => .d_ignored 242 | | dt_gnu_posflag_1 => .d_val 243 | | dt_gnu_relcount => .d_val 244 | | dt_gnu_relacount => .d_val 245 | | dt_gnu_syminent => .d_val 246 | | dt_gnu_syminfo => .d_ptr 247 | | dt_gnu_syminsz => .d_val 248 | | dt_gnu_verdef => .d_ptr 249 | | dt_gnu_verdefnum => .d_val 250 | | dt_gnu_verneed => .d_ptr 251 | | dt_gnu_verneednum => .d_val 252 | | dt_gnu_versym => .d_ptr 253 | | dt_gnu_hash => .d_ptr 254 | | dt_gnu_flags_1 => .d_val 255 | | dt_gnu_checksum => .d_val 256 | | dt_gnu_prelinked => .d_val 257 | 258 | class DynamicEntry (α : Type) where 259 | d_tag : α → Int 260 | d_un : α → DynamicUnion Nat Nat 261 | bytes : α → (isBigendian : Bool) → ByteArray 262 | 263 | structure ELF32DynamicEntry where 264 | d_tag : elf32_sword 265 | d_un : DynamicUnion elf32_word elf32_addr 266 | deriving Repr 267 | 268 | def mkELF32DynamicEntry? 269 | (isBigEndian : Bool) 270 | (bs : ByteArray) 271 | (offset : Nat) 272 | (h : bs.size - offset ≥ 0x8) 273 | : Except String ELF32DynamicEntry := 274 | let tagval := getUInt32from (offset + 0x0) (by omega) 275 | let rawTag := Dynamic.Tag.mkRawTag (⟨tagval⟩ : SInt32).toInt 276 | match rawTag.toFieldInterpretation 277 | (λx => Dynamic.Tag.GNU.mkRawTag x >>= pure ∘ Dynamic.Tag.GNU.toFieldInterpretation) 278 | (λ_ => .error "proc reserved tag, not implemented") 279 | with 280 | | .ok .d_val => .ok { d_tag := ⟨tagval⟩, d_un := .d_val $ getUInt32from (offset + 0x4) (by omega) } 281 | | .ok .d_ptr => .ok { d_tag := ⟨tagval⟩, d_un := .d_ptr $ getUInt32from (offset + 0x4) (by omega) } 282 | | .ok .d_ignored => .ok { d_tag := ⟨tagval⟩, d_un := .d_ignored $ bs.extract (offset + 0x4) (offset + 0x8) } 283 | | .error e => .error e 284 | where 285 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 286 | 287 | def ELF32DynamicEntry.bytes (de : ELF32DynamicEntry) (isBigEndian : Bool) := 288 | let union_bytes := match de.d_un with | .d_val b => getBytes32 b | .d_ptr b => getBytes32 b | .d_ignored bytes => bytes 289 | getBytes32 de.d_tag.bytes ++ union_bytes 290 | where getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 291 | 292 | instance : DynamicEntry ELF32DynamicEntry where 293 | d_tag de := de.d_tag.toInt 294 | d_un de := match de.d_un with 295 | | .d_val v => .d_val v.toNat 296 | | .d_ptr p => .d_ptr p.toNat 297 | | .d_ignored bs => .d_ignored bs 298 | bytes de := de.bytes 299 | 300 | structure ELF64DynamicEntry where 301 | d_tag : elf64_sxword 302 | d_un : DynamicUnion elf64_xword elf64_addr 303 | deriving Repr 304 | 305 | def mkELF64DynamicEntry? 306 | (isBigEndian : Bool) 307 | (bs : ByteArray) 308 | (offset : Nat) 309 | (h : bs.size - offset ≥ 0x10) 310 | : Except String ELF64DynamicEntry := 311 | let tagval := getUInt64from (offset + 0x00) (by omega) 312 | let rawTag := Dynamic.Tag.mkRawTag (⟨tagval⟩ : SInt64).toInt 313 | match rawTag.toFieldInterpretation 314 | (λx => Dynamic.Tag.GNU.mkRawTag x >>= pure ∘ Dynamic.Tag.GNU.toFieldInterpretation) 315 | (λ_ => .error "proc reserved tag, not implemented") 316 | with 317 | | .ok .d_val => .ok { d_tag := ⟨tagval⟩, d_un := .d_val $ getUInt64from (offset + 0x08) (by omega) } 318 | | .ok .d_ptr => .ok { d_tag := ⟨tagval⟩, d_un := .d_ptr $ getUInt64from (offset + 0x08) (by omega) } 319 | | .ok .d_ignored => .ok { d_tag := ⟨tagval⟩, d_un := .d_ignored $ bs.extract (offset + 0x08) (offset + 0x10) } 320 | | .error e => .error e 321 | where 322 | getUInt64from := if isBigEndian then bs.getUInt64BEfrom else bs.getUInt64LEfrom 323 | 324 | def ELF64DynamicEntry.bytes (de : ELF64DynamicEntry) (isBigEndian : Bool) := 325 | let union_bytes := match de.d_un with | .d_val b => getBytes64 b | .d_ptr b => getBytes64 b | .d_ignored bytes => bytes 326 | getBytes64 de.d_tag.bytes ++ union_bytes 327 | where getBytes64 := if isBigEndian then UInt64.getBytesBEfrom else UInt64.getBytesLEfrom 328 | 329 | instance : DynamicEntry ELF64DynamicEntry where 330 | d_tag de := de.d_tag.toInt 331 | d_un de := match de.d_un with 332 | | .d_val v => .d_val v.toNat 333 | | .d_ptr p => .d_ptr p.toNat 334 | | .d_ignored bs => .d_ignored bs 335 | bytes de := de.bytes 336 | 337 | inductive RawDynamicEntry := 338 | | elf64 : ELF64DynamicEntry → RawDynamicEntry 339 | | elf32 : ELF32DynamicEntry → RawDynamicEntry 340 | deriving Repr 341 | 342 | def mkRawDynamicEntry? 343 | (bs : ByteArray) 344 | (is64Bit : Bool) 345 | (isBigendian : Bool) 346 | (offset : Nat) 347 | : Except String RawDynamicEntry := 348 | match is64Bit with 349 | | true => 350 | if h : bs.size - offset ≥ 0x10 351 | then .elf64 <$> mkELF64DynamicEntry? isBigendian bs offset h 352 | else throw $ err 0x10 353 | | false => 354 | if h : bs.size - offset ≥ 0x8 355 | then .elf32 <$> mkELF32DynamicEntry? isBigendian bs offset h 356 | else throw $ err 0x08 357 | where 358 | err size := s! "Dynamic entry offset {offset} doesn't leave enough space for the entry, " ++ 359 | s! "which requires {size} bytes." 360 | -------------------------------------------------------------------------------- /ELFSage/Types/ELFHeader.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Util.ByteArray 3 | import ELFSage.Constants.ELFHeader 4 | import ELFSage.Util.Hex 5 | 6 | open Hex 7 | 8 | class ELFHeader (α : Type) where 9 | e_ident : α → NByteArray 16 10 | /-- The object file type -/ 11 | e_type : α → Nat 12 | /-- Required machine architecture -/ 13 | e_machine : α → Nat 14 | /-- Object file version -/ 15 | e_version : α → Nat 16 | /-- Virtual address for transfer of control -/ 17 | e_entry : α → Nat 18 | /-- Program header table offset in bytes -/ 19 | e_phoff : α → Nat 20 | /-- Section header table offset in bytes -/ 21 | e_shoff : α → Nat 22 | /-- Processor-specific flags -/ 23 | e_flags : α → Nat 24 | /-- ELF header size in bytes -/ 25 | e_ehsize : α → Nat 26 | /-- Program header table entry size in bytes -/ 27 | e_phentsize: α → Nat 28 | /-- Number of entries in program header table -/ 29 | e_phnum : α → Nat 30 | /-- Section header table entry size in bytes -/ 31 | e_shentsize: α → Nat 32 | /-- Number of entries in section header table -/ 33 | e_shnum : α → Nat 34 | /-- Section header table entry for section name string table -/ 35 | e_shstrndx : α → Nat 36 | /-- Underlying ByteArray -/ 37 | bytes : α → ByteArray 38 | 39 | def ELFHeader.isBigendian [ELFHeader α] (eh : α) := let ⟨bytes, _⟩ := e_ident eh; bytes[0x5] == 2 40 | 41 | def ELFHeader.is64Bit [ELFHeader α] (eh : α) := let ⟨bytes, _⟩ := e_ident eh; bytes[0x4] == 2 42 | 43 | def ELFHeader.getSectionHeaderOffsets [ELFHeader α] (eh : α) : List Nat := 44 | (List.range (ELFHeader.e_shnum eh)).map λidx ↦ ELFHeader.e_shoff eh + ELFHeader.e_shentsize eh * idx 45 | 46 | def ELFHeader.getProgramHeaderOffsets [ELFHeader α] (eh : α) : List Nat := 47 | (List.range (ELFHeader.e_phnum eh)).map λidx ↦ ELFHeader.e_phoff eh + ELFHeader.e_phentsize eh * idx 48 | 49 | def ELFHeader.e_type_val [ELFHeader α] (eh : α) := 50 | ELFHeader.e_type.fromNat ∘ ELFHeader.e_type $ eh 51 | 52 | def ELFHeader.e_machine_val [ELFHeader α] (eh : α) := 53 | ELFHeader.e_machine.fromNat ∘ ELFHeader.e_machine $ eh 54 | 55 | def ELFHeader.ei_class_val [ELFHeader α] (eh : α) := 56 | ELFHeader.ei_class.fromNat ei_class 57 | where ei_class := let ⟨bytes, _⟩ := e_ident eh; bytes[0x4].toNat 58 | 59 | def ELFHeader.ei_data_val [ELFHeader α] (eh : α) := 60 | ELFHeader.ei_data.fromNat ei_data 61 | where ei_data := let ⟨bytes, _⟩ := e_ident eh; bytes[0x5].toNat 62 | 63 | def ELFHeader.ei_osabi_val [ELFHeader α] (eh : α) := 64 | ELFHeader.ei_osabi.fromNat ei_osabi 65 | where ei_osabi := let ⟨bytes, _⟩ := e_ident eh; bytes[0x7].toNat 66 | 67 | def ELFHeader.sh_end [ELFHeader α] (eh : α) := 68 | ELFHeader.e_shoff eh + ELFHeader.e_shentsize eh * ELFHeader.e_shnum eh 69 | 70 | def ELFHeader.ph_end [ELFHeader α] (eh : α) := 71 | ELFHeader.e_phoff eh + ELFHeader.e_phentsize eh * ELFHeader.e_phnum eh 72 | 73 | instance [ELFHeader α] : ToString α where 74 | toString eh := 75 | let ident (i : Fin 16) := (ELFHeader.e_ident eh).bytes.get ⟨ i, by simp [(ELFHeader.e_ident eh).sized] ⟩ 76 | let identAsHex (i: Fin 16) := toHex (ident i).toNat 77 | let identAsHexLength2 (i: Fin 16) := toHexMinLength (ident i).toNat 2 78 | "ElfHeader {\n" ++ 79 | " Ident {\n" ++ 80 | s!" Magic: ({identAsHexLength2 0} {identAsHexLength2 1} {identAsHexLength2 2} {identAsHexLength2 3})\n" ++ 81 | s!" Class: {ELFHeader.ei_class_val eh} (0x{identAsHex 4})\n" ++ 82 | s!" DataEncoding: {ELFHeader.ei_data_val eh} (0x{identAsHex 5})\n" ++ 83 | s!" FileVersion: {ident 6}\n" ++ 84 | s!" OS/ABI: {ELFHeader.ei_osabi_val eh} (0x{identAsHex 7})\n" ++ 85 | s!" ABIVersion: {ident 8}\n" ++ 86 | s!" Unused: ({identAsHexLength2 9} {identAsHexLength2 10} {identAsHexLength2 11} {identAsHexLength2 12} {identAsHexLength2 13} {identAsHexLength2 14} {identAsHexLength2 15})\n" ++ 87 | " }\n" ++ 88 | s!" Type: {ELFHeader.e_type_val eh} (0x{toHex $ ELFHeader.e_type eh})\n" ++ 89 | s!" Machine: {ELFHeader.e_machine_val eh} (0x{toHex $ ELFHeader.e_machine eh})\n" ++ 90 | s!" Version: {ELFHeader.e_version eh}\n" ++ 91 | s!" Entry: 0x{toHex $ ELFHeader.e_entry eh}\n" ++ 92 | s!" ProgramHeaderOffset: 0x{toHex $ ELFHeader.e_phoff eh}\n" ++ 93 | s!" SectionHeaderOffset: 0x{toHex $ ELFHeader.e_shoff eh}\n" ++ 94 | s!" Flags [ (0x{toHex $ ELFHeader.e_flags eh})\n" ++ 95 | " ]\n" ++ 96 | s!" HeaderSize: {ELFHeader.e_ehsize eh}\n" ++ 97 | s!" ProgramHeaderEntrySize: {ELFHeader.e_phentsize eh}\n" ++ 98 | s!" ProgramHeaderCount: {ELFHeader.e_phnum eh}\n" ++ 99 | s!" SectionHeaderEntrySize: {ELFHeader.e_shentsize eh}\n" ++ 100 | s!" SectionHeaderCount: {ELFHeader.e_shnum eh}\n" ++ 101 | s!" StringTableSectionIndex: {ELFHeader.e_shstrndx eh}\n" ++ 102 | "}" 103 | 104 | structure ELF64Header where 105 | /-- Identification field -/ 106 | e_ident : NByteArray 16 107 | /-- The object file type -/ 108 | e_type : elf64_half 109 | /-- Required machine architecture -/ 110 | e_machine : elf64_half 111 | /-- Object file version -/ 112 | e_version : elf64_word 113 | /-- Virtual address for transfer of control -/ 114 | e_entry : elf64_addr 115 | /-- Program header table offset in bytes -/ 116 | e_phoff : elf64_off 117 | /-- Section header table offset in bytes -/ 118 | e_shoff : elf64_off 119 | /-- Processor-specific flags -/ 120 | e_flags : elf64_word 121 | /-- ELF header size in bytes -/ 122 | e_ehsize : elf64_half 123 | /-- Program header table entry size in bytes -/ 124 | e_phentsize: elf64_half 125 | /-- Number of entries in program header table -/ 126 | e_phnum : elf64_half 127 | /-- Section header table entry size in bytes -/ 128 | e_shentsize: elf64_half 129 | /-- Number of entries in section header table -/ 130 | e_shnum : elf64_half 131 | /-- Section header table entry for section name string table -/ 132 | e_shstrndx : elf64_half 133 | deriving Repr 134 | 135 | /-- A simple parser for extracting an ELF64 header, just a test, no validation -/ 136 | def mkELF64Header (bs : ByteArray) (h : bs.size ≥ 0x40) : ELF64Header := { 137 | e_ident := NByteArray.extract bs 0x10 (by omega), 138 | e_type := getUInt16from 0x10 (by omega), 139 | e_machine := getUInt16from 0x12 (by omega), 140 | e_version := getUInt32from 0x14 (by omega), 141 | e_entry := getUInt64from 0x18 (by omega), 142 | e_phoff := getUInt64from 0x20 (by omega), 143 | e_shoff := getUInt64from 0x28 (by omega), 144 | e_flags := getUInt32from 0x30 (by omega), 145 | e_ehsize := getUInt16from 0x34 (by omega), 146 | e_phentsize := getUInt16from 0x36 (by omega), 147 | e_phnum := getUInt16from 0x38 (by omega), 148 | e_shentsize := getUInt16from 0x3A (by omega), 149 | e_shnum := getUInt16from 0x3C (by omega), 150 | e_shstrndx := getUInt16from 0x3E (by omega), 151 | } where 152 | isBigEndian := bs.get ⟨0x5,by omega⟩ == 2 153 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 154 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 155 | getUInt64from := if isBigEndian then bs.getUInt64BEfrom else bs.getUInt64LEfrom 156 | 157 | def mkELF64Header? (bs: ByteArray) : Except String ELF64Header := 158 | if h : bs.size ≥ 0x40 then .ok $ mkELF64Header bs h 159 | else .error "We're looking for a 64 bit ELF header but there aren't enough bytes." 160 | 161 | def ELF64Header.bytes (eh : ELF64Header) : ByteArray := 162 | eh.e_ident.bytes ++ 163 | getBytes16 eh.e_type ++ 164 | getBytes16 eh.e_machine ++ 165 | getBytes32 eh.e_version ++ 166 | getBytes64 eh.e_entry ++ 167 | getBytes64 eh.e_phoff ++ 168 | getBytes64 eh.e_shoff ++ 169 | getBytes32 eh.e_flags ++ 170 | getBytes16 eh.e_ehsize ++ 171 | getBytes16 eh.e_phentsize ++ 172 | getBytes16 eh.e_phnum ++ 173 | getBytes16 eh.e_shentsize ++ 174 | getBytes16 eh.e_shnum ++ 175 | getBytes16 eh.e_shstrndx 176 | where isBigEndian := eh.e_ident.get 0x5 (by omega) == 2 177 | getBytes16 := if isBigEndian then UInt16.getBytesBEfrom else UInt16.getBytesLEfrom 178 | getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 179 | getBytes64 := if isBigEndian then UInt64.getBytesBEfrom else UInt64.getBytesLEfrom 180 | 181 | instance : ELFHeader ELF64Header where 182 | e_ident eh := eh.e_ident 183 | e_type eh := eh.e_type.toNat 184 | e_machine eh := eh.e_machine.toNat 185 | e_version eh := eh.e_version.toNat 186 | e_entry eh := eh.e_entry.toNat 187 | e_phoff eh := eh.e_phoff.toNat 188 | e_shoff eh := eh.e_shoff.toNat 189 | e_flags eh := eh.e_flags.toNat 190 | e_ehsize eh := eh.e_ehsize.toNat 191 | e_phentsize eh := eh.e_phentsize.toNat 192 | e_phnum eh := eh.e_phnum.toNat 193 | e_shentsize eh := eh.e_shentsize.toNat 194 | e_shnum eh := eh.e_shnum.toNat 195 | e_shstrndx eh := eh.e_shstrndx.toNat 196 | bytes eh := eh.bytes 197 | 198 | structure ELF32Header where 199 | /-- Identification field -/ 200 | e_ident : NByteArray 16 201 | /-- The object file type -/ 202 | e_type : elf32_half 203 | /-- Required machine architecture -/ 204 | e_machine : elf32_half 205 | /-- Object file version -/ 206 | e_version : elf32_word 207 | /-- Virtual address for transfer of control -/ 208 | e_entry : elf32_addr 209 | /-- Program header table offset in bytes -/ 210 | e_phoff : elf32_off 211 | /-- Section header table offset in bytes -/ 212 | e_shoff : elf32_off 213 | /-- Processor-specific flags -/ 214 | e_flags : elf32_word 215 | /-- ELF header size in bytes -/ 216 | e_ehsize : elf32_half 217 | /-- Program header table entry size in bytes -/ 218 | e_phentsize: elf32_half 219 | /-- Number of entries in program header table -/ 220 | e_phnum : elf32_half 221 | /-- Section header table entry size in bytes -/ 222 | e_shentsize: elf32_half 223 | /-- Number of entries in section header table -/ 224 | e_shnum : elf32_half 225 | /-- Section header table entry for section name string table -/ 226 | e_shstrndx : elf32_half 227 | deriving Repr 228 | 229 | /-- A simple parser for extracting an ELF32 header, just a test, no validation -/ 230 | def mkELF32Header (bs : ByteArray) (h : bs.size ≥ 0x34) : ELF32Header := { 231 | e_ident := NByteArray.extract bs 0x10 (by omega), 232 | e_type := getUInt16from 0x10 (by omega), 233 | e_machine := getUInt16from 0x12 (by omega), 234 | e_version := getUInt32from 0x14 (by omega), 235 | e_entry := getUInt32from 0x18 (by omega), 236 | e_phoff := getUInt32from 0x1C (by omega), 237 | e_shoff := getUInt32from 0x20 (by omega), 238 | e_flags := getUInt32from 0x24 (by omega), 239 | e_ehsize := getUInt16from 0x28 (by omega), 240 | e_phentsize := getUInt16from 0x2A (by omega), 241 | e_phnum := getUInt16from 0x2C (by omega), 242 | e_shentsize := getUInt16from 0x2E (by omega), 243 | e_shnum := getUInt16from 0x30 (by omega), 244 | e_shstrndx := getUInt16from 0x32 (by omega), 245 | } where 246 | isBigEndian := bs.get ⟨0x5,by omega⟩ == 2 247 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 248 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 249 | 250 | def mkELF32Header? (bs: ByteArray) : Except String ELF32Header := 251 | if h : bs.size ≥ 0x34 then .ok $ mkELF32Header bs h 252 | else .error "We're looking for a 32 bit ELF header but there aren't enough bytes." 253 | 254 | def ELF32Header.bytes (eh : ELF32Header) : ByteArray := 255 | eh.e_ident.bytes ++ 256 | getBytes16 eh.e_type ++ 257 | getBytes16 eh.e_machine ++ 258 | getBytes32 eh.e_version ++ 259 | getBytes32 eh.e_entry ++ 260 | getBytes32 eh.e_phoff ++ 261 | getBytes32 eh.e_shoff ++ 262 | getBytes32 eh.e_flags ++ 263 | getBytes16 eh.e_ehsize ++ 264 | getBytes16 eh.e_phentsize ++ 265 | getBytes16 eh.e_phnum ++ 266 | getBytes16 eh.e_shentsize ++ 267 | getBytes16 eh.e_shnum ++ 268 | getBytes16 eh.e_shstrndx 269 | where isBigEndian := eh.e_ident.get 0x5 (by omega) == 2 270 | getBytes16 := if isBigEndian then UInt16.getBytesBEfrom else UInt16.getBytesLEfrom 271 | getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 272 | 273 | instance : ELFHeader ELF32Header where 274 | e_ident eh := eh.e_ident 275 | e_type eh := eh.e_type.toNat 276 | e_machine eh := eh.e_machine.toNat 277 | e_version eh := eh.e_version.toNat 278 | e_entry eh := eh.e_entry.toNat 279 | e_phoff eh := eh.e_phoff.toNat 280 | e_shoff eh := eh.e_shoff.toNat 281 | e_flags eh := eh.e_flags.toNat 282 | e_ehsize eh := eh.e_ehsize.toNat 283 | e_phentsize eh := eh.e_phentsize.toNat 284 | e_phnum eh := eh.e_phnum.toNat 285 | e_shentsize eh := eh.e_shentsize.toNat 286 | e_shnum eh := eh.e_shnum.toNat 287 | e_shstrndx eh := eh.e_shstrndx.toNat 288 | bytes eh := eh.bytes 289 | 290 | inductive RawELFHeader := 291 | | elf32 : ELF32Header → RawELFHeader 292 | | elf64 : ELF64Header → RawELFHeader 293 | deriving Repr 294 | 295 | instance : ELFHeader RawELFHeader where 296 | e_ident eh := match eh with | .elf64 eh => eh.e_ident | .elf32 eh => eh.e_ident 297 | e_type eh := match eh with | .elf64 eh => eh.e_type.toNat | .elf32 eh => eh.e_type.toNat 298 | e_machine eh := match eh with | .elf64 eh => eh.e_machine.toNat | .elf32 eh => eh.e_machine.toNat 299 | e_version eh := match eh with | .elf64 eh => eh.e_version.toNat | .elf32 eh => eh.e_version.toNat 300 | e_entry eh := match eh with | .elf64 eh => eh.e_entry.toNat | .elf32 eh => eh.e_entry.toNat 301 | e_phoff eh := match eh with | .elf64 eh => eh.e_phoff.toNat | .elf32 eh => eh.e_phoff.toNat 302 | e_shoff eh := match eh with | .elf64 eh => eh.e_shoff.toNat | .elf32 eh => eh.e_shoff.toNat 303 | e_flags eh := match eh with | .elf64 eh => eh.e_flags.toNat | .elf32 eh => eh.e_flags.toNat 304 | e_ehsize eh := match eh with | .elf64 eh => eh.e_ehsize.toNat | .elf32 eh => eh.e_ehsize.toNat 305 | e_phentsize eh := match eh with | .elf64 eh => eh.e_phentsize.toNat | .elf32 eh => eh.e_phentsize.toNat 306 | e_phnum eh := match eh with | .elf64 eh => eh.e_phnum.toNat | .elf32 eh => eh.e_phnum.toNat 307 | e_shentsize eh := match eh with | .elf64 eh => eh.e_shentsize.toNat | .elf32 eh => eh.e_shentsize.toNat 308 | e_shnum eh := match eh with | .elf64 eh => eh.e_shnum.toNat | .elf32 eh => eh.e_shnum.toNat 309 | e_shstrndx eh := match eh with | .elf64 eh => eh.e_shstrndx.toNat | .elf32 eh => eh.e_shstrndx.toNat 310 | bytes eh := match eh with | .elf64 eh => eh.bytes | .elf32 eh => eh.bytes 311 | 312 | def mkRawELFHeader? (bs : ByteArray) : Except String RawELFHeader := 313 | if h : bs.size < 5 then throw "Can't determine if this is a 32 or 64 bit binary (not enough bytes)." 314 | else match bs.get ⟨0x4, by omega⟩ with 315 | | 1 => .elf32 <$> mkELF32Header? bs 316 | | 2 => .elf64 <$> mkELF64Header? bs 317 | | _ => throw "Can't determine if this is a 32 of 64 bit binary (byte 0x5 of the elf header is bad)" 318 | -------------------------------------------------------------------------------- /ELFSage/Types/File.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.ELFHeader 2 | import ELFSage.Types.SectionHeaderTable 3 | import ELFSage.Types.ProgramHeaderTable 4 | import ELFSage.Types.Section 5 | import ELFSage.Types.Segment 6 | import ELFSage.Util.Flags 7 | import ELFSage.Util.Hex 8 | import ELFSage.Util.List 9 | 10 | open Hex 11 | open Flags 12 | 13 | def getInhabitedRanges 14 | [ELFHeader α] (eh : α) 15 | [SectionHeaderTableEntry β] (sht : List β) 16 | [ProgramHeaderTableEntry γ] (pht : List γ) 17 | : List (Nat × Nat) := 18 | (0, ELFHeader.e_ehsize eh) --ELF header 19 | :: (ELFHeader.e_phoff eh, ELFHeader.e_phoff eh + (ELFHeader.e_phnum eh * ELFHeader.e_phentsize eh)) --Program header entries 20 | :: (ELFHeader.e_shoff eh, ELFHeader.e_shoff eh + (ELFHeader.e_shnum eh * ELFHeader.e_shentsize eh)) --Section header entries 21 | :: pht.map (λphte ↦ (ProgramHeaderTableEntry.p_offset phte, ProgramHeaderTableEntry.p_offset phte + ProgramHeaderTableEntry.p_filesz phte)) --segments 22 | ++ sht.map (λshte ↦ (SectionHeaderTableEntry.sh_offset shte, SectionHeaderTableEntry.sh_offset shte + SectionHeaderTableEntry.sh_size shte)) --sections 23 | 24 | def getBitsAndBobs 25 | [ELFHeader α] (eh : α) 26 | [SectionHeaderTableEntry β] (sht : List β) 27 | [ProgramHeaderTableEntry γ] (pht : List γ) 28 | (bytes : ByteArray) 29 | : List (Nat × ByteArray) := 30 | let ranges := getInhabitedRanges eh sht pht 31 | let sorted := (ranges.toArray.qsort (λr1 r2 ↦ r1.fst < r2.fst)).toList 32 | let gaps := toGaps sorted 33 | gaps.map λgap ↦ (gap.fst, bytes.extract gap.fst gap.snd) 34 | --toGaps takes a sorted list of pairs of the form (offset, offset+size) and 35 | --returns the gaps. So for example if the As represent the 36 | --in-range bytes: 37 | -- 38 | -- AAABBBAAABBB 39 | -- 40 | --Then the ranges would be (0,3),(6,9), and the gaps would be (3,6) (9,12) 41 | where toGaps : List (Nat × Nat) → List (Nat × Nat) 42 | | [] => [] 43 | | [h] => if h.snd < bytes.size then [(h.snd , bytes.size)] else [] 44 | | h₁ :: h₂ :: t => 45 | if h₁.fst = h₂.fst 46 | then if h₁.snd < h₂.snd 47 | then toGaps (h₂ :: t) 48 | else toGaps (h₁ :: t) 49 | else if h₁.snd < h₂.fst 50 | then (h₁.snd, h₂.fst) :: toGaps (h₂ :: t) 51 | else if (h₁.snd < h₂.snd) 52 | then toGaps (h₂ :: t) 53 | else toGaps (h₁ :: t) 54 | 55 | -- NOTE: this diverges from the LinkSem implementation by using 56 | -- `interpreted_segments` of type ProgramHeaderEntry × InterpretedSegment (and 57 | -- similarly for sections) rather than having separate fields and asking 58 | -- that they have the same length as an invariant. 59 | 60 | structure ELF32File where 61 | /-- The ELF Header -/ 62 | file_header : ELF32Header 63 | /-- The Program Header Entries -/ 64 | interpreted_segments : List (ELF32ProgramHeaderTableEntry × InterpretedSegment) 65 | /-- The Sections of the file -/ 66 | interpreted_sections : List (ELF32SectionHeaderTableEntry × InterpretedSection) 67 | /-- Bits and Bobs: binary contents not covered by any section or segment -/ 68 | bits_and_bobs : List (Nat × ByteArray) 69 | deriving Repr 70 | 71 | def mkELF32File? (bytes : ByteArray) : Except String ELF32File := do 72 | let file_header ← mkELF32Header? bytes 73 | 74 | let program_header_table ← file_header.mkELF32ProgramHeaderTable? bytes 75 | 76 | let section_header_table ← file_header.mkELF32SectionHeaderTable? bytes 77 | 78 | let interpreted_segments ← getInterpretedSegments program_header_table bytes 79 | 80 | let interpreted_sections ← SectionHeaderTableEntry.getInterpretedSections section_header_table file_header bytes 81 | 82 | let bits_and_bobs := getBitsAndBobs file_header section_header_table program_header_table bytes 83 | 84 | .ok { 85 | file_header 86 | interpreted_segments 87 | interpreted_sections 88 | bits_and_bobs 89 | } 90 | 91 | structure ELF64File where 92 | /-- The ELF Header -/ 93 | file_header : ELF64Header 94 | /-- The Segments of the file -/ 95 | interpreted_segments : List (ELF64ProgramHeaderTableEntry × InterpretedSegment) 96 | /-- The Sections of the file -/ 97 | interpreted_sections : List (ELF64SectionHeaderTableEntry × InterpretedSection) 98 | /-- Bits and Bobs: binary contents not covered by any section or segment -/ 99 | bits_and_bobs : List (Nat × ByteArray) 100 | deriving Repr 101 | 102 | def mkELF64File? (bytes : ByteArray) : Except String ELF64File := do 103 | let file_header ← mkELF64Header? bytes 104 | 105 | let program_header_table ← file_header.mkELF64ProgramHeaderTable? bytes 106 | 107 | let section_header_table ← file_header.mkELF64SectionHeaderTable? bytes 108 | 109 | let interpreted_segments ← getInterpretedSegments program_header_table bytes 110 | 111 | let interpreted_sections ← SectionHeaderTableEntry.getInterpretedSections section_header_table file_header bytes 112 | 113 | let bits_and_bobs := getBitsAndBobs file_header section_header_table program_header_table bytes 114 | 115 | .ok { 116 | file_header 117 | interpreted_segments 118 | interpreted_sections 119 | bits_and_bobs 120 | } 121 | 122 | inductive RawELFFile where 123 | | elf32 : ELF32File → RawELFFile 124 | | elf64 : ELF64File → RawELFFile 125 | 126 | def mkRawELFFile? (bytes : ByteArray) : Except String RawELFFile := 127 | if h : bytes.size < 5 then throw "Can't determine if this is a 32 or 64 bit binary (not enough bytes)." 128 | else match bytes.get ⟨0x4, by omega⟩ with 129 | | 1 => .elf32 <$> mkELF32File? bytes 130 | | 2 => .elf64 <$> mkELF64File? bytes 131 | | _ => throw "Can't determine if this is a 32 of 64 bit binary (byte 0x5 of the elf header is bad)" 132 | 133 | def RawELFFile.getRawSectionHeaderTableEntries : RawELFFile → List (RawSectionHeaderTableEntry × InterpretedSection) 134 | | .elf32 elffile => elffile.interpreted_sections.map (λshte => (.elf32 shte.fst, shte.snd)) 135 | | .elf64 elffile => elffile.interpreted_sections.map (λshte => (.elf64 shte.fst, shte.snd)) 136 | 137 | def RawELFFile.getRawProgramHeaderTableEntries : RawELFFile → List (RawProgramHeaderTableEntry × InterpretedSegment) 138 | | .elf32 elffile => elffile.interpreted_segments.map (λshte => (.elf32 shte.fst, shte.snd)) 139 | | .elf64 elffile => elffile.interpreted_segments.map (λshte => (.elf64 shte.fst, shte.snd)) 140 | 141 | def RawELFFile.getRawELFHeader : RawELFFile → RawELFHeader 142 | | .elf32 elffile => .elf32 elffile.file_header 143 | | .elf64 elffile => .elf64 elffile.file_header 144 | 145 | -- TODO Perhaps just make ELFFile an ELFHeader instance 146 | 147 | def RawELFFile.isBigendian (elffile : RawELFFile) := ELFHeader.isBigendian elffile.getRawELFHeader 148 | 149 | def RawELFFile.is64Bit (elffile : RawELFFile) := ELFHeader.is64Bit elffile.getRawELFHeader 150 | 151 | /-- 152 | Get the section of type SHT_SYMTAB. 153 | There's at most one: https://refspecs.linuxbase.org/elf/gabi4+/ch4.sheader.html 154 | -/ 155 | def RawELFFile.getSymbolTable? (elffile : RawELFFile) 156 | : Except String (RawSectionHeaderTableEntry × InterpretedSection) := 157 | symbolSections[0]?.elim noSymTable .ok 158 | where noSymTable := .error "No symbol table present, no st_size given, can't guess byte range" 159 | symbolSections := elffile.getRawSectionHeaderTableEntries.filter $ λ⟨shte, _⟩↦ 160 | SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_SYMTAB 161 | 162 | /-- 163 | Get the section .shstrtab. Defined by e_shstrndx in the ELF header and therefore unique 164 | -/ 165 | def RawELFFile.getSectionHeaderStringTable? (elffile : RawELFFile) 166 | : Except String (RawSectionHeaderTableEntry × InterpretedSection) := 167 | let sections := elffile.getRawSectionHeaderTableEntries 168 | match sections[ELFHeader.e_shstrndx elffile.getRawELFHeader]? with 169 | | none => .error "e_shstrndx in ELF header refers to a nonexistent section" 170 | | some entry => .ok entry 171 | 172 | /-- 173 | Get the section of type SHT_DYNSYM 174 | There's at most one: https://refspecs.linuxbase.org/elf/gabi4+/ch4.sheader.html 175 | -/ 176 | def RawELFFile.getDynamicSymbolTable? (elffile : RawELFFile) 177 | : Except String (RawSectionHeaderTableEntry × InterpretedSection) := 178 | dynamicSymbolSections[0]?.elim noSymTable .ok 179 | where noSymTable := .error "No symbol table present, no st_size given, can't guess byte range" 180 | dynamicSymbolSections := elffile.getRawSectionHeaderTableEntries.filter $ λ⟨shte, _⟩↦ 181 | SectionHeaderTableEntry.sh_type shte == ELFSectionHeaderTableEntry.Type.SHT_DYNSYM 182 | 183 | private def getProgramHeaderType (n: Nat) := match n with 184 | -- TODO: Finish populating this list of magic numbers, it is incomplete. 185 | | 1 => "PT_LOAD" -- (0x1) 186 | | 2 => "PT_DYNAMIC" -- (0x2) 187 | | 3 => "PT_INTERP" -- (0x3) 188 | | 4 => "PT_NOTE" -- (0x4) 189 | | 6 => "PT_PHDR" -- (0x6) 190 | | 1685382480 => "PT_GNU_EH_FRAME" -- (0x6474E550) 191 | | 1685382481 => "PT_GNU_STACK" -- (0x6474E551) 192 | | 1685382482 => "PT_GNU_RELRO" -- (0x6474E552) 193 | | 1685382483 => "PT_GNU_PROPERTY" -- (0x6474E553) 194 | | _ => panic s!"Unrecognized program header type {n}" 195 | 196 | private def getProgramHeaderFlag (flagIndex: Nat) := match flagIndex with 197 | -- TODO: Finish populating this list of magic numbers, it is incomplete. 198 | | 0 => "PF_X" -- (0x1) 199 | | 1 => "PF_W" -- (0x2) 200 | | 2 => "PF_R" -- (0x4) 201 | | _ => panic s!"Unrecognized program header flag {flagIndex}" 202 | 203 | private def programHeaderFlagsToString (flags: Nat) (indent: String) : String := 204 | getFlagBits flags 32 205 | |> .map (λ flag => s!"{indent}{getProgramHeaderFlag flag} (0x{toHex (1 <<< flag)})\n") 206 | |> (λ a => .insertionSort a (. < .)) -- sort by flag name 207 | |> String.join 208 | 209 | def RawELFFile.programHeadersToString (elffile : RawELFFile) := Id.run do 210 | let mut out := "ProgramHeaders [\n" 211 | let headers := elffile.getRawProgramHeaderTableEntries 212 | let mut idx := 0 213 | for ⟨phte, _⟩ in headers do 214 | let nextHeader := 215 | " ProgramHeader {\n" ++ 216 | s!" Type: {getProgramHeaderType $ ProgramHeaderTableEntry.p_type phte} (0x{toHex $ ProgramHeaderTableEntry.p_type phte})\n" ++ 217 | s!" Offset: 0x{toHex $ ProgramHeaderTableEntry.p_offset phte}\n" ++ 218 | s!" VirtualAddress: 0x{toHex $ ProgramHeaderTableEntry.p_vaddr phte}\n" ++ 219 | s!" PhysicalAddress: 0x{toHex $ ProgramHeaderTableEntry.p_paddr phte}\n" ++ 220 | s!" FileSize: {ProgramHeaderTableEntry.p_filesz phte}\n" ++ 221 | s!" MemSize: {ProgramHeaderTableEntry.p_memsz phte}\n" ++ 222 | s!" Flags [ (0x{toHex $ ProgramHeaderTableEntry.p_flags phte})\n" ++ 223 | s!"{ programHeaderFlagsToString (ProgramHeaderTableEntry.p_flags phte) " "}" ++ 224 | " ]\n" ++ 225 | s!" Alignment: {ProgramHeaderTableEntry.p_align phte}\n" ++ 226 | " }\n" 227 | out := out ++ nextHeader 228 | idx := idx + 1 229 | 230 | out := out ++ "]" 231 | return out 232 | 233 | private def getSectionHeaderType (n: Nat) := match n with 234 | -- TODO: Finish populating this list of magic numbers, it is incomplete. 235 | | 0 => "SHT_NULL" -- (0x0) 236 | | 1 => "SHT_PROGBITS" -- (0x1) 237 | | 3 => "SHT_STRTAB" -- (0x3) 238 | | 4 => "SHT_RELA" -- (0x4) 239 | | 6 => "SHT_DYNAMIC" -- (0x6) 240 | | 7 => "SHT_NOTE" -- (0x7) 241 | | 8 => "SHT_NOBITS" -- (0x8) 242 | | 11 => "SHT_DYNSYM" -- (0xB) 243 | | 14 => "SHT_INIT_ARRAY" -- (0xE) 244 | | 15 => "SHT_FINI_ARRAY" -- (0xF) 245 | | 1879048182 => "SHT_GNU_HASH" -- (0x6FFFFFF6) 246 | | 1879048190 => "SHT_GNU_verneed" -- (0x6FFFFFFE) 247 | | 1879048191 => "SHT_GNU_versym" -- (0x6FFFFFFF) 248 | | _ => panic s!"Unrecognized section header type {n}" 249 | 250 | private def getSectionHeaderFlag (flagIndex: Nat) := match flagIndex with 251 | -- TODO: Finish populating this list of magic numbers, it is incomplete. 252 | | 0 => "SHF_WRITE" -- (0x1) 253 | | 1 => "SHF_ALLOC" -- (0x2) 254 | | 2 => "SHF_EXECINSTR" -- (0x4) 255 | | 4 => "SHF_MERGE" -- (0x10) 256 | | 5 => "SHF_STRINGS" -- (0x20) 257 | | _ => panic s!"Unrecognized section header flag {flagIndex}" 258 | 259 | private def sectionHeaderFlagsToString (flags: Nat) (indent: String) : String := 260 | getFlagBits flags 64 261 | |> .map (λ flag => s!"{indent}{getSectionHeaderFlag flag} (0x{toHex (1 <<< flag)})\n") 262 | |> (λ a => .insertionSort a (. < .)) -- sort by flag name 263 | |> String.join 264 | 265 | def RawELFFile.sectionHeadersToString (elffile : RawELFFile) := Id.run do 266 | let mut out := "Sections [\n" 267 | let headers := elffile.getRawSectionHeaderTableEntries 268 | let mut idx := 0 269 | for ⟨phte, sec⟩ in headers do 270 | let name := match sec.section_name_as_string with | .some s => s | _ => "" 271 | let nextHeader := 272 | " Section {\n" ++ 273 | s!" Index: {idx}\n" ++ 274 | s!" Name: {name} ({SectionHeaderTableEntry.sh_name phte})\n" ++ 275 | s!" Type: {getSectionHeaderType $ SectionHeaderTableEntry.sh_type phte} (0x{toHex $ SectionHeaderTableEntry.sh_type phte})\n" ++ 276 | s!" Flags [ (0x{toHex $ SectionHeaderTableEntry.sh_flags phte})\n" ++ 277 | s!"{ sectionHeaderFlagsToString (SectionHeaderTableEntry.sh_flags phte) " "}" ++ 278 | " ]\n" ++ 279 | s!" Address: 0x{toHex $ SectionHeaderTableEntry.sh_addr phte}\n" ++ 280 | s!" Offset: 0x{toHex $ SectionHeaderTableEntry.sh_offset phte}\n" ++ 281 | s!" Size: {SectionHeaderTableEntry.sh_size phte}\n" ++ 282 | s!" Link: {SectionHeaderTableEntry.sh_link phte}\n" ++ 283 | s!" Info: {SectionHeaderTableEntry.sh_info phte}\n" ++ 284 | s!" AddressAlignment: {SectionHeaderTableEntry.sh_addralign phte}\n" ++ 285 | s!" EntrySize: {SectionHeaderTableEntry.sh_entsize phte}\n" ++ 286 | " }\n" 287 | out := out ++ nextHeader 288 | idx := idx + 1 289 | 290 | out := out ++ "]" 291 | return out 292 | 293 | def RawELFFile.headersToString (elffile : RawELFFile) := 294 | toString elffile.getRawELFHeader ++ "\n" ++ 295 | RawELFFile.sectionHeadersToString elffile ++ "\n" ++ 296 | RawELFFile.programHeadersToString elffile 297 | -------------------------------------------------------------------------------- /ELFSage/Types/Note.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Util.ByteArray 3 | 4 | class NoteEntry (α : Type) where 5 | /-- The size of the name field -/ 6 | note_namesz : α → Nat 7 | /-- The size of the description field -/ 8 | note_descsz : α → Nat 9 | /-- The type of note -/ 10 | note_type : α → Nat 11 | /-- The note name as a ByteArray-/ 12 | note_name : α → ByteArray 13 | /-- The note description as a ByteArray -/ 14 | note_desc : α → ByteArray 15 | 16 | structure ELF64NoteEntry where 17 | /-- The size of the name field -/ 18 | note_namesz : elf64_word 19 | /-- The size of the description field -/ 20 | note_descsz : elf64_word 21 | /-- The type of note -/ 22 | note_type : elf64_word 23 | /-- The note name as a ByteArray-/ 24 | note_name : ByteArray 25 | /-- The note description as a ByteArray -/ 26 | note_desc : ByteArray 27 | deriving Repr 28 | 29 | -- TODO this should check that the note doesn't overrun the end of the note section 30 | def mkELF64NoteEntry? 31 | (isBigEndian : Bool) 32 | (bs : ByteArray) 33 | (offset : Nat) 34 | (h : bs.size - offset ≥ 0xc) 35 | : Except String ELF64NoteEntry := 36 | let namesz := getUInt32from (offset + 0x0) (by omega) 37 | let descsz := getUInt32from (offset + 0x4) (by omega) 38 | if bs.size ≥ alignTo4 (offset + 0xc + namesz.toNat) + alignTo4 descsz.toNat 39 | then .ok { 40 | note_namesz := namesz 41 | note_descsz := descsz 42 | note_type := getUInt32from (offset + 0x8) (by omega) 43 | note_name := bs.extract (offset + 0xc) (offset + 0xc + namesz.toNat) 44 | note_desc := bs.extract (alignTo4 $ offset + 0xc + namesz.toNat) ((alignTo4 $ offset + 0xc + namesz.toNat) + descsz.toNat) 45 | } 46 | else 47 | .error "Note section specifies a name and description size that runs past the end of the binary" 48 | where 49 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 50 | -- the desc and name fields are required to have 4 byte alignment 51 | alignTo4 n := n + (n % 4) 52 | 53 | -- Because ELF32 and ELF64 words are the same size, the two note types are really one and the same 54 | abbrev ELF32NoteEntry := ELF64NoteEntry 55 | 56 | instance : NoteEntry ELF64NoteEntry where 57 | note_namesz ne := ne.note_namesz.toNat 58 | note_descsz ne := ne.note_descsz.toNat 59 | note_type ne := ne.note_type.toNat 60 | note_name ne := ne.note_name 61 | note_desc ne := ne.note_desc 62 | 63 | inductive RawNoteEntry where 64 | | elf64 : ELF64NoteEntry → RawNoteEntry 65 | | elf32 : ELF32NoteEntry → RawNoteEntry 66 | deriving Repr 67 | 68 | instance : NoteEntry RawNoteEntry where 69 | note_namesz ne := match ne with | .elf64 ne => ne.note_namesz.toNat | .elf32 ne => ne.note_namesz.toNat 70 | note_descsz ne := match ne with | .elf64 ne => ne.note_descsz.toNat | .elf32 ne => ne.note_descsz.toNat 71 | note_type ne := match ne with | .elf64 ne => ne.note_type.toNat | .elf32 ne => ne.note_type.toNat 72 | note_name ne := match ne with | .elf64 ne => ne.note_name | .elf32 ne => ne.note_name 73 | note_desc ne := match ne with | .elf64 ne => ne.note_desc | .elf32 ne => ne.note_desc 74 | 75 | def mkRawNoteEntry? 76 | (bs : ByteArray) 77 | (isBigendian : Bool) 78 | (is64Bit : Bool) 79 | (offset : Nat) 80 | : Except String RawNoteEntry := 81 | if h : bs.size - offset ≥ 0xc 82 | then if is64Bit then .elf64 <$> mkELF64NoteEntry? isBigendian bs offset h else .elf32 <$> mkELF64NoteEntry? isBigendian bs offset h 83 | else .error "Note entry offset {offset} doesn't leave enough space for the note, which requires at least 12 bytes" 84 | -------------------------------------------------------------------------------- /ELFSage/Types/ProgramHeaderTable.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Util.ByteArray 3 | import ELFSage.Types.ELFHeader 4 | import ELFSage.Util.Hex 5 | 6 | class ProgramHeaderTableEntry (α : Type) where 7 | /-- Type of the segment -/ 8 | p_type : α → Nat 9 | /-- Segment flags -/ 10 | p_flags : α → Nat 11 | /-- Offset from beginning of file for segment -/ 12 | p_offset : α → Nat 13 | /-- Virtual address for segment in memory -/ 14 | p_vaddr : α → Nat 15 | /-- Physical address for segment -/ 16 | p_paddr : α → Nat 17 | /-- Size of segment in file, in bytes -/ 18 | p_filesz : α → Nat 19 | /-- Size of segment in memory image, in bytes -/ 20 | p_memsz : α → Nat 21 | /-- Segment alignment memory for memory and file -/ 22 | p_align : α → Nat 23 | /-- Underlying Bytes, requires Endianness --/ 24 | bytes : α → (isBigendian : Bool) → ByteArray 25 | 26 | def ProgramHeaderTableEntry.ph_end [ProgramHeaderTableEntry α] (ph : α) := 27 | p_offset ph + p_filesz ph 28 | 29 | -- Alignment requirements from man 5 elf 30 | -- For now, we assumpe 4K as page size. For future reference: 31 | -- https://stackoverflow.com/questions/3351940/detecting-the-memory-page-size 32 | def ProgramHeaderTableEntry.checkAlignment 33 | [ProgramHeaderTableEntry α] 34 | (ph : α) 35 | : Except String Unit := do 36 | if p_type ph == PT_LOAD ∧ p_vaddr ph % 0x1000 != p_offset ph % 0x1000 then .error $ 37 | s! "Misaligned loadable segment: p_vaddr={Hex.toHex $ p_vaddr ph} and " ++ 38 | s! "p_offset={Hex.toHex $ p_offset ph} are not aligned modulo the page size 0x1000" 39 | if p_align ph < 2 then return () 40 | if p_vaddr ph % p_align ph != p_offset ph % p_align ph then .error $ 41 | s! "Misaligned segment: p_offset is {Hex.toHex $ p_offset ph}, " ++ 42 | s! "and p_vaddr is {Hex.toHex $ p_vaddr ph}. These are required to be " ++ 43 | s! "congruent mod p_align={Hex.toHex $ p_align ph}." 44 | where PT_LOAD := 1 -- TODO: replace me with a constant 45 | 46 | structure ELF64ProgramHeaderTableEntry where 47 | /-- Type of the segment -/ 48 | p_type : elf64_word 49 | /-- Segment flags -/ 50 | p_flags : elf64_word 51 | /-- Offset from beginning of file for segment -/ 52 | p_offset : elf64_off 53 | /-- Virtual address for segment in memory -/ 54 | p_vaddr : elf64_addr 55 | /-- Physical address for segment -/ 56 | p_paddr : elf64_addr 57 | /-- Size of segment in file, in bytes -/ 58 | p_filesz : elf64_xword 59 | /-- Size of segment in memory image, in bytes -/ 60 | p_memsz : elf64_xword 61 | /-- Segment alignment memory for memory and file -/ 62 | p_align : elf64_xword 63 | deriving Repr 64 | 65 | def mkELF64ProgramHeaderTableEntry 66 | (isBigEndian : Bool) 67 | (bs : ByteArray) 68 | (offset : Nat) 69 | (h : bs.size - offset ≥ 0x38) : 70 | ELF64ProgramHeaderTableEntry := { 71 | p_type := getUInt32from (offset + 0x00) (by omega), 72 | p_flags := getUInt32from (offset + 0x04) (by omega), 73 | p_offset := getUInt64from (offset + 0x08) (by omega), 74 | p_vaddr := getUInt64from (offset + 0x10) (by omega), 75 | p_paddr := getUInt64from (offset + 0x18) (by omega), 76 | p_filesz := getUInt64from (offset + 0x20) (by omega), 77 | p_memsz := getUInt64from (offset + 0x28) (by omega), 78 | p_align := getUInt64from (offset + 0x30) (by omega), 79 | } where 80 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 81 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 82 | getUInt64from := if isBigEndian then bs.getUInt64BEfrom else bs.getUInt64LEfrom 83 | 84 | def mkELF64ProgramHeaderTableEntry? 85 | (isBigEndian : Bool) 86 | (bs : ByteArray) 87 | (offset : Nat) 88 | : Except String ELF64ProgramHeaderTableEntry := 89 | if h : bs.size - offset ≥ 0x38 90 | then .ok $ mkELF64ProgramHeaderTableEntry isBigEndian bs offset h 91 | else .error $ "Program header table entry offset {offset} doesn't leave enough space for the entry, " ++ 92 | "which requires 0x20 bytes." 93 | 94 | def ELF64ProgramHeaderTableEntry.bytes (phte : ELF64ProgramHeaderTableEntry) (isBigEndian : Bool) := 95 | getBytes32 phte.p_type ++ 96 | getBytes32 phte.p_flags ++ 97 | getBytes64 phte.p_offset ++ 98 | getBytes64 phte.p_vaddr ++ 99 | getBytes64 phte.p_paddr ++ 100 | getBytes64 phte.p_filesz ++ 101 | getBytes64 phte.p_memsz ++ 102 | getBytes64 phte.p_align 103 | where getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 104 | getBytes64 := if isBigEndian then UInt64.getBytesBEfrom else UInt64.getBytesLEfrom 105 | 106 | def ELF64Header.mkELF64ProgramHeaderTable? 107 | (eh : ELF64Header) 108 | (bytes : ByteArray) 109 | : Except String (List ELF64ProgramHeaderTableEntry):= 110 | let isBigendian := ELFHeader.isBigendian eh 111 | List.mapM 112 | (λoffset ↦ mkELF64ProgramHeaderTableEntry? isBigendian bytes offset) 113 | (ELFHeader.getProgramHeaderOffsets eh) 114 | 115 | instance : ProgramHeaderTableEntry ELF64ProgramHeaderTableEntry where 116 | p_type ph := ph.p_type.toNat 117 | p_flags ph := ph.p_flags.toNat 118 | p_offset ph := ph.p_offset.toNat 119 | p_vaddr ph := ph.p_vaddr.toNat 120 | p_paddr ph := ph.p_paddr.toNat 121 | p_filesz ph := ph.p_filesz.toNat 122 | p_memsz ph := ph.p_memsz.toNat 123 | p_align ph := ph.p_align.toNat 124 | bytes ph := ph.bytes 125 | 126 | structure ELF32ProgramHeaderTableEntry where 127 | /-- Type of the segment -/ 128 | p_type : elf32_word 129 | /-- Offset from beginning of file for segment -/ 130 | p_offset : elf32_off 131 | /-- Virtual address for segment in memory -/ 132 | p_vaddr : elf32_addr 133 | /-- Physical address for segment -/ 134 | p_paddr : elf32_addr 135 | /-- Size of segment in file, in bytes -/ 136 | p_filesz : elf32_word 137 | /-- Size of segment in memory image, in bytes -/ 138 | p_memsz : elf32_word 139 | /-- Segment flags -/ 140 | p_flags : elf32_word 141 | /-- Segment alignment memory for memory and file -/ 142 | p_align : elf64_word 143 | deriving Repr 144 | 145 | def mkELF32ProgramHeaderTableEntry 146 | (isBigEndian : Bool) 147 | (bs : ByteArray) 148 | (offset : Nat) 149 | (h : bs.size - offset ≥ 0x20) : 150 | ELF32ProgramHeaderTableEntry := { 151 | p_type := getUInt32from (offset + 0x00) (by omega), 152 | p_offset := getUInt32from (offset + 0x04) (by omega), 153 | p_vaddr := getUInt32from (offset + 0x08) (by omega), 154 | p_paddr := getUInt32from (offset + 0x0C) (by omega), 155 | p_filesz := getUInt32from (offset + 0x10) (by omega), 156 | p_memsz := getUInt32from (offset + 0x14) (by omega), 157 | p_flags := getUInt32from (offset + 0x18) (by omega), 158 | p_align := getUInt32from (offset + 0x1C) (by omega), 159 | } where 160 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 161 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 162 | 163 | def ELF32ProgramHeaderTableEntry.bytes (phte : ELF32ProgramHeaderTableEntry) (isBigEndian : Bool) := 164 | getBytes32 phte.p_type ++ 165 | getBytes32 phte.p_flags ++ 166 | getBytes32 phte.p_offset ++ 167 | getBytes32 phte.p_vaddr ++ 168 | getBytes32 phte.p_paddr ++ 169 | getBytes32 phte.p_filesz ++ 170 | getBytes32 phte.p_memsz ++ 171 | getBytes32 phte.p_align 172 | where getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 173 | 174 | def mkELF32ProgramHeaderTableEntry? 175 | (isBigEndian : Bool) 176 | (bs : ByteArray) 177 | (offset : Nat) 178 | : Except String ELF32ProgramHeaderTableEntry := 179 | if h : bs.size - offset ≥ 0x20 180 | then .ok $ mkELF32ProgramHeaderTableEntry isBigEndian bs offset h 181 | else .error $ "Program header table entry offset {offset} doesn't leave enough space for the entry, " ++ 182 | "which requires 0x20 bytes." 183 | 184 | def ELF32Header.mkELF32ProgramHeaderTable? 185 | (eh : ELF32Header) 186 | (bytes : ByteArray) 187 | : Except String (List ELF32ProgramHeaderTableEntry):= 188 | let isBigendian := ELFHeader.isBigendian eh 189 | List.mapM 190 | (λoffset ↦ mkELF32ProgramHeaderTableEntry? isBigendian bytes offset) 191 | (ELFHeader.getProgramHeaderOffsets eh) 192 | 193 | instance : ProgramHeaderTableEntry ELF32ProgramHeaderTableEntry where 194 | p_type ph := ph.p_type.toNat 195 | p_flags ph := ph.p_flags.toNat 196 | p_offset ph := ph.p_offset.toNat 197 | p_vaddr ph := ph.p_vaddr.toNat 198 | p_paddr ph := ph.p_paddr.toNat 199 | p_filesz ph := ph.p_filesz.toNat 200 | p_memsz ph := ph.p_memsz.toNat 201 | p_align ph := ph.p_align.toNat 202 | bytes ph := ph.bytes 203 | 204 | inductive RawProgramHeaderTableEntry where 205 | | elf32 : ELF32ProgramHeaderTableEntry → RawProgramHeaderTableEntry 206 | | elf64 : ELF64ProgramHeaderTableEntry → RawProgramHeaderTableEntry 207 | deriving Repr 208 | 209 | instance : ProgramHeaderTableEntry RawProgramHeaderTableEntry where 210 | p_type ph := match ph with | .elf32 ph => ph.p_type.toNat | .elf64 ph => ph.p_type.toNat 211 | p_flags ph := match ph with | .elf32 ph => ph.p_flags.toNat | .elf64 ph => ph.p_flags.toNat 212 | p_offset ph := match ph with | .elf32 ph => ph.p_offset.toNat | .elf64 ph => ph.p_offset.toNat 213 | p_vaddr ph := match ph with | .elf32 ph => ph.p_vaddr.toNat | .elf64 ph => ph.p_vaddr.toNat 214 | p_paddr ph := match ph with | .elf32 ph => ph.p_paddr.toNat | .elf64 ph => ph.p_paddr.toNat 215 | p_filesz ph := match ph with | .elf32 ph => ph.p_filesz.toNat | .elf64 ph => ph.p_filesz.toNat 216 | p_memsz ph := match ph with | .elf32 ph => ph.p_memsz.toNat | .elf64 ph => ph.p_memsz.toNat 217 | p_align ph := match ph with | .elf32 ph => ph.p_align.toNat | .elf64 ph => ph.p_align.toNat 218 | bytes ph := match ph with | .elf32 ph => ph.bytes | .elf64 ph => ph.bytes 219 | 220 | def mkRawProgramHeaderTableEntry? 221 | (bs : ByteArray) 222 | (is64Bit : Bool) 223 | (isBigendian : Bool) 224 | (offset : Nat) 225 | : Except String RawProgramHeaderTableEntry := 226 | match is64Bit with 227 | | true => .elf64 <$> mkELF64ProgramHeaderTableEntry? isBigendian bs offset 228 | | false => .elf32 <$> mkELF32ProgramHeaderTableEntry? isBigendian bs offset 229 | 230 | inductive RawProgramHeaderTable where 231 | | elf32 : List ELF32ProgramHeaderTableEntry → RawProgramHeaderTable 232 | | elf64 : List ELF64ProgramHeaderTableEntry → RawProgramHeaderTable 233 | 234 | def RawProgramHeaderTable.length : RawProgramHeaderTable → Nat 235 | | elf32 pht => pht.length 236 | | elf64 pht => pht.length 237 | 238 | def ELFHeader.mkRawProgramHeaderTable? 239 | [ELFHeader α] 240 | (eh : α) 241 | (bytes : ByteArray) 242 | : Except String RawProgramHeaderTable := 243 | let shoffsets := (List.range (ELFHeader.e_phnum eh)).map λidx ↦ ELFHeader.e_phoff eh + ELFHeader.e_phentsize eh * idx 244 | let isBigendian := ELFHeader.isBigendian eh 245 | let is64Bit := ELFHeader.is64Bit eh 246 | if is64Bit 247 | then .elf64 <$> List.mapM (λoffset ↦ mkELF64ProgramHeaderTableEntry? isBigendian bytes offset) shoffsets 248 | else .elf32 <$> List.mapM (λoffset ↦ mkELF32ProgramHeaderTableEntry? isBigendian bytes offset) shoffsets 249 | -------------------------------------------------------------------------------- /ELFSage/Types/Relocation.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Util.ByteArray 3 | 4 | class Relocation (α : Type) where 5 | /-- Address at which to relocate -/ 6 | r_offset : α → Nat 7 | /-- Symbol table index or type of relocation to apply -/ 8 | r_info : α → Nat 9 | /-- Underlying Bytes, requires Endianness --/ 10 | bytes : α → (isBigendian : Bool) → ByteArray 11 | 12 | class RelocationA (α : Type) where 13 | /-- Address at which to relocate -/ 14 | ra_offset : α → Nat 15 | /-- Symbol table index or type of relocation to apply -/ 16 | ra_info : α → Nat 17 | /-- Addend used to compute value to be stored -/ 18 | ra_addend : α → Int 19 | /-- Underlying Bytes, requires Endianness --/ 20 | bytes : α → (isBigendian : Bool) → ByteArray 21 | 22 | structure ELF32Relocation where 23 | /-- Address at which to relocate -/ 24 | r_offset : elf32_addr 25 | /-- Symbol table index or type of relocation to apply -/ 26 | r_info : elf32_word 27 | deriving Repr 28 | 29 | def mkELF32Relocation 30 | (isBigEndian : Bool) 31 | (bs : ByteArray) 32 | (offset : Nat) 33 | (h : bs.size - offset ≥ 0x8) : 34 | ELF32Relocation := { 35 | r_offset := getUInt32from (offset + 0x0) (by omega) 36 | r_info := getUInt32from (offset + 0x4) (by omega) 37 | } where 38 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 39 | 40 | def ELF32Relocation.bytes (r : ELF32Relocation) (isBigEndian : Bool) := 41 | getBytes32 r.r_offset ++ getBytes32 r.r_info 42 | where getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 43 | 44 | instance : Relocation ELF32Relocation where 45 | r_offset r := r.r_offset.toNat 46 | r_info r := r.r_info.toNat 47 | bytes r := r.bytes 48 | 49 | structure ELF32RelocationA where 50 | /-- Address at which to relocate -/ 51 | ra_offset : elf32_addr 52 | /-- Symbol table index or type of relocation to apply -/ 53 | ra_info : elf32_word 54 | /-- Addend used to compute value to be stored -/ 55 | ra_addend : elf32_sword 56 | deriving Repr 57 | 58 | def mkELF32RelocationA 59 | (isBigEndian : Bool) 60 | (bs : ByteArray) 61 | (offset : Nat) 62 | (h : bs.size - offset ≥ 0xc) : 63 | ELF32RelocationA := { 64 | ra_offset := getUInt32from (offset + 0x0) (by omega) 65 | ra_info := getUInt32from (offset + 0x4) (by omega) 66 | ra_addend := ⟨getUInt32from (offset + 0x8) (by omega)⟩ 67 | } where 68 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 69 | 70 | def ELF32RelocationA.bytes (ra: ELF32RelocationA) (isBigEndian : Bool) := 71 | getBytes32 ra.ra_offset 72 | ++ getBytes32 ra.ra_info 73 | ++ getBytes32 ra.ra_addend.bytes 74 | where getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 75 | 76 | instance : RelocationA ELF32RelocationA where 77 | ra_offset ra := ra.ra_offset.toNat 78 | ra_info ra := ra.ra_info.toNat 79 | ra_addend ra := ra.ra_addend.toInt 80 | bytes ra := ra.bytes 81 | 82 | structure ELF64Relocation where 83 | /-- Address at which to relocate -/ 84 | r_offset : elf64_addr 85 | /-- Symbol table index or type of relocation to apply -/ 86 | r_info : elf64_xword 87 | deriving Repr 88 | 89 | def mkELF64Relocation 90 | (isBigEndian : Bool) 91 | (bs : ByteArray) 92 | (offset : Nat) 93 | (h : bs.size - offset ≥ 0x10) : 94 | ELF64Relocation := { 95 | r_offset := getUInt64from (offset + 0x0) (by omega) 96 | r_info := getUInt64from (offset + 0x8) (by omega) 97 | } where 98 | getUInt64from := if isBigEndian then bs.getUInt64BEfrom else bs.getUInt64LEfrom 99 | 100 | def ELF64Relocation.bytes (r : ELF64Relocation) (isBigEndian : Bool) := 101 | getBytes64 r.r_offset ++ getBytes64 r.r_info 102 | where getBytes64 := if isBigEndian then UInt64.getBytesBEfrom else UInt64.getBytesLEfrom 103 | 104 | instance : Relocation ELF64Relocation where 105 | r_offset r := r.r_offset.toNat 106 | r_info r := r.r_info.toNat 107 | bytes r := r.bytes 108 | 109 | structure ELF64RelocationA where 110 | /-- Address at which to relocate -/ 111 | ra_offset : elf64_addr 112 | /-- Symbol table index or type of relocation to apply -/ 113 | ra_info : elf64_xword 114 | /-- Addend used to compute value to be stored -/ 115 | ra_addend : elf64_sxword 116 | deriving Repr 117 | 118 | def mkELF64RelocationA 119 | (isBigEndian : Bool) 120 | (bs : ByteArray) 121 | (offset : Nat) 122 | (h : bs.size - offset ≥ 0x18) : 123 | ELF64RelocationA := { 124 | ra_offset := getUInt64from (offset + 0x0) (by omega) 125 | ra_info := getUInt64from (offset + 0x8) (by omega) 126 | ra_addend := ⟨getUInt64from (offset + 0x10) (by omega)⟩ 127 | } where 128 | getUInt64from := if isBigEndian then bs.getUInt64BEfrom else bs.getUInt64LEfrom 129 | 130 | def ELF64RelocationA.bytes (ra: ELF64RelocationA) (isBigEndian : Bool) := 131 | getBytes64 ra.ra_offset 132 | ++ getBytes64 ra.ra_info 133 | ++ getBytes64 ra.ra_addend.bytes 134 | where getBytes64 := if isBigEndian then UInt64.getBytesBEfrom else UInt64.getBytesLEfrom 135 | 136 | instance : RelocationA ELF64RelocationA where 137 | ra_offset ra := ra.ra_offset.toNat 138 | ra_info ra := ra.ra_info.toNat 139 | ra_addend ra := ra.ra_addend.toInt 140 | bytes ra := ra.bytes 141 | 142 | inductive RawRelocation := 143 | | elf64 : ELF64Relocation → RawRelocation 144 | | elf32 : ELF32Relocation → RawRelocation 145 | deriving Repr 146 | 147 | instance : Relocation RawRelocation where 148 | r_offset r := match r with | .elf64 r => r.r_offset.toNat | .elf32 r => r.r_offset.toNat 149 | r_info r := match r with | .elf64 r => r.r_info.toNat | .elf32 r => r.r_info.toNat 150 | bytes r := match r with | .elf64 r => r.bytes | .elf32 r => r.bytes 151 | 152 | def mkRawRelocation? 153 | (bs : ByteArray) 154 | (is64Bit : Bool) 155 | (isBigendian : Bool) 156 | (offset : Nat) 157 | : Except String RawRelocation := 158 | match is64Bit with 159 | | true => 160 | if h : bs.size - offset ≥ 0x10 161 | then pure (.elf64 $ mkELF64Relocation isBigendian bs offset h) 162 | else throw $ err 0x10 163 | | false => 164 | if h : bs.size - offset ≥ 0x08 165 | then pure (.elf32 $ mkELF32Relocation isBigendian bs offset h) 166 | else throw $ err 0xd 167 | where 168 | err size := 169 | s! "Tried to find a relocation at {offset}, but that doesn't leave enough space for the entry, " ++ 170 | s! "which requires {size} bytes." 171 | 172 | inductive RawRelocationA := 173 | | elf64 : ELF64RelocationA → RawRelocationA 174 | | elf32 : ELF32RelocationA → RawRelocationA 175 | deriving Repr 176 | 177 | instance : RelocationA RawRelocationA where 178 | ra_offset ra := match ra with | .elf64 ra => ra.ra_offset.toNat | .elf32 ra => ra.ra_offset.toNat 179 | ra_info ra := match ra with | .elf64 ra => ra.ra_info.toNat | .elf32 ra => ra.ra_info.toNat 180 | ra_addend ra := match ra with | .elf64 ra => ra.ra_addend.toInt | .elf32 ra => ra.ra_addend.toInt 181 | bytes ra := match ra with | .elf64 ra => ra.bytes | .elf32 ra => ra.bytes 182 | 183 | def mkRawRelocationA? 184 | (bs : ByteArray) 185 | (is64Bit : Bool) 186 | (isBigendian : Bool) 187 | (offset : Nat) 188 | : Except String RawRelocationA := 189 | match is64Bit with 190 | | true => 191 | if h : bs.size - offset ≥ 0x18 192 | then pure (.elf64 $ mkELF64RelocationA isBigendian bs offset h) 193 | else throw $ err 0x10 194 | | false => 195 | if h : bs.size - offset ≥ 0x0c 196 | then pure (.elf32 $ mkELF32RelocationA isBigendian bs offset h) 197 | else throw $ err 0xd 198 | where 199 | err size := 200 | s! "Tried to find a relocation at {offset}, but that doesn't leave enough space for the entry, " ++ 201 | s! "which requires {size} bytes." 202 | -------------------------------------------------------------------------------- /ELFSage/Types/Section.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.SectionHeaderTable 2 | import ELFSage.Constants.SectionHeaderTable 3 | 4 | structure InterpretedSection where 5 | /-- Name of the section -/ 6 | section_name : Nat 7 | /-- Type of the section -/ 8 | section_type : Nat 9 | /-- Flags associated with the section -/ 10 | section_flags : Nat 11 | /-- Base address of the section in memory -/ 12 | section_addr : Nat 13 | /-- Offset from beginning of file -/ 14 | section_offset : Nat 15 | /-- Section size in bytes -/ 16 | section_size : Nat 17 | /-- Section header table index link -/ 18 | section_link : Nat 19 | /-- Extra information, depends on section type -/ 20 | section_info : Nat 21 | /-- Alignment constraints for section -/ 22 | section_align : Nat 23 | /-- Size of each entry in table, if section is one -/ 24 | section_entsize : Nat 25 | /-- Body of section -/ 26 | section_body : ByteArray 27 | /-- Name of the section, as a string, if the section has one -/ 28 | section_name_as_string : Option String 29 | deriving Repr 30 | 31 | instance : SectionHeaderTableEntry InterpretedSection where 32 | sh_name sh := sh.section_name 33 | sh_type sh := sh.section_type 34 | sh_flags sh := sh.section_flags 35 | sh_addr sh := sh.section_addr 36 | sh_offset sh := sh.section_offset 37 | sh_size sh := sh.section_size 38 | sh_link sh := sh.section_link 39 | sh_info sh := sh.section_info 40 | sh_addralign sh := sh.section_align 41 | sh_entsize sh := sh.section_entsize 42 | bytes sh _ := sh.section_body 43 | 44 | def SectionHeaderTableEntry.toSection? 45 | [SectionHeaderTableEntry α] 46 | (shte : α) 47 | (bytes : ByteArray) 48 | (name : Option String) 49 | : Except String InterpretedSection := 50 | let hasBits := sh_type shte != ELFSectionHeaderTableEntry.Type.SHT_NOBITS 51 | if bytes.size < (sh_offset shte) + (sh_size shte) ∧ hasBits 52 | then .error $ 53 | s! "{match name with | some n => n | _ => "A section"} specified in the " ++ 54 | s! "section header table at offset 0x{Hex.toHex $ sh_offset shte}, with size 0x{Hex.toHex $ sh_size shte} " ++ 55 | s! "runs off the end of the binary." 56 | else .ok { 57 | section_name := sh_name shte 58 | section_type := sh_type shte 59 | section_flags := sh_flags shte 60 | section_addr := sh_addr shte 61 | section_offset := sh_offset shte 62 | section_size := sh_size shte 63 | section_link := sh_link shte 64 | section_info := sh_info shte 65 | section_align := sh_addralign shte 66 | section_entsize := sh_entsize shte 67 | section_body := if hasBits 68 | then bytes.extract (sh_offset shte) (sh_offset shte + sh_size shte) 69 | else ByteArray.empty 70 | section_name_as_string := name 71 | } 72 | 73 | def SectionHeaderTableEntry.getInterpretedSections 74 | [SectionHeaderTableEntry α] (sht : List α) 75 | [ELFHeader β] (eh : β) 76 | (bytes : ByteArray) 77 | : Except String (List (α × InterpretedSection)) := do 78 | let shstrndx := ELFHeader.e_shstrndx eh 79 | let section_names ← getSectionNames shstrndx sht bytes 80 | sht.mapM $ λshte ↦ 81 | if SectionHeaderTableEntry.sh_name shte == 0 82 | then do 83 | let sec ← SectionHeaderTableEntry.toSection? shte bytes .none 84 | return (shte, sec) 85 | else do 86 | let name := section_names.stringAt $ SectionHeaderTableEntry.sh_name shte 87 | let sec ← SectionHeaderTableEntry.toSection? shte bytes name 88 | return (shte, sec) 89 | -------------------------------------------------------------------------------- /ELFSage/Types/SectionHeaderTable.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Types.StringTable 3 | import ELFSage.Util.ByteArray 4 | import ELFSage.Types.ELFHeader 5 | import ELFSage.Util.Hex 6 | 7 | class SectionHeaderTableEntry (α : Type) where 8 | /-- Name of the section -/ 9 | sh_name : α → Nat 10 | /-- Type of the section and its semantics -/ 11 | sh_type : α → Nat 12 | /-- Flags associated with the section -/ 13 | sh_flags : α → Nat 14 | /-- Address of first byte of section in memory image -/ 15 | sh_addr : α → Nat 16 | /-- Offset from beginning of file of first byte of section -/ 17 | sh_offset : α → Nat 18 | /-- Section size in bytes -/ 19 | sh_size : α → Nat 20 | /-- Section header table index link -/ 21 | sh_link : α → Nat 22 | /-- Extra information, contents depends on type of section -/ 23 | sh_info : α → Nat 24 | /-- Alignment constraints for section -/ 25 | sh_addralign : α → Nat 26 | /-- Size of each entry in table, if section is composed of entries. Otherwise zero. -/ 27 | sh_entsize : α → Nat 28 | /-- Underlying Bytes, requires Endianness --/ 29 | bytes : α → (isBigendian : Bool) → ByteArray 30 | 31 | def SectionHeaderTableEntry.sh_end [SectionHeaderTableEntry α] (shte : α) := 32 | sh_offset shte + sh_size shte 33 | 34 | def SectionHeaderTableEntry.getSectionNames 35 | (shstrndx : Nat) 36 | [SectionHeaderTableEntry α] (sht : List α) 37 | (bytes : ByteArray) 38 | : Except String ELFStringTable := if h : shstrndx ≥ sht.length 39 | then .error "the shstrndx from the elf header requests a non-existent section" 40 | else 41 | let shstr_start := sh_offset sht[shstrndx] 42 | let shstr_end := shstr_start + sh_size sht[shstrndx] 43 | if shstr_end > bytes.size 44 | then .error "the section header string table runs off the end of the binary" 45 | else .ok ⟨bytes.extract shstr_start shstr_end⟩ 46 | 47 | -- Alignment requirments from man 5 elf 48 | def SectionHeaderTableEntry.checkAlignment 49 | [SectionHeaderTableEntry α] (sh : α) 50 | : Except String Unit := 51 | if sh_addralign sh < 2 then pure () 52 | else do 53 | if sh_addralign sh % 2 != 0 then .error $ 54 | s! "Corrupted sh_addralign: value is {Hex.toHex $ sh_addralign sh}, which is not a power of 2" 55 | if sh_addr sh % sh_addralign sh != 0 then .error $ 56 | s! "Misaligned section: sh_addr is {Hex.toHex $ sh_addr sh}, " ++ 57 | s! "but sh_addralign is {Hex.toHex $ sh_addralign sh}." 58 | 59 | structure ELF64SectionHeaderTableEntry where 60 | /-- Name of the section -/ 61 | sh_name : elf64_word 62 | /-- Type of the section and its semantics -/ 63 | sh_type : elf64_word 64 | /-- Flags associated with the section -/ 65 | sh_flags : elf64_xword 66 | /-- Address of first byte of section in memory image -/ 67 | sh_addr : elf64_addr 68 | /-- Offset from beginning of file of first byte of section -/ 69 | sh_offset : elf64_off 70 | /-- Section size in bytes -/ 71 | sh_size : elf64_xword 72 | /-- Section header table index link -/ 73 | sh_link : elf64_word 74 | /-- Extra information, contents depends on type of section -/ 75 | sh_info : elf64_word 76 | /-- Alignment constraints for section -/ 77 | sh_addralign : elf64_xword 78 | /-- Size of each entry in table, if section is composed of entries. Otherwise zero. -/ 79 | sh_entsize : elf64_xword 80 | deriving Repr 81 | 82 | def mkELF64SectionHeaderTableEntry 83 | (isBigEndian : Bool) 84 | (bs : ByteArray) 85 | (offset : Nat) 86 | (h : bs.size - offset ≥ 0x40) : 87 | ELF64SectionHeaderTableEntry := { 88 | sh_name := getUInt32from (offset + 0x00) (by omega), 89 | sh_type := getUInt32from (offset + 0x04) (by omega), 90 | sh_flags := getUInt64from (offset + 0x08) (by omega), 91 | sh_addr := getUInt64from (offset + 0x10) (by omega), 92 | sh_offset := getUInt64from (offset + 0x18) (by omega), 93 | sh_size := getUInt64from (offset + 0x20) (by omega), 94 | sh_link := getUInt32from (offset + 0x28) (by omega), 95 | sh_info := getUInt32from (offset + 0x2C) (by omega), 96 | sh_addralign := getUInt64from (offset + 0x30) (by omega), 97 | sh_entsize := getUInt64from (offset + 0x38) (by omega), 98 | } where 99 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 100 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 101 | getUInt64from := if isBigEndian then bs.getUInt64BEfrom else bs.getUInt64LEfrom 102 | 103 | def mkELF64SectionHeaderTableEntry? 104 | (isBigEndian : Bool) 105 | (bs : ByteArray) 106 | (offset : Nat) 107 | : Except String ELF64SectionHeaderTableEntry := 108 | if h : bs.size - offset ≥ 0x40 109 | then .ok $ mkELF64SectionHeaderTableEntry isBigEndian bs offset h 110 | else .error $ 111 | s!"The section header table entry offset 0x{Hex.toHex offset}, doesn't leave enough space for the entry. " ++ 112 | s!"The entry requires 0x40 bytes, but the file ends at 0x{Hex.toHex bs.size}." 113 | 114 | def ELF64SectionHeaderTableEntry.bytes (shte : ELF64SectionHeaderTableEntry) (isBigEndian : Bool) := 115 | getBytes32 shte.sh_name ++ 116 | getBytes32 shte.sh_type ++ 117 | getBytes64 shte.sh_flags ++ 118 | getBytes64 shte.sh_addr ++ 119 | getBytes64 shte.sh_offset ++ 120 | getBytes64 shte.sh_size ++ 121 | getBytes32 shte.sh_link ++ 122 | getBytes32 shte.sh_info ++ 123 | getBytes64 shte.sh_addralign ++ 124 | getBytes64 shte.sh_entsize 125 | where getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 126 | getBytes64 := if isBigEndian then UInt64.getBytesBEfrom else UInt64.getBytesLEfrom 127 | 128 | instance : SectionHeaderTableEntry ELF64SectionHeaderTableEntry where 129 | sh_name sh := sh.sh_name.toNat 130 | sh_type sh := sh.sh_type.toNat 131 | sh_flags sh := sh.sh_flags.toNat 132 | sh_addr sh := sh.sh_addr.toNat 133 | sh_offset sh := sh.sh_offset.toNat 134 | sh_size sh := sh.sh_size.toNat 135 | sh_link sh := sh.sh_link.toNat 136 | sh_info sh := sh.sh_info.toNat 137 | sh_addralign sh := sh.sh_addralign.toNat 138 | sh_entsize sh := sh.sh_entsize.toNat 139 | bytes sh := sh.bytes 140 | 141 | def ELF64Header.mkELF64SectionHeaderTable? 142 | (eh : ELF64Header) 143 | (bytes : ByteArray) 144 | : Except String (List ELF64SectionHeaderTableEntry) := 145 | let isBigendian := ELFHeader.isBigendian eh 146 | let shoffsets := (List.range (ELFHeader.e_shnum eh)).map λidx ↦ ELFHeader.e_shoff eh + ELFHeader.e_shentsize eh * idx 147 | List.mapM (λoffset ↦ mkELF64SectionHeaderTableEntry? isBigendian bytes offset) shoffsets 148 | 149 | structure ELF32SectionHeaderTableEntry where 150 | /-- Name of the section -/ 151 | sh_name : elf32_word 152 | /-- Type of the section and its semantics -/ 153 | sh_type : elf32_word 154 | /-- Flags associated with the section -/ 155 | sh_flags : elf32_word 156 | /-- Address of first byte of section in memory image -/ 157 | sh_addr : elf32_addr 158 | /-- Offset from beginning of file of first byte of section -/ 159 | sh_offset : elf32_off 160 | /-- Section size in bytes -/ 161 | sh_size : elf32_word 162 | /-- Section header table index link -/ 163 | sh_link : elf32_word 164 | /-- Extra information, contents depends on type of section -/ 165 | sh_info : elf32_word 166 | /-- Alignment constraints for section -/ 167 | sh_addralign : elf32_word 168 | /-- Size of each entry in table, if section is composed of entries. Otherwise zero. -/ 169 | sh_entsize : elf32_word 170 | deriving Repr 171 | 172 | def mkELF32SectionHeaderTableEntry 173 | (isBigEndian : Bool) 174 | (bs : ByteArray) 175 | (offset : Nat) 176 | (h : bs.size - offset ≥ 0x28) : 177 | ELF32SectionHeaderTableEntry := { 178 | sh_name := getUInt32from (offset + 0x00) (by omega), 179 | sh_type := getUInt32from (offset + 0x04) (by omega), 180 | sh_flags := getUInt32from (offset + 0x08) (by omega), 181 | sh_addr := getUInt32from (offset + 0x0C) (by omega), 182 | sh_offset := getUInt32from (offset + 0x10) (by omega), 183 | sh_size := getUInt32from (offset + 0x14) (by omega), 184 | sh_link := getUInt32from (offset + 0x18) (by omega), 185 | sh_info := getUInt32from (offset + 0x1C) (by omega), 186 | sh_addralign := getUInt32from (offset + 0x20) (by omega), 187 | sh_entsize := getUInt32from (offset + 0x24) (by omega), 188 | } where 189 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 190 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 191 | 192 | def mkELF32SectionHeaderTableEntry? 193 | (isBigEndian : Bool) 194 | (bs : ByteArray) 195 | (offset : Nat) 196 | : Except String ELF32SectionHeaderTableEntry := 197 | if h : bs.size - offset ≥ 0x28 198 | then .ok $ mkELF32SectionHeaderTableEntry isBigEndian bs offset h 199 | else .error $ 200 | s!"The section header table entry offset {Hex.toHex offset}, doesn't leave enough space for the entry. " ++ 201 | "The entry requires 0x28 bytes, but the file ends at {Hex.toHex bs.size}." 202 | 203 | def ELF32SectionHeaderTableEntry.bytes (shte : ELF32SectionHeaderTableEntry) (isBigEndian : Bool) := 204 | getBytes32 shte.sh_name ++ 205 | getBytes32 shte.sh_type ++ 206 | getBytes32 shte.sh_flags ++ 207 | getBytes32 shte.sh_addr ++ 208 | getBytes32 shte.sh_offset ++ 209 | getBytes32 shte.sh_size ++ 210 | getBytes32 shte.sh_link ++ 211 | getBytes32 shte.sh_info ++ 212 | getBytes32 shte.sh_addralign ++ 213 | getBytes32 shte.sh_entsize 214 | where getBytes32 := if isBigEndian then UInt32.getBytesBEfrom else UInt32.getBytesLEfrom 215 | 216 | instance : SectionHeaderTableEntry ELF32SectionHeaderTableEntry where 217 | sh_name sh := sh.sh_name.toNat 218 | sh_type sh := sh.sh_type.toNat 219 | sh_flags sh := sh.sh_flags.toNat 220 | sh_addr sh := sh.sh_addr.toNat 221 | sh_offset sh := sh.sh_offset.toNat 222 | sh_size sh := sh.sh_size.toNat 223 | sh_link sh := sh.sh_link.toNat 224 | sh_info sh := sh.sh_info.toNat 225 | sh_addralign sh := sh.sh_addralign.toNat 226 | sh_entsize sh := sh.sh_entsize.toNat 227 | bytes sh := sh.bytes 228 | 229 | def ELF32Header.mkELF32SectionHeaderTable? 230 | (eh : ELF32Header) 231 | (bytes : ByteArray) 232 | : Except String (List ELF32SectionHeaderTableEntry) := 233 | let isBigendian := ELFHeader.isBigendian eh 234 | let shoffsets := (List.range (ELFHeader.e_shnum eh)).map λidx ↦ ELFHeader.e_shoff eh + ELFHeader.e_shentsize eh * idx 235 | List.mapM (λoffset ↦ mkELF32SectionHeaderTableEntry? isBigendian bytes offset) shoffsets 236 | 237 | 238 | inductive RawSectionHeaderTableEntry := 239 | | elf32 : ELF32SectionHeaderTableEntry → RawSectionHeaderTableEntry 240 | | elf64 : ELF64SectionHeaderTableEntry → RawSectionHeaderTableEntry 241 | deriving Repr 242 | 243 | instance : SectionHeaderTableEntry RawSectionHeaderTableEntry where 244 | sh_name sh := match sh with | .elf64 sh => sh.sh_name.toNat | .elf32 sh => sh.sh_name.toNat 245 | sh_type sh := match sh with | .elf64 sh => sh.sh_type.toNat | .elf32 sh => sh.sh_type.toNat 246 | sh_flags sh := match sh with | .elf64 sh => sh.sh_flags.toNat | .elf32 sh => sh.sh_flags.toNat 247 | sh_addr sh := match sh with | .elf64 sh => sh.sh_addr.toNat | .elf32 sh => sh.sh_addr.toNat 248 | sh_offset sh := match sh with | .elf64 sh => sh.sh_offset.toNat | .elf32 sh => sh.sh_offset.toNat 249 | sh_size sh := match sh with | .elf64 sh => sh.sh_size.toNat | .elf32 sh => sh.sh_size.toNat 250 | sh_link sh := match sh with | .elf64 sh => sh.sh_link.toNat | .elf32 sh => sh.sh_link.toNat 251 | sh_info sh := match sh with | .elf64 sh => sh.sh_info.toNat | .elf32 sh => sh.sh_info.toNat 252 | sh_addralign sh := match sh with | .elf64 sh => sh.sh_addralign.toNat | .elf32 sh => sh.sh_addralign.toNat 253 | sh_entsize sh := match sh with | .elf64 sh => sh.sh_entsize.toNat | .elf32 sh => sh.sh_entsize.toNat 254 | bytes sh := match sh with | .elf64 sh => sh.bytes | .elf32 sh => sh.bytes 255 | 256 | def mkRawSectionHeaderTableEntry? 257 | (bs : ByteArray) 258 | (is64Bit : Bool) 259 | (isBigendian : Bool) 260 | (offset : Nat) 261 | : Except String RawSectionHeaderTableEntry := 262 | if is64Bit 263 | then .elf64 <$> mkELF64SectionHeaderTableEntry? isBigendian bs offset 264 | else .elf32 <$> mkELF32SectionHeaderTableEntry? isBigendian bs offset 265 | 266 | inductive RawSectionHeaderTable := 267 | | elf32 : List ELF32SectionHeaderTableEntry → RawSectionHeaderTable 268 | | elf64 : List ELF64SectionHeaderTableEntry → RawSectionHeaderTable 269 | -------------------------------------------------------------------------------- /ELFSage/Types/Segment.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.ProgramHeaderTable 2 | 3 | structure InterpretedSegment where 4 | /-- Type of the segment -/ 5 | segment_type : Nat 6 | /-- Size of the segment in bytes -/ 7 | segment_size : Nat 8 | /-- Size of the segment in memory in bytes -/ 9 | segment_memsz : Nat 10 | /-- Base address of the segment -/ 11 | segment_base : Nat 12 | /-- Physical address of segment -/ 13 | segment_paddr : Nat 14 | /-- Alignment of the segment -/ 15 | segment_align : Nat 16 | /-- Offset of the segment -/ 17 | segment_offset : Nat 18 | /-- Body of the segment -/ 19 | segment_body : ByteArray 20 | /-- READ, WRITE, EXECUTE flags. -/ 21 | segment_flags : Bool × Bool × Bool 22 | deriving Repr 23 | 24 | def ProgramHeaderTableEntry.toSegment? 25 | [ProgramHeaderTableEntry α] 26 | (phte : α) 27 | (bytes : ByteArray) 28 | : Except String InterpretedSegment := 29 | if bytes.size < p_offset phte + p_filesz phte 30 | then .error $ 31 | s! "A segment specified in the program header table at offset {p_offset phte}, " ++ 32 | s! "with size {p_filesz phte}, runs off the end of the binary." 33 | else .ok { 34 | segment_type := p_type phte 35 | segment_size := p_filesz phte 36 | segment_memsz := p_memsz phte 37 | segment_base := p_vaddr phte 38 | segment_paddr := p_paddr phte 39 | segment_align := p_align phte 40 | segment_offset := p_offset phte 41 | segment_body := bytes.extract (p_offset phte) (p_offset phte + p_filesz phte) 42 | segment_flags := ⟨ 43 | (p_flags phte / 4) % 2 == 0, -- Readable Segment 44 | (p_flags phte / 2) % 2 == 0, -- Writable Segment 45 | (p_flags phte / 1) % 2 == 0, -- Executable Segment 46 | ⟩ 47 | } 48 | 49 | def getInterpretedSegments 50 | [ProgramHeaderTableEntry α] (pht : List α) 51 | (bytes : ByteArray) 52 | : Except String (List (α × InterpretedSegment)) := 53 | pht.mapM $ λphte ↦ do 54 | let seg ← ProgramHeaderTableEntry.toSegment? phte bytes 55 | return (phte,seg) 56 | -------------------------------------------------------------------------------- /ELFSage/Types/Sizes.lean: -------------------------------------------------------------------------------- 1 | /- Lean doesn't seem to have fixed-width signed Ints in the stdlib. -/ 2 | structure SInt32 := bytes : UInt32 3 | 4 | def SInt32.toInt (si :SInt32) : Int := 5 | let uval := si.bytes.toNat 6 | if uval < 2 ^ 31 then ↑uval 7 | else (↑uval : Int) - (2 ^ 32) 8 | 9 | instance : Repr SInt32 where 10 | reprPrec sint := reprPrec $ sint.toInt 11 | 12 | structure SInt64 := bytes : UInt64 13 | 14 | def SInt64.toInt (si :SInt64) : Int := 15 | let uval := si.bytes.toNat 16 | if uval < 2 ^ 63 then ↑uval 17 | else (↑uval : Int) - (2 ^ 64) 18 | 19 | instance : Repr SInt64 where 20 | reprPrec sint := reprPrec $ sint.toInt 21 | 22 | /-- ELF64 Half word -/ 23 | abbrev elf64_half := UInt16 24 | /-- ELF64 Offset size -/ 25 | abbrev elf64_off := UInt64 26 | /-- ELF64 Addresse size -/ 27 | abbrev elf64_addr := UInt64 28 | /-- ELF64 Unsigned word size -/ 29 | abbrev elf64_word := UInt32 30 | /-- ELF64 Signed word size -/ 31 | abbrev elf64_sword := SInt32 32 | /-- ELF64 Extra wide word size -/ 33 | abbrev elf64_xword := UInt64 34 | /-- ELF64 Extra wide signed word size -/ 35 | abbrev elf64_sxword := SInt64 36 | 37 | /-- ELF32 Half word -/ 38 | abbrev elf32_half := UInt16 39 | /-- ELF32 Offset size -/ 40 | abbrev elf32_off := UInt32 41 | /-- ELF32 Addresse size -/ 42 | abbrev elf32_addr := UInt32 43 | /-- ELF32 Unsigned word size -/ 44 | abbrev elf32_word := UInt32 45 | /-- ELF32 Signed word size -/ 46 | abbrev elf32_sword := SInt32 47 | -------------------------------------------------------------------------------- /ELFSage/Types/StringTable.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Util.ByteArray 3 | 4 | structure ELFStringTable where 5 | strings : ByteArray 6 | 7 | def mkELFStringTable 8 | (bs : ByteArray) 9 | (offset : Nat) 10 | (size : Nat) 11 | (_ : bs.size ≥ offset + size) 12 | : ELFStringTable 13 | := ⟨ bs.extract offset (offset + size) ⟩ 14 | 15 | def ELFStringTable.stringAt (st : ELFStringTable) (idx : Nat) : String := 16 | match st.strings.findIdx? (· == 0) idx with 17 | | Option.none => 18 | let range := st.strings.extract idx st.strings.size 19 | let chars := range.toList.map (λbyte => Char.ofNat byte.toNat) 20 | String.mk chars 21 | | Option.some idx₂ => 22 | let range := st.strings.extract idx idx₂ 23 | let chars := range.toList.map (λbyte => Char.ofNat byte.toNat) 24 | String.mk chars 25 | 26 | structure StringTableEntry where 27 | /-- String associated with a string table entry --/ 28 | string : String 29 | /-- Offset within the associated string table --/ 30 | offset : Nat 31 | deriving Repr 32 | 33 | -- Get all the string entries in an ELFStringTable as a list 34 | def ELFStringTable.allStrings (st : ELFStringTable) : List StringTableEntry := Id.run do 35 | let mut idx := 0 36 | let mut idx₂ := 0 37 | let mut rslt := [] 38 | for ptr in st.strings do 39 | if ptr = 0 then do 40 | let range := st.strings.extract idx idx₂ 41 | let chars := range.toList.map (λbyte => Char.ofNat byte.toNat) 42 | rslt := ⟨String.mk chars, idx⟩::rslt 43 | idx := idx₂ + 1 -- point to the head of the next string, if there is one 44 | idx₂ := idx₂ + 1 -- sync with ptr 45 | return rslt 46 | 47 | -- Get the offset associated with a given string inside of a string table 48 | def ELFStringTable.stringToOffset (st : ELFStringTable) (str : String) : Option Nat := do 49 | let strings := st.allStrings 50 | let entry ← strings.find? (·.string = str) 51 | return entry.offset 52 | -------------------------------------------------------------------------------- /ELFSage/Types/Symbol.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.SymbolTable 2 | import ELFSage.Types.File 3 | 4 | structure InterpretedSymbolTableEntry where 5 | /-- Index into the object file's string table -/ 6 | st_name : Nat 7 | /-- Specifies the symbol's type and binding attributes -/ 8 | st_info : Nat 9 | /-- Currently specifies the symbol's visibility -/ 10 | st_other : Nat 11 | /-- Section header index symbol is defined with respect to -/ 12 | st_shndx : Nat 13 | /-- Gives the value of the associated symbol -/ 14 | st_value : Nat 15 | /-- Size of the associated symbol -/ 16 | st_size : Nat 17 | /-- Name of the symbol as a string, if available -/ 18 | st_name_as_string : Option String 19 | /-- Body of the symbol, if available -/ 20 | st_body : Option ByteArray 21 | 22 | instance : SymbolTableEntry InterpretedSymbolTableEntry where 23 | st_name ste := ste.st_name 24 | st_info ste := ste.st_info 25 | st_other ste := ste.st_other 26 | st_shndx ste := ste.st_shndx 27 | st_value ste := ste.st_value 28 | st_size ste := ste.st_size 29 | 30 | /-- Given a symbol table entry, this finds the index and interpreted section 31 | corresponding to the section in which the symbol body occurs -/ 32 | def SymbolTableEntry.getTarget? [SymbolTableEntry α] (ste : α) (elffile : RawELFFile) : Except String (Nat × InterpretedSection) := do 33 | let target_secidx := SymbolTableEntry.st_shndx ste 34 | if target_secidx ∈ [0,0xfff1] then errSpecialSection 35 | let ⟨_, target_sec⟩ ← elffile.getRawSectionHeaderTableEntries[target_secidx]?.elim errNoSection Except.ok 36 | .ok ⟨target_secidx, target_sec⟩ 37 | where errNoSection := .error "The section that symbol points to doesn't exist!" 38 | errSpecialSection := .error "special section, not implemented" 39 | 40 | /-- A symbol's st_value field can mean either an offset in a section, in 41 | a relocatable file, or an intended virtual address in memory in an executable 42 | or a shared object file. 43 | This computes the section offset for a symbol -/ 44 | def SymbolTableEntry.getSectionOffset? [SymbolTableEntry α] (ste : α) (elffile : RawELFFile) : Except String Nat := do 45 | match ELFHeader.e_type_val elffile.getRawELFHeader with 46 | | .et_exec | .et_dyn => do 47 | let ⟨_, target_sec⟩ ← SymbolTableEntry.getTarget? ste elffile 48 | return SymbolTableEntry.st_value ste - target_sec.section_addr 49 | | _ => return SymbolTableEntry.st_value ste 50 | 51 | /-- Given a symbol table entry, this finds the offset of the next symbol in the 52 | same section, or the end of the section -/ 53 | def SymbolTableEntry.nextSymbolOffset? 54 | [SymbolTableEntry α] 55 | (ste : α) 56 | (elffile : RawELFFile) 57 | : Except String Nat := do 58 | let ⟨target_secidx, target_sec⟩ ← SymbolTableEntry.getTarget? ste elffile 59 | let ⟨symshte, symsec⟩ ← elffile.getSymbolTable? 60 | let firstByte ← SymbolTableEntry.getSectionOffset? ste elffile 61 | let mut candidate := target_sec.section_size 62 | for idx in [:SectionHeaderTableEntry.sh_size symshte / SectionHeaderTableEntry.sh_entsize symshte] do 63 | match mkRawSymbolTableEntry? 64 | symsec.section_body elffile.is64Bit elffile.isBigendian 65 | (idx * SectionHeaderTableEntry.sh_entsize symshte) 66 | with 67 | | .error _ => pure () 68 | | .ok otherste => 69 | if SymbolTableEntry.st_shndx otherste ≠ target_secidx then continue 70 | let sectionOffset ← SymbolTableEntry.getSectionOffset? otherste elffile 71 | if sectionOffset < candidate && sectionOffset > firstByte 72 | then candidate := sectionOffset 73 | else candidate := candidate 74 | return candidate 75 | 76 | def SymbolTableEntry.toBody? 77 | [SymbolTableEntry α] 78 | (ste : α) 79 | (elffile : RawELFFile) 80 | : Except String ByteArray := do 81 | let ⟨_, target_sec⟩ ← SymbolTableEntry.getTarget? ste elffile 82 | let firstByte ← SymbolTableEntry.getSectionOffset? ste elffile 83 | let lastByte ← if SymbolTableEntry.st_size ste = 0 84 | then SymbolTableEntry.nextSymbolOffset? ste elffile 85 | else pure $ firstByte + SymbolTableEntry.st_size ste 86 | if lastByte > target_sec.section_body.size 87 | then .error "the address calculated for this symbol doesn't correspond to anything in the binary" 88 | else .ok $ target_sec.section_body.extract firstByte lastByte 89 | 90 | 91 | def RawELFFile.symbolNameByLinkAndOffset 92 | (elffile : RawELFFile) 93 | (linkIdx: Nat) 94 | (offset : Nat) 95 | : Except String String := 96 | match elffile.getRawSectionHeaderTableEntries[linkIdx]? with 97 | | .none => .error "The section the symbol table references for names doesn't exist" 98 | | .some ⟨_, sec⟩ => .ok $ (ELFStringTable.mk sec.section_body).stringAt offset 99 | 100 | /- Get the name and symbol table entry of the `symidx`-th symbol, given the 101 | symbol table's section header `shte` and section `sec`. -/ 102 | def RawELFFile.getSymbolTableEntryInSection 103 | (elffile : RawELFFile) 104 | (shte : RawSectionHeaderTableEntry) 105 | (sec : InterpretedSection) 106 | (symidx : Nat) 107 | : Except String (String × RawSymbolTableEntry) := do 108 | let offset := symidx * SectionHeaderTableEntry.sh_entsize shte 109 | let ste ← mkRawSymbolTableEntry? sec.section_body elffile.is64Bit elffile.isBigendian offset 110 | let sym_name ← symbolNameByLinkAndOffset elffile 111 | (SectionHeaderTableEntry.sh_link shte) 112 | (SymbolTableEntry.st_name ste) 113 | .ok (sym_name, ste) 114 | 115 | /- Return the symbol table entry corresponding to a symbol `symname`, if it 116 | exists, between entries with index greater than or equal to `symidx` and less 117 | than `maxidx`. -/ 118 | def RawELFFile.findSymbolTableEntryInSection 119 | (elffile : RawELFFile) 120 | (symidx maxidx : Nat) 121 | (shte : RawSectionHeaderTableEntry) 122 | (sec : InterpretedSection) 123 | (symname : String) 124 | : Except String RawSymbolTableEntry := do 125 | if symidx >= maxidx then throw s!"Symbol {symname} not found!" 126 | else do 127 | let (str, ste) ← getSymbolTableEntryInSection elffile shte sec symidx 128 | if str == symname then return ste 129 | else findSymbolTableEntryInSection elffile (symidx + 1) maxidx shte sec symname 130 | termination_by (maxidx - symidx) 131 | 132 | /- Get the `st_value` of the symbol `symbolname` and its contents 133 | (as a `ByteArray`). -/ 134 | def RawELFFile.getSymbolContents (elffile : RawELFFile) (symbolname : String) : 135 | Except String (Nat × ByteArray) := do 136 | let (shte, sec) ← elffile.getSymbolTable? 137 | let entry_size := SectionHeaderTableEntry.sh_entsize shte 138 | let table_size := SectionHeaderTableEntry.sh_size shte 139 | let total_entries := table_size / entry_size 140 | let ste ← findSymbolTableEntryInSection elffile 0 total_entries shte sec symbolname 141 | let bytes ← SymbolTableEntry.toBody? ste elffile 142 | .ok (SymbolTableEntry.st_value ste, bytes) 143 | -------------------------------------------------------------------------------- /ELFSage/Types/SymbolTable.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Types.Sizes 2 | import ELFSage.Util.ByteArray 3 | 4 | class SymbolTableEntry (α : Type) where 5 | /-- Index into the object file's string table -/ 6 | st_name : α → Nat 7 | /-- Specifies the symbol's type and binding attributes -/ 8 | st_info : α → Nat 9 | /-- Currently specifies the symbol's visibility -/ 10 | st_other : α → Nat 11 | /-- Section header index symbol is defined with respect to -/ 12 | st_shndx : α → Nat 13 | /-- Gives the value of the associated symbol -/ 14 | st_value : α → Nat 15 | /-- Size of the associated symbol -/ 16 | st_size : α → Nat 17 | 18 | structure ELF64SymbolTableEntry where 19 | /-- Index into the object file's string table -/ 20 | st_name : elf64_word 21 | /-- Specifies the symbol's type and binding attributes -/ 22 | st_info : UInt8 23 | /-- Currently specifies the symbol's visibility -/ 24 | st_other : UInt8 25 | /-- Section header index symbol is defined with respect to -/ 26 | st_shndx : elf64_half 27 | /-- Gives the value of the associated symbol -/ 28 | st_value : elf64_addr 29 | /-- Size of the associated symbol -/ 30 | st_size : elf64_xword 31 | deriving Repr 32 | 33 | instance : SymbolTableEntry ELF64SymbolTableEntry where 34 | st_name ste := ste.st_name.toNat 35 | st_info ste := ste.st_info.toNat 36 | st_other ste := ste.st_other.toNat 37 | st_shndx ste := ste.st_shndx.toNat 38 | st_value ste := ste.st_value.toNat 39 | st_size ste := ste.st_size.toNat 40 | 41 | def mkELF64SymbolTableEntry 42 | (isBigEndian : Bool) 43 | (bs : ByteArray) 44 | (offset : Nat) 45 | (h : bs.size - offset ≥ 0x18) : 46 | ELF64SymbolTableEntry := { 47 | st_name := getUInt32from (offset + 0x00) (by omega), 48 | st_info := bs.get ⟨offset + 0x4, by omega⟩, 49 | st_other := bs.get ⟨offset + 0x5, by omega⟩, 50 | st_shndx := getUInt16from (offset + 0x6) (by omega), 51 | st_value := getUInt64from (offset + 0x8) (by omega), 52 | st_size := getUInt64from (offset + 0x10) (by omega), 53 | } where 54 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 55 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 56 | getUInt64from := if isBigEndian then bs.getUInt64BEfrom else bs.getUInt64LEfrom 57 | 58 | structure ELF32SymbolTableEntry where 59 | /-- Index into the object file's string table -/ 60 | st_name : elf32_word 61 | /-- Gives the value of the associated symbol -/ 62 | st_value : elf32_addr 63 | /-- Size of the associated symbol -/ 64 | st_size : elf32_word 65 | /-- Specifies the symbol's type and binding attributes -/ 66 | st_info : UInt8 67 | /-- Currently specifies the symbol's visibility -/ 68 | st_other : UInt8 69 | /-- Section header index symbol is defined with respect to -/ 70 | st_shndx : elf32_half 71 | deriving Repr 72 | 73 | instance : SymbolTableEntry ELF32SymbolTableEntry where 74 | st_name ste := ste.st_name.toNat 75 | st_info ste := ste.st_info.toNat 76 | st_other ste := ste.st_other.toNat 77 | st_shndx ste := ste.st_shndx.toNat 78 | st_value ste := ste.st_value.toNat 79 | st_size ste := ste.st_size.toNat 80 | 81 | def mkELF32SymbolTableEntry 82 | (isBigEndian : Bool) 83 | (bs : ByteArray) 84 | (offset : Nat) 85 | (h : bs.size - offset ≥ 0xd) : 86 | ELF32SymbolTableEntry := { 87 | st_name := getUInt32from (offset + 0x00) (by omega), 88 | st_value := getUInt32from (offset + 0x04) (by omega), 89 | st_size := getUInt32from (offset + 0x08) (by omega), 90 | st_info := bs.get ⟨offset + 0x9, by omega⟩, 91 | st_other := bs.get ⟨offset + 0xa, by omega⟩, 92 | st_shndx := getUInt16from (offset + 0xb) (by omega) , 93 | } where 94 | getUInt16from := if isBigEndian then bs.getUInt16BEfrom else bs.getUInt16LEfrom 95 | getUInt32from := if isBigEndian then bs.getUInt32BEfrom else bs.getUInt32LEfrom 96 | 97 | inductive RawSymbolTableEntry := 98 | | elf32 : ELF32SymbolTableEntry → RawSymbolTableEntry 99 | | elf64 : ELF64SymbolTableEntry → RawSymbolTableEntry 100 | deriving Repr 101 | 102 | instance : SymbolTableEntry RawSymbolTableEntry where 103 | st_name ste := match ste with | .elf64 ste => ste.st_name.toNat | .elf32 ste => ste.st_name.toNat 104 | st_info ste := match ste with | .elf64 ste => ste.st_info.toNat | .elf32 ste => ste.st_info.toNat 105 | st_other ste := match ste with | .elf64 ste => ste.st_other.toNat | .elf32 ste => ste.st_other.toNat 106 | st_shndx ste := match ste with | .elf64 ste => ste.st_shndx.toNat | .elf32 ste => ste.st_shndx.toNat 107 | st_value ste := match ste with | .elf64 ste => ste.st_value.toNat | .elf32 ste => ste.st_value.toNat 108 | st_size ste := match ste with | .elf64 ste => ste.st_size.toNat | .elf32 ste => ste.st_size.toNat 109 | 110 | def mkRawSymbolTableEntry? 111 | (bs : ByteArray) 112 | (is64Bit : Bool) 113 | (isBigendian : Bool) 114 | (offset : Nat) 115 | : Except String RawSymbolTableEntry := 116 | match is64Bit with 117 | | true => 118 | if h : bs.size - offset ≥ 0x18 119 | then pure (.elf64 $ mkELF64SymbolTableEntry isBigendian bs offset h) 120 | else throw $ err 0x18 121 | | false => 122 | if h : bs.size - offset ≥ 0xd 123 | then pure (.elf32 $ mkELF32SymbolTableEntry isBigendian bs offset h) 124 | else throw $ err 0xd 125 | where 126 | err size := s! "Symbol table entry offset {offset} doesn't leave enough space for the entry, " ++ 127 | s! "which requires {size} bytes." 128 | -------------------------------------------------------------------------------- /ELFSage/Util/ByteArray.lean: -------------------------------------------------------------------------------- 1 | def ByteArray.getUInt64BEfrom (bs : ByteArray) (offset : Nat) (h: bs.size - offset ≥ 8) : UInt64 := 2 | (bs.get ⟨offset + 0, by omega⟩).toUInt64 <<< 0x38 ||| 3 | (bs.get ⟨offset + 1, by omega⟩).toUInt64 <<< 0x30 ||| 4 | (bs.get ⟨offset + 2, by omega⟩).toUInt64 <<< 0x28 ||| 5 | (bs.get ⟨offset + 3, by omega⟩).toUInt64 <<< 0x20 ||| 6 | (bs.get ⟨offset + 4, by omega⟩).toUInt64 <<< 0x18 ||| 7 | (bs.get ⟨offset + 5, by omega⟩).toUInt64 <<< 0x10 ||| 8 | (bs.get ⟨offset + 6, by omega⟩).toUInt64 <<< 0x08 ||| 9 | (bs.get ⟨offset + 7, by omega⟩).toUInt64 10 | 11 | 12 | def UInt64.getBytesBEfrom (i : UInt64) : ByteArray := 13 | ⟨#[ (i >>> 0x38).toUInt8 14 | , (i >>> 0x30).toUInt8 15 | , (i >>> 0x28).toUInt8 16 | , (i >>> 0x20).toUInt8 17 | , (i >>> 0x18).toUInt8 18 | , (i >>> 0x10).toUInt8 19 | , (i >>> 0x8).toUInt8 20 | , (i >>> 0x0).toUInt8 21 | ]⟩ 22 | 23 | def ByteArray.getUInt32BEfrom (bs : ByteArray) (offset : Nat) (h: bs.size - offset ≥ 4) : UInt32 := 24 | (bs.get ⟨offset + 0, by omega⟩).toUInt32 <<< 0x18 ||| 25 | (bs.get ⟨offset + 1, by omega⟩).toUInt32 <<< 0x10 ||| 26 | (bs.get ⟨offset + 2, by omega⟩).toUInt32 <<< 0x08 ||| 27 | (bs.get ⟨offset + 3, by omega⟩).toUInt32 28 | 29 | def UInt32.getBytesBEfrom (i : UInt32) : ByteArray := 30 | ⟨#[ (i >>> 0x18).toUInt8 31 | , (i >>> 0x10).toUInt8 32 | , (i >>> 0x8).toUInt8 33 | , (i >>> 0x0).toUInt8 34 | ]⟩ 35 | 36 | def ByteArray.getUInt16BEfrom (bs : ByteArray) (offset : Nat) (h: bs.size - offset ≥ 2) : UInt16 := 37 | (bs.get ⟨offset + 0, by omega⟩).toUInt16 <<< 0x08 ||| 38 | (bs.get ⟨offset + 1, by omega⟩).toUInt16 39 | 40 | def UInt16.getBytesBEfrom (i : UInt16) : ByteArray := 41 | ⟨#[ (i >>> 0x8).toUInt8 42 | , (i >>> 0x0).toUInt8 43 | ]⟩ 44 | 45 | --LittleEndian 46 | 47 | def ByteArray.getUInt64LEfrom (bs : ByteArray) (offset : Nat) (h: bs.size - offset ≥ 8) : UInt64 := 48 | (bs.get ⟨offset + 7, by omega⟩).toUInt64 <<< 0x38 ||| 49 | (bs.get ⟨offset + 6, by omega⟩).toUInt64 <<< 0x30 ||| 50 | (bs.get ⟨offset + 5, by omega⟩).toUInt64 <<< 0x28 ||| 51 | (bs.get ⟨offset + 4, by omega⟩).toUInt64 <<< 0x20 ||| 52 | (bs.get ⟨offset + 3, by omega⟩).toUInt64 <<< 0x18 ||| 53 | (bs.get ⟨offset + 2, by omega⟩).toUInt64 <<< 0x10 ||| 54 | (bs.get ⟨offset + 1, by omega⟩).toUInt64 <<< 0x08 ||| 55 | (bs.get ⟨offset + 0, by omega⟩).toUInt64 56 | 57 | def UInt64.getBytesLEfrom (i : UInt64) : ByteArray := 58 | ⟨#[ (i >>> 0x00).toUInt8 59 | , (i >>> 0x8).toUInt8 60 | , (i >>> 0x10).toUInt8 61 | , (i >>> 0x18).toUInt8 62 | , (i >>> 0x20).toUInt8 63 | , (i >>> 0x28).toUInt8 64 | , (i >>> 0x30).toUInt8 65 | , (i >>> 0x38).toUInt8 66 | ]⟩ 67 | 68 | def ByteArray.getUInt32LEfrom (bs : ByteArray) (offset : Nat) (h: bs.size - offset ≥ 4) : UInt32 := 69 | (bs.get ⟨offset + 3, by omega⟩).toUInt32 <<< 0x18 ||| 70 | (bs.get ⟨offset + 2, by omega⟩).toUInt32 <<< 0x10 ||| 71 | (bs.get ⟨offset + 1, by omega⟩).toUInt32 <<< 0x08 ||| 72 | (bs.get ⟨offset + 0, by omega⟩).toUInt32 73 | 74 | def UInt32.getBytesLEfrom (i : UInt32) : ByteArray := 75 | ⟨#[ (i >>> 0x00).toUInt8 76 | , (i >>> 0x8).toUInt8 77 | , (i >>> 0x10).toUInt8 78 | , (i >>> 0x18).toUInt8 79 | ]⟩ 80 | 81 | def ByteArray.getUInt16LEfrom (bs : ByteArray) (offset : Nat) (h: bs.size - offset ≥ 2) : UInt16 := 82 | (bs.get ⟨offset + 1, by omega⟩).toUInt16 <<< 0x08 ||| 83 | (bs.get ⟨offset + 0, by omega⟩).toUInt16 84 | 85 | def UInt16.getBytesLEfrom (i : UInt16) : ByteArray := 86 | ⟨#[ (i >>> 0x00).toUInt8 87 | , (i >>> 0x8).toUInt8 88 | ]⟩ 89 | 90 | /- An alternative approach is to define a byte parsing monad, à la: 91 | 92 | https://github.com/risc0/risc0-lean4/blob/31c956fc9246bbfc84359021d66ed94972afd86b/R0sy/ByteDeserial.lean 93 | 94 | And just have some runtime error possibilities, rather than proof obligations. 95 | -/ 96 | 97 | def ByteArray.getEntriesFrom 98 | (bs : ByteArray) 99 | (offset : Nat) 100 | (num : Nat) 101 | (entsize : Nat) 102 | (entreq : Nat) 103 | (suffSize : entsize ≥ entreq) 104 | (spaceAvail : bs.size ≥ offset + num * entsize) 105 | (toEnt : (offset₂ : Nat) → (bs.size - offset₂ ≥ entreq) → α) 106 | : List α := recur num [] (by omega) 107 | where recur (idx : Nat) acc (h₁ : idx ≤ num) : List α 108 | := match idx with 109 | | 0 => acc 110 | | i + 1 => 111 | let ent := toEnt (offset + (i * entsize)) $ by 112 | have h₂ : num * entsize ≥ i * entsize + entsize := by 113 | conv => rhs; rw [Nat.mul_comm] 114 | rw [←Nat.mul_succ] 115 | conv => rhs; rw [Nat.mul_comm] 116 | exact Nat.mul_le_mul_right entsize h₁ 117 | omega 118 | recur i (ent :: acc) (by omega) 119 | 120 | structure NByteArray (n : Nat) where 121 | bytes: ByteArray 122 | sized: bytes.size = n 123 | 124 | def NByteArray.get {n : Nat} (bs : NByteArray n) (m: Nat) (p: m < n) := 125 | have size := bs.sized 126 | bs.bytes.get ⟨m, by omega⟩ 127 | 128 | instance : Repr (NByteArray n) where 129 | reprPrec nbs := reprPrec $ nbs.bytes.toList.map λbyte ↦ 130 | Nat.toDigits 16 byte.toNat 131 | |> (λl ↦ if l.length == 1 then '0' :: l else l) 132 | |> List.asString 133 | 134 | instance : Repr ByteArray where 135 | reprPrec nbs := reprPrec $ nbs.data.toList.map λbyte ↦ 136 | Nat.toDigits 16 byte.toNat 137 | |> (λl ↦ if l.length == 1 then '0' :: l else l) 138 | |> List.asString 139 | 140 | /- 141 | 142 | These are some simple theorems related to Arrays and ByteArrays. They start 143 | from the function Array.extract.loop, associated with Array.extract: 144 | 145 | https://github.com/leanprover/lean4/blob/6fce8f7d5cd18a4419bca7fd51780c71c9b1cc5a/src/Init/Prelude.lean#L2722 146 | 147 | `Array.extract.loop src i j dst` essentially returns dst if i is 0 or 148 | j ≥ src.size, and otherwise calls itself as `Array.extract.loop src (i - 1) (j 149 | + 1) (dst ++ src[j])`. 150 | 151 | Some things it would be nice to prove, since it would give us an instance of 152 | 153 | https://leanprover-community.github.io/mathlib4_docs/Mathlib/Algebra/Group/Defs.html#AddCancelMonoid 154 | 155 | ∀{a b: ByteArray}, (a ++ b).size = a.size + b.size 156 | ∀{a b c: ByteArray}, a ++ b = c ++ b ↔ a = c 157 | ∀{a b c: ByteArray}, a ++ b = a ++ c ↔ b = c 158 | ∀{a b c: ByteArray}, (a ++ b) ++ c = a ++ (b ++ c) 159 | ∀{a: ByteArray}, a ++ ByteArray.empty = a 160 | ∀{a: ByteArray}, ByteArray.empty ++ a = a 161 | 162 | It would also be good to improve NByteArray extract to take an offset in addition to a length 163 | 164 | -/ 165 | 166 | theorem Array.extract_len_aux {src: Array α} : 167 | ∀b l dst, (b + l ≤ src.size) → 168 | List.length (Array.extract.loop src b l dst).toList = b + dst.size:= by 169 | intro b 170 | induction b 171 | case zero => simp [Array.extract.loop] 172 | case succ n ih => 173 | intro l dst 174 | unfold Array.extract.loop 175 | intro lt 176 | simp; split 177 | · have : n + l + 1 ≤ size src := by omega 178 | simp only [length_toList] at ih 179 | rw [ih (l + 1) (push dst src[l]) this] 180 | simp_arith 181 | · omega 182 | 183 | theorem Array.extract_loop_len {src : Array α} : 184 | ∀ b l dst, 185 | (b + l ≤ src.size) → 186 | (Array.extract.loop src b l dst).size = b + dst.size:= by 187 | intro i s e h 188 | rw [Array.size] 189 | apply Array.extract_len_aux _ _ _ h 190 | 191 | def NByteArray.extract (bs : ByteArray) (n : Nat) (h : bs.size ≥ n) : NByteArray n := 192 | let bytes := bs.extract 0 n 193 | let proof : bytes.size = n := by 194 | simp [ ByteArray.size 195 | , ByteArray.extract 196 | , ByteArray.copySlice 197 | , Array.extract 198 | , ByteArray.empty 199 | , ByteArray.mkEmpty 200 | ] 201 | have : ∀α, ∀n : Nat, @Array.extract.loop α #[] 0 n #[] = #[] := by 202 | unfold Array.extract.loop 203 | split; simp; contradiction 204 | rw [this, this] 205 | simp 206 | have : bs.data.size = bs.size := by 207 | simp [ByteArray.size] 208 | have : (min n (List.length bs.toList)) + 0 ≤ bs.data.size := by 209 | rw [Nat.min_def] 210 | split <;> omega 211 | rw [Array.extract_loop_len (min n bs.data.size) 0 #[]] 212 | simp only [ByteArray.size, Array.size, 213 | ge_iff_le, Array.size_append, 214 | List.length_append, implies_true, 215 | Nat.min_def, Nat.zero_add, 216 | Array.toList_toArray, List.length_nil, 217 | Nat.add_zero, ite_eq_left_iff, Nat.not_le] 218 | at * 219 | · omega 220 | · simp [Nat.min_def]; split 221 | · assumption 222 | · simp only [ByteArray.size, Array.size, 223 | ge_iff_le, Array.size_append, 224 | List.length_append, implies_true, 225 | Nat.min_def, Nat.zero_add, 226 | Nat.not_le, Nat.le_refl] 227 | at * 228 | ⟨bytes, proof⟩ 229 | 230 | theorem UInt16.ByteArray_size : ∀ i : UInt16, i.getBytesBEfrom.size = 2 := by 231 | intro i 232 | simp [UInt16.getBytesBEfrom, ByteArray.size] 233 | 234 | theorem UInt8_to_UInt16_round: ∀{i : UInt8}, i.toUInt16.toUInt8 = i := by 235 | rintro ⟨i⟩ 236 | simp [UInt16.toUInt8, Nat.toUInt8, UInt8.ofNat] 237 | apply UInt8.eq_of_val_eq 238 | simp 239 | rcases i with ⟨val, lt⟩ 240 | apply Fin.eq_of_val_eq 241 | simp [Fin.ofNat, UInt16.toNat, UInt8.toUInt16, Nat.toUInt16, UInt16.ofNat, UInt8.toNat] 242 | unfold UInt8.size at lt 243 | omega 244 | 245 | theorem UInt16_to_UInt8_round: ∀{i : UInt16}, i.toUInt8.toUInt16 = i % 256:= by 246 | rintro ⟨i⟩ 247 | simp [UInt8.toUInt16, Nat.toUInt16, UInt16.ofNat, HMod.hMod, instHMod, Mod.mod, UInt16.mod] 248 | apply UInt16.eq_of_val_eq 249 | simp only [UInt16.toUInt8, Nat.toUInt8, UInt8.ofNat, Fin.ofNat, Nat.succ_eq_add_one, 250 | Nat.reduceAdd, UInt16.toNat, Fin.mod, Nat.reduceMod] 251 | 252 | theorem Nat.bitwise_sum : ∀{n m k : Nat}, m % 2^n = 0 → k < 2^n → m ||| k = m + k := by 253 | intro n m k eq lt 254 | have : 2^n * (m / 2^n) = m := by 255 | apply Nat.mul_div_cancel' 256 | exact Nat.dvd_of_mod_eq_zero eq 257 | rw [←this] 258 | rw [Nat.mul_add_lt_is_or] 259 | assumption 260 | 261 | /-- 262 | OK, so the idea for doing these proofs more systematically would be to prove that 263 | 264 | i = ((i >>> 0x8) <<< 0x8 ) ||| i % 256 265 | 266 | for nat, then use this repeatedly (substituting for i >>> 0x8, i >>> 0x10) to get something like 267 | 268 | i = (((((i >>> 0x8) >>> 0x8) <<< 0x8 ) ||| (i >>> 0x8) % 256) <<< 0x8) ||| i % 256 269 | = (((i >>> 0x10) <<< 0x8 ) ||| (i >>> 0x8) % 256) <<< 0x8 ||| i % 256 --consolidate 270 | = (((i >>> 0x10) <<< 0x8 ) << 0x8) ||| ((i >>> 0x8) % 256) <<< 0x8 ||| i % 256 --distribute 271 | = (((i >>> 0x10) <<< 0x10) ||| ((i >>> 0x8) % 256) <<< 0x8 ||| i % 256 --consolidate 272 | ... and so on 273 | 274 | and then use these to prove the corresponding UInt facts 275 | 276 | -/ 277 | 278 | macro "lower_to_nat" i:Lean.Parser.Tactic.casesTarget: tactic => `(tactic|( 279 | simp [ 280 | HShiftRight.hShiftRight, 281 | ShiftRight.shiftRight, 282 | UInt16.shiftRight, 283 | UInt32.shiftRight, 284 | Fin.shiftRight, 285 | 286 | HShiftLeft.hShiftLeft, 287 | ShiftLeft.shiftLeft, 288 | UInt32.shiftLeft, 289 | UInt16.shiftLeft, 290 | Fin.shiftLeft, 291 | 292 | HMod.hMod, 293 | Mod.mod, 294 | UInt16.mod, 295 | UInt16.modn, 296 | UInt32.mod, 297 | UInt32.modn, 298 | Fin.mod, 299 | Fin.div, 300 | show (8 : UInt16).val = 8 by decide, 301 | show (8 : UInt32).val = 8 by decide, 302 | show (16 : UInt32).val = 16 by decide, 303 | show (24 : UInt32).val = 24 by decide, 304 | show (8 : Fin (2^16)).val = 8 by decide, 305 | show (8 : Fin (2^32)).val = 8 by decide, 306 | show (16 : Fin (2^32)).val = 16 by decide, 307 | show (24 : Fin (2^32)).val = 24 by decide, 308 | show Nat.mod 8 16 = 8 by decide, 309 | show Nat.mod 8 32 = 8 by decide, 310 | show Nat.mod 16 32 = 16 by decide, 311 | show Nat.mod 24 32 = 24 by decide, 312 | show Nat.mod 256 65536 = 256 by decide, 313 | 314 | HOr.hOr, 315 | OrOp.or, 316 | ] 317 | rcases $i with ⟨val⟩ 318 | try apply UInt16.eq_of_val_eq 319 | try apply UInt32.eq_of_val_eq 320 | simp_arith 321 | rcases val with ⟨val, lt_val⟩ 322 | apply Fin.eq_of_val_eq 323 | simp_arith 324 | )) 325 | 326 | theorem UInt16.nullShift : ∀{i : UInt16}, i >>> 0 = i := by 327 | intro i; lower_to_nat i; apply Nat.mod_eq_of_lt; assumption 328 | 329 | @[simp] 330 | theorem Nat.bitwise_zero_left : bitwise f 0 m = if f false true then m else 0 := by 331 | simp only [bitwise, ↓reduceIte] 332 | 333 | @[simp] 334 | theorem Nat.bitwise_zero_right : bitwise f n 0 = if f true false then n else 0 := by 335 | unfold bitwise 336 | simp only [ite_self, decide_False, Nat.zero_div, ite_true, ite_eq_right_iff] 337 | rintro ⟨⟩ 338 | split <;> rfl 339 | 340 | theorem Nat.shiftLeft_toExp : x <<< n = 2^n * x := by 341 | rw [Nat.mul_comm]; apply Nat.shiftLeft_eq 342 | 343 | theorem Nat.shiftRight_toDiv : x >>> n = x / 2^n := by 344 | induction n 345 | case zero => simp 346 | case succ n ih => 347 | rw [Nat.pow_succ 2 n, Nat.shiftRight_succ, ih] 348 | rw [Nat.div_div_eq_div_mul] 349 | 350 | theorem Nat.shiftRightLeft_leq : ∀n : Nat, (x >>> n) <<< n ≤ x := by 351 | intro n 352 | rw [Nat.shiftRight_toDiv, Nat.shiftLeft_toExp] 353 | apply Nat.mul_div_le 354 | 355 | private theorem Nat.bitwise_dist_lem : f false false = false → 2 * Nat.bitwise f m n = Nat.bitwise f (2 * m) (2 * n) := by 356 | intro hyp 357 | unfold bitwise; split <;> split <;> try split <;> try split <;> try split 358 | all_goals try simp_all 359 | all_goals try omega 360 | (conv => rhs; unfold bitwise) 361 | simp_all only [ite_false] 362 | apply Nat.two_mul 363 | 364 | theorem Nat.mod_pow_zero : n % m^(succ k) = 0 → n % m = 0 := by 365 | intro hyp 366 | rw [Nat.pow_succ] at hyp 367 | have : n % (m ^ k * m) % m = 0 % m := by rw [hyp] 368 | simp [Nat.mod_mul_left_mod n (m^k) m] at this 369 | assumption 370 | 371 | theorem Nat.zero_lt_pow : 0 < n → 0 < n^m := by 372 | intro hyp; induction m 373 | case zero => simp 374 | case succ m ih => 375 | simp [Nat.pow_succ] 376 | have := (Nat.mul_lt_mul_right hyp).mpr ih 377 | simp at this 378 | assumption 379 | 380 | theorem Nat.mod_pow_lt_inner : ∀{l m n k : Nat}, l ≤ m → n % k^m % k^l = n % k^l := by 381 | intro l m n k hyp; apply Nat.mod_mod_of_dvd; apply Nat.pow_dvd_pow; assumption 382 | 383 | theorem Nat.mod_pow_lt_outer : ∀{l m n k : Nat}, 1 < k → l ≤ m → n % k^l % k^m = n % k^l := by 384 | intro l m n k hyp₁ hyp₂ 385 | cases Nat.le_iff_lt_or_eq.mp hyp₂ 386 | case inl hyp₂ => 387 | apply Nat.mod_eq_of_lt 388 | apply Nat.lt_trans (m := k ^ l) 389 | · apply Nat.mod_lt; apply Nat.zero_lt_pow; omega 390 | · apply Nat.pow_lt_pow_of_lt hyp₁; assumption 391 | case inr hyp₂ => rw [hyp₂, Nat.mod_mod] 392 | 393 | theorem Nat.shiftLeft_mod : w + m ≥ k → (n % 2^w) <<< m % 2^k = n <<< m % 2^k := by 394 | simp_all [Nat.shiftLeft_toExp] 395 | revert m 396 | induction k <;> intro m 397 | case zero => simp [Nat.mod_one] 398 | case succ k ih_k => 399 | intro hyp 400 | cases m 401 | case zero => simp_all; exact Nat.mod_pow_lt_inner hyp 402 | case succ m => 403 | simp [Nat.pow_succ] 404 | suffices 2 * 2 ^ m * (n % 2 ^ w) % (2 * 2 ^ k) = 2 * 2 ^ m * n % (2 * 2 ^ k) by 405 | simp_all [Nat.mul_comm] 406 | suffices 2 * (2 ^ m * (n % 2 ^ w)) % (2 * 2 ^ k) = 2 * (2 ^ m * n) % (2 * 2 ^ k) by 407 | simp_all [Nat.mul_assoc] 408 | simp [Nat.mul_mod_mul_left] 409 | rw [ih_k] 410 | simp_arith at hyp 411 | assumption 412 | 413 | --TODO: this should generalize to arbitrary bitwise ops. 414 | theorem Nat.bitwise_mod : Nat.bitwise or m n % 2^k = Nat.bitwise or (m % 2^k) (n % 2^k) := by 415 | apply Nat.eq_of_testBit_eq 416 | intro i 417 | simp only [testBit_mod_two_pow] 418 | have h_m := @Nat.mod_lt m (2^k) (by exact Nat.two_pow_pos k) 419 | have h_n := @Nat.mod_lt n (2^k) (by exact Nat.two_pow_pos k) 420 | by_cases h : i < k 421 | case pos => 422 | simp only [h, decide_True, Bool.true_and] 423 | rw [@testBit_bitwise or (by simp only [Bool.or_self]) (m % 2^k) (n % 2^k) i] 424 | rw [@testBit_bitwise or (by simp only [Bool.or_self]) m n i] 425 | simp only [testBit_mod_two_pow, h, decide_True, Bool.true_and] 426 | case neg => 427 | simp only [h, decide_False, Bool.false_and, Bool.false_eq] 428 | have h_k_i : 2^k <= 2^i := by 429 | apply Nat.pow_le_pow_of_le (by omega) (by omega) 430 | generalize h_x : (bitwise or (m % 2 ^ k) (n % 2 ^ k)) = x 431 | have h_lt := @bitwise_lt_two_pow (m % 2^k) k (n % 2^k) or h_m h_n 432 | apply testBit_lt_two_pow (by omega) 433 | 434 | theorem Nat.bitwise_or_mod : (m ||| n) % 2^k = m % 2^k ||| n % 2^k := by 435 | simp [HOr.hOr]; apply Nat.bitwise_mod 436 | 437 | theorem Nat.bitwise_dist : f false false = false → 2^k * Nat.bitwise f m n = Nat.bitwise f (2^k * m) (2^k * n) := by 438 | induction k 439 | case zero => simp 440 | case succ k ih => 441 | intro hyp 442 | simp [Nat.pow_succ, show 2^k * 2 = 2 * 2^k by apply Nat.mul_comm, Nat.mul_assoc] 443 | conv => rhs; rw [←Nat.bitwise_dist_lem hyp] 444 | rw [ih hyp] 445 | 446 | theorem Nat.or_mul_dist : 2^k * (x ||| y) = 2^k * x ||| 2^k * y := by 447 | simp [HOr.hOr]; apply Nat.bitwise_dist; simp 448 | 449 | theorem Nat.and_mul_dist : 2^k * (x &&& y) = 2^k * x &&& 2^k * y := by 450 | simp [HAnd.hAnd]; apply Nat.bitwise_dist; simp 451 | 452 | theorem Nat.and_xor_dist : 2^k * (x ^^^ y) = 2^k * x ^^^ 2^k * y := by 453 | simp [HXor.hXor]; apply Nat.bitwise_dist; simp 454 | 455 | theorem Nat.shiftLeft_distribute : ∀n : Nat, (x ||| y) <<< n = (x <<< n) ||| (y <<< n) := by 456 | intro n; simp [Nat.shiftLeft_toExp, Nat.or_mul_dist] 457 | 458 | @[simp] 459 | theorem UInt16.byteCeiling : ∀(i : UInt16), (i >>> 0x8) % 256 = i >>> 0x8 := by 460 | intro i; lower_to_nat i; rename_i val lt₁ 461 | 462 | suffices (val >>> 0x8) % size % 256 = val >>> 0x8 % size by assumption 463 | rw [Nat.shiftRight_toDiv] 464 | have lt₂ : val / 2^8 < 256 := by 465 | apply (Nat.div_lt_iff_lt_mul (show 0 < 2^8 by decide)).mpr 466 | exact lt₁ 467 | have lt₃ : val / 2^8 < size := by 468 | apply Nat.lt_trans lt₂; decide 469 | simp [Nat.mod_eq_of_lt lt₂, Nat.mod_eq_of_lt lt₃] 470 | 471 | theorem Nat.splitBytes : ∀n: Nat, n = n >>> 0x8 <<< 0x8 ||| n % 256 := by 472 | intro n 473 | suffices 256 * (n / 256) ||| n % 256 = 256 * (n / 256) + n % 256 by 474 | simp [Nat.shiftLeft_toExp, Nat.shiftRight_toDiv] 475 | rw [this, Nat.add_comm, Nat.mod_add_div] 476 | apply Nat.bitwise_sum (n:=8) 477 | · rw [Nat.mul_comm]; apply Nat.mul_mod_left 478 | · apply Nat.mod_lt; trivial 479 | 480 | theorem UInt16.shiftUnshift : ∀(i : UInt16), i = (i >>> 0x8 % 256) <<< 0x8 ||| i % 256 := by 481 | intro i; lower_to_nat i; rename_i val lt₁ 482 | unfold lor; unfold Fin.lor 483 | simp [ 484 | show ∀x y, Nat.lor x y = x ||| y by intro x y; rfl, 485 | show ∀x y, Nat.shiftRight x y = x >>> y by intro x y; rfl, 486 | show ∀x y, Nat.shiftLeft x y = x <<< y by intro x y; rfl, 487 | show ∀x y, Nat.mod x y = x % y by intro x y; rfl, 488 | ] 489 | rw [ 490 | show 256 = 2^8 by decide, 491 | show size = 2^16 by decide, 492 | Nat.shiftLeft_mod (show 8 + 8 ≥ 16 by decide), 493 | Nat.shiftLeft_mod (show 16 + 8 ≥ 16 by decide), 494 | ←Nat.mod_pow_lt_outer (show 2 > 1 by decide) (show 16 ≥ 8 by decide), 495 | ←Nat.bitwise_or_mod, 496 | ←Nat.splitBytes, 497 | Nat.mod_mod 498 | ] 499 | apply Eq.symm 500 | apply Nat.mod_eq_of_lt 501 | assumption 502 | 503 | theorem UInt32.shiftUnshift : ∀(i : UInt32), 504 | i = (i >>> 0x18 % 256) <<< 0x18 ||| 505 | (i >>> 0x10 % 256) <<< 0x10 ||| 506 | (i >>> 0x08 % 256) <<< 0x08 ||| 507 | i % 256 := by 508 | intro i; 509 | lower_to_nat i; rename_i val lt₁ 510 | unfold lor 511 | unfold Fin.lor; simp 512 | simp [ 513 | show ∀x y, Nat.lor x y = x ||| y by intro x y; rfl, 514 | show ∀x y, Nat.shiftRight x y = x >>> y by intro x y; rfl, 515 | show ∀x y, Nat.shiftLeft x y = x <<< y by intro x y; rfl, 516 | show ∀x y, Nat.mod x y = x % y by intro x y; rfl, 517 | ] 518 | rw [show 256 = 2^8 by decide] 519 | rw [show size = 2^32 by decide] 520 | repeat rw [Nat.mod_pow_lt_inner] 521 | all_goals try decide 522 | suffices 523 | val = (val >>> 24 % 2^8) <<< 24 ||| 524 | (val >>> 16 % 2^8) <<< 16 ||| 525 | (val >>> 8 % 2^8) <<< 8 ||| 526 | val % 2^8 527 | by 528 | have eq : val % 2^32 = val % 2^32 := by rfl 529 | conv at eq => rhs; rw [this] 530 | conv at eq => lhs; rw [Nat.mod_eq_of_lt lt₁] 531 | repeat rw [Nat.bitwise_or_mod] 532 | rw [←Nat.mod_mod, ←Nat.mod_mod, ←Nat.mod_mod] at eq 533 | repeat rw [Nat.bitwise_or_mod] at eq 534 | repeat rw [Nat.mod_mod] at eq 535 | repeat rw [Nat.mod_mod] 536 | exact eq 537 | have : val >>> 24 % 2 ^ 8 = val >>> 24 := by 538 | apply Nat.mod_eq_of_lt 539 | apply Nat.lt_of_lt_of_le (m:= size >>> 24) 540 | · repeat rw [Nat.shiftRight_toDiv] 541 | apply Nat.div_lt_of_lt_mul 542 | apply Nat.lt_of_lt_of_le (m:= size) 543 | · assumption 544 | · rw [Nat.mul_comm, Nat.div_mul_cancel] <;> simp_arith 545 | · simp_arith 546 | rw [ 547 | this, 548 | Nat.shiftRight_add val 16 8, 549 | Nat.shiftLeft_add _ 8 16, 550 | ←Nat.shiftLeft_distribute, 551 | ←Nat.splitBytes, 552 | Nat.shiftRight_add val 8 8, 553 | Nat.shiftLeft_add _ 8 8, 554 | ←Nat.shiftLeft_distribute, 555 | ←Nat.splitBytes, 556 | ←Nat.splitBytes 557 | ] 558 | 559 | theorem UInt64.shiftUnshift : ∀(i : UInt64), 560 | i = (i >>> 0x38 % 256) <<< 0x38 ||| 561 | (i >>> 0x30 % 256) <<< 0x30 ||| 562 | (i >>> 0x28 % 256) <<< 0x28 ||| 563 | (i >>> 0x20 % 256) <<< 0x20 ||| 564 | (i >>> 0x18 % 256) <<< 0x18 ||| 565 | (i >>> 0x10 % 256) <<< 0x10 ||| 566 | (i >>> 0x08 % 256) <<< 0x08 ||| 567 | i % 256 := sorry 568 | 569 | theorem UInt16.ByteArray_roundtrip : 570 | ∀ i : UInt16, ∀l, i.getBytesBEfrom.getUInt16BEfrom 0 l = i := by 571 | intro i l 572 | 573 | simp [ 574 | UInt16.getBytesBEfrom, 575 | ByteArray.getUInt16BEfrom, 576 | ByteArray.get, 577 | Array.get, 578 | List.get, 579 | UInt16_to_UInt8_round, 580 | UInt16_to_UInt8_round, 581 | UInt16.nullShift, 582 | ] 583 | 584 | apply Eq.symm 585 | apply UInt16.shiftUnshift i 586 | -------------------------------------------------------------------------------- /ELFSage/Util/Cli.lean: -------------------------------------------------------------------------------- 1 | import Cli 2 | 3 | instance : Cli.ParseableType System.FilePath where 4 | name := "FilePath" 5 | parse? s := ↑s 6 | -------------------------------------------------------------------------------- /ELFSage/Util/Flags.lean: -------------------------------------------------------------------------------- 1 | namespace Flags 2 | 3 | /-- 4 | Get the list of indices that match the set bits in a bitvector of length `numBits`. 5 | The bitvector is represented as a Nat. The indices are zero-indexed. 6 | -/ 7 | def getFlagBits (flags: Nat) (numBits: Nat) : List Nat := 8 | (List.range numBits).filter (λ n => flags.testBit n) 9 | 10 | end Flags 11 | -------------------------------------------------------------------------------- /ELFSage/Util/Hex.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Util.String 2 | 3 | namespace Hex 4 | 5 | def toHex (n: Nat) := String.toUpper $ String.mk $ Nat.toDigits 16 n 6 | 7 | def toHexMinLength (n: Nat) (minChars: Nat) := (toHex n).leftpad minChars '0' 8 | 9 | end Hex 10 | -------------------------------------------------------------------------------- /ELFSage/Util/IO.lean: -------------------------------------------------------------------------------- 1 | def dumpBytesAsHex (bytes : ByteArray) : IO Unit := do 2 | let mut idx := 0 3 | for byte in bytes do 4 | if idx % 8 == 0 then do IO.print " " 5 | if idx % 16 == 0 then do IO.print "\n" 6 | IO.print $ (Nat.toDigits 16 (byte.toNat)) 7 | |> (λl ↦ if l.length == 1 then '0' :: l else l) 8 | |> List.asString 9 | IO.print " " 10 | idx ← pure $ idx + 1 11 | -------------------------------------------------------------------------------- /ELFSage/Util/List.lean: -------------------------------------------------------------------------------- 1 | namespace List 2 | 3 | -- homegrown 4 | 5 | /-- 6 | Wrapper for Array.insertionSort. 7 | -/ 8 | def insertionSort (a : List α) (lt : α → α → Bool) : List α := 9 | (a.toArray.insertionSort lt).toList 10 | 11 | end List 12 | -------------------------------------------------------------------------------- /ELFSage/Util/String.lean: -------------------------------------------------------------------------------- 1 | import ELFSage.Util.List 2 | 3 | namespace String 4 | 5 | -- from Mathlib 6 | 7 | /-- Pad `s : String` with repeated occurrences of `c : Char` until it's of length `n`. 8 | If `s` is initially larger than `n`, just return `s`. -/ 9 | def leftpad (n : Nat) (c : Char) (s : String) : String := 10 | ⟨List.leftpad n c s.data⟩ 11 | 12 | end String 13 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 The Charles Stark Draper Laboratory, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENCE_USG: -------------------------------------------------------------------------------- 1 | Other licenses notwithstanding, this software is provided to the U.S. Government with Unlimited Rights as defined in 2 | DFARS 252.227-7013, Rights in Technical Data – Other Than Commercial Products and Commercial Services. 3 | -------------------------------------------------------------------------------- /Main.lean: -------------------------------------------------------------------------------- 1 | import «ELFSage» 2 | import Cli 3 | 4 | open Cli 5 | 6 | def getHelp (p : Cli.Parsed): IO UInt32 := do 7 | p.printHelp 8 | return 0 9 | 10 | def hexDumpCmd : Cmd := `[Cli| 11 | hexdump VIA runHexDumpCmd; ["0.0.0"] 12 | "hexdump, but in Lean!" 13 | 14 | ARGS: 15 | targetBinary : System.FilePath; "The ELF binary to be analyzed" 16 | ] 17 | 18 | def validateCmd: Cmd := `[Cli| 19 | validate VIA runValidateCmd; ["0.0.0"] 20 | "Check an ELF binary for structural problems" 21 | 22 | ARGS: 23 | targetBinary : System.FilePath; "The ELF binary to be analyzed" 24 | ] 25 | 26 | /- Some major missing features: 27 | 28 | * DWARF debug information, CTF info 29 | * string-dump/hex-dump don't take section names yet 30 | 31 | -/ 32 | 33 | /-- an incomplete readelf clone interface -/ 34 | def readCmd : Cmd := `[Cli| 35 | read VIA runReadCmd; ["0.0.0"] 36 | "Display information about the contents of ELF format files" 37 | 38 | FLAGS: 39 | a, all; "Equivalent to: --file-header -l -S -s -r -d -V -A" 40 | "file-header"; "Display the ELF file header" 41 | l, "program-headers"; "Display the program headers" 42 | segments; "An alias for --program-headers" 43 | S, "section-headers"; "Display the sections' header" 44 | sections; "An alias for --section-headers" 45 | g, "section-groups"; "Display the section groups" 46 | t, "section-details"; "Display the section details" 47 | e, "headers"; "Equivalent to: --file-header -l -S" 48 | s, "syms"; "Display the symbol table" 49 | symbols; "An alias for --syms" 50 | "dyn-syms"; "Display the dynamic symbol table" 51 | "lto-syms"; "Display LTO symbol tables" 52 | "sym-base"; "--sym-base=[0|8|10|16]. Force base for symbol sizes." ++ 53 | "The options are mixed (the default), octal, decimal, hexadecimal." 54 | C, "demangle"; "--demangle [STYLE]. Decode mangled/processed symbol names" 55 | n, "notes"; "Display the core notes (if present)" 56 | r, "relocs"; "Display the relocations (if present)" 57 | u, "unwind"; "Display the unwind info (if present)" 58 | d, "dynamic"; "Display the dynamic section (if present)" 59 | V, "version-info"; "Display the version sections (if present)" 60 | A, "arch-specific"; "Display architecture specific information (if any)" 61 | c, "archive-index"; "Display the symbol/file index in an archive" 62 | D, "use-dynamic"; "Use the dynamic section info when displaying symbols" 63 | L, "lint"; "Display warning messages for possible problems" 64 | x, "hex-dump" : String; "hex-dump=. " ++ 65 | "Dump the contents of section as bytes" 66 | p, "string-dump" : Nat; "--string-dump=. " ++ 67 | "Dump the contents of section as strings" 68 | "sym-dump" : Nat; "sym-dump=. " ++ 69 | "Dump the bytes designated by symbol " 70 | R, "relocated-dump"; "--relocated-dump=" ++ 71 | "Dump the relocated contents of section " 72 | z, "decompress"; "Decompress section before dumping it" 73 | 74 | ARGS: 75 | targetBinary : System.FilePath; "elf-file" 76 | ] 77 | 78 | def addSpaceCmd : Cli.Cmd := `[Cli| 79 | addSpace VIA runAddSpaceCmd; ["0.0.0"] 80 | "Add new program header table entries" 81 | 82 | ARGS: 83 | targetBinary : System.FilePath; "The ELF binary to be analyzed" 84 | outPath : System.FilePath; "The path for the resulting modified binary" 85 | count : Nat; "The number of entries to add" 86 | ] 87 | 88 | def noopCmd : Cli.Cmd := `[Cli| 89 | noop VIA runNoopCmd; ["0.0.0"] 90 | "Add new program header table entries" 91 | 92 | ARGS: 93 | targetBinary : System.FilePath; "The ELF binary to be analyzed" 94 | outPath : System.FilePath; "The path for the resulting modified binary" 95 | ] 96 | 97 | def patchCmd : Cmd := `[Cli| 98 | patch VIA getHelp; ["0.0.0"] 99 | "Apply some transformation to an ELF file" 100 | 101 | SUBCOMMANDS: 102 | noopCmd; 103 | addSpaceCmd 104 | ] 105 | 106 | def mainCmd : Cmd := `[Cli| 107 | elfSage VIA getHelp; ["0.0.0"] 108 | "An ELF validator" 109 | 110 | SUBCOMMANDS: 111 | hexDumpCmd; 112 | readCmd; 113 | patchCmd; 114 | validateCmd 115 | ] 116 | 117 | def main (args: List String): IO UInt32 := 118 | mainCmd.validate args 119 | -------------------------------------------------------------------------------- /Test.lean: -------------------------------------------------------------------------------- 1 | import «ELFSage».Test.TestRunner 2 | 3 | def main (_: List String): IO UInt32 := 4 | let testDirRelative: System.FilePath := System.mkFilePath [ "ELFSage", "Test" ] 5 | -- TODO: pass the absolute path instead of the relative path 6 | -- let rootDir ← IO.currentDir 7 | -- let testDir: System.FilePath := rootDir.join testDirRelative 8 | runTests testDirRelative 9 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": "1.1.0", 2 | "packagesDir": ".lake/packages", 3 | "packages": 4 | [{"url": "https://github.com/leanprover/lean4-cli.git", 5 | "type": "git", 6 | "subDir": null, 7 | "scope": "", 8 | "rev": "2cf1030dc2ae6b3632c84a09350b675ef3e347d0", 9 | "name": "Cli", 10 | "manifestFile": "lake-manifest.json", 11 | "inputRev": "main", 12 | "inherited": false, 13 | "configFile": "lakefile.toml"}], 14 | "name": "ELFSage", 15 | "lakeDir": ".lake"} 16 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | package «ELFSage» where 5 | -- add package configuration options here 6 | 7 | require Cli from git 8 | "https://github.com/leanprover/lean4-cli.git"@"main" 9 | 10 | lean_lib «ELFSage» where 11 | -- add library configuration options here 12 | 13 | @[default_target] 14 | lean_exe «elfsage» where 15 | root := `Main 16 | 17 | lean_exe test where 18 | root := `Test 19 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:nightly-2024-10-07 --------------------------------------------------------------------------------