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 1/9] 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. *) From 7bef25f19fd536b365664e277d75e25cea9b57f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Thu, 30 Jan 2025 12:39:39 +0100 Subject: [PATCH 2/9] Switch to cachet --- bin/dune | 2 +- bin/main.ml | 27 ++++---- lib/dune | 2 +- lib/elf.ml | 148 ++++++++++++++++++++---------------------- lib/solo5_elftool.ml | 8 +-- lib/solo5_elftool.mli | 4 +- 6 files changed, 90 insertions(+), 101 deletions(-) diff --git a/bin/dune b/bin/dune index 462cfa5..e7420d7 100644 --- a/bin/dune +++ b/bin/dune @@ -1,4 +1,4 @@ (executable (public_name osolo5-elftool) (name main) - (libraries solo5-elftool owee cstruct cmdliner)) + (libraries solo5-elftool owee cachet cmdliner)) diff --git a/bin/main.ml b/bin/main.ml index 49adfaa..b63dfe2 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -1,19 +1,18 @@ -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 () +let map_binary file = + let fd = Unix.openfile file [Unix.O_RDONLY; Unix.O_CLOEXEC] 0 in + let stat = Unix.fstat fd in + let map () ~pos len = + let len = Int.min (stat.Unix.st_size - pos) len in + let pos = Int64.of_int pos in + let barr = + Unix.map_file fd ~pos Bigarray.char Bigarray.c_layout false [| len |] + in + Bigarray.array1_of_genarray barr in - loop (); - close_in_noerr ic; - Buffer.contents res + Cachet.make ~map () let query_manifest file = - read_binary file + map_binary file |> Solo5_elftool.query_manifest |> Result.fold ~ok:(fun mft -> @@ -22,7 +21,7 @@ let query_manifest file = Fmt.epr "%s\n" e) let query_abi file = - read_binary file + map_binary file |> Solo5_elftool.query_abi |> Result.fold ~ok:(fun abi -> Fmt.pr "%a\n" Solo5_elftool.pp_abi abi) diff --git a/lib/dune b/lib/dune index e0a60de..46c94c2 100644 --- a/lib/dune +++ b/lib/dune @@ -1,4 +1,4 @@ (library (public_name solo5-elftool) (name solo5_elftool) - (libraries owee cstruct fmt)) + (libraries owee cstruct cachet fmt)) diff --git a/lib/elf.ml b/lib/elf.ml index ee89d16..4826ad2 100644 --- a/lib/elf.ml +++ b/lib/elf.ml @@ -1,3 +1,5 @@ +module Bstr = Cachet.Bstr + exception Elf_error (* only the bits we care about *) @@ -22,60 +24,61 @@ let typ_mft1 = 0x3154464d let typ_abi1 = 0x31494241 let get_uint16 = function - | `LE -> String.get_uint16_le - | `BE -> String.get_uint16_be + | `LE -> Cachet.get_uint16_le + | `BE -> Cachet.get_uint16_be let get_uint32 en s off = let get = match en with - | `LE -> String.get_int32_le - | `BE -> String.get_int32_be + | `LE -> Cachet.get_int32_le + | `BE -> Cachet.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 + | `LE -> Cachet.get_int64_le + | `BE -> Cachet.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) +let c_string seq maxlen = + let res = Buffer.create maxlen in + let rec scan i = function + | Seq.Nil -> raise Elf_error + | Seq.Cons (s, seq) -> + match String.index_opt s '\000' with + | None -> + let i = i + String.length s in + if i >= maxlen then + raise Elf_error; + Buffer.add_string res s; + scan i (seq ()) + | Some l -> + let i = i + l in + if i >= maxlen then + raise Elf_error; + Buffer.add_substring res s 0 l; + Buffer.contents res in - String.sub s off (scan_c_string 0) + scan 0 (seq ()) -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 read_magic c = + if not (Cachet.get_uint8 c 0 = 0x7f && + String.equal (Cachet.get_string c ~len:3 1) "ELF") + then raise Elf_error 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 +let read_identification c = + let elf_class = Cachet.get_uint8 c 4 in + let elf_data = Cachet.get_uint8 c 5 in + let _elf_version = Cachet.get_uint8 c 6 in + let _elf_osabi = Cachet.get_uint8 c 7 in + let _elf_abiversion = Cachet.get_uint8 c 8 in + for i = 9 to 15 do + if Cachet.get_uint8 c i <> 0 then raise Elf_error done; (* we only support ELFCLASS64 *) @@ -87,39 +90,33 @@ let read_identification s off = | 2 -> `BE | _ -> raise Elf_error in - endianness, off+12 + endianness -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 +let read_header en c = + let e_shoff = get_uint32 en c 0x28 in + let e_shentsize = get_uint16 en c 0x3a in + let e_shnum = get_uint16 en c 0x3c in + let e_shstrndx = get_uint16 en c 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 read_section en c 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 + let sh_name_off = get_uint32 en c off in + let sh_offset = get_uint64 en c (off + 24) in + let sh_size = get_uint64 en c (off + 32) in { sh_name_off; sh_offset; sh_size; sh_name = "" } -let read_section_name shstrndx s section = +let read_section_name shstrndx c 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) + c_string (Cachet.get_seq c 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 read_sections en c hdr = + let sections = Array.init hdr.e_shnum (read_section en c hdr) in let shstrndx = sections.(hdr.e_shstrndx) in Array.map - (fun section -> { section with sh_name = read_section_name shstrndx s section }) + (fun section -> { section with sh_name = read_section_name shstrndx c section }) sections let find_section sections name = @@ -127,39 +124,32 @@ let find_section sections name = (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 +let desc en c section ~expected_owner ~expected_type = + let off = section.sh_offset in + if section.sh_size < 12 then raise Elf_error; + let namesz = get_uint32 en c off + and descsz = get_uint32 en c (off + 4) + and typ = get_uint32 en c (off + 8) in if typ <> expected_type || String.length expected_owner + 1 <> namesz || not (String.equal (expected_owner ^ "\000") - (String.sub section_body 12 namesz)) + (Cachet.get_string c (off+12) ~len:namesz)) then None else - let off = 12 + namesz in + let off = off + 12 + namesz in (* padding *) let off = off + ((4 - (off land 3)) land 3) in - Some (String.sub section_body off descsz) + Some (Cachet.get_string c off ~len: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 +let find c section_name typ = + let () = read_magic c in + let en = read_identification c in + let hdr = read_header en c in + let sections = read_sections en c 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 + desc en c section ~expected_owner:note_name ~expected_type:typ diff --git a/lib/solo5_elftool.ml b/lib/solo5_elftool.ml index 8bf26e7..ed957f4 100644 --- a/lib/solo5_elftool.ml +++ b/lib/solo5_elftool.ml @@ -160,14 +160,14 @@ let parse_abi buf = (* XXX: should we check version = 1l ? *) Ok { target; version } -let query_manifest buf = - match Elf.find buf Elf.section_manifest Elf.typ_mft1 with +let query_manifest c = + match Elf.find c 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 = - match Elf.find buf Elf.section_abi Elf.typ_abi1 with +let query_abi c = + match Elf.find c 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 f66c60a..7246244 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 : string -> (mft, [> `Msg of string ]) result +val query_manifest : 'fd Cachet.t -> (mft, [> `Msg of string ]) result (** [query_manifest buf] is the solo5 manifest of [buf], or an error message. *) -val query_abi : string -> (abi, [> `Msg of string ]) result +val query_abi : 'fd Cachet.t -> (abi, [> `Msg of string ]) result (** [query_abi buf] is the solo5 abi of [buf], or an error message. *) From bf003875b97f72960470072b28fbfbbd5bfdaf0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Thu, 30 Jan 2025 12:43:16 +0100 Subject: [PATCH 3/9] osolo5-elftool: close file --- bin/main.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/main.ml b/bin/main.ml index b63dfe2..149aac1 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -9,6 +9,7 @@ let map_binary file = in Bigarray.array1_of_genarray barr in + at_exit (fun () -> Unix.close fd); Cachet.make ~map () let query_manifest file = From 04cc6350b1e759b2aa07ac82b3941f812c5326a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Thu, 30 Jan 2025 13:22:56 +0100 Subject: [PATCH 4/9] Get rid of cstruct And update opam file --- lib/dune | 2 +- lib/solo5_elftool.ml | 59 +++++++++++++++++++++----------------------- solo5-elftool.opam | 3 +-- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/lib/dune b/lib/dune index 46c94c2..df9a12b 100644 --- a/lib/dune +++ b/lib/dune @@ -1,4 +1,4 @@ (library (public_name solo5-elftool) (name solo5_elftool) - (libraries owee cstruct cachet fmt)) + (libraries owee cachet fmt)) diff --git a/lib/solo5_elftool.ml b/lib/solo5_elftool.ml index ed957f4..84fced3 100644 --- a/lib/solo5_elftool.ml +++ b/lib/solo5_elftool.ml @@ -70,43 +70,41 @@ let guard m b = if not b then Error (`Msg m) else Ok () let sizeof_mft_entry = 104 let mft_max_entries = 64l -let parse_mft_entry buf = +let parse_mft_entry s = (* invariant: Cstruct.length buf = sizeof_mft_entry *) - let name_raw = Cstruct.sub buf 0 68 in - let typ = Cstruct.LE.get_uint32 buf 68 in - let u = Cstruct.sub buf 72 16 in - let b = Cstruct.sub buf 88 8 in - let attached = Cstruct.get_uint8 buf 96 <> 0 in + let name_raw = String.sub s 0 68 in + let typ = String.get_int32_le s 68 in + let u = String.sub s 72 16 in + let b = String.sub s 88 8 in + let attached = String.get_uint8 s 96 <> 0 in let* name = - Cstruct.cut ~sep:(Cstruct.create 1) name_raw - |> Option.map (fun (name, _) -> Cstruct.to_string name) + String.index_opt name_raw '\000' + |> Option.map (fun idx -> String.sub name_raw 0 idx) |> Option.to_result ~none:(`Msg "unterminated device name") in - let* () = guard "non-zero mft_entry.u" (Cstruct.for_all ((=) '\000') u) in - let* () = guard "non-zero mft_entry.b" (Cstruct.for_all ((=) '\000') b) in + let* () = guard "non-zero mft_entry.u" (String.for_all ((=) '\000') u) in + let* () = guard "non-zero mft_entry.b" (String.for_all ((=) '\000') b) in let* () = guard "non-zero mft_entry.attached" (not attached) in let* typ = mft_type_of_int typ in match typ with | Reserved_first -> - let* () = guard "non-zero RESERVED_FIRST" (Cstruct.for_all ((=) '\000') name_raw) in + let* () = guard "non-zero RESERVED_FIRST" (String.for_all ((=) '\000') name_raw) in Ok `Reserved_first | Dev_block_basic -> Ok (`Dev_block_basic name) | Dev_net_basic -> Ok (`Dev_net_basic name) -let parse_mft buf = - let buf = Cstruct.of_string buf in +let parse_mft s = let* () = guard "manifest too small" - (Cstruct.length buf >= 4 + 8 + sizeof_mft_entry) + (String.length s >= 4 + 8 + sizeof_mft_entry) in (* Solo5 defines a struct mft1_note consisting of the ELF note header * followed by a struct mft for reading and writing the ELF note. The note * header is 20 bytes long, so to get 8-byte alignment the note header is * padded with 4 bytes. See {[solo5/mft_abi.h]}. *) - let buf = Cstruct.shift buf 4 in - let version = Cstruct.LE.get_uint32 buf 0 - and entries = Cstruct.LE.get_uint32 buf 4 + let version = String.get_int32_le s 4 + and entries = String.get_int32_le s 8 in let* () = guard "unsupported manifest version" (version = 1l) in let* () = guard "zero manifest entries" (Int32.unsigned_compare entries 0l > 0) in @@ -118,25 +116,25 @@ let parse_mft buf = * mft_max_entries, so this is safely equivalent to: * (Option.get (Int32.unsigned_to_int entries) *) let entries = Int32.to_int entries in - let buf = Cstruct.shift buf 8 in + let off = 12 in let* () = guard "unexpected note size" - (Cstruct.length buf = entries * sizeof_mft_entry) + (String.length s = entries * sizeof_mft_entry + 12) in let* () = - match parse_mft_entry (Cstruct.sub buf 0 sizeof_mft_entry) with + match parse_mft_entry (String.sub s off sizeof_mft_entry) with | Ok `Reserved_first -> Ok () | _ -> Error (`Msg "expected RESERVED_FIRST") in - let buf = Cstruct.shift buf sizeof_mft_entry in + let off = off + sizeof_mft_entry in let entries = Array.init (entries - 1) - (fun i -> Cstruct.sub buf (i * sizeof_mft_entry) sizeof_mft_entry) + (fun i -> String.sub s (off + i * sizeof_mft_entry) sizeof_mft_entry) in let* entries = Array.fold_left - (fun r buf -> + (fun r s -> let* acc = r in - let* mft_entry = parse_mft_entry buf in + let* mft_entry = parse_mft_entry s in match mft_entry with | `Dev_block_basic name -> Ok (Dev_block_basic name :: acc) | `Dev_net_basic name -> Ok (Dev_net_basic name :: acc) @@ -147,13 +145,12 @@ let parse_mft buf = in Ok { version = Int32.to_int version; entries } -let parse_abi buf = - let buf = Cstruct.of_string buf in - let* () = guard "abi manifest size mismatch" (Cstruct.length buf = 4 * 4) in - let target = Cstruct.LE.get_uint32 buf 0 in - let version = Cstruct.LE.get_uint32 buf 4 in - let reserved0 = Cstruct.LE.get_uint32 buf 8 in - let reserved1 = Cstruct.LE.get_uint32 buf 12 in +let parse_abi s = + let* () = guard "abi manifest size mismatch" (String.length s = 4 * 4) in + let target = String.get_int32_le s 0 in + let version = String.get_int32_le s 4 in + let reserved0 = String.get_int32_le s 8 in + let reserved1 = String.get_int32_le s 12 in let* target = abi_target_of_int target in let* () = guard "non-zero reserved0" (reserved0 = 0l) in let* () = guard "non-zero reserved1" (reserved1 = 0l) in diff --git a/solo5-elftool.opam b/solo5-elftool.opam index 1580103..a36e000 100644 --- a/solo5-elftool.opam +++ b/solo5-elftool.opam @@ -15,8 +15,7 @@ build: [ depends: [ "ocaml" {>= "4.08.0"} "dune" {>= "2.9"} - "owee" {>= "0.4"} - "cstruct" {>= "6.0.0"} + "cachet" "fmt" {>= "0.8.7"} "cmdliner" {>= "1.1.0"} ] From 742799de5b979738c2f9368b7e4585f754b7e902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Thu, 30 Jan 2025 14:44:07 +0100 Subject: [PATCH 5/9] Catch exceptions in interface --- lib/solo5_elftool.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/solo5_elftool.ml b/lib/solo5_elftool.ml index 84fced3..b0be89c 100644 --- a/lib/solo5_elftool.ml +++ b/lib/solo5_elftool.ml @@ -161,10 +161,12 @@ let query_manifest c = match Elf.find c 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")*) + | exception Elf.Elf_error | exception Cachet.Out_of_bounds _ -> + Error (`Msg "error during ELF parsing") let query_abi c = match Elf.find c 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")*) + | exception Elf.Elf_error | exception Cachet.Out_of_bounds _ -> + Error (`Msg "error during ELF parsing") From 649872703fe11f4452dd4db2f1317abfbdd59fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Thu, 30 Jan 2025 15:54:02 +0100 Subject: [PATCH 6/9] Update dune files with dependency changes --- bin/dune | 2 +- lib/dune | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/dune b/bin/dune index e7420d7..37aa158 100644 --- a/bin/dune +++ b/bin/dune @@ -1,4 +1,4 @@ (executable (public_name osolo5-elftool) (name main) - (libraries solo5-elftool owee cachet cmdliner)) + (libraries solo5-elftool unix cachet cmdliner)) diff --git a/lib/dune b/lib/dune index df9a12b..f924065 100644 --- a/lib/dune +++ b/lib/dune @@ -1,4 +1,4 @@ (library (public_name solo5-elftool) (name solo5_elftool) - (libraries owee cachet fmt)) + (libraries cachet fmt)) From 8feb54d5f806d1287eeed656874c9a9f9079af92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Fri, 31 Jan 2025 17:28:01 +0100 Subject: [PATCH 7/9] Mark as unavailable on 32 bit platforms --- solo5-elftool.opam | 1 + 1 file changed, 1 insertion(+) diff --git a/solo5-elftool.opam b/solo5-elftool.opam index a36e000..74b52c8 100644 --- a/solo5-elftool.opam +++ b/solo5-elftool.opam @@ -20,6 +20,7 @@ depends: [ "cmdliner" {>= "1.1.0"} ] +available: arch != "arm32" & arch != "x86_32" conflicts: [ "result" {< "1.5"} ] From e98cc6d669a519d76d5f4e7ce416d3d503609c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Fri, 31 Jan 2025 17:45:34 +0100 Subject: [PATCH 8/9] Add cram test The test uses osolo5-elftool on the test_hello application from the solo5 tests to query their abi and manifest. The manifest is empty and boring. --- dune-project | 1 + test/dune | 7 +++++++ test/osolo5-elftool.t | 20 ++++++++++++++++++++ test/test_hello.hvt | Bin 0 -> 30000 bytes test/test_hello.muen | Bin 0 -> 34096 bytes test/test_hello.spt | Bin 0 -> 21528 bytes test/test_hello.virtio | Bin 0 -> 62768 bytes test/test_hello.xen | Bin 0 -> 66944 bytes 8 files changed, 28 insertions(+) create mode 100644 test/dune create mode 100644 test/osolo5-elftool.t create mode 100755 test/test_hello.hvt create mode 100755 test/test_hello.muen create mode 100755 test/test_hello.spt create mode 100755 test/test_hello.virtio create mode 100755 test/test_hello.xen diff --git a/dune-project b/dune-project index a5c8776..6a643c7 100644 --- a/dune-project +++ b/dune-project @@ -1,2 +1,3 @@ (lang dune 2.9) (name solo5-elftool) +(cram enable) diff --git a/test/dune b/test/dune new file mode 100644 index 0000000..1497d7d --- /dev/null +++ b/test/dune @@ -0,0 +1,7 @@ +(cram + (deps + test_hello.hvt + test_hello.muen + test_hello.spt + test_hello.virtio + test_hello.xen)) diff --git a/test/osolo5-elftool.t b/test/osolo5-elftool.t new file mode 100644 index 0000000..a8ade20 --- /dev/null +++ b/test/osolo5-elftool.t @@ -0,0 +1,20 @@ +Execute osolo5-elftool on hvt binary + + $ osolo5-elftool query-abi test_hello.hvt + { "type": "solo5.abi", "target": "hvt", "version": 2 + } + $ osolo5-elftool query-manifest test_hello.hvt + { "type": "solo5.manifest", "version": 1, "devices": [ ] + } + $ osolo5-elftool query-abi test_hello.muen + { "type": "solo5.abi", "target": "muen", "version": 3 + } + $ osolo5-elftool query-abi test_hello.spt + { "type": "solo5.abi", "target": "spt", "version": 2 + } + $ osolo5-elftool query-abi test_hello.virtio + { "type": "solo5.abi", "target": "virtio", "version": 1 + } + $ osolo5-elftool query-abi test_hello.xen + { "type": "solo5.abi", "target": "xen", "version": 1 + } diff --git a/test/test_hello.hvt b/test/test_hello.hvt new file mode 100755 index 0000000000000000000000000000000000000000..d8aeb2d3c9a0e52e025631aade159baa70ae8b45 GIT binary patch literal 30000 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 II-idIUqM8={{R30 literal 0 HcmV?d00001 diff --git a/test/test_hello.muen b/test/test_hello.muen new file mode 100755 index 0000000000000000000000000000000000000000..b3cb4872066ab15df0687feaab7d72a0ed039704 GIT binary patch literal 34096 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 literal 0 HcmV?d00001 diff --git a/test/test_hello.spt b/test/test_hello.spt new file mode 100755 index 0000000000000000000000000000000000000000..f38b99815a4794bf5ced0c215b14b281ae1ea090 GIT binary patch literal 21528 zcmeHvdw5h;mhY*gLW&UTlu9%{IkY7vJVFXVlaNSNQU$x@1S1JTOPm;zLZTtTqzdR6 z)Yzwr=1g(Ab8U6F-E(K0>Arq;xBcCop1z1&8!H6zK-561jW3M&Iz>=a6e22he`}vp zNhM9UeeXAa+&fN_5*!@lHam`1nH3an zp-7mF@2NtzkdC&Mx(xJ`iUG)_K0L|NW;D5s466Sbx}%E?#AKPoBi$xZS5hCh$-peL z4KiqEK4v%3k)B0q$&PO+O}fl)K^xzlIzFyv5~0dum@G~B>SVv!O*+!&DYwz_RH4z4 zER%LjmfZFx4b``sblXk3Wa%{63CVIi9j4QGOgbS+Z-Td*Ws=TpP81Usn6SWv1tu&o zVSxz?OjuyT0uvUPu)u@`CM@v(i3R4hw6+AcG_?lyUKSSY)ao+lG(WC!9horo% z2d**7N{QpmgjA+IFsxTnzg9L0ZT-GHK~Q`3D?qodK%5@t;Dh9qO#K4*f6#jqiG-|vs6U4ieMRWg|DEdUDg9RlY$n0}nZw@J|G|Js z1dF2XI`dot()C-%3KOTh1-d=iX_d4G0PB9kOf0gHUzk1%Y(o+M?Ux4PP!FIJb`iS#Mk`#T`*@@Dt zW;3=b?A$LZmVh3tj0|2b956zQ`M5G13DyqhaSx6h8H^7uCm6^(gOH z2v>Y;A1m(iszyAXXZ z#sQTDUnCJ+LRJhP!?hS;GPREepd=h(cpKDJ5@}*&FDl_9Vt5zI9&dKuTBdn0)?q1l zOOMx@SI9I*LzjY!Xmnz@ihvhb(?fP|^yhrMa7lW&dhf9b9v#r`B_)=Wjf9-Q3=$bgmzZ zTAgq%rfp2fTC+8esX=G1sjs;A{=_nE+VzG85Kij&m5(g)qEctuAnBUB8?qq>`pBYp zm^8XoYb>=ut#C7<&I_l192k}A`||8&46XQ-xUCu;(ATdVu*mB1l=1nM{T4pPQ=5;+ z=W;B>DeRW!MTlgJ;mI(OtDoio(Ga{jzU6!(0cDMR#^8aG2e~>lxmN%e(~Q}@9JL;= zGjHu~RD}2}5Ro0=qSjCezHMUU06J)X7=lv_Hv+?$y@xrj!5@ArM!er767eVSwXQSi z0JF&f;^zp)^MV;UaMqw>)NwKMz+mc&O!?R%KDv(_@R69b4_wX%*Lc;#$~#ghJA5Rx ziUO$vxa5#qP?86(W=hNwzlUUuzcnT=KfrsbRh8nPQ>kpvTc z`xvr8uFr)v(&`jS8eIe(u+6eG41Ll&_~=AV+~e?S&k+K zN9`%@eZbQ30^3Q9jzI}l%)^i!ojZRlh8wbE}L z`hb^?p<}$X4ZVuer@oKq^=nJ9j?vI%amgSV!iDzn!~lYoc~yN(uyQ$uURG`CKm0z>SfgsL-q(1oLX%r2}L?gXn!2g=zPs#lJ|67nq3_3Is{aQlU-z zsbAGG90swL?uG>VqEYurl~|>1r_?#hVrk zvsDbgiT5U`trf%Hz?+nJPVLEp;Q?t1X5qV0jNAuYwNDv5f98Ny^z~5=%vE2E3J-lC zAORAl82)#IU!xfQ1!@sQ@geX%bHE}-wu0@MF1Hw&$txXV_!7JZ!O+AXbfLnBr}jWk zHJzomJH)3a-R8qOTkWaxsIe7s;!M*6In84DWwcBOitoWN&7SpQ_?Ohv&!;B-IC@wV zSvER}%LzbF(-gNm)JZIQg}CbyEU9ATUP|G;F_@0$f-b1Yv|u{C?&we^@=x(6!O1jv z9YsRuv;3o2yCt>nI@&^@Y72_6h2$-cdBmkL>QmlHLzL!Wm4-H0Mcd^3cbVGkEcB`e zm|E+^j-u8%hxQm@a&PP(-i*plTckfYRjW)yJ`%&}AXHx<4$}We$3|;{tIyq zkGQMGYC#s;=Y^NdVNtLLJP$jBjl>UXc#6BlZSRAVM;XFUnw>UJez#xqIxT+fDkR_o zLs^D@kRzM9!mpk*Vumk+kX8Iv2(bg&q031fuEK~y&s_)Yd18tGE7BJ?w$eYtwD&9M zOtCl3=RFQj)jbY5n#QoCNsws>(`TKbB)4DktNY`tDG`#EFe6jD#jt~BhtK1s$~pMU zJR}Qb@qKXVGs9?p$tn zxDhHG>Lt-4kFi0}TGAsF4{2V}5AHo_@q1_iF*IHdjc-HtxZM`-fwL$5j{^057?lTV zCLD$b>KaHm(}h620T3QQhfoLUsxNRC&qyk%jTBk(*r0(M`Oo|odf98dM&OyL2EgKuG zT4y1x;*JzOdHg{^9)3yJV~enl3mqpT6q4B`+K2K;FY;&=79=mQmEcK{e?-WTh1zL< zLQcY>6`1NOOm(#_J_x(*p?ApfU!%rHxR0zuh$$&oj@obYRLg3gr;2qAFJ6K5NOryG z?}{&G`8|I1lQ;u+PW_V$P$#qX3OPC{eho0W|Hk)V7P)Cz6pqK2E{`q#OE@3r^yLLO zdBwf<_%D*0(nfT=rsA$i@nf7ZA8I=oFX3xqya*ZGG(ZaUEiJKV5+4TTNw6s^5D7YH!pY?;oA16h$qDqMNslj+hohH61qn2rl=hL;Dn#{8MNx z_~2kBLAVfKaY0LET+mVl7g7ttH(pTq4d6i;`-v|uq(m`j$U)mO!h8(;v7X|v zj)VCtb1}8R$i=}N`Ti~niXx`ILoiG!NM+RAGQf0>!|e`7cj$*GjZnO@*;6~gB$Pqt zuWtR3Q$`+*{|X4B>9~`r!?I?*jHzET9CfF5y0^RN5G)sWg+9kY4P1w*;|f>5k!QW) zF00+U-D)E&R(v@0aq0~O^ap>*}W&m^s$| zG$7djAncD34ZV>ZFXpTR@&)Ak!$UivtzR98zYK-D(Zk1iBy{;u{-}NEaIf^>bHWn5 z7}=vJGdLvf$`p59nm>T?x?*GJSKM`(xXaGZutkT1R}5Wm%EJgVX&Wf+k|!mK4u(W* zzlL0~{66!ZO5C-N>f|4;BN&%yQ>N^*g!@CELOFB3)L}li;?UKGzTo*}X3#;~%hN1> zI5|H5?!&_OOvsBO(_`yArXv_nSznB;>sf<0&MIe> zVXOF9Oi4)MBl{)~;Fy+b0Y5E7pCKwSCAHj6H-}@v?~?i4;-fw8a6){v%N>3(SjLaD z?xNw)4g8#sxS5QNX=xVFX91047Y%!G{`Su3!AyF@M`JXp-f&OoFa~POH%vG@bb8bu zqZvnv1)&TT(~X02;nn1z3m5XD%!NY# z*$M7n)p`k`tH8>r`z~tN37;T?s2S}CyGRh$09-MuUYzm?qDRZ|UByYNcv0p;p#@nFH_5E2~hmU_W2CAgElrhXErE9(~)Fz$RZVFrG)BX%iiz$CWT{+5Z{W ziR12jfx0(|im^a{9p)anLEbv;PS zX9qXy4(E21GWuyIWp#&hC(3qQ92*pHvbi7-Bflc&cxXQ{RgbQ@6ETVj3Oi$HGp*T~ z$JEb~Yu8>%<LFWStL?Hzg@mScG0PFA)Uy_+vlkI^OiGgELy6gBhAUg`n# zfAMm6Ua&3H(oi2H`YYLP)B=t9S)84ucYu&-Tb)<#rE6IIJ~ZQQq*eU~@IjNGTGlob z*>W@=KC`gozED#jh}-{_xMunaMy(R}E{3r@KqNVg?VdZAzq%=*<~6i7I~nK#ZEdY> z90eO%**Tv*)4H%^bA4M2hgk{vBKj&}yBC%;2G)k|Mu+U2%<-C9*0u5`mIAEo$Pl5c z;y;GoMjjCN{vC33H!iUgVt6YJX9YI4@X`-D774)?GV?XE*65sLj%&|UZq<+JOs~-?-fkC|7h1rE;hNVvFn5$T1K zfpN(@4`*C4a*QM?`>SvxK=($*Is>i5$YvOO2-%+29WCgz?xA@g>AN`7(6x}Bt*b@@ z(zAuo&!5Bm(&;2yuO|g|5aC55g!db`vp8Xu(a0h~F>*JA8E?Ia*a_me3=q+mnAUh( zFXJR)qy#;D=-DGYrh><{!~^FQQedi1hl98cApQ0aM$Va9PVsxEz%^q&FT82C)a3#9 zu{oC;{TEYV8DJKq^3f2)`j^`5#MLGC9a_$N?{cPZQGqZfLT%woH$W51FjECRh+6=p zM`gdwuji#IRE zM#RlDYbV|<;zWrRviP&mtcM72we%mLypdl2iFTBY&Lq=v*O<1MI8?= z6M{p#VUvWR$N2pL)0XBkbrNLZ_zss(OC2rG3xB!agZEebxG%&RR)9Aa-)RlH)uSV0(WV`M{(`qSct8%lEo)nIY;g8Ha6aU&gkFw4H{zwI zGV)O{+oRqo3{3(bdhGzC%Ia{+ooC8?E!u2tpRJyuRm8dppKVNAz{M2T^vc*=Yw}Gv zUiFRG8;EF)&NuGZ0h zNBb&gZ2Rn(Hl^0{JZR#BbT_A*%rinLn$bA}Z6p(ZhzL4udIAVA!3@@fl}*yie9Novla)txVEZh^<$ z&>#%dM=z~mAYPR9qtI`C2RhFhqci!kUp+*xuE%PLU4PkS&{F@2snSZX`1Ja!XDF^^ z^+YA^))rt>$r_HZ$ca!sX(;EPV8tieu4WBeR^x>gu<%Y!KWC*)j=%T-HlA9xpS)S^ zGn|>{hy6CrbB)@^-@uKF$G@oG=6TTX9G!b%vj1EC?)g8}FJ-&p7NV;phAHpOIE@$N z^r$(Xq844{gcqDbP0e|Y+ErF;$QR(mxx#e@8R73KV5beN>r}SLX(1c7)q9O~C5AjU z5Q@|5-%0A!e+6l(LDqHBL?!)+ro!~62oT{u(2NXtkc+phvu7}HR|Jre_@}Uv;Jc4Ut4)mwgrWL>KOd%;M=J& zLEiN;zm_pv3|{pbj{S&-=XJNK+#+%qeii(&{uBlUg|~=IO?Smc*K53I{$kCYSVBBF z)x&8AL#gR?(kRz{yNc}LDQnJMy!#`iA_dN}7=(mtW93kj*!&j#@ zcWPEQxOc!Pu%n17gQD}RHz>vQ$%pTK2c7i_qG339*s9<5&uW%of278YFDP{R^zmKc4IG+@EGX{L&qJ(_lGVw$1ke`I0I+1KMbjuv z;596oMYk__zrHUUZ)pJwogDR#$eAsRS>%l%-cc-KQQuMQ!RZ(gx{hf9eGR>0zmu}} zhKry&78nN=Ws6YgzkL(VqrHVW(sGJ8eBolF`zq@GF}i7d(WMSV)_ORSmxhE+yAR6O)vV#wdDf@jk(#rtyFy7qgeRM~zmAyu= zXHD4e_`Ufe*O`vRLU7t>{-L|kj?$Zi&>8)|7-S7Z7BR{0=Va*}MJ$BPN6sSj8S5ln zY#R5Wwvwz+F77x#g2-7!>o;jVpv4m0&GX8{*MtQoEHGh#2@Cw^wg8<3gZNY~sc}`) z&;QNgV#_Je=RfFie;Xgt16yvvQ|=$pgAR1`Nj|t$q~CFIy>8tYXeqD82K!qcKBf}M zQ-14;ni8oxSl@83)X>`27TOqWYHg7k1HnK8?&#?kbVBnV6N&w=8lT3LdJ>je)|&k_ zDR^GgXS|k3ya65q_)wW?wyA!hPZDo@AHf&Bo=Ch0p8pv>)}w#jI0)|vjQc|P7lM&( z2wt0Ft5c?|RD7ySDbH4;URPJQHA|2r6!?;ACCM!7(8v;MsgUx3yA}V8Qp%S=0>Zj^ z=&qY5)otY^hhzzx3JZz~Tyq!9y}N$xy0r`EH@aMc>4fD?EsaercehKM0&VT&eb=>T zCHqVV6-op+YkPa3jl1Vv!u8U+`le=b&>0&78|s={8}1D>3O6hi&?;yUmbV6_Kuc@r z?)4G?t!>{E7A_EOx=~mksNY!E9;|N*N~Ka>sHr7*+VSflWeDVNxo8YgC87 z{|V+@*O)Ig)HiccroKjR{o2;HVBTnFxqNHIvbu6lwZB4G?yE6tD}2?y6}S7mbxZs! zs%wOG_08=8!L?waP*5-re_Pau5zlLA-LRpxW!{E$L1^CAAeEI#g~AOt3GEw$^VXWS zLC=C4g}~gK>w}2Z^)%KZ1A&H4y~VuQ5+Ein{3S~ZhJ-&}YL{*%cCH%=c}6gYP|1xV zZK&T^B3;+q9Fi)_rTWIkwm^G({o3Y036FasF!3b`U4gbufkw$KTR^!5 zZ}G<9k^ck(TY?52VT?f?;RlYbjrGC$Z^x6QzZQ>Xmk*a~h8f08Bd?X3s*2XSU}^)!(hIt6Ac$tMOO*R@8aQtEn3RXz)%>yi?FzxN#bLQJJJUvQc?pgiHDODCBJlxXcb2)78B6W_e?ZxZ{*%C)L3mYm2dz<0;bVI~kw^}XU@?5GfF;*VlK(P3 z0$>Me(Si;?N-MYL{xIEVmo#g+J#R;b$6mN?lGk3OWG=OLTGAg(%d!`FyX}RZ9(!K- zA-h!mf<3qVMZ2T?CA-ZxMHmE|JP6qOQX=s&3Fkba_n+|e5YOfI4omuS%U|HhrlkJ} z{o5@*h%dMAw0P~0TYOW}W0n+HIbe4Xt364m*TlSwp#POKC^9F7zCXxA-y!tjmmvHh+m`~?2N-U! zM_^Tey$sm3DX>PsJ^}2f<|GmA7QnW?kx1apCVvRF1F$CnyEz5+IPn9F{Ei;N`yF6| zfZfj_2CNS-8CREhWyBxCdlRtLfNe5i>%d#z&(Z%4 z^>g1&dw~$Dfj~MGqW@)Fo<^u2UYoYmuo1<)3N%z}h>1ivo?70=)b_d+i=7n71@g(qTapRwfIz1GF&jPj@uq!Yo zL8e#(k8SB5inXQojp;MtA`<*M7mOV6YugfIE}n3ICLXms|iqT zcid^}oeTI=fRk?Y(75IR_9S2d0(k9(X&z(lsgHgl^)>p|ntcx#U3_ef=&Qo(Qe67* zhtEG?GGLDpgwOv{ntvHwY5G0p^dtKiIrT~4KMVYa2_Lr5D5sfua|z{5qFj?@&x7Mg znD1Q7w*z_1Rxt%>bqRnKDL{kg*WhV;?>~+w*{TW$iW=}t&Nq#<5wPC?_5|rZcC2+N zV_imLrQbL`0{p`F6N!5WA7kB-<|n_dB)`5J!I; zUOcC9*-H#NOB)~>!NH?!8H|(*Bk_hnI_DzLwu0{dk@&2fI?1P{@31Nv>9Z|a-Q|bM zU-0x`we(D(aWmi_!mmT{&YnL6s|D;RV03<>hhQ54dwCoz4A=l*+&>UIFJQ58w5tJoHU&m{Hv_gCFcc#X$qobX8^BZ7k)0;}wI(LvJp~wzFLfP> z0oI70aiy*!F9X&ISn4{W6FzFK__5tz&4q>5RW3j zo&xM=1cP5GX=(q0Y541(tf{B={`#0ese5TfMTwN>4XkadZ;@Q{3+5LT&UG!wH>=~i z3(@VGdn0;zO`2ag-&N>x6$(N@`}+1^Td;nuP=Iqsplzd2KpSPDprth!C}^ikh5~H= zn$}^jI0jh1wrNZo_;sx~=dW#V6bg)8v``ROU$?HUenUVgpxrW98^zl8cA9Pm7o0o2cL2qSmVb( zUjFSsOSRvissEr!PwPnPBc(P_{~LT#>9^l#SZMna1F;HzpZbiq|9W&1eR>kfZCbd> z0Pq}_>_a)8z6CH&pBz|~L7#2Xr;ze0;if(zG>GSje3M?d%fMP~6eHiJb|dY-MU8kC jjzEk`Qod=1k=~^I)N(}j2$mqMF!fhbU{p=gCBgq2VPExL literal 0 HcmV?d00001 diff --git a/test/test_hello.virtio b/test/test_hello.virtio new file mode 100755 index 0000000000000000000000000000000000000000..5ed1483e698689e2d1a5c6d482454148b186a76c GIT binary patch literal 62768 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

nmn`j(=AlK?szP1 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 m<^S(UCeG4+Y{~qm^rI*9PshHz` Date: Fri, 31 Jan 2025 18:00:50 +0100 Subject: [PATCH 9/9] Add cram dependency --- test/dune | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dune b/test/dune index 1497d7d..bf7b9ea 100644 --- a/test/dune +++ b/test/dune @@ -1,5 +1,6 @@ (cram (deps + %{bin:osolo5-elftool} test_hello.hvt test_hello.muen test_hello.spt