Merge changes Ie43c82bd,I37197456 into main

* changes:
  Introduce VmTerminalApp
  Define VmLauncherService
diff --git a/libs/libvmbase/example/src/layout.rs b/libs/libvmbase/example/src/layout.rs
index 49e4aa7..50ecb7e 100644
--- a/libs/libvmbase/example/src/layout.rs
+++ b/libs/libvmbase/example/src/layout.rs
@@ -17,14 +17,13 @@
 use aarch64_paging::paging::{MemoryRegion, VirtualAddress};
 use core::ops::Range;
 use log::info;
-use vmbase::layout;
+use vmbase::{layout, memory::PAGE_SIZE};
 
 /// The first 1 GiB of memory are used for MMIO.
 pub const DEVICE_REGION: MemoryRegion = MemoryRegion::new(0, 0x40000000);
 
 /// Writable data region for the stack.
 pub fn boot_stack_range() -> Range<VirtualAddress> {
-    const PAGE_SIZE: usize = 4 << 10;
     layout::stack_range(40 * PAGE_SIZE)
 }
 
diff --git a/libs/libvmbase/example/src/main.rs b/libs/libvmbase/example/src/main.rs
index a01f619..4d535cc 100644
--- a/libs/libvmbase/example/src/main.rs
+++ b/libs/libvmbase/example/src/main.rs
@@ -35,7 +35,7 @@
 use libfdt::Fdt;
 use log::{debug, error, info, trace, warn, LevelFilter};
 use vmbase::{
-    bionic, configure_heap,
+    bionic, configure_heap, generate_image_header,
     layout::{
         crosvm::{FDT_MAX_SIZE, MEM_START},
         rodata_range, scratch_range, text_range,
@@ -48,6 +48,7 @@
 static mut ZEROED_DATA: [u32; 10] = [0; 10];
 static mut MUTABLE_DATA: [u32; 4] = [1, 2, 3, 4];
 
+generate_image_header!();
 main!(main);
 configure_heap!(SIZE_64KB);
 
diff --git a/libs/libvmbase/sections.ld b/libs/libvmbase/sections.ld
index 01b7e39..7d464bc 100644
--- a/libs/libvmbase/sections.ld
+++ b/libs/libvmbase/sections.ld
@@ -34,6 +34,8 @@
 	 * as executable-only.
 	 */
 	.text : ALIGN(4096) {
+		KEEP(*(.init.head));
+		*(.init.head)
 		text_begin = .;
 		*(.init.entry)
 		*(.init.*)
diff --git a/libs/libvmbase/src/entry.rs b/libs/libvmbase/src/entry.rs
index ad633ed..99f28fc 100644
--- a/libs/libvmbase/src/entry.rs
+++ b/libs/libvmbase/src/entry.rs
@@ -18,7 +18,7 @@
     bionic, console, heap, hyp,
     layout::{UART_ADDRESSES, UART_PAGE_ADDR},
     logger,
-    memory::{SIZE_16KB, SIZE_4KB},
+    memory::{PAGE_SIZE, SIZE_16KB, SIZE_4KB},
     power::{reboot, shutdown},
     rand,
 };
@@ -129,3 +129,37 @@
         }
     };
 }
+
+/// Prepends a Linux kernel header to the generated binary image.
+///
+/// See https://docs.kernel.org/arch/arm64/booting.html
+/// ```
+#[macro_export]
+macro_rules! generate_image_header {
+    () => {
+        #[cfg(not(target_endian = "little"))]
+        compile_error!("Image header uses wrong endianness: bootloaders expect LE!");
+
+        core::arch::global_asm!(
+            // This section gets linked at the start of the image.
+            ".section .init.head, \"ax\"",
+            // This prevents the macro from being called more than once.
+            ".global image_header",
+            "image_header:",
+            // Linux uses a special NOP to be ELF-compatible; we're not.
+            "nop",                          // code0
+            "b entry",                      // code1
+            ".quad 0",                      // text_offset
+            ".quad bin_end - image_header", // image_size
+            ".quad (1 << 1)",               // flags (PAGE_SIZE=4KiB)
+            ".quad 0",                      // res2
+            ".quad 0",                      // res3
+            ".quad 0",                      // res4
+            ".ascii \"ARM\x64\"",           // magic
+            ".long 0",                      // res5
+        );
+    };
+}
+
+// If this fails, the image header flags are out-of-sync with PAGE_SIZE!
+static_assertions::const_assert_eq!(PAGE_SIZE, SIZE_4KB);
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
index b9b9fbc..c68e8a9 100755
--- a/tests/ferrochrome/ferrochrome.sh
+++ b/tests/ferrochrome/ferrochrome.sh
@@ -168,7 +168,7 @@
 
 adb shell mkdir -p "${fecr_screenshot_dir}"
 while [[ $((EPOCHSECONDS - fecr_start_time)) -lt ${FECR_BOOT_TIMEOUT} ]]; do
-  adb shell screencap -a -p "${fecr_screenshot_dir}/screenshot-${EPOCHSECONDS}.png"
+  adb shell screencap -p "${fecr_screenshot_dir}/screenshot-${EPOCHSECONDS}.png"
   adb shell grep -soF \""${fecr_boot_completed_log}"\" "${log_path}" && exit 0 || true
   sleep 10
 done