Compare commits

...

No commits in common. "main" and "gh-pages" have entirely different histories.

58 changed files with 2118 additions and 557 deletions

View file

@ -1,25 +0,0 @@
## v0.4.0 (2025-02-04)
* Reimplement the necessary ELF parsing and drop the owee dependency. This makes it possible to use this library in Mirage.
* **BREAKING**: Switch to cachet instead of requiring the whole binary in memory.
## v0.3.1 (2022-03-16)
* Update to cmdliner 1.1.0
## v0.3.0 (2022-01-28)
* Exceptions from Owee are caught in `query_abi` and `query_manifest`
## v0.2.0 (2021-12-16)
* Rename binary from solo5-elftool to osolo5-elftool so it can co-exist with
the C binary
* Implement `query_abi` and `osolo5-elftool query-abi`
* Return a result error instead of an assertion exception on unknown manifest
entry types
* Remove noop tests
## v0.1.0 (2021-12-15)
* Initial public release

View file

@ -1,23 +0,0 @@
Copyright (c) 2021, Reynir Björnsson
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0
README Normal file
View file

View file

@ -1,8 +0,0 @@
## Solo5-elftool - OCaml Solo5 elftool for querying solo5 manifests
Solo5 embeds a manifest of which devices a unikernel expects.
Solo5-elftool can be used to read and inspect this manifest.
One advantage over calling out to Solo5's `solo5-elftool` is that a user of the library can read the manifest from an ELF executable held in memory; no need to write the executable to disk first!

View file

@ -1,4 +0,0 @@
(executable
(public_name osolo5-elftool)
(name main)
(libraries solo5-elftool unix cachet cmdliner))

View file

