From 0a04cc0bcc3fe067b92feb40a7467a733df686b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Fri, 24 Jan 2025 20:34:20 +0100 Subject: [PATCH] Drop owee dependency and work on strings --- bin/main.ml | 30 ++++++-- lib/elf.ml | 165 ++++++++++++++++++++++++++++++++++++++++++ lib/solo5_elftool.ml | 54 ++------------ lib/solo5_elftool.mli | 4 +- 4 files changed, 200 insertions(+), 53 deletions(-) create mode 100644 lib/elf.ml diff --git a/bin/main.ml b/bin/main.ml index c30478a..49adfaa 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -1,13 +1,33 @@ +let read_binary file = + let ic = open_in_bin file in + let res = Buffer.create 16384 in + let buf = Bytes.create 16384 in + let rec loop () = + let len = input ic buf 0 16384 in + if len > 0 then + let () = Buffer.add_subbytes res buf 0 len in + loop () + in + loop (); + close_in_noerr ic; + Buffer.contents res + let query_manifest file = - Owee_buf.map_binary file + read_binary file |> Solo5_elftool.query_manifest - |> Result.iter (fun mft -> - Fmt.pr "%a\n" Solo5_elftool.pp_mft mft) + |> Result.fold + ~ok:(fun mft -> + Fmt.pr "%a\n" Solo5_elftool.pp_mft mft) + ~error:(fun (`Msg e) -> + Fmt.epr "%s\n" e) let query_abi file = - Owee_buf.map_binary file + read_binary file |> Solo5_elftool.query_abi - |> Result.iter (Fmt.pr "%a\n" Solo5_elftool.pp_abi) + |> Result.fold + ~ok:(fun abi -> Fmt.pr "%a\n" Solo5_elftool.pp_abi abi) + ~error:(fun (`Msg e) -> + Fmt.epr "%s\n" e) let file = let doc = "Solo5 executable" in diff --git a/lib/elf.ml b/lib/elf.ml new file mode 100644 index 0000000..ee89d16 --- /dev/null +++ b/lib/elf.ml @@ -0,0 +1,165 @@ +exception Elf_error + +(* only the bits we care about *) +type header = { + e_shoff : int; + e_shentsize : int; + e_shnum : int; + e_shstrndx : int; +} + +type section = { + sh_offset : int; + sh_size : int; + sh_name_off : int; + sh_name : string; +} + +let section_manifest = ".note.solo5.manifest" +let section_abi = ".note.solo5.abi" +let note_name = "Solo5" +let typ_mft1 = 0x3154464d +let typ_abi1 = 0x31494241 + +let get_uint16 = function + | `LE -> String.get_uint16_le + | `BE -> String.get_uint16_be + +let get_uint32 en s off = + let get = match en with + | `LE -> String.get_int32_le + | `BE -> String.get_int32_be + in + Int32.to_int (get s off) land 0xFFFF_FFFF + +let get_uint64 en s off = + let get = match en with + | `LE -> String.get_int64_le + | `BE -> String.get_int64_be + in + match Int64.unsigned_to_int (get s off) with + | None -> raise Elf_error + | Some n -> n + +let c_string s off maxlen = + let rec scan_c_string i = + if String.length s < off + i || i = maxlen then + raise Elf_error + else if s.[i+off] = '\000' then + i + else + scan_c_string (succ i) + in + String.sub s off (scan_c_string 0) + +let read_magic s off = + if String.length s < off + 4 then + raise Elf_error; + let valid = + String.get_uint8 s off = 0x7f && + s.[off+1] = 'E' && s.[off+2] = 'L' && s.[off+3] = 'F' + in + if not valid then + raise Elf_error; + off+4 + +let elfclass64 = 2 + +let read_identification s off = + if String.length s < off + 12 then + raise Elf_error; + let elf_class = String.get_uint8 s off in + let elf_data = String.get_uint8 s (off+1) in + let _elf_version = String.get_uint8 s (off+2) in + let _elf_osabi = String.get_uint8 s (off+3) in + let _elf_abiversion = String.get_uint8 s (off+4) in + (* Check padding *) + for i = off + 5 to off+11 do + if s.[i] <> '\000' then + raise Elf_error + done; + (* we only support ELFCLASS64 *) + if elf_class <> elfclass64 then + raise Elf_error; + let endianness = + match elf_data with + | 1 -> `LE + | 2 -> `BE + | _ -> raise Elf_error + in + endianness, off+12 + +let read_header en s = + if String.length s < 16 + 48 then + raise Elf_error; + let e_shoff = get_uint32 en s 0x28 in + let e_shentsize = get_uint16 en s 0x3a in + let e_shnum = get_uint16 en s 0x3c in + let e_shstrndx = get_uint16 en s 0x3e in + if Sys.int_size <= 32 then + raise Elf_error; + { e_shoff; e_shentsize; e_shnum; e_shstrndx } + +let read_section en s hdr i = + let off = hdr.e_shoff + i * hdr.e_shentsize in + if String.length s < off + 64 then + raise Elf_error; + let sh_name_off = get_uint32 en s off in + let sh_offset = get_uint64 en s (off + 24) in + let sh_size = get_uint64 en s (off + 32) in + { sh_name_off; sh_offset; sh_size; sh_name = "" } + +let read_section_name shstrndx s section = + let off = shstrndx.sh_offset + section.sh_name_off in + if String.length s < off + 1 then + raise Elf_error; + c_string s off (shstrndx.sh_size - section.sh_name_off) + +let read_sections en s hdr = + let sections = Array.init hdr.e_shnum (read_section en s hdr) in + let shstrndx = sections.(hdr.e_shstrndx) in + Array.map + (fun section -> { section with sh_name = read_section_name shstrndx s section }) + sections + +let find_section sections name = + Array.find_opt + (fun section -> String.equal section.sh_name name) + sections + +let section_body s section = + if section.sh_offset < 0 || String.length s < section.sh_offset + section.sh_size then + raise Elf_error; + String.sub s section.sh_offset section.sh_size + +let desc en section_body ~expected_owner ~expected_type = + if String.length section_body < 12 then + raise Elf_error; + let namesz = get_uint32 en section_body 0 in + let descsz = get_uint32 en section_body 4 + and typ = get_uint32 en section_body 8 in + if String.length section_body < 12 + namesz + descsz then + raise Elf_error; + if typ <> expected_type || + String.length expected_owner + 1 <> namesz || + not (String.equal + (expected_owner ^ "\000") + (String.sub section_body 12 namesz)) + then + None + else + let off = 12 + namesz in + (* padding *) + let off = off + ((4 - (off land 3)) land 3) in + Some (String.sub section_body off descsz) + +let find s section_name typ = + let off = read_magic s 0 in + let en, _off = read_identification s off in + let hdr = read_header en s in + let sections = read_sections en s hdr in + match find_section sections section_name with + | None -> None + | Some section -> + let body = section_body s section in + desc en body ~expected_owner:note_name ~expected_type:typ diff --git a/lib/solo5_elftool.ml b/lib/solo5_elftool.ml index 181579b..8bf26e7 100644 --- a/lib/solo5_elftool.ml +++ b/lib/solo5_elftool.ml @@ -160,52 +160,14 @@ let parse_abi buf = (* XXX: should we check version = 1l ? *) Ok { target; version } -let ( let* ) = Result.bind - -let note_name = "Solo5" -let typ_mft1 = 0x3154464d -let typ_abi1 = 0x31494241 - -let query_manifest_exn buf = - let _header, sections = Owee_elf.read_elf buf in - let* section = - Owee_elf.find_section sections ".note.solo5.manifest" - |> Option.to_result ~none:(`Msg "section .note.solo5.manifest not found") - in - let body = Owee_elf.section_body buf section in - let cursor = Owee_buf.cursor body in - let descsz = - Owee_elf_notes.read_desc_size cursor - ~expected_owner:note_name - ~expected_type:typ_mft1 - in - let desc = Owee_buf.Read.fixed_string cursor descsz in - let* () = guard "extra data" (Owee_buf.at_end cursor) in - parse_mft desc - let query_manifest buf = - try query_manifest_exn buf with - | Out_of_memory -> raise Out_of_memory - | e -> Error (`Msg ("query manifest failure: " ^ Printexc.to_string e)) - -let query_abi_exn buf = - let _header, sections = Owee_elf.read_elf buf in - let* section = - Owee_elf.find_section sections ".note.solo5.abi" - |> Option.to_result ~none:(`Msg "section .note.solo5.abi not found") - in - let body = Owee_elf.section_body buf section in - let cursor = Owee_buf.cursor body in - let descsz = - Owee_elf_notes.read_desc_size cursor - ~expected_owner:note_name - ~expected_type:typ_abi1 - in - let desc = Owee_buf.Read.fixed_string cursor descsz in - let* () = guard "extra data" (Owee_buf.at_end cursor) in - parse_abi desc + match Elf.find buf Elf.section_manifest Elf.typ_mft1 with + | None -> Error (`Msg "manifest not found") + | Some desc -> parse_mft desc + (*| exception Elf.Elf_error -> Error (`Msg "error during ELF parsing")*) let query_abi buf = - try query_abi_exn buf with - | Out_of_memory -> raise Out_of_memory - | e -> Error (`Msg ("query abi failure: " ^ Printexc.to_string e)) + match Elf.find buf Elf.section_abi Elf.typ_abi1 with + | None -> Error (`Msg "manifest not found") + | Some desc -> parse_abi desc + (*| exception Elf.Elf_error -> Error (`Msg "error during ELF parsing")*) diff --git a/lib/solo5_elftool.mli b/lib/solo5_elftool.mli index 289f1a3..f66c60a 100644 --- a/lib/solo5_elftool.mli +++ b/lib/solo5_elftool.mli @@ -36,8 +36,8 @@ val pp_abi : Format.formatter -> abi -> unit (** Pretty-prints the manifest as JSON in a similar style as the Solo5 command * line tool {[solo5-elftool query-abi]}. *) -val query_manifest : Owee_buf.t -> (mft, [> `Msg of string ]) result +val query_manifest : string -> (mft, [> `Msg of string ]) result (** [query_manifest buf] is the solo5 manifest of [buf], or an error message. *) -val query_abi : Owee_buf.t -> (abi, [> `Msg of string ]) result +val query_abi : string -> (abi, [> `Msg of string ]) result (** [query_abi buf] is the solo5 abi of [buf], or an error message. *)