@ -1,59 +0,0 @@
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
at_exit (fun () -> Unix.close fd);
Cachet.make ~map ()
let query_manifest file =
map_binary file
|> Solo5_elftool.query_manifest
|> 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 =
map_binary file
|> Solo5_elftool.query_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
Cmdliner.Arg.(required & pos 0 (some file) None &
info ~doc ~docv:"EXECUTABLE" [])
let query_manifest_cmd =
let doc = "query solo5 manifest" in
Cmdliner.Cmd.v
(Cmdliner.Cmd.info ~doc "query-manifest")
Cmdliner.Term.(const query_manifest $ file)
let query_abi_cmd =
let doc = "query solo5 abi" in
Cmdliner.Cmd.v
(Cmdliner.Cmd.info ~doc "query-abi")
Cmdliner.Term.(const query_abi $ file)
let default_cmd =
let open Cmdliner.Term in
ret (const (fun man_format -> `Help (man_format, None)) $ Cmdliner.Arg.man_format)
let () =
let cmd =
Cmdliner.Cmd.group ~default:default_cmd
(Cmdliner.Cmd.info "osolo5-elftool")
[query_manifest_cmd; query_abi_cmd]
in
exit (Cmdliner.Cmd.eval cmd)

19
doc/index.html Normal file
View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>index</title>
<link rel="stylesheet" href="./odoc.support/odoc.css"/>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
</head>
<body>
<main class="content">
<div class="by-name">
<h2>OCaml package documentation</h2>
<ol>
<li><a href="solo5-elftool/index.html">solo5-elftool</a> <span class="version">0.4.0</span></li>
</ol>
</div>
</main>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

1
doc/odoc.support/katex.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1
doc/odoc.support/katex.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1393
doc/odoc.support/odoc.css Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,66 @@
/* The browsers interpretation of the CORS origin policy prevents to run
webworkers from javascript files fetched from the file:// protocol. This hack
is to workaround this restriction. */
function createWebWorker() {
var searchs = search_urls.map((search_url) => {
let parts = document.location.href.split("/");
parts[parts.length - 1] = search_url;
return '"' + parts.join("/") + '"';
});
blobContents = ["importScripts(" + searchs.join(",") + ");"];
var blob = new Blob(blobContents, { type: "application/javascript" });
var blobUrl = URL.createObjectURL(blob);
var worker = new Worker(blobUrl);
URL.revokeObjectURL(blobUrl);
return worker;
}
var worker;
var waiting = 0;
function wait() {
waiting = waiting + 1;
document.querySelector(".search-snake").classList.add("search-busy");
}
function stop_waiting() {
if (waiting > 0) waiting = waiting - 1;
else waiting = 0;
if (waiting == 0) {
document.querySelector(".search-snake").classList.remove("search-busy");
}
}
document.querySelector(".search-bar").addEventListener("focus", (ev) => {
if (typeof worker == "undefined") {
worker = createWebWorker();
worker.onmessage = (e) => {
stop_waiting();
let results = e.data;
let search_results = document.querySelector(".search-result");
search_results.innerHTML = "";
let f = (entry) => {
let search_result = document.createElement("a");
search_result.classList.add("search-entry");
search_result.href = base_url + entry.url;
search_result.innerHTML = entry.html;
search_results.appendChild(search_result);
};
results.forEach(f);
let search_request = document.querySelector(".search-bar").value;
if (results.length == 0 && search_request != "") {
let no_result = document.createElement("div");
no_result.classList.add("search-no-result");
no_result.innerText = "No result...";
search_results.appendChild(no_result);
}
};
}
});
document.querySelector(".search-bar").addEventListener("input", (ev) => {
wait();
worker.postMessage(ev.target.value);
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>index (solo5-elftool.index)</title><meta charset="utf-8"/><link rel="stylesheet" href="../odoc.support/odoc.css"/><meta name="generator" content="odoc 2.4.4"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><script src="../odoc.support/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body class="odoc"><nav class="odoc-nav"><a href="../index.html">Up</a> solo5-elftool</nav><header class="odoc-preamble"><h1 id="solo5-elftool-index"><a href="#solo5-elftool-index" class="anchor"></a>solo5-elftool index</h1></header><nav class="odoc-toc"><ul><li><a href="#library-solo5-elftool">Library solo5-elftool</a></li></ul></nav><div class="odoc-content"><h2 id="library-solo5-elftool"><a href="#library-solo5-elftool" class="anchor"></a>Library solo5-elftool</h2><p>The entry point of this library is the module: <a href="Solo5_elftool/index.html"><code>Solo5_elftool</code></a>.</p></div></body></html>

View file

@ -1,3 +0,0 @@
(lang dune 2.9)
(name solo5-elftool)
(cram enable)

View file

@ -1,4 +0,0 @@
(library
(public_name solo5-elftool)
(name solo5_elftool)
(libraries cachet fmt))

View file

@ -1,155 +0,0 @@
module Bstr = Cachet.Bstr
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 -> Cachet.get_uint16_le
| `BE -> Cachet.get_uint16_be
let get_uint32 en s off =
let get = match en with
| `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 -> 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 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
scan 0 (seq ())
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 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 *)
if elf_class <> elfclass64 then
raise Elf_error;
let endianness =
match elf_data with
| 1 -> `LE
| 2 -> `BE
| _ -> raise Elf_error
in
endianness
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 c hdr i =
let off = hdr.e_shoff + i * hdr.e_shentsize 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 c section =
let off = shstrndx.sh_offset + section.sh_name_off in
c_string (Cachet.get_seq c off) (shstrndx.sh_size - section.sh_name_off)
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 c section })
sections
let find_section sections name =
Array.find_opt
(fun section -> String.equal section.sh_name name)
sections
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")
(Cachet.get_string c (off+12) ~len:namesz))
then
None
else
let off = off + 12 + namesz in
(* padding *)
let off = off + ((4 - (off land 3)) land 3) in
Some (Cachet.get_string c off ~len:descsz)
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 ->
desc en c section ~expected_owner:note_name ~expected_type:typ

View file

@ -1,172 +0,0 @@
type mft_type =
| Dev_block_basic
| Dev_net_basic
| Reserved_first
type mft_entry =
| Dev_block_basic of string
| Dev_net_basic of string
type mft = {
version : int;
entries : mft_entry list;
}
type abi_target =
| Hvt
| Spt
| Virtio
| Muen
| Genode
| Xen
type abi = {
target : abi_target;
version : int32;
}
let mft_type_of_int : int32 -> (mft_type, _) result = function
| 1l -> Ok Dev_block_basic
| 2l -> Ok Dev_net_basic
| 1073741824l -> Ok Reserved_first
| v -> Error (`Msg ("unknown manifest entry type: " ^ Int32.to_string v))
let abi_target_of_int : int32 -> (abi_target, _) result = function
| 1l -> Ok Hvt
| 2l -> Ok Spt
| 3l -> Ok Virtio
| 4l -> Ok Muen
| 5l -> Ok Genode
| 6l -> Ok Xen
| v -> Error (`Msg ("unknown abi target: " ^ Int32.to_string v))
let pp_mft_entry ppf = function
| Dev_block_basic name ->
Fmt.pf ppf {|{@[<1>@ "name": %S,@ "type": "BLOCK_BASIC"@]@ }|} name
| Dev_net_basic name ->
Fmt.pf ppf {|{@[<1>@ "name": %S,@ "type": "NET_BASIC"@]@ }|} name
let pp_mft ppf { version; entries } =
Fmt.pf ppf
{|{@[<1>@ "type": "solo5.manifest",@ "version": %d,@ "devices": [@[<1>@ %a@]@ ]@]@ }|}
version Fmt.(list ~sep:(append (any ",") sp) pp_mft_entry) entries
let pp_abi_target ppf = function
| Hvt -> Format.fprintf ppf "hvt"
| Spt -> Format.fprintf ppf "spt"
| Virtio -> Format.fprintf ppf "virtio"
| Muen -> Format.fprintf ppf "muen"
| Genode -> Format.fprintf ppf "genode"
| Xen -> Format.fprintf ppf "xen"
let pp_abi ppf { version; target } =
Fmt.pf ppf
{|{@[<1>@ "type": "solo5.abi",@ "target": "%a",@ "version": %lu@ @]@ }|}
pp_abi_target target version
let ( let* ) = Result.bind
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 s =
(* invariant: Cstruct.length buf = sizeof_mft_entry *)
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 =
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" (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" (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 s =
let* () = guard "manifest too small"
(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 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
(* this implicitly checks [Int32.to_int entries > 0] *)
let* () = guard "too many manifest entries"
(Int32.unsigned_compare entries mft_max_entries <= 0)
in
(* We have checked that entries interpreted unsigned is between 0 and
* mft_max_entries, so this is safely equivalent to:
* (Option.get (Int32.unsigned_to_int entries) *)
let entries = Int32.to_int entries in
let off = 12 in
let* () = guard "unexpected note size"
(String.length s = entries * sizeof_mft_entry + 12)
in
let* () =
match parse_mft_entry (String.sub s off sizeof_mft_entry) with
| Ok `Reserved_first -> Ok ()
| _ -> Error (`Msg "expected RESERVED_FIRST")
in
let off = off + sizeof_mft_entry in
let entries =
Array.init (entries - 1)
(fun i -> String.sub s (off + i * sizeof_mft_entry) sizeof_mft_entry)
in
let* entries =
Array.fold_left
(fun r s ->
let* acc = r 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)
| `Reserved_first -> Error (`Msg "found RESERVED_FIRST not as first entry"))
(Ok [])
entries
|> Result.map List.rev
in
Ok { version = Int32.to_int version; entries }
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
(* XXX: should we check version = 1l ? *)
Ok { target; version }
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 | 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 | exception Cachet.Out_of_bounds _ ->
Error (`Msg "error during ELF parsing")

View file

@ -1,43 +0,0 @@
(** An entry in the manifest representing a device. *)
type mft_entry =
| Dev_block_basic of string
| Dev_net_basic of string
(** The Solo5 manifest *)
type mft = {
version : int;
(** [version] is at the moment always 1. *)
entries : mft_entry list;
(** [entries] in the manifest. *)
}
(** The known solo5 targets *)
type abi_target =
| Hvt
| Spt
| Virtio
| Muen
| Genode
| Xen
(** abi taget and abi version *)
type abi = {
target : abi_target; (** abi target *)
version : int32; (** abi version *)
}
val pp_mft_entry : Format.formatter -> mft_entry -> unit
val pp_mft : Format.formatter -> mft -> unit
(** Pretty-prints the manifest as JSON in a similar style as the Solo5 command
* line tool {[solo5-elftool query-manifest]}. *)
val pp_abi_target : Format.formatter -> abi_target -> unit
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 : 'fd Cachet.t -> (mft, [> `Msg of string ]) result
(** [query_manifest cachet] is the solo5 manifest of [cachet], or an error message. *)
val query_abi : 'fd Cachet.t -> (abi, [> `Msg of string ]) result
(** [query_abi cachet] is the solo5 abi of [cachet], or an error message. *)

View file

@ -1,33 +0,0 @@
opam-version: "2.0"
homepage: "https://git.robur.coop/robur/ocaml-solo5-elftool"
dev-repo: "git+https://git.robur.coop/robur/ocaml-solo5-elftool.git"
bug-reports: "https://github.com/robur-coop/ocaml-solo5-elftool/issues"
doc: "https://robur-coop.github.io/ocaml-solo5-elftool/doc"
maintainer: [ "team@robur.coop" ]
authors: [ "Reynir Björnsson <reynir@reynir.dk>" ]
license: "BSD-2-Clause"
build: [
["dune" "subst"] {dev}
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.08.0"}
"dune" {>= "2.9"}
"cachet"
"fmt" {>= "0.8.7"}
"cmdliner" {>= "1.1.0"}
]
available: arch != "arm32" & arch != "x86_32"
conflicts: [
"result" {< "1.5"}
]
synopsis: "OCaml Solo5 elftool for querying solo5 manifests"
description: """
OCaml Solo5 elftool is a library and executable for reading solo5 device
manifests from solo5 ELF executables.
"""
x-maintenance-intent: [ "(latest)" ]

View file

@ -1,8 +0,0 @@
(cram
(deps
%{bin:osolo5-elftool}
test_hello.hvt
test_hello.muen
test_hello.spt
test_hello.virtio
test_hello.xen))

View file

@ -1,20 +0,0 @@
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
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.