diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2255b608..7d328968c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,16 +188,20 @@ jobs: tool: nextest@${{ env.NEXTEST_VERSION }} - uses: Swatinem/rust-cache@v2 - run: cargo clippy --locked --verbose --all-targets --all-features -p litebox_runner_linux_on_windows_userland + - run: cargo clippy --locked --verbose --all-targets --all-features -p litebox_packager - run: cargo build --locked --verbose -p litebox_runner_linux_on_windows_userland + - run: cargo build --locked --verbose -p litebox_packager - run: cargo nextest run --locked --profile ci -p litebox_runner_linux_on_windows_userland + - run: cargo nextest run --locked --profile ci -p litebox_packager - run: cargo nextest run --locked --profile ci -p litebox_shim_linux --no-default-features --features platform_windows_userland - run: | cargo test --locked --verbose --doc -p litebox_runner_linux_on_windows_userland + cargo test --locked --verbose --doc -p litebox_packager # We need to run `cargo test --doc` separately because doc tests # aren't included in nextest at the moment. See relevant discussion at # https://github.com/nextest-rs/nextest/issues/16 - name: Build documentation (fail on warnings) - run: cargo doc --locked --verbose --no-deps --all-features --document-private-items -p litebox_runner_linux_on_windows_userland + run: cargo doc --locked --verbose --no-deps --all-features --document-private-items -p litebox_runner_linux_on_windows_userland -p litebox_packager build_and_test_snp: name: Build and Test SNP diff --git a/Cargo.lock b/Cargo.lock index ca5e7a9a3..4b4a073c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1685,6 +1685,7 @@ dependencies = [ "litebox", "litebox_common_linux", "litebox_platform_multiplex", + "litebox_syscall_rewriter", "once_cell", "ringbuf", "seq-macro", @@ -1723,7 +1724,6 @@ dependencies = [ "clap", "iced-x86", "insta", - "memmap2", "object", "similar", "tempfile", diff --git a/dev_bench/unixbench/prepare_unixbench.py b/dev_bench/unixbench/prepare_unixbench.py index 0d472d505..4eee4e6e1 100644 --- a/dev_bench/unixbench/prepare_unixbench.py +++ b/dev_bench/unixbench/prepare_unixbench.py @@ -61,8 +61,8 @@ def prepare_benchmark( """ Prepare a single benchmark using litebox_packager. - The packager discovers dependencies, rewrites all ELFs, and creates a tar - (including litebox_rtld_audit.so). The rewritten main binary is extracted + The packager discovers dependencies, rewrites all ELFs, and creates a tar. + The rewritten main binary is extracted from the tar and placed alongside it. Returns True on success. diff --git a/dev_tests/src/boilerplate.rs b/dev_tests/src/boilerplate.rs index a32cf70b6..c29e14ebf 100644 --- a/dev_tests/src/boilerplate.rs +++ b/dev_tests/src/boilerplate.rs @@ -133,7 +133,6 @@ const SKIP_FILES: &[&str] = &[ "LICENSE", "litebox/src/sync/mutex.rs", "litebox/src/sync/rwlock.rs", - "litebox_rtld_audit/Makefile", "litebox_runner_linux_on_windows_userland/tests/test-bins/hello_exec_nolibc", "litebox_runner_linux_on_windows_userland/tests/test-bins/hello_thread", "litebox_runner_linux_on_windows_userland/tests/test-bins/hello_thread_static", diff --git a/dev_tests/src/ratchet.rs b/dev_tests/src/ratchet.rs index 276452d4d..8e6d35034 100644 --- a/dev_tests/src/ratchet.rs +++ b/dev_tests/src/ratchet.rs @@ -40,7 +40,6 @@ fn ratchet_globals() -> Result<()> { ("litebox_platform_lvbs/", 23), ("litebox_platform_multiplex/", 1), ("litebox_platform_windows_userland/", 8), - ("litebox_runner_linux_userland/", 1), ("litebox_runner_lvbs/", 5), ("litebox_runner_snp/", 1), ("litebox_shim_linux/", 1), diff --git a/litebox_common_linux/src/loader.rs b/litebox_common_linux/src/loader.rs index 3ae61266e..1f714402e 100644 --- a/litebox_common_linux/src/loader.rs +++ b/litebox_common_linux/src/loader.rs @@ -128,6 +128,8 @@ pub enum ElfParseError { BadTrampoline, #[error("Invalid trampoline version")] BadTrampolineVersion, + #[error("Binary not patched for syscall rewriting")] + UnpatchedBinary, #[error("Unsupported ELF type")] UnsupportedType, #[error("Bad interpreter")] @@ -141,6 +143,7 @@ impl> From> for Errno { | ElfParseError::BadFormat | ElfParseError::BadTrampoline | ElfParseError::BadTrampolineVersion + | ElfParseError::UnpatchedBinary | ElfParseError::BadInterp | ElfParseError::UnsupportedType => Errno::ENOEXEC, ElfParseError::Io(err) => err.into(), @@ -218,6 +221,11 @@ impl ElfParsedFile { }) } + /// Returns `true` if a trampoline was parsed and will be mapped by `load()`. + pub fn has_trampoline(&self) -> bool { + self.trampoline.is_some() + } + /// Parse the LiteBox trampoline data, if any. /// /// The trampoline header is located at the end of the file (last 32/20 bytes). @@ -251,7 +259,8 @@ impl ElfParsedFile { // File must be large enough to contain the header if file_size < header_size as u64 { - return Ok(()); + // Too small for a trampoline header — binary is unpatched. + return Err(ElfParseError::UnpatchedBinary); } // Read the header from the end of the file @@ -267,8 +276,9 @@ impl ElfParsedFile { if &header_buf[0..7] == b"LITEBOX" { return Err(ElfParseError::BadTrampolineVersion); } - // No trampoline found, which is OK (not all binaries are rewritten) - return Ok(()); + // No trampoline found. When using the syscall rewriter backend + // (syscall_entry_point != 0), all binaries must be patched. + return Err(ElfParseError::UnpatchedBinary); } let (file_offset, vaddr, trampoline_size) = if cfg!(target_pointer_width = "64") { @@ -293,9 +303,11 @@ impl ElfParsedFile { ) }; - // Validate trampoline size + // trampoline_size == 0 means the rewriter checked this binary and found + // no syscall instructions. The magic header acts as a "checked" marker so + // the runtime skips eager code-segment patching. No trampoline to map. if trampoline_size == 0 { - return Err(ElfParseError::BadTrampoline); + return Ok(()); } // Verify the file offset is page-aligned (as required by the rewriter) @@ -567,6 +579,24 @@ pub trait ReadAt { fn size(&mut self) -> Result; } +impl ReadAt for &[u8] { + type Error = Errno; + + fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<(), Self::Error> { + let offset: usize = offset.truncate(); + let end = offset.checked_add(buf.len()).ok_or(Errno::EINVAL)?; + if end > self.len() { + return Err(Errno::EINVAL); + } + buf.copy_from_slice(&self[offset..end]); + Ok(()) + } + + fn size(&mut self) -> Result { + Ok(self.len() as u64) + } +} + pub trait MapMemory { type Error; diff --git a/litebox_packager/build.rs b/litebox_packager/build.rs deleted file mode 100644 index 77956be92..000000000 --- a/litebox_packager/build.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -use std::path::PathBuf; - -const RTLD_AUDIT_DIR: &str = "../litebox_rtld_audit"; - -fn main() { - let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - if target_arch != "x86_64" { - return; - } - - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); - let mut make_cmd = std::process::Command::new("make"); - make_cmd - .current_dir(RTLD_AUDIT_DIR) - .env("OUT_DIR", &out_dir) - .env("ARCH", &target_arch); - // Always build without DEBUG for the packager -- packaged binaries are - // release artifacts. - make_cmd.env_remove("DEBUG"); - // Force rebuild in case a stale artifact exists from a different config. - let _ = std::fs::remove_file(out_dir.join("litebox_rtld_audit.so")); - - let output = make_cmd - .output() - .expect("Failed to execute make for rtld_audit"); - assert!( - output.status.success(), - "failed to build rtld_audit.so via make:\nstdout: {}\nstderr: {}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr), - ); - assert!( - out_dir.join("litebox_rtld_audit.so").exists(), - "Build failed to create litebox_rtld_audit.so" - ); - - println!("cargo:rerun-if-changed={RTLD_AUDIT_DIR}/rtld_audit.c"); - println!("cargo:rerun-if-changed={RTLD_AUDIT_DIR}/Makefile"); - println!("cargo:rerun-if-changed=build.rs"); -} diff --git a/litebox_packager/src/lib.rs b/litebox_packager/src/lib.rs index 223d027cf..3ef09bbf6 100644 --- a/litebox_packager/src/lib.rs +++ b/litebox_packager/src/lib.rs @@ -1,18 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -// Restrict this crate to only work on Linux, as it relies on `ldd` for -// dependency discovery and other Linux-specific functionality. -#![cfg(target_os = "linux")] - #[cfg(target_arch = "x86_64")] pub mod oci; use anyhow::{Context, bail}; use clap::Parser; use rayon::prelude::*; -use std::collections::{BTreeMap, BTreeSet}; -use std::os::unix::fs::MetadataExt as _; +#[cfg(target_os = "linux")] +use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::path::{Path, PathBuf}; use tar::{Builder, Header}; @@ -48,10 +45,16 @@ pub struct CliArgs { #[arg(short = 'o', long = "output", default_value = "litebox_packager.tar")] pub output: PathBuf, - /// Include extra files in the tar. + /// Include extra files in the tar (host mode only). + /// ELF files are automatically run through the syscall rewriter; non-ELF + /// files are included as-is. /// Format: HOST_PATH:TAR_PATH (split on the first colon, so the tar path /// may contain colons but the host path must not). - #[arg(long = "include", value_name = "HOST_PATH:TAR_PATH")] + #[arg( + long = "include", + value_name = "HOST_PATH:TAR_PATH", + conflicts_with = "oci_image" + )] pub include: Vec, /// Skip rewriting specific files (by their absolute path on the host). @@ -64,11 +67,13 @@ pub struct CliArgs { } /// Parsed `--include` entry. +#[cfg(target_os = "linux")] struct IncludeEntry { host_path: PathBuf, tar_path: String, } +#[cfg(target_os = "linux")] fn parse_include(spec: &str) -> anyhow::Result { let Some(colon_idx) = spec.find(':') else { bail!("invalid --include format: expected HOST_PATH:TAR_PATH, got: {spec}"); @@ -99,7 +104,24 @@ pub fn run(args: CliArgs) -> anyhow::Result<()> { } } - // --- Phase 1: Validate inputs --- + // Host mode (local ELF files + ldd dependency discovery) is Linux-only. + #[cfg(target_os = "linux")] + { + run_host_mode(args) + } + + #[cfg(not(target_os = "linux"))] + { + bail!( + "Host mode (local ELF files) is only supported on Linux. \ + Use --oci-image to pull a container image instead." + ); + } +} + +/// Host mode: package local ELF files with ldd-based dependency discovery. +#[cfg(target_os = "linux")] +fn run_host_mode(args: CliArgs) -> anyhow::Result<()> { let input_files: Vec = args .input_files .iter() @@ -151,12 +173,15 @@ pub fn run(args: CliArgs) -> anyhow::Result<()> { let par_results: Vec>> = file_map_vec .into_par_iter() - .map(|(real_path, tar_paths)| { + .map(|(real_path, tar_paths): (&PathBuf, &Vec)| { let data = std::fs::read(real_path) .with_context(|| format!("failed to read {}", real_path.display()))?; - let mode = std::fs::metadata(real_path) - .with_context(|| format!("failed to stat {}", real_path.display()))? - .mode(); + let mode = { + use std::os::unix::fs::MetadataExt as _; + std::fs::metadata(real_path) + .with_context(|| format!("failed to stat {}", real_path.display()))? + .mode() + }; let rewritten = if no_rewrite.contains(real_path) { if verbose { @@ -194,7 +219,47 @@ pub fn run(args: CliArgs) -> anyhow::Result<()> { } } - finalize_tar(tar_entries, added_tar_paths, &args)?; + // Append --include files (ELF files are automatically rewritten). + let includes: Vec = args + .include + .iter() + .map(|s| parse_include(s)) + .collect::>>()?; + + for inc in &includes { + if !inc.host_path.exists() { + bail!("included file does not exist: {}", inc.host_path.display()); + } + if !added_tar_paths.insert(inc.tar_path.clone()) { + bail!( + "duplicate tar path from --include: '{}' (already present)", + inc.tar_path + ); + } + let data = std::fs::read(&inc.host_path) + .with_context(|| format!("failed to read included file {}", inc.host_path.display()))?; + let mode = { + use std::os::unix::fs::MetadataExt as _; + std::fs::metadata(&inc.host_path) + .map(|m| m.mode()) + .unwrap_or(0o755) + }; + let rewritten = rewrite_elf(&data, &inc.host_path, args.verbose)?; + if args.verbose { + eprintln!( + " including {} as {}", + inc.host_path.display(), + inc.tar_path + ); + } + tar_entries.push(TarEntry { + tar_path: inc.tar_path.clone(), + data: rewritten, + mode, + }); + } + + finalize_tar(tar_entries, &args)?; Ok(()) } @@ -208,7 +273,12 @@ fn run_oci(image_ref: &str, args: &CliArgs) -> anyhow::Result<()> { // --- Phase 2: Scan rootfs for files --- eprintln!("Scanning rootfs..."); - let file_map = oci::scan_rootfs(&extracted.rootfs_path, args.verbose)?; + let file_map = oci::scan_rootfs( + &extracted.rootfs_path, + &extracted.symlink_map, + &extracted.permissions, + args.verbose, + )?; let no_rewrite: BTreeSet = args .no_rewrite @@ -303,78 +373,17 @@ fn run_oci(image_ref: &str, args: &CliArgs) -> anyhow::Result<()> { } } - finalize_tar(tar_entries, added_tar_paths, args)?; + finalize_tar(tar_entries, args)?; Ok(()) } // --------------------------------------------------------------------------- -// Shared finalization: includes, rtld audit injection, tar build, size report +// Shared finalization: tar build, size report // --------------------------------------------------------------------------- -/// Append `--include` files, inject the rtld audit library, build the output -/// tar, and print a size summary. -/// -/// Both host mode and OCI mode call this after producing their rewritten -/// `TarEntry` list. -fn finalize_tar( - mut tar_entries: Vec, - mut added_tar_paths: BTreeSet, - args: &CliArgs, -) -> anyhow::Result<()> { - // Parse and append --include files. - let includes: Vec = args - .include - .iter() - .map(|s| parse_include(s)) - .collect::>>()?; - - for inc in &includes { - if !inc.host_path.exists() { - bail!("included file does not exist: {}", inc.host_path.display()); - } - if !added_tar_paths.insert(inc.tar_path.clone()) { - bail!( - "duplicate tar path from --include: '{}' (already present)", - inc.tar_path - ); - } - let data = std::fs::read(&inc.host_path) - .with_context(|| format!("failed to read included file {}", inc.host_path.display()))?; - let mode = std::fs::metadata(&inc.host_path) - .map(|m| m.mode()) - .unwrap_or(0o644); - if args.verbose { - eprintln!( - " including {} as {}", - inc.host_path.display(), - inc.tar_path - ); - } - tar_entries.push(TarEntry { - tar_path: inc.tar_path.clone(), - data, - mode, - }); - } - - // Include the rtld audit library so the rewriter backend can load it. - #[cfg(target_arch = "x86_64")] - { - const RTLD_AUDIT_TAR_PATH: &str = "lib/litebox_rtld_audit.so"; - if !added_tar_paths.insert(RTLD_AUDIT_TAR_PATH.to_string()) { - bail!( - "tar already contains {RTLD_AUDIT_TAR_PATH} -- \ - remove the conflicting entry or use --no-rewrite" - ); - } - tar_entries.push(TarEntry { - tar_path: RTLD_AUDIT_TAR_PATH.to_string(), - data: include_bytes!(concat!(env!("OUT_DIR"), "/litebox_rtld_audit.so")).to_vec(), - mode: 0o755, - }); - } - +/// Build the output tar and print a size summary. +fn finalize_tar(tar_entries: Vec, args: &CliArgs) -> anyhow::Result<()> { // Build tar. eprintln!("Creating {}...", args.output.display()); build_tar(&tar_entries, &args.output)?; @@ -394,21 +403,20 @@ fn finalize_tar( Ok(()) } -// --------------------------------------------------------------------------- -// Dependency discovery (via ldd) -// --------------------------------------------------------------------------- - +#[cfg(target_os = "linux")] struct ResolvedDep { ldd_path: PathBuf, real_path: PathBuf, } +#[cfg(target_os = "linux")] struct DepDiscoveryResult { resolved: Vec, missing: Vec, } /// Run `ldd` on the given ELF and return resolved dependencies. +#[cfg(target_os = "linux")] fn find_dependencies(elf_path: &Path, verbose: bool) -> anyhow::Result { let output = std::process::Command::new("ldd") .arg(elf_path) @@ -500,6 +508,7 @@ fn find_dependencies(elf_path: &Path, verbose: bool) -> anyhow::Result anyhow::Result { + Ok((rewritten, skipped_addrs)) => { + if !skipped_addrs.is_empty() { + eprintln!( + " warning: {} has {} unpatchable syscall instruction(s) at {:?}", + path.display(), + skipped_addrs.len(), + skipped_addrs, + ); + } if verbose { eprintln!(" {} (rewritten)", path.display()); } @@ -592,7 +609,7 @@ fn rewrite_elf(data: &[u8], path: &Path, verbose: bool) -> anyhow::Result { + Err(litebox_syscall_rewriter::Error::UnsupportedObjectFile(_)) => { if verbose { eprintln!( " warning: {} is not a supported ELF, including as-is", @@ -610,6 +627,12 @@ fn rewrite_elf(data: &[u8], path: &Path, verbose: bool) -> anyhow::Result { + anyhow::bail!( + "{} is a Bun-packaged executable and cannot be safely packaged without syscall rewriting", + path.display() + ) + } Err(e) => Err(e).with_context(|| format!("failed to rewrite {}", path.display())), } } @@ -630,7 +653,7 @@ fn build_tar(entries: &[TarEntry], output: &Path) -> anyhow::Result<()> { let mut builder = Builder::new(file); for entry in entries { - let mut header = Header::new_gnu(); + let mut header = Header::new_ustar(); header.set_size(entry.data.len() as u64); // Mask to permission bits only (rwxrwxrwx). The full st_mode from // MetadataExt::mode() includes file type bits (e.g., 0o100755) which diff --git a/litebox_packager/src/main.rs b/litebox_packager/src/main.rs index 2acb1167d..01987d6e8 100644 --- a/litebox_packager/src/main.rs +++ b/litebox_packager/src/main.rs @@ -1,18 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -// Restrict this crate to only work on Linux, as it relies on `ldd` for -// dependency discovery and other Linux-specific functionality. - -#[cfg(target_os = "linux")] fn main() -> anyhow::Result<()> { use clap::Parser as _; use litebox_packager::CliArgs; litebox_packager::run(CliArgs::parse()) } - -#[cfg(not(target_os = "linux"))] -fn main() { - eprintln!("This program is only supported on Linux"); - std::process::exit(1); -} diff --git a/litebox_packager/src/oci.rs b/litebox_packager/src/oci.rs index adb951833..eb97a55f2 100644 --- a/litebox_packager/src/oci.rs +++ b/litebox_packager/src/oci.rs @@ -7,9 +7,8 @@ //! extracts its filesystem layers into a temporary rootfs directory, then //! walks the rootfs to discover all ELF files for syscall rewriting. -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::io::Read; -use std::os::unix::fs::PermissionsExt as _; use std::path::{Path, PathBuf}; use anyhow::Context; @@ -38,6 +37,13 @@ pub struct ExtractedImage { pub config: ImageConfig, /// Raw OCI image config JSON blob (the full config descriptor data). pub config_json: Vec, + /// Symlink map from layer extraction: maps relative paths inside the + /// rootfs to their (Unix-style) link targets for cross-platform resolution. + pub symlink_map: HashMap, + /// Unix permission modes captured from tar headers during extraction. + /// Keyed by relative path inside the rootfs. Used instead of querying + /// filesystem metadata, which loses Unix mode bits on non-Unix hosts. + pub permissions: HashMap, } /// Result of scanning an extracted rootfs for files to package. @@ -96,6 +102,17 @@ pub fn pull_and_extract(image_ref: &str, verbose: bool) -> anyhow::Result anyhow::Result = Vec::new(); + let mut permissions: HashMap = HashMap::new(); for (i, layer) in image_data.layers.iter().enumerate() { if verbose { eprintln!( @@ -143,10 +162,29 @@ pub fn pull_and_extract(image_ref: &str, verbose: bool) -> anyhow::Result = symlinks + .iter() + .map(|s| (s.rel_path.clone(), s.link_target.clone())) + .collect(); + + // Materialize symlinks cross-platform: resolve chains through the in-memory + // map and copy target files (or create directories) instead of OS symlinks. + if verbose { + eprintln!(" Resolving {} symlinks...", symlinks.len()); + } + materialize_symlinks(&symlink_map, &rootfs_path, &mut permissions, verbose)?; + if verbose { eprintln!(" Rootfs extracted to {}", rootfs_path.display()); } @@ -188,6 +226,8 @@ pub fn pull_and_extract(image_ref: &str, verbose: bool) -> anyhow::Result String { /// Extract a single OCI layer (tar or tar+gzip) into the rootfs directory. /// /// Handles OCI whiteout files (`.wh.*` prefixed entries) which indicate -/// files deleted in upper layers. -fn extract_layer(data: &[u8], media_type: &str, rootfs: &Path) -> anyhow::Result<()> { +/// files deleted in upper layers. Symlinks are collected into `symlinks` for +/// cross-platform resolution after all layers are extracted. Permission modes +/// from tar headers are recorded in `permissions` for cross-platform use. +fn extract_layer( + data: &[u8], + media_type: &str, + rootfs: &Path, + symlinks: &mut Vec, + permissions: &mut HashMap, +) -> anyhow::Result<()> { // Determine if the layer is gzipped let is_gzip = media_type.contains("gzip") || is_gzip_data(data); if is_gzip { let decoder = flate2::read::GzDecoder::new(data); - extract_tar(decoder, rootfs) + extract_tar(decoder, rootfs, symlinks, permissions) } else { - extract_tar(data, rootfs) + extract_tar(data, rootfs, symlinks, permissions) } } @@ -304,13 +352,32 @@ struct DeferredHardLink { target: PathBuf, /// Source path inside the rootfs (the file the hard link points to). link_source: PathBuf, + /// Original link name from the tar header (used for permission lookup). + link_name: PathBuf, +} + +/// Tracked symlink from a container image layer. +struct DeferredSymlink { + /// Relative path inside the rootfs (e.g., `usr/lib64/ld-linux-x86-64.so.2`). + rel_path: PathBuf, + /// Symlink target as stored in the tar (Unix-style, may be relative or absolute). + link_target: PathBuf, } /// Extract a tar archive into the rootfs, handling OCI whiteout files. /// -/// Hard links whose targets appear later in the archive are collected during -/// the first pass and resolved after all regular entries have been extracted. -fn extract_tar(reader: R, rootfs: &Path) -> anyhow::Result<()> { +/// Symlinks are NOT created as OS symlinks. Instead they are tracked in +/// `symlinks` so the caller can resolve them cross-platform after all layers +/// are extracted. Hard links whose targets appear later in the archive are +/// collected during the first pass and resolved after all regular entries +/// have been extracted. Permission modes from tar headers are recorded in +/// `permissions` keyed by relative path. +fn extract_tar( + reader: R, + rootfs: &Path, + symlinks: &mut Vec, + permissions: &mut HashMap, +) -> anyhow::Result<()> { let mut archive = tar::Archive::new(reader); archive.set_preserve_permissions(true); archive.set_unpack_xattrs(true); @@ -319,7 +386,9 @@ fn extract_tar(reader: R, rootfs: &Path) -> anyhow::Result<()> { for entry_result in archive.entries()? { let mut entry = entry_result.context("failed to read tar entry")?; - let path = entry.path()?.into_owned(); + // Normalize the path to prevent path traversal (../ and absolute paths) + // and to strip inconsistent ./ prefixes that tar entries may carry. + let path = normalize_path(&entry.path()?); let path_str = path.to_string_lossy(); // Handle OCI whiteout files @@ -340,17 +409,39 @@ fn extract_tar(reader: R, rootfs: &Path) -> anyhow::Result<()> { } } } + // Also prune in-memory symlinks under this directory so + // they are not resurrected by materialize_symlinks. + // Guard: Path::starts_with("") matches everything, so skip + // pruning when parent is empty (root-level opaque whiteout + // already cleared the filesystem above). + if parent.as_os_str().is_empty() { + symlinks.clear(); + permissions.clear(); + } else { + symlinks.retain(|s| !s.rel_path.starts_with(parent)); + // Prune permissions for files under the cleared directory. + permissions.retain(|p, _| !p.starts_with(parent)); + } } continue; } if let Some(target_name) = file_name.strip_prefix(".wh.") { // Regular whiteout: delete the specific file/directory if let Some(parent) = path.parent() { - let target = rootfs.join(parent).join(target_name); + let whiteout_rel = parent.join(target_name); + let target = rootfs.join(&whiteout_rel); if target.is_dir() { let _ = std::fs::remove_dir_all(&target); + // Prune symlinks under the removed directory. + symlinks.retain(|s| !s.rel_path.starts_with(&whiteout_rel)); + // Prune permissions under the removed directory. + permissions.retain(|p, _| !p.starts_with(&whiteout_rel)); } else { let _ = std::fs::remove_file(&target); + // Prune the exact symlink entry if present. + symlinks.retain(|s| s.rel_path != whiteout_rel); + // Prune the exact permissions entry. + permissions.remove(&whiteout_rel); } } continue; @@ -364,16 +455,18 @@ fn extract_tar(reader: R, rootfs: &Path) -> anyhow::Result<()> { std::fs::create_dir_all(parent)?; } + let entry_type = entry.header().entry_type(); + // Handle hard links: copy the link target instead of creating an OS // hard link. The tar crate's unpack() tries std::fs::hard_link which // can fail if the target hasn't been extracted yet (ordering issue), // and the litebox filesystem doesn't support hard links anyway. - let entry_type = entry.header().entry_type(); if entry_type == tar::EntryType::Link { - let link_name = entry - .link_name()? - .context("hard link entry has no link name")? - .into_owned(); + let link_name = normalize_path( + &entry + .link_name()? + .context("hard link entry has no link name")?, + ); let link_source = rootfs.join(&link_name); if link_source.exists() { std::fs::copy(&link_source, &target).with_context(|| { @@ -383,20 +476,50 @@ fn extract_tar(reader: R, rootfs: &Path) -> anyhow::Result<()> { target.display() ) })?; + // Copy permission mode from the link source. + let link_rel = normalize_path(&link_name); + if let Some(&mode) = permissions.get(&link_rel) { + permissions.insert(path.clone(), mode); + } } else { // Target hasn't been extracted yet — defer to second pass. deferred_links.push(DeferredHardLink { target, link_source, + link_name: link_name.clone(), }); } continue; } - // Normal file/directory/symlink: use the standard unpack + // Track symlinks in memory instead of creating OS symlinks. + // OS symlinks on Windows require special privileges and don't handle + // Unix-style relative paths reliably, so we resolve them ourselves + // after all layers are extracted. + if entry_type == tar::EntryType::Symlink { + let link_target = entry + .link_name()? + .context("symlink entry has no link name")? + .into_owned(); + // A later layer may override this symlink, so remove any stale + // entry with the same rel_path. + symlinks.retain(|s| s.rel_path != path); + symlinks.push(DeferredSymlink { + rel_path: path.clone(), + link_target, + }); + continue; + } + + // Normal file/directory: use the standard unpack entry .unpack(&target) .with_context(|| format!("failed to unpack entry: {path_str}"))?; + + // Record the permission mode from the tar header for cross-platform use. + if let Ok(mode) = entry.header().mode() { + permissions.insert(path.clone(), mode); + } } // Second pass: resolve deferred hard links now that all entries are extracted. @@ -412,6 +535,12 @@ fn extract_tar(reader: R, rootfs: &Path) -> anyhow::Result<()> { link.target.display() ) })?; + // Copy permission mode from the link source. + let link_rel = normalize_path(&link.link_name); + if let Some(&mode) = permissions.get(&link_rel) { + let target_rel = link.target.strip_prefix(rootfs).unwrap_or(&link.target); + permissions.insert(target_rel.to_path_buf(), mode); + } } else { // Target still doesn't exist after the full layer extraction — // this is unusual but not fatal; warn and skip. @@ -426,20 +555,241 @@ fn extract_tar(reader: R, rootfs: &Path) -> anyhow::Result<()> { Ok(()) } +/// Resolve a symlink target within the rootfs using the symlink map. +/// +/// Handles both absolute targets (e.g., `/lib/x86_64-linux-gnu/ld.so`) and +/// relative targets (e.g., `../lib/x86_64-linux-gnu/ld.so`). Follows symlink +/// chains up to `max_depth` hops. +fn resolve_symlink_in_rootfs( + rel_path: &Path, + rootfs: &Path, + symlink_map: &HashMap, + max_depth: u32, +) -> Option { + if max_depth == 0 { + return None; + } + + // Empty rel_path would resolve to the rootfs directory itself — treat + // as unresolvable to avoid accidentally matching the entire rootfs. + if rel_path.as_os_str().is_empty() { + return None; + } + + // Check if this rel_path is itself a symlink + if let Some(link_target) = symlink_map.get(rel_path) { + // Resolve the target to a new rel_path + let resolved_rel = if is_unix_absolute(link_target) { + strip_unix_root(link_target) + } else { + // Relative target: resolve from parent of the symlink + let parent = rel_path.parent().unwrap_or(Path::new("")); + normalize_path(&parent.join(link_target)) + }; + // Recurse to follow chains + return resolve_symlink_in_rootfs(&resolved_rel, rootfs, symlink_map, max_depth - 1); + } + + // Not a symlink — check if any ancestor is a symlink (e.g., `lib64/foo` where + // `lib64` → `usr/lib64`). + let components: Vec<_> = rel_path.components().collect(); + for i in 1..components.len() { + let prefix: PathBuf = components[..i].iter().collect(); + if let Some(link_target) = symlink_map.get(&prefix) { + let resolved_prefix = if is_unix_absolute(link_target) { + strip_unix_root(link_target) + } else { + let parent = prefix.parent().unwrap_or(Path::new("")); + normalize_path(&parent.join(link_target)) + }; + let suffix: PathBuf = components[i..].iter().collect(); + let new_rel = resolved_prefix.join(suffix); + return resolve_symlink_in_rootfs(&new_rel, rootfs, symlink_map, max_depth - 1); + } + } + + let host_path = rootfs.join(rel_path); + if host_path.exists() { + Some(host_path) + } else { + None + } +} + +/// Check if a path starts with `/` (Unix-style absolute). +/// +/// On Windows, `Path::is_absolute()` requires a drive letter, so Unix-style +/// paths like `/lib/foo` are not detected as absolute. This helper checks +/// the raw string instead. +fn is_unix_absolute(path: &Path) -> bool { + path.as_os_str() + .to_str() + .is_some_and(|s| s.starts_with('/')) +} + +/// Strip the leading `/` from a Unix-style absolute path to make it +/// rootfs-relative. Returns the path unchanged if it doesn't start with `/`. +fn strip_unix_root(path: &Path) -> PathBuf { + if let Some(stripped) = path.as_os_str().to_str().and_then(|s| s.strip_prefix('/')) { + return PathBuf::from(stripped); + } + path.strip_prefix("/").unwrap_or(path).to_path_buf() +} + +/// Normalize a path by resolving `.` and `..` components without touching the +/// filesystem (no symlink resolution, no existence checks). Strips any root +/// component so the result is always a relative path. +fn normalize_path(path: &Path) -> PathBuf { + let mut result = Vec::new(); + for component in path.components() { + match component { + std::path::Component::ParentDir => { + result.pop(); + } + std::path::Component::CurDir + | std::path::Component::RootDir + | std::path::Component::Prefix(_) => {} + c @ std::path::Component::Normal(_) => result.push(c), + } + } + result.iter().collect() +} + +/// Materialize all deferred symlinks by copying or creating directories. +/// +/// This is called after all OCI layers have been extracted, so every real file +/// should be on disk. Symlinks are resolved through the in-memory map (handling +/// chains like `lib64` → `usr/lib64` → real dir) and then: +/// - File symlinks: the target file is copied to the symlink location. +/// The resolved target's permission mode is also recorded for the symlink path. +/// - Directory symlinks: an empty directory is created (its contents will be +/// expanded by `scan_rootfs`'s dir-symlink logic). +fn materialize_symlinks( + symlink_map: &HashMap, + rootfs: &Path, + permissions: &mut HashMap, + verbose: bool, +) -> anyhow::Result<()> { + for (rel_path, link_target) in symlink_map { + let host_path = rootfs.join(rel_path); + if host_path.exists() { + // A later layer may have replaced the symlink with a real file. + continue; + } + + if let Some(resolved) = resolve_symlink_in_rootfs( + rel_path, + rootfs, + symlink_map, + 32, // max chain depth + ) { + if let Some(parent) = host_path.parent() { + std::fs::create_dir_all(parent)?; + } + + if resolved.is_dir() { + // Directory symlink: create directory placeholder. + // scan_rootfs will discover this is a "dir symlink" and expand + // it through the symlink_map. + std::fs::create_dir_all(&host_path)?; + if verbose { + eprintln!( + " [symlink→dir] {} -> {}", + rel_path.display(), + link_target.display() + ); + } + } else if resolved.is_file() { + std::fs::copy(&resolved, &host_path).with_context(|| { + format!( + "failed to materialize symlink {} -> {}", + rel_path.display(), + resolved.display() + ) + })?; + // Record the resolved target's permission mode for this symlink path. + let resolved_rel = resolved + .strip_prefix(rootfs) + .unwrap_or(&resolved) + .to_path_buf(); + if let Some(&mode) = permissions.get(&resolved_rel) { + permissions.insert(rel_path.clone(), mode); + } + if verbose { + eprintln!( + " [symlink→file] {} -> {}", + rel_path.display(), + link_target.display() + ); + } + } + } else if verbose { + eprintln!( + " [symlink-broken] {} -> {} (unresolvable)", + rel_path.display(), + link_target.display() + ); + } + } + + Ok(()) +} + +/// Look up the Unix permission mode for a file. +/// +/// Look up the Unix file mode for a rootfs-relative path from the OCI tar +/// header permissions map. Defaults to 0o644 if not found. +fn lookup_mode(rel_path: &Path, permissions: &HashMap) -> u32 { + if let Some(&mode) = permissions.get(rel_path) { + mode & 0o7777 + } else { + 0o644 + } +} + /// Scan an extracted rootfs directory and build a file map for packaging. /// /// Walks the rootfs directory tree and collects all regular files with their -/// paths and permission bits. Symlinks are resolved within the rootfs context -/// and flattened into regular file copies (the litebox tar RO filesystem does -/// not support symlinks). +/// paths and permission bits. After `materialize_symlinks` has been called, +/// file symlinks are already materialized as regular file copies on disk. +/// +/// `symlink_map` provides the original symlink mapping from extraction so +/// that **directory symlinks** (e.g., `lib64` → `usr/lib64`) can be expanded: +/// all files under the target directory are duplicated under the symlink's +/// path prefix so that paths like `lib64/ld-linux-x86-64.so.2` exist in the tar. /// -/// **Directory symlinks** (e.g., `/lib64` → `/usr/lib64`) are expanded: all -/// files under the target directory are duplicated under the symlink's path -/// prefix so that paths like `/lib64/ld-linux-x86-64.so.2` exist in the tar. -pub fn scan_rootfs(rootfs: &Path, verbose: bool) -> anyhow::Result { +/// `permissions` provides Unix permission modes captured from tar headers +/// during extraction, so permission bits are accurate on non-Unix hosts. +#[allow(clippy::implicit_hasher)] +pub fn scan_rootfs( + rootfs: &Path, + symlink_map: &HashMap, + permissions: &HashMap, + verbose: bool, +) -> anyhow::Result { let mut files = BTreeMap::new(); - // Collect directory symlinks to expand after the initial walk. + + // Identify directory symlinks and their resolved targets on disk. let mut dir_symlinks: Vec<(PathBuf, PathBuf)> = Vec::new(); + for (rel_path, link_target) in symlink_map { + let host_path = rootfs.join(rel_path); + if host_path.is_dir() { + // This dir symlink was materialized as an empty directory. + // Resolve the target to find the real directory to expand from. + if let Some(resolved) = + resolve_symlink_in_rootfs(rel_path, rootfs, symlink_map, 32).filter(|r| r.is_dir()) + { + if verbose { + eprintln!( + " [dir-symlink] {} -> {}", + rel_path.display(), + link_target.display() + ); + } + dir_symlinks.push((host_path, resolved)); + } + } + } for entry in walkdir::WalkDir::new(rootfs) .follow_links(false) @@ -454,10 +804,11 @@ pub fn scan_rootfs(rootfs: &Path, verbose: bool) -> anyhow::Result anyhow::Result {}", resolved.display()); } @@ -552,6 +898,8 @@ pub fn scan_rootfs(rootfs: &Path, verbose: bool) -> anyhow::Result anyhow::Result Option usr/lib64 + let r = resolve_symlink_in_rootfs(Path::new("lib64"), rootfs, &symlink_map, 32); + assert_eq!(r, Some(rootfs.join("usr/lib64"))); + + // Chain: a -> b -> c + let r = resolve_symlink_in_rootfs(Path::new("a"), rootfs, &symlink_map, 32); + assert_eq!(r, Some(rootfs.join("c"))); + + // Absolute target: bin/sh -> /usr/bin/sh + let r = resolve_symlink_in_rootfs(Path::new("bin/sh"), rootfs, &symlink_map, 32); + assert_eq!(r, Some(rootfs.join("usr/bin/sh"))); + + // Relative target: usr/lib64/libfoo.so -> ../lib/libfoo.so + let r = + resolve_symlink_in_rootfs(Path::new("usr/lib64/libfoo.so"), rootfs, &symlink_map, 32); + assert_eq!(r, Some(rootfs.join("usr/lib/libfoo.so"))); + + // Ancestor is symlink: lib64/foo.so resolves via lib64 -> usr/lib64 + let r = resolve_symlink_in_rootfs(Path::new("lib64/foo.so"), rootfs, &symlink_map, 32); + assert_eq!(r, Some(rootfs.join("usr/lib64/foo.so"))); } #[test] - fn test_resolve_in_rootfs_max_depth_zero() { - let result = resolve_in_rootfs(Path::new("/tmp"), Path::new("/tmp"), 0); - assert!(result.is_none()); + fn resolve_symlink_in_rootfs_edge_cases() { + let tmp = tempfile::tempdir().unwrap(); + let rootfs = tmp.path(); + std::fs::write(rootfs.join("hello.txt"), b"hi").unwrap(); + + // Cycle: a -> b -> a + let mut cycle_map = HashMap::new(); + cycle_map.insert(PathBuf::from("a"), PathBuf::from("b")); + cycle_map.insert(PathBuf::from("b"), PathBuf::from("a")); + assert!(resolve_symlink_in_rootfs(Path::new("a"), rootfs, &cycle_map, 32).is_none()); + + let empty_map = HashMap::new(); + + // Empty path + assert!(resolve_symlink_in_rootfs(Path::new(""), rootfs, &empty_map, 32).is_none()); + + // Nonexistent path + assert!( + resolve_symlink_in_rootfs(Path::new("does/not/exist"), rootfs, &empty_map, 32) + .is_none() + ); + + // Regular file (not a symlink) returns host path directly + let r = resolve_symlink_in_rootfs(Path::new("hello.txt"), rootfs, &empty_map, 32); + assert_eq!(r, Some(rootfs.join("hello.txt"))); } } diff --git a/litebox_platform_linux_userland/src/lib.rs b/litebox_platform_linux_userland/src/lib.rs index c3e60a83a..8c9d57b02 100644 --- a/litebox_platform_linux_userland/src/lib.rs +++ b/litebox_platform_linux_userland/src/lib.rs @@ -537,6 +537,8 @@ core::arch::global_asm!( " .section .tbss .align 8 +saved_restart_addr: + .quad 0 scratch: .quad 0 host_sp: @@ -651,6 +653,13 @@ syscall_callback: // expectations of `interrupt_signal_handler`. mov BYTE PTR gs:in_guest@tpoff, 0 + // Save guest R11 (syscall call-site restart address from the rewriter + // trampoline) to TLS before it is clobbered by the fsbase/gsbase save + // sequence below. This value is not placed in pt_regs (which holds + // RFLAGS in the r11 slot per the kernel ABI); instead it is kept in + // TLS for future SA_RESTART support. + mov gs:saved_restart_addr@tpoff, r11 + // Restore host fs base. rdfsbase r11 mov gs:guest_fsbase@tpoff, r11 @@ -660,6 +669,25 @@ syscall_callback: // Switch to the top of the guest context. mov r11, rsp mov rsp, fs:guest_context_top@tpoff + jmp .Lsyscall_save_regs + + .globl syscall_callback_redzone +syscall_callback_redzone: + // Same as syscall_callback, but the trampoline has already reserved + // 128 bytes below RSP to protect the SysV red zone. + mov BYTE PTR gs:in_guest@tpoff, 0 + mov gs:saved_restart_addr@tpoff, r11 + rdfsbase r11 + mov gs:guest_fsbase@tpoff, r11 + rdgsbase r11 + wrfsbase r11 + + // The trampoline lowered RSP by 128 bytes with LEA, so recover the + // architectural guest stack pointer before saving pt_regs. + lea r11, [rsp + 128] + mov rsp, fs:guest_context_top@tpoff + +.Lsyscall_save_regs: // TODO: save float and vector registers (xsave or fxsave) // Save caller-saved registers @@ -678,7 +706,7 @@ syscall_callback: push r8 // pt_regs->r8 push r9 // pt_regs->r9 push r10 // pt_regs->r10 - push [rsp + 88] // pt_regs->r11 = rflags + push [rsp + 88] // pt_regs->r11 = rflags (matching real syscall ABI) push rbx // pt_regs->bx push rbp // pt_regs->bp push r12 // pt_regs->r12 @@ -1967,6 +1995,8 @@ impl litebox::platform::StdioProvider for LinuxUserland { unsafe extern "C" { // Defined in asm blocks above fn syscall_callback() -> isize; + #[cfg(target_arch = "x86_64")] + fn syscall_callback_redzone() -> isize; fn exception_callback(); fn interrupt_callback(); fn switch_to_guest_start(); @@ -2047,7 +2077,14 @@ impl ThreadContext<'_> { impl litebox::platform::SystemInfoProvider for LinuxUserland { fn get_syscall_entry_point(&self) -> usize { - syscall_callback as *const () as usize + #[cfg(target_arch = "x86_64")] + { + syscall_callback_redzone as *const () as usize + } + #[cfg(target_arch = "x86")] + { + syscall_callback as *const () as usize + } } fn get_vdso_address(&self) -> Option { @@ -2714,7 +2751,12 @@ unsafe fn interrupt_signal_handler( // FUTURE: handle trampoline code, too. This is somewhat less important // because it's probably fine for the shim to observe a guest context that // is inside the trampoline. - if ip == syscall_callback as *const () as usize { + #[cfg(target_arch = "x86")] + let is_at_syscall_callback = ip == syscall_callback as *const () as usize; + #[cfg(target_arch = "x86_64")] + let is_at_syscall_callback = ip == syscall_callback_redzone as *const () as usize + || ip == syscall_callback as *const () as usize; + if is_at_syscall_callback { // No need to clear `in_guest` or set interrupt; the syscall handler will // clear `in_guest` and call into the shim. return; diff --git a/litebox_platform_windows_userland/src/lib.rs b/litebox_platform_windows_userland/src/lib.rs index 9d827e057..0284e8b6c 100644 --- a/litebox_platform_windows_userland/src/lib.rs +++ b/litebox_platform_windows_userland/src/lib.rs @@ -414,6 +414,10 @@ struct TlsState { host_bp: Cell<*mut u128>, guest_context_top: Cell<*mut litebox_common_linux::PtRegs>, scratch: Cell, + /// Syscall call-site restart address from the rewriter trampoline, + /// saved here for future SA_RESTART support. Not stored in pt_regs + /// (which holds RFLAGS in the r11 slot per the kernel ABI). + saved_restart_addr: Cell, is_in_guest: Cell, interrupt: Cell, continue_context: @@ -433,6 +437,7 @@ impl TlsState { host_bp: Cell::new(core::ptr::null_mut()), guest_context_top: core::ptr::null_mut::().into(), scratch: 0.into(), + saved_restart_addr: 0.into(), is_in_guest: false.into(), interrupt: false.into(), continue_context: Box::default(), @@ -549,19 +554,37 @@ unsafe extern "C-unwind" fn run_thread_arch(thread_ctx: &mut ThreadContext, tls_ jmp .Ldone // This entry point is called from the guest when it issues a syscall - // instruction. + // instruction. The rewriter trampoline has already: + // 1. Reserved 128 bytes below RSP to protect the SysV red zone + // 2. Loaded the call-site restart address into R11 (for SA_RESTART) + // 3. Loaded the return address into RCX // - // At entry, the register context is the guest context with the - // return address in rcx. r11 is an available scratch register (it would - // contain rflags if the syscall instruction had actually been issued). - .globl syscall_callback -syscall_callback: + // All other registers hold guest state. + .globl syscall_callback_redzone +syscall_callback_redzone: + // Save guest R11 (restart address from rewriter trampoline) to + // TEB.ArbitraryUserPointer (gs:[0x28]) before the TLS index lookup + // clobbers R11. This slot is per-thread and the window is very + // narrow: only ~20 instructions of inline asm with no API calls, + // no Rust code, and no DLL activity, so the ntdll loader (which + // also uses this slot for debugger communication) cannot interfere. + mov gs:[0x28], r11 // Get the TLS state from the TLS slot and clear the in-guest flag. mov r11d, DWORD PTR [rip + {TLS_INDEX}] mov r11, QWORD PTR gs:[r11 * 8 + TEB_TLS_SLOTS_OFFSET] mov BYTE PTR [r11 + {IS_IN_GUEST}], 0 - // Set rsp to the top of the guest context. + // Recover the restart address from the TEB slot and store it in TLS. + // We use SCRATCH as a temporary since all guest GPRs must be preserved + // and RSP modifications would break the stack pointer recovery below. + push QWORD PTR gs:[0x28] + pop QWORD PTR [r11 + {SAVED_RESTART_ADDR}] + // Recover the architectural guest stack pointer (undo the 128-byte + // red zone reservation) and store it in SCRATCH. LEA is used instead + // of ADD to avoid clobbering RFLAGS before pushfq. + lea rsp, [rsp + 128] mov QWORD PTR [r11 + {SCRATCH}], rsp + +.Lsyscall_callback_common: mov rsp, QWORD PTR [r11 + {GUEST_CONTEXT_TOP}] // TODO: save float and vector registers (xsave or fxsave) @@ -581,7 +604,7 @@ syscall_callback: push r8 // pt_regs->r8 push r9 // pt_regs->r9 push r10 // pt_regs->r10 - push [rsp + 88] // pt_regs->r11 = rflags + push [rsp + 88] // pt_regs->r11 = rflags (matching real syscall ABI) push rbx // pt_regs->bx push rbp // pt_regs->bp push r12 @@ -646,6 +669,7 @@ interrupt_callback: HOST_BP = const core::mem::offset_of!(TlsState, host_bp), GUEST_CONTEXT_TOP = const core::mem::offset_of!(TlsState, guest_context_top), SCRATCH = const core::mem::offset_of!(TlsState, scratch), + SAVED_RESTART_ADDR = const core::mem::offset_of!(TlsState, saved_restart_addr), IS_IN_GUEST = const core::mem::offset_of!(TlsState, is_in_guest), ); } @@ -1947,7 +1971,7 @@ impl litebox::mm::allocator::MemoryProvider for WindowsUserland { unsafe extern "C" { // Defined in asm blocks above - fn syscall_callback() -> isize; + fn syscall_callback_redzone() -> isize; fn exception_callback() -> isize; fn interrupt_callback(); fn switch_to_guest_start(); @@ -2037,7 +2061,7 @@ impl ThreadContext<'_> { impl litebox::platform::SystemInfoProvider for WindowsUserland { fn get_syscall_entry_point(&self) -> usize { - syscall_callback as *const () as usize + syscall_callback_redzone as *const () as usize } fn get_vdso_address(&self) -> Option { diff --git a/litebox_rtld_audit/.gitignore b/litebox_rtld_audit/.gitignore deleted file mode 100644 index 140f8cf80..000000000 --- a/litebox_rtld_audit/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.so diff --git a/litebox_rtld_audit/Makefile b/litebox_rtld_audit/Makefile deleted file mode 100644 index b3a3ad3a3..000000000 --- a/litebox_rtld_audit/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -SRC = rtld_audit.c -OUT_DIR ?= . -OUTPUT = $(OUT_DIR)/litebox_rtld_audit.so -CC ?= cc -CFLAGS ?= -Wall -Werror -fPIC -shared -nostdlib -ARCH ?= $(shell uname -m) -ifeq ($(ARCH),x86_64) - CFLAGS += -m64 -else - $(error Unsupported target architecture: $(ARCH)) -endif -ifdef DEBUG - CFLAGS += -DDEBUG -endif -all: $(OUTPUT) - -$(OUTPUT): $(SRC) - $(CC) $(CFLAGS) -o $@ $< - -clean: - rm -f $(OUTPUT) - -.PHONY: all clean diff --git a/litebox_rtld_audit/rtld_audit.c b/litebox_rtld_audit/rtld_audit.c deleted file mode 100644 index 51713f941..000000000 --- a/litebox_rtld_audit/rtld_audit.c +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#define _GNU_SOURCE -#include -#include -#include - -// The magic number used to identify the LiteBox trampoline. -// This must match `TRAMPOLINE_MAGIC` in `litebox_syscall_rewriter` and `litebox_common_linux`. -// Value 0x30584f424554494c is "LITEBOX0" in little-endian (bytes: 'L','I','T','E','B','O','X','0') -#define TRAMPOLINE_MAGIC ((uint64_t)0x30584f424554494c) - -#if !defined(__x86_64__) -# error "rtld_audit.c: build target must be x86_64" -#endif - -// Linux syscall numbers (x86_64) -#define SYS_openat 257 -#define SYS_read 0 -#define SYS_write 1 -#define SYS_close 3 -#define SYS_fstat 5 -#define SYS_mmap 9 -#define SYS_mprotect 10 -#define SYS_munmap 11 -#define SYS_exit_group 231 -#define AT_FDCWD -100 - -// Maximum valid userspace address (48-bit address space) -#define MAX_USERSPACE_ADDR 0x7FFFFFFFFFFFUL - -// Trampoline header layout for x86_64: magic(8) + file_offset(8) + vaddr(8) + size(8) = 32 bytes -struct __attribute__((packed)) TrampolineHeader { - uint64_t magic; - uint64_t file_offset; - uint64_t vaddr; - uint64_t trampoline_size; -}; - -// Linux flags -#define MAP_PRIVATE 0x02 -#define MAP_FIXED 0x10 -#define PROT_READ 0x1 -#define PROT_WRITE 0x2 -#define PROT_EXEC 0x4 - -typedef long (*syscall_stub_t)(void); -static syscall_stub_t syscall_entry = 0; -static char interp[256] = {0}; // Buffer for interpreter path - -#ifdef DEBUG -#define syscall_print(str, len) \ - do_syscall(SYS_write, 1, (long)(str), len, 0, 0, 0) -#else -#define syscall_print(str, len) -#endif - -static long do_syscall(long num, long a1, long a2, long a3, long a4, long a5, - long a6) { - if (!syscall_entry) - return -1; - - register long rax __asm__("rax") = num; - register long rdi __asm__("rdi") = a1; - register long rsi __asm__("rsi") = a2; - register long rdx __asm__("rdx") = a3; - register long r10 __asm__("r10") = a4; - register long r8 __asm__("r8") = a5; - register long r9 __asm__("r9") = a6; - - __asm__ volatile("leaq 1f(%%rip), %%rcx\n" - "jmp *%[entry]\n" - "1:\n" - : "+r"(rax) - : [entry] "r"(syscall_entry), "r"(rdi), "r"(rsi), "r"(rdx), - "r"(r10), "r"(r8), "r"(r9) - : "rcx", "r11", "memory"); - return rax; -} - -/* Re-implement some utility functions and re-define the structures to avoid - * dependency on libc. */ - -// Define the FileStat structure -struct FileStat { - unsigned long st_dev; - unsigned long st_ino; - unsigned long st_nlink; - - unsigned int st_mode; - unsigned int st_uid; - unsigned int st_gid; - unsigned int __pad0; - unsigned long st_rdev; - long st_size; - long st_blksize; - long st_blocks; /* Number 512-byte blocks allocated. */ - - unsigned long st_atime; - unsigned long st_atime_nsec; - unsigned long st_mtime; - unsigned long st_mtime_nsec; - unsigned long st_ctime; - unsigned long st_ctime_nsec; - long __unused[3]; -}; - -int memcmp(const void *s1, const void *s2, size_t n) { - const unsigned char *p1 = s1; - const unsigned char *p2 = s2; - while (n--) { - if (*p1 != *p2) { - return *p1 - *p2; - } - p1++; - p2++; - } - return 0; -} - -int strcmp(const char *s1, const char *s2) { - while (*s1 && (*s1 == *s2)) { - s1++; - s2++; - } - return *(unsigned char *)s1 - *(unsigned char *)s2; -} - -char *strncpy(char *dest, const char *src, size_t n) { - char *d = dest; - const char *s = src; - while (n-- && *s) { - *d++ = *s++; - } - while (n--) { - *d++ = '\0'; - } - return dest; -} - -static uint64_t read_u64(const void *p) { - uint64_t v; - __builtin_memcpy(&v, p, 8); - return v; -} - -static size_t align_up(size_t val, size_t align) { - size_t result = (val + align - 1) & ~(align - 1); - // Check for overflow (result < val means we wrapped) - if (result < val) return (size_t)-1; - return result; -} - -unsigned int la_version(unsigned int version __attribute__((unused))) { - return LAV_CURRENT; -} - -/// print value in hex -void print_hex(uint64_t data) { -#ifdef DEBUG - for (int i = 15; i >= 0; i--) { - unsigned char byte = (data >> (i * 4)) & 0xF; - if (byte < 10) { - syscall_print((&"0123456789"[byte]), 1); - } else { - syscall_print((&"abcdef"[byte - 10]), 1); - } - } - syscall_print("\n", 1); -#endif -} - -/// @brief Parse object to find the syscall entry point and the interpreter -/// path. -/// -/// The trampoline is already mapped by the litebox loader at (base + vaddr). -/// The entry point is at offset 0 of the mapped trampoline. The litebox loader -/// already validated the magic when parsing the file header. -int parse_object(const struct link_map *map) { - unsigned long max_addr = 0; - Elf64_Ehdr *eh = (Elf64_Ehdr *)map->l_addr; - if (memcmp(eh->e_ident, - "\x7f" - "ELF", - 4) != 0) { - syscall_print("[audit] not an ELF file\n", 24); - return 1; - } - Elf64_Phdr *phdrs = (Elf64_Phdr *)((char *)map->l_addr + eh->e_phoff); - for (int i = 0; i < eh->e_phnum; i++) { - if (phdrs[i].p_type == PT_LOAD) { - unsigned long vaddr_end = (phdrs[i].p_vaddr + phdrs[i].p_memsz); - if (vaddr_end > max_addr) { - max_addr = vaddr_end; - } - } else if (phdrs[i].p_type == PT_INTERP) { - strncpy(interp, (char *)map->l_addr + phdrs[i].p_vaddr, - sizeof(interp) - 1); - interp[sizeof(interp) - 1] = '\0'; // Ensure null termination - } - } - max_addr = align_up(max_addr, 0x1000); - void *trampoline_addr = (void *)map->l_addr + max_addr; - // The trampoline code has the syscall entry point at offset 0. - syscall_entry = (syscall_stub_t)read_u64(trampoline_addr); - if (syscall_entry == 0) { - syscall_print("[audit] syscall entry is null\n", 30); - return 1; - } - print_hex((uint64_t)syscall_entry); - return 0; -} - -unsigned int la_objopen(struct link_map *map, - Lmid_t lmid __attribute__((unused)), - uintptr_t *cookie __attribute__((unused))) { - syscall_print("[audit] la_objopen called\n", 26); - const char *path = map->l_name; - - if (!path || path[0] == '\0') { - // main binary should be called first. - if (map->l_addr != 0) { - // `map->l_addr` is zero for the main binary if it is not position - // independent. - if (parse_object(map) != 0) { - syscall_print("[audit] failed to parse main binary\n", 36); - return 0; - } - syscall_print("[audit] main binary is patched by libOS\n", 40); - syscall_print("[audit] interp=", 15); - syscall_print(interp, sizeof(interp) - 1); - syscall_print("\n", 1); - } - return 0; // main binary is patched by libOS - } - - if (syscall_entry == 0) { - // failed to get the syscall entry point from the main binary - // fall back to get it from ld-*.so, which should be called next. - if (parse_object(map) != 0) { - syscall_print("[audit] failed to parse ld\n", 27); - return 0; - } - syscall_print("[audit] ld is patched by libOS: \n", 33); - syscall_print(path, 32); - syscall_print("\n", 1); - return 0; // ld.so is patched by libOS - } - - if (interp[0] != '\0' && strcmp(path, interp) == 0) { - // successfully get the entry point and interpreter from the main binary - syscall_print("[audit] ld-*.so is patched by libOS\n", 36); - return 0; // ld.so is patched by libOS - } - - // Other shared libraries - syscall_print("[audit] la_objopen: path=", 25); - syscall_print(path, 32); - syscall_print("\n", 1); - - if (!syscall_entry) { - return 0; - } - - int fd = do_syscall(SYS_openat, AT_FDCWD, (long)path, 0, 0, 0, 0); - if (fd < 0) { - syscall_print("[audit] failed to open file\n", 28); - return 0; - } - - struct FileStat st; - if (do_syscall(SYS_fstat, fd, (long)&st, 0, 0, 0, 0) < 0) { - syscall_print("[audit] fstat failed\n", 21); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - long file_size = st.st_size; - - // File must be large enough to contain at least a trampoline header - if (file_size < (long)sizeof(struct TrampolineHeader)) { - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // The trampoline header is at the end of the file (last 32 bytes for x86_64). - // File layout: [ELF][padding][trampoline code][header] - // Read the last page that contains the header. - long header_offset = file_size - sizeof(struct TrampolineHeader); - long header_page_offset = header_offset & ~0xFFFUL; - - // Map the page containing the header - void *header_page = (void *)do_syscall(SYS_mmap, 0, 0x1000, PROT_READ, MAP_PRIVATE, fd, header_page_offset); - if ((uintptr_t)header_page >= (uintptr_t)-4096) { - syscall_print("[audit] mmap header page failed\n", 32); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // Read header from the mapped page - long header_in_page_offset = header_offset - header_page_offset; - const struct TrampolineHeader *header = (const struct TrampolineHeader *)((const char *)header_page + header_in_page_offset); - - // Check magic - if (header->magic != TRAMPOLINE_MAGIC) { - // If the prefix matches but the version differs, fail explicitly. - if (memcmp(header, "LITEBOX", 7) == 0) { - syscall_print("[audit] invalid trampoline version\n", 36); - do_syscall(SYS_munmap, (long)header_page, 0x1000, 0, 0, 0, 0); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - // No trampoline found - do_syscall(SYS_munmap, (long)header_page, 0x1000, 0, 0, 0, 0); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // Copy fields before unmapping - uint64_t tramp_file_offset = header->file_offset; - uint64_t tramp_vaddr = header->vaddr; - uint64_t tramp_size_raw = header->trampoline_size; - - do_syscall(SYS_munmap, (long)header_page, 0x1000, 0, 0, 0, 0); - syscall_print("[audit] found trampoline header at end of file\n", 47); - - // Validate trampoline size - if (tramp_size_raw == 0) { - syscall_print("[audit] trampoline code size invalid\n", 37); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // Verify file offset is page-aligned - if ((tramp_file_offset & 0xFFF) != 0) { - syscall_print("[audit] trampoline code not page-aligned\n", 41); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // The trampoline code should immediately precede the header. - if (tramp_file_offset + tramp_size_raw != (uint64_t)header_offset) { - syscall_print("[audit] trampoline extends beyond header\n", 41); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // Validate tramp_vaddr is within reasonable userspace bounds and page-aligned - if (tramp_vaddr > MAX_USERSPACE_ADDR || (tramp_vaddr & 0xFFF) != 0) { - syscall_print("[audit] trampoline vaddr out of bounds\n", 39); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - uint64_t tramp_addr = map->l_addr + tramp_vaddr; - uint64_t tramp_size = align_up(tramp_size_raw, 0x1000); - - // Check for overflow in align_up or address calculation - if (tramp_size == (size_t)-1 || tramp_addr < map->l_addr) { - syscall_print("[audit] trampoline size/addr overflow\n", 38); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // Use MAP_FIXED to place the trampoline at the exact required address. - // The loader ensures this range is not used by other mappings. - void *mapped = - (void *)do_syscall(SYS_mmap, tramp_addr, tramp_size, - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd, tramp_file_offset); - if ((uintptr_t)mapped >= (uintptr_t)-4096) { - syscall_print("[audit] mmap failed for trampoline\n", 35); - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; - } - - // Write the syscall entry point at the start of the trampoline code - __builtin_memcpy((char *)mapped, (const void *)&syscall_entry, 8); - do_syscall(SYS_mprotect, (long)mapped, tramp_size, PROT_READ | PROT_EXEC, 0, - 0, 0); - syscall_print("[audit] trampoline patched and protected\n", 41); - - do_syscall(SYS_close, fd, 0, 0, 0, 0, 0); - return 0; -} diff --git a/litebox_runner_linux_on_windows_userland/src/lib.rs b/litebox_runner_linux_on_windows_userland/src/lib.rs index c5afcbc71..826d42923 100644 --- a/litebox_runner_linux_on_windows_userland/src/lib.rs +++ b/litebox_runner_linux_on_windows_userland/src/lib.rs @@ -14,16 +14,16 @@ use std::path::PathBuf; /// Run Linux programs with LiteBox on unmodified Windows. /// -/// The program binary and all its dependencies (including `litebox_rtld_audit.so`) -/// must be provided inside a tar archive via `--initial-files`. The program path -/// refers to a path inside the tar archive. +/// The program binary and all its dependencies must be provided inside a tar +/// archive via `--initial-files`. The program path refers to a path inside the +/// tar archive. #[derive(Parser, Debug)] pub struct CliArgs { /// The program and arguments passed to it (e.g., `/bin/ls --color`). /// /// The program path refers to a path inside the tar archive provided via /// `--initial-files`. All binaries must be pre-rewritten with the syscall - /// rewriter and the tar must include `litebox_rtld_audit.so`. + /// rewriter. #[arg(required = true, trailing_var_arg = true, value_hint = clap::ValueHint::CommandWithArguments)] pub program_and_arguments: Vec, /// Environment variables passed to the program (`K=V` pairs; can be invoked multiple times) @@ -35,7 +35,7 @@ pub struct CliArgs { /// Allow using unstable options #[arg(short = 'Z', long = "unstable")] pub unstable: bool, - /// Tar archive containing the program, its shared libraries, and litebox_rtld_audit.so. + /// Tar archive containing the program and its shared libraries. /// /// All ELF binaries should be pre-rewritten with the syscall rewriter /// (e.g., via `litebox-packager`). @@ -60,7 +60,7 @@ pub fn run(cli_args: CliArgs) -> Result<()> { let platform = Platform::new(); litebox_platform_multiplex::set_platform(platform); - let mut shim_builder = litebox_shim_linux::LinuxShimBuilder::new(); + let shim_builder = litebox_shim_linux::LinuxShimBuilder::new(); let litebox = shim_builder.litebox(); // The program path is a Unix-style path inside the tar archive. @@ -83,7 +83,6 @@ pub fn run(cli_args: CliArgs) -> Result<()> { }; let initial_file_system = std::sync::Arc::new(initial_file_system); - shim_builder.set_load_filter(fixup_env); let shim = shim_builder.build(); let argv = cli_args .program_and_arguments @@ -128,13 +127,3 @@ pub fn run(cli_args: CliArgs) -> Result<()> { } std::process::exit(program.process.wait()) } - -fn fixup_env(envp: &mut Vec) { - // Always inject LD_AUDIT so the dynamic linker loads the audit library - // that sets up trampolines for rewritten binaries. - let p = c"LD_AUDIT=/lib/litebox_rtld_audit.so"; - let has_ld_audit = envp.iter().any(|var| var.as_c_str() == p); - if !has_ld_audit { - envp.push(p.into()); - } -} diff --git a/litebox_runner_linux_on_windows_userland/tests/loader.rs b/litebox_runner_linux_on_windows_userland/tests/loader.rs index e6f470e34..1a0849aef 100644 --- a/litebox_runner_linux_on_windows_userland/tests/loader.rs +++ b/litebox_runner_linux_on_windows_userland/tests/loader.rs @@ -4,9 +4,8 @@ //! Tests for the Windows userland runner. //! //! **NOTE:** These tests depend on pre-built Linux ELF binaries in `tests/test-bins/`, -//! including `litebox_rtld_audit.so`, shared libraries (`libc.so.6`, `ld-linux-x86-64.so.2`), -//! and test executables. These binaries must be rebuilt on Linux and re-committed whenever -//! the corresponding source code changes (e.g., `litebox_rtld_audit/rtld_audit.c`). +//! including shared libraries (`libc.so.6`, `ld-linux-x86-64.so.2`) +//! and test executables. #![cfg(all(target_os = "windows", target_arch = "x86_64"))] @@ -198,7 +197,6 @@ fn test_static_linked_prog_with_rewriter() { fn run_dynamic_linked_prog_with_rewriter( libs_to_rewrite: &[(&str, &str)], - libs_without_rewrite: &[(&str, &str)], exec_name: &str, cmd_args: &[&str], install_files: fn(std::path::PathBuf), @@ -276,22 +274,6 @@ fn run_dynamic_linked_prog_with_rewriter( ); } - // Copy libraries that are not needed to be rewritten (`litebox_rtld_audit.so`) - // to the tar directory - for (file, prefix) in libs_without_rewrite { - let src = test_dir.join(file); - let dst_dir = tar_src_path.join(prefix.trim_start_matches('/')); - let dst = dst_dir.join(file); - std::fs::create_dir_all(&dst_dir).unwrap(); - let _ = std::fs::remove_file(&dst); - println!( - "Copying {} to {}", - src.to_str().unwrap(), - dst.to_str().unwrap() - ); - std::fs::copy(&src, &dst).unwrap(); - } - // Install the required files (e.g., scripts) to tar directory's /out install_files(tar_src_path.join("out")); @@ -361,14 +343,6 @@ fn test_testcase_dynamic_with_rewriter() { ("libc.so.6", "/lib/x86_64-linux-gnu"), ("ld-linux-x86-64.so.2", "/lib64"), ]; - let libs_without_rewrite = [("litebox_rtld_audit.so", "/lib")]; - // Run - run_dynamic_linked_prog_with_rewriter( - &libs_to_rewrite, - &libs_without_rewrite, - exec_name, - &[], - |_| {}, - ); + run_dynamic_linked_prog_with_rewriter(&libs_to_rewrite, exec_name, &[], |_| {}); } diff --git a/litebox_runner_linux_userland/build.rs b/litebox_runner_linux_userland/build.rs deleted file mode 100644 index 3360e452a..000000000 --- a/litebox_runner_linux_userland/build.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -use std::path::PathBuf; - -const RTLD_AUDIT_DIR: &str = "../litebox_rtld_audit"; - -fn main() { - let mut make_cmd = std::process::Command::new("make"); - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); - let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - if target_arch != "x86_64" { - // XXX: Currently 32-bit x86 is unsupported (unimplemented), skip building - return; - } - make_cmd - .current_dir(RTLD_AUDIT_DIR) - .env("OUT_DIR", &out_dir) - .env("ARCH", target_arch); - if std::env::var("PROFILE").unwrap_or_default() == "debug" { - make_cmd.env("DEBUG", "1"); - } else { - // Explicitly remove DEBUG to prevent inheriting it from the - // parent environment, which would cause the C library to be - // built with debug prints enabled. - make_cmd.env_remove("DEBUG"); - } - // Force rebuild in case CFLAGS changed (e.g., debug -> release) but - // the source did not. - let _ = std::fs::remove_file(out_dir.join("litebox_rtld_audit.so")); - let output = make_cmd - .output() - .expect("Failed to execute make for rtld_audit"); - assert!( - output.status.success(), - "failed to build rtld_audit.so via make:\nstdout: {}\nstderr: {}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr), - ); - assert!( - out_dir.join("litebox_rtld_audit.so").exists(), - "Build failed to create necessary file" - ); - - println!("cargo:rerun-if-changed={RTLD_AUDIT_DIR}/rtld_audit.c"); - println!("cargo:rerun-if-changed={RTLD_AUDIT_DIR}/Makefile"); - println!("cargo:rerun-if-changed=build.rs"); -} diff --git a/litebox_runner_linux_userland/src/lib.rs b/litebox_runner_linux_userland/src/lib.rs index f15d1f7ab..cbfba14d6 100644 --- a/litebox_runner_linux_userland/src/lib.rs +++ b/litebox_runner_linux_userland/src/lib.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -use anyhow::{Result, anyhow}; +use anyhow::{Context as _, Result, anyhow}; use clap::Parser; use litebox::fs::{FileSystem as _, Mode}; use litebox_platform_multiplex::Platform; @@ -89,9 +89,6 @@ pub enum InterceptionBackend { Rewriter, } -static REQUIRE_RTLD_AUDIT: core::sync::atomic::AtomicBool = - core::sync::atomic::AtomicBool::new(false); - struct MmappedFile { data: &'static [u8], abs_path: PathBuf, @@ -130,14 +127,14 @@ pub fn run(cli_args: CliArgs) -> Result<()> { ) } - // --program-from-tar loads pre-rewritten binaries that depend on litebox_rtld_audit.so, - // which is only injected by the rewriter backend. + // --program-from-tar loads pre-rewritten binaries that require the rewriter + // backend's runtime trampoline setup. if cli_args.program_from_tar && !matches!(cli_args.interception_backend, InterceptionBackend::Rewriter) { anyhow::bail!( "--program-from-tar requires --interception-backend=rewriter \ - (the packaged binary is pre-rewritten and needs the audit library)" + (the packaged binary is pre-rewritten and needs the rewriter runtime)" ); } @@ -188,9 +185,17 @@ pub fn run(cli_args: CliArgs) -> Result<()> { .collect(); let file = mmapped_file(&prog)?; let data = if cli_args.rewrite_syscalls { - litebox_syscall_rewriter::hook_syscalls_in_elf(file.data, None) - .unwrap() - .into() + let (rewritten, skipped_addrs) = + litebox_syscall_rewriter::hook_syscalls_in_elf(file.data, None) + .with_context(|| format!("failed to rewrite {}", prog.display()))?; + if !skipped_addrs.is_empty() { + eprintln!( + "warning: program has {} unpatchable syscall instruction(s) at {:?}", + skipped_addrs.len(), + skipped_addrs, + ); + } + rewritten.into() } else { let data = file.data.into(); cow_eligible_regions.push(file); @@ -219,7 +224,7 @@ pub fn run(cli_args: CliArgs) -> Result<()> { } litebox_platform_multiplex::set_platform(platform); - let mut shim_builder = litebox_shim_linux::LinuxShimBuilder::new(); + let shim_builder = litebox_shim_linux::LinuxShimBuilder::new(); let litebox = shim_builder.litebox(); let initial_file_system = { let mut in_mem = litebox::fs::in_mem::FileSystem::new(litebox); @@ -298,34 +303,10 @@ pub fn run(cli_args: CliArgs) -> Result<()> { } }); - // When using the rewriter backend, automatically include litebox_rtld_audit.so - // in the filesystem so tests and users don't need to include it in tar files + // When using the rewriter backend, the shim's mmap hook handles + // syscall patching at runtime — no audit library needed. match cli_args.interception_backend { - InterceptionBackend::Rewriter => { - #[cfg(not(target_arch = "x86_64"))] - eprintln!("WARN: litebox_rtld_audit not currently supported on non-x86_64 arch"); - #[cfg(target_arch = "x86_64")] - in_mem.with_root_privileges(|fs| { - let rwxr_xr_x = Mode::RWXU | Mode::RGRP | Mode::XGRP | Mode::ROTH | Mode::XOTH; - let _ = fs.mkdir("/lib", rwxr_xr_x); - let fd = fs - .open( - "/lib/litebox_rtld_audit.so", - litebox::fs::OFlags::WRONLY | litebox::fs::OFlags::CREAT, - rwxr_xr_x, - ) - .expect("Failed to create /lib/litebox_rtld_audit.so"); - fs.initialize_primarily_read_heavy_file( - &fd, - include_bytes!(concat!(env!("OUT_DIR"), "/litebox_rtld_audit.so")).into(), - ); - fs.close(&fd) - .expect("Failed to close /lib/litebox_rtld_audit.so"); - }); - } - InterceptionBackend::Seccomp => { - // No need to include rtld_audit.so for seccomp backend - } + InterceptionBackend::Rewriter | InterceptionBackend::Seccomp => {} } let tar_ro = litebox::fs::tar_ro::FileSystem::new(litebox, tar_data.into()); @@ -349,7 +330,6 @@ pub fn run(cli_args: CliArgs) -> Result<()> { let initial_file_system = std::sync::Arc::new(initial_file_system); - shim_builder.set_load_filter(fixup_env); let shim = shim_builder.build(); let shutdown = std::sync::Arc::new(core::sync::atomic::AtomicBool::new(false)); @@ -388,7 +368,7 @@ pub fn run(cli_args: CliArgs) -> Result<()> { match cli_args.interception_backend { InterceptionBackend::Seccomp => platform.enable_seccomp_based_syscall_interception(), InterceptionBackend::Rewriter => { - REQUIRE_RTLD_AUDIT.store(true, core::sync::atomic::Ordering::SeqCst); + // Runtime patching is handled by the shim's mmap hook — nothing to do here. } } @@ -469,14 +449,3 @@ fn pin_thread_to_cpu(cpu: usize) { } } } - -fn fixup_env(envp: &mut Vec) { - // Enable the audit library to load trampoline code for rewritten binaries. - if REQUIRE_RTLD_AUDIT.load(core::sync::atomic::Ordering::SeqCst) { - let p = c"LD_AUDIT=/lib/litebox_rtld_audit.so"; - let has_ld_audit = envp.iter().any(|var| var.as_c_str() == p); - if !has_ld_audit { - envp.push(p.into()); - } - } -} diff --git a/litebox_runner_linux_userland/tests/common/mod.rs b/litebox_runner_linux_userland/tests/common/mod.rs index e9b6a9810..3f761f64a 100644 --- a/litebox_runner_linux_userland/tests/common/mod.rs +++ b/litebox_runner_linux_userland/tests/common/mod.rs @@ -80,7 +80,7 @@ fn find_rewriter_source_files() -> Vec { /// Compile C code into an executable with caching pub fn compile(src_path: &str, unique_name: &str, exec_or_lib: bool, nolibc: bool) -> PathBuf { - let dir_path = std::env::var("OUT_DIR").unwrap(); + let dir_path = env!("CARGO_TARGET_TMPDIR").to_string(); let path = std::path::Path::new(dir_path.as_str()).join(unique_name); let output = path.to_str().unwrap(); diff --git a/litebox_runner_linux_userland/tests/loader.rs b/litebox_runner_linux_userland/tests/loader.rs index 9850ba843..2ff79f97c 100644 --- a/litebox_runner_linux_userland/tests/loader.rs +++ b/litebox_runner_linux_userland/tests/loader.rs @@ -234,7 +234,7 @@ void _start() { #[test] fn test_syscall_rewriter() { - let dir_path = std::env::var("OUT_DIR").unwrap(); + let dir_path = env!("CARGO_TARGET_TMPDIR").to_string(); let src_path = std::path::Path::new(dir_path.as_str()).join("hello_exec_nolibc.c"); std::fs::write(src_path.clone(), HELLO_WORLD_NOLIBC).unwrap(); let path = std::path::Path::new(dir_path.as_str()).join("hello_exec_nolibc"); diff --git a/litebox_runner_linux_userland/tests/run.rs b/litebox_runner_linux_userland/tests/run.rs index 219d27da1..3da964a4f 100644 --- a/litebox_runner_linux_userland/tests/run.rs +++ b/litebox_runner_linux_userland/tests/run.rs @@ -32,7 +32,7 @@ impl Runner { Backend::Rewriter => "rewriter", Backend::Seccomp => "seccomp", }; - let dir_path = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let dir_path = PathBuf::from(env!("CARGO_TARGET_TMPDIR")); let path = match backend { Backend::Seccomp => target.to_path_buf(), Backend::Rewriter => { @@ -206,7 +206,7 @@ fn find_c_test_files(dir: &str) -> Vec { files } -// our rtld_audit does not support x86 yet +// Syscall rewriting does not support x86 yet #[cfg(target_arch = "x86_64")] #[test] fn test_dynamic_lib_with_rewriter() { diff --git a/litebox_runner_optee_on_linux_userland/src/lib.rs b/litebox_runner_optee_on_linux_userland/src/lib.rs index 4cc021dea..16c31b117 100644 --- a/litebox_runner_optee_on_linux_userland/src/lib.rs +++ b/litebox_runner_optee_on_linux_userland/src/lib.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -use anyhow::Result; +use anyhow::{Context as _, Result}; use clap::Parser; use litebox_common_optee::{TeeUuid, UteeEntryFunc, UteeParamOwned}; use litebox_platform_multiplex::Platform; @@ -65,9 +65,21 @@ pub enum InterceptionBackend { pub fn run(cli_args: CliArgs) -> Result<()> { let ldelf_data: Vec = { let ldelf = PathBuf::from(&cli_args.ldelf); - let data = std::fs::read(ldelf).unwrap(); + let data = + std::fs::read(&ldelf).with_context(|| format!("failed to read {}", cli_args.ldelf))?; if cli_args.rewrite_syscalls { - litebox_syscall_rewriter::hook_syscalls_in_elf(&data, None).unwrap() + let (rewritten, skipped_addrs) = + litebox_syscall_rewriter::hook_syscalls_in_elf(&data, None) + .with_context(|| format!("failed to rewrite {}", cli_args.ldelf))?; + if !skipped_addrs.is_empty() { + eprintln!( + "warning: {} has {} unpatchable syscall instruction(s) at {:?}", + cli_args.ldelf, + skipped_addrs.len(), + skipped_addrs, + ); + } + rewritten } else { data } @@ -75,9 +87,21 @@ pub fn run(cli_args: CliArgs) -> Result<()> { let prog_data: Vec = { let prog = PathBuf::from(&cli_args.program); - let data = std::fs::read(prog).unwrap(); + let data = + std::fs::read(&prog).with_context(|| format!("failed to read {}", cli_args.program))?; if cli_args.rewrite_syscalls { - litebox_syscall_rewriter::hook_syscalls_in_elf(&data, None).unwrap() + let (rewritten, skipped_addrs) = + litebox_syscall_rewriter::hook_syscalls_in_elf(&data, None) + .with_context(|| format!("failed to rewrite {}", cli_args.program))?; + if !skipped_addrs.is_empty() { + eprintln!( + "warning: {} has {} unpatchable syscall instruction(s) at {:?}", + cli_args.program, + skipped_addrs.len(), + skipped_addrs, + ); + } + rewritten } else { data } diff --git a/litebox_shim_linux/Cargo.toml b/litebox_shim_linux/Cargo.toml index 94d889a7f..ff0b4ea4e 100644 --- a/litebox_shim_linux/Cargo.toml +++ b/litebox_shim_linux/Cargo.toml @@ -16,6 +16,7 @@ syscalls = { version = "0.6", default-features = false } seq-macro = "0.3" ringbuf = { version = "0.4.8", default-features = false, features = ["alloc"] } zerocopy = { version = "0.8", default-features = false, features = ["derive"] } +litebox_syscall_rewriter = { version = "0.1.0", path = "../litebox_syscall_rewriter", default-features = false } [features] default = ["platform_linux_userland"] diff --git a/litebox_shim_linux/src/lib.rs b/litebox_shim_linux/src/lib.rs index 2834f7b72..38f96b535 100644 --- a/litebox_shim_linux/src/lib.rs +++ b/litebox_shim_linux/src/lib.rs @@ -200,6 +200,7 @@ impl LinuxShimBuilder { next_thread_id: 2.into(), // start from 2, as 1 is used by the main thread litebox: self.litebox, unix_addr_table: litebox::sync::RwLock::new(syscalls::unix::UnixAddrTable::new()), + elf_patch_cache: litebox::sync::Mutex::new(alloc::collections::BTreeMap::new()), }); LinuxShim(global) } @@ -257,6 +258,7 @@ impl LinuxShim { fs: Arc::new(syscalls::file::FsState::new()).into(), files: files.into(), signals: syscalls::signal::SignalState::new_process(), + suppress_elf_runtime_patch: Cell::new(false), }, }; entrypoints.task.load_program( @@ -1059,6 +1061,8 @@ struct GlobalState { next_thread_id: core::sync::atomic::AtomicI32, /// UNIX domain socket address table unix_addr_table: litebox::sync::RwLock>, + /// Per-process collection of ELF patching state for runtime syscall rewriting. + elf_patch_cache: litebox::sync::Mutex, } struct Task { @@ -1082,6 +1086,9 @@ struct Task { files: RefCell>>, /// Signal state signals: syscalls::signal::SignalState, + /// Suppresses runtime ELF patching in `do_mmap_file` while the ELF loader + /// is actively loading a binary (prevents double-mapping the trampoline). + suppress_elf_runtime_patch: Cell, } impl Drop for Task { @@ -1121,6 +1128,7 @@ mod test_utils { fs: Arc::new(syscalls::file::FsState::new()).into(), files: files.into(), signals: syscalls::signal::SignalState::new_process(), + suppress_elf_runtime_patch: Cell::new(false), global: self, } } @@ -1145,6 +1153,7 @@ mod test_utils { fs: self.fs.clone(), files: self.files.clone(), signals: self.signals.clone_for_new_task(), + suppress_elf_runtime_patch: Cell::new(false), }; Some(task) } diff --git a/litebox_shim_linux/src/loader/elf.rs b/litebox_shim_linux/src/loader/elf.rs index 0d62030a8..36f6933aa 100644 --- a/litebox_shim_linux/src/loader/elf.rs +++ b/litebox_shim_linux/src/loader/elf.rs @@ -7,10 +7,14 @@ use alloc::{ffi::CString, vec::Vec}; use litebox::{ fs::{Mode, OFlags}, mm::linux::{CreatePagesFlags, MappingError, PAGE_SIZE}, - platform::{RawConstPointer as _, SystemInfoProvider as _}, + platform::{RawConstPointer as _, RawMutPointer as _, SystemInfoProvider as _}, utils::{ReinterpretSignedExt, TruncateExt}, }; -use litebox_common_linux::{MapFlags, errno::Errno, loader::ElfParsedFile}; +use litebox_common_linux::{ + MapFlags, + errno::Errno, + loader::{ElfParsedFile, ReadAt as _}, +}; use thiserror::Error; use crate::{ @@ -148,6 +152,85 @@ impl litebox_common_linux::loader::MapMemory for ElfFile<'_, FS> { } } +/// A [`MapMemory`](litebox_common_linux::loader::MapMemory) wrapper that reads +/// file-backed data from an in-memory buffer instead of from a file descriptor. +/// Used when the loader has patched the ELF binary on the fly (e.g. syscall +/// rewriting of the dynamic linker). +/// +/// `reserve`, `map_zero`, and `protect` are delegated to the underlying +/// [`ElfFile`]; `map_file` is replaced by `map_zero` + a memory copy from the +/// patched buffer. +struct PatchedMapper<'a, 'b, FS: ShimFS> { + inner: &'b mut ElfFile<'a, FS>, + data: &'b [u8], +} + +impl litebox_common_linux::loader::MapMemory for PatchedMapper<'_, '_, FS> { + type Error = Errno; + + fn reserve(&mut self, len: usize, align: usize) -> Result { + self.inner.reserve(len, align) + } + + fn map_file( + &mut self, + address: usize, + len: usize, + offset: u64, + prot: &litebox_common_linux::loader::Protection, + ) -> Result<(), Self::Error> { + // Allocate anonymous RW pages, copy from the in-memory buffer, then + // apply the requested protection. + // + // TODO: if the copy or protect step fails, the pages allocated by + // map_zero are leaked because the MapMemory trait has no unmap + // method, and no caller cleans up partially-mapped segments either. + // Add an `unmap` method to MapMemory and clean up the reserved + // region on failure in ElfParsedFile::load(). + self.inner.map_zero( + address, + len, + &litebox_common_linux::loader::Protection { + read: true, + write: true, + execute: false, + }, + )?; + + let offset: usize = offset.truncate(); + if offset < self.data.len() { + let end = core::cmp::min(offset + len, self.data.len()); + let src = &self.data[offset..end]; + let dest = MutPtr::::from_usize(address); + dest.copy_from_slice(0, src).ok_or(Errno::EFAULT)?; + } + + // Set final permissions if different from the writable mapping above. + if !prot.write || prot.execute { + self.inner.protect(address, len, prot)?; + } + Ok(()) + } + + fn map_zero( + &mut self, + address: usize, + len: usize, + prot: &litebox_common_linux::loader::Protection, + ) -> Result<(), Self::Error> { + self.inner.map_zero(address, len, prot) + } + + fn protect( + &mut self, + address: usize, + len: usize, + prot: &litebox_common_linux::loader::Protection, + ) -> Result<(), Self::Error> { + self.inner.protect(address, len, prot) + } +} + /// Struct to hold the information needed to start the program /// (entry point and user stack top). pub struct ElfLoadInfo { @@ -165,6 +248,9 @@ pub(crate) struct ElfLoader<'a, FS: ShimFS> { struct FileAndParsed<'a, FS: ShimFS> { file: ElfFile<'a, FS>, parsed: ElfParsedFile, + /// When the rewriter backend is active and the binary was not pre-patched, + /// the loader patches it on the fly and loads from this in-memory copy. + patched_data: Option>, } impl<'a, FS: ShimFS> FileAndParsed<'a, FS> { @@ -172,8 +258,124 @@ impl<'a, FS: ShimFS> FileAndParsed<'a, FS> { let file = ElfFile::new(task, path).map_err(ElfLoaderError::OpenError)?; let mut parsed = litebox_common_linux::loader::ElfParsedFile::parse(&mut &file) .map_err(ElfLoaderError::ParseError)?; - parsed.parse_trampoline(&mut &file, task.global.platform.get_syscall_entry_point())?; - Ok(Self { file, parsed }) + + let syscall_entry_point = task.global.platform.get_syscall_entry_point(); + let trampoline_result = parsed.parse_trampoline(&mut &file, syscall_entry_point); + + // If the rewriter backend is active (syscall_entry_point != 0) and the + // binary lacks a trampoline, patch it on the fly so that both the main + // program and the dynamic linker are covered. + // + // Only attempt runtime patching for UnpatchedBinary — other errors + // (BadTrampolineVersion, BadTrampoline, Io) indicate a corrupt or + // incompatible pre-patched binary that should not be re-patched. + let patched_data = if syscall_entry_point != 0 + && matches!( + trampoline_result, + Err(litebox_common_linux::loader::ElfParseError::UnpatchedBinary) + ) { + let size: usize = (&mut &file) + .size() + .map_err(ElfLoaderError::OpenError)? + .truncate(); + let mut buf = alloc::vec![0u8; size]; + (&mut &file) + .read_at(0, &mut buf) + .map_err(ElfLoaderError::OpenError)?; + + match litebox_syscall_rewriter::hook_syscalls_in_elf(&buf, None) { + Ok((patched, skipped_addrs)) => { + if !skipped_addrs.is_empty() { + litebox::log_println!( + task.global.platform, + "warning: {} unpatchable syscall instruction(s) (addresses: {:?})", + skipped_addrs.len(), + skipped_addrs, + ); + } + // Re-parse the patched binary and extract its trampoline. + parsed = + litebox_common_linux::loader::ElfParsedFile::parse(&mut patched.as_slice()) + .map_err(ElfLoaderError::ParseError)?; + parsed + .parse_trampoline(&mut patched.as_slice(), syscall_entry_point) + .map_err(ElfLoaderError::ParseError)?; + Some(patched) + } + Err( + litebox_syscall_rewriter::Error::UnsupportedExecutable(_) + | litebox_syscall_rewriter::Error::UnsupportedObjectFile(_) + | litebox_syscall_rewriter::Error::NoTextSectionFound + | litebox_syscall_rewriter::Error::NoSyscallInstructionsFound + | litebox_syscall_rewriter::Error::AlreadyHooked, + ) => { + // These are expected non-fatal cases: + // - BUN: can't be statically patched but the runtime mmap + // hook will patch code segments as they are mapped. + // - Object files / no .text / no syscalls / already hooked: + // nothing to patch. + None + } + Err(e) => { + // Unexpected rewriter failure (parse error, disassembly + // failure, etc.). Proceed without a trampoline — the + // runtime mmap hook may still patch individual segments. + litebox::log_println!( + task.global.platform, + "warning: syscall rewriter failed: {}; \ + falling back to runtime patching", + e + ); + None + } + } + } else if syscall_entry_point != 0 { + // Rewriter is active but trampoline_result is an error other than + // UnpatchedBinary (e.g. BadTrampolineVersion, BadTrampoline, Io). + // Propagate the error rather than silently proceeding. + trampoline_result.map_err(ElfLoaderError::ParseError)?; + None + } else { + None + }; + + Ok(Self { + file, + parsed, + patched_data, + }) + } + + /// Load the ELF into guest memory, choosing the right mapper depending on + /// whether the binary was patched in memory. + fn load_mapped( + &mut self, + platform: &(impl litebox::platform::RawPointerProvider + litebox::platform::SystemInfoProvider), + ) -> Result { + // Suppress runtime ELF patching (maybe_patch_exec_segment) when the + // loader will map the trampoline itself via load_trampoline(). Without + // this, both paths would map the same region — the second MAP_FIXED + // destroys the first mapping. + // + // When patched_data is Some the PatchedMapper path doesn't go through + // do_mmap_file so the flag is a no-op, but setting it is harmless and + // keeps the logic simple. + self.file + .task + .suppress_elf_runtime_patch + .set(self.patched_data.is_some() || self.parsed.has_trampoline()); + let result = if let Some(ref data) = self.patched_data { + let mut mapper = PatchedMapper { + inner: &mut self.file, + data, + }; + self.parsed.load(&mut mapper, &mut &*platform) + } else { + self.parsed.load(&mut self.file, &mut &*platform) + }; + self.file.task.suppress_elf_runtime_patch.set(false); + + Ok(result?) } } @@ -204,18 +406,11 @@ impl<'a, FS: ShimFS> ElfLoader<'a, FS> { let global = &self.main.file.task.global; // Load the main ELF file first so that it gets privileged addresses. - let info = self - .main - .parsed - .load(&mut self.main.file, &mut &*global.platform)?; + let info = self.main.load_mapped(global.platform)?; // Load the interpreter ELF file, if any. let interp = if let Some(interp) = &mut self.interp { - Some( - interp - .parsed - .load(&mut interp.file, &mut &*global.platform)?, - ) + Some(interp.load_mapped(global.platform)?) } else { None }; diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index 03bf151ad..b0877001d 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -536,6 +536,10 @@ impl Task { /// Handle syscall `close` pub(crate) fn sys_close(&self, fd: i32) -> Result<(), Errno> { + // Finalize any in-progress ELF patching for this fd (mprotect + // trampoline RW→RX) before closing the descriptor. + self.finalize_elf_patch(fd); + let Ok(raw_fd) = u32::try_from(fd).and_then(usize::try_from) else { return Err(Errno::EBADF); }; @@ -2012,8 +2016,14 @@ impl Task { Ok(oldfd) }; } - // Close whatever is at newfd before duping into it + // Close whatever is at newfd before duping into it. + // Finalize any in-progress ELF patching for the target fd first, + // since dup2/dup3 implicitly closes it without going through + // sys_close. let newfd_usize = usize::try_from(newfd).or(Err(Errno::EBADF))?; + if let Ok(fd) = i32::try_from(newfd) { + self.finalize_elf_patch(fd); + } let _ = self.do_close(newfd_usize); self.do_dup_inner( oldfd_usize, diff --git a/litebox_shim_linux/src/syscalls/mm.rs b/litebox_shim_linux/src/syscalls/mm.rs index ce6c3513c..42826a538 100644 --- a/litebox_shim_linux/src/syscalls/mm.rs +++ b/litebox_shim_linux/src/syscalls/mm.rs @@ -4,10 +4,11 @@ //! Implementation of memory management related syscalls, eg., `mmap`, `munmap`, etc. //! Most of these syscalls which are not backed by files are implemented in [`litebox_common_linux::mm`]. +use alloc::collections::BTreeMap; use litebox::{ mm::linux::{MappingError, PAGE_SIZE, PageRange}, platform::{ - PageManagementProvider, RawConstPointer, RawMutPointer, + PageManagementProvider, RawConstPointer, RawMutPointer, SystemInfoProvider, page_mgmt::{FixedAddressBehavior, MemoryRegionPermissions}, }, }; @@ -17,6 +18,32 @@ use crate::MutPtr; use crate::ShimFS; use crate::Task; +/// Per-fd state for the shim's runtime ELF syscall rewriter. +/// +/// Tracks base address and trampoline write cursor for each ELF file that +/// has executable segments mapped via `do_mmap_file()`. +pub(crate) struct ElfPatchState { + /// Whether this file is already pre-patched (trampoline magic found at file tail). + pub pre_patched: bool, + /// For pre-patched binaries: file offset and size of the trampoline data. + pub trampoline_file_offset: u64, + pub trampoline_file_size: usize, + /// Start address of the trampoline region (runtime). + pub trampoline_addr: usize, + /// Current write position within the trampoline (byte offset from `trampoline_addr`). + pub trampoline_cursor: usize, + /// Whether the trampoline region has been allocated. + pub trampoline_mapped: bool, + /// Total number of trampoline bytes currently mapped. + pub trampoline_mapped_len: usize, + /// Whether any runtime-generated stubs were successfully linked from code + /// in this fd to the trampoline. + pub runtime_patches_committed: bool, +} + +/// Per-process collection of ELF patching state, keyed by fd number. +pub(crate) type ElfPatchCache = BTreeMap; + #[inline] fn align_up(addr: usize, align: usize) -> usize { debug_assert!(align.is_power_of_two()); @@ -76,12 +103,42 @@ impl Task { fd: i32, offset: usize, ) -> Result, MappingError> { - if let Some(cow_result) = + let is_exec = prot.contains(ProtFlags::PROT_EXEC); + + // Perform the normal mmap first (CoW or memcpy fallback). + let result = if let Some(cow_result) = self.try_cow_mmap_file(suggested_addr, len, &prot, &flags, fd, offset) { - return cow_result; + cow_result? + } else { + self.do_mmap_file_memcpy(suggested_addr, len, prot, flags, fd, offset)? + }; + + // Runtime syscall rewriting: patch PROT_EXEC segments in-place. + // Suppressed during ELF loader's load() sequence because the loader + // maps the trampoline itself via load_trampoline(). Running both + // paths would double-map the trampoline, with the second MAP_FIXED + // destroying the first mapping. + if !self.suppress_elf_runtime_patch.get() { + if is_exec { + let syscall_entry = self.global.platform.get_syscall_entry_point(); + if syscall_entry != 0 + && !self.maybe_patch_exec_segment(result, len, fd, offset, syscall_entry) + { + // Trampoline setup failed for a pre-patched binary whose + // .text already contains JMPs to the trampoline address. + // Continuing would guarantee a SIGSEGV on the first + // rewritten syscall, so fail the mmap instead. + let _ = self.sys_munmap(result, len); + return Err(MappingError::OutOfMemory); + } + } else if offset == 0 { + // First mmap at offset 0: record the base address for later patching. + self.init_elf_patch_state(fd, result.as_usize()); + } } - self.do_mmap_file_memcpy(suggested_addr, len, prot, flags, fd, offset) + + Ok(result) } /// Attempt to create a CoW mapping for a file with static backing data. @@ -352,6 +409,502 @@ impl Task { ) -> Result<(), Errno> { litebox_common_linux::mm::sys_madvise(&self.global.pm, addr, len, advice) } + + // ── Runtime ELF syscall patching ───────────────────────────────────── + + /// Initialize ELF patch state for an fd on its first mmap at offset 0. + /// + /// Reads the ELF header to determine the trampoline address (page-aligned + /// end of the highest PT_LOAD segment) and checks the file tail for the + /// trampoline magic to determine if it's pre-patched. + /// + /// x86_64 only: assumes 64-bit ELF layout and program header offsets. + #[allow(clippy::cast_possible_truncation)] + fn init_elf_patch_state(&self, fd: i32, base_addr: usize) { + // Quick check: skip if already initialized. + if self.global.elf_patch_cache.lock().contains_key(&fd) { + return; + } + + // Read the ELF header (first 64 bytes covers both 32-bit and 64-bit). + let mut ehdr_buf = [0u8; 64]; + match self.sys_read(fd, &mut ehdr_buf, Some(0)) { + Ok(n) if n == ehdr_buf.len() => {} + _ => return, // Not readable or short read, skip + } + + // Verify ELF magic + if &ehdr_buf[0..4] != b"\x7fELF" { + return; // Not an ELF file + } + + // Parse as 64-bit ELF (runtime patching is x86-64 only). + let e_phoff = u64::from_le_bytes(ehdr_buf[32..40].try_into().unwrap()) as usize; + let e_phentsize = u16::from_le_bytes(ehdr_buf[54..56].try_into().unwrap()) as usize; + let e_phnum = u16::from_le_bytes(ehdr_buf[56..58].try_into().unwrap()) as usize; + let e_type = u16::from_le_bytes(ehdr_buf[16..18].try_into().unwrap()); + + // Validate e_phentsize: must be at least sizeof(Elf64_Phdr) = 56 bytes, + // otherwise the field accesses (e.g. ph[40..48] for p_memsz) will panic. + if e_phentsize < 56 { + return; + } + + // Read program headers to find max PT_LOAD end + let phdrs_size = e_phentsize * e_phnum; + if phdrs_size == 0 || phdrs_size > 0x10000 { + return; // Sanity check + } + let mut phdrs_buf = alloc::vec![0u8; phdrs_size]; + match self.sys_read(fd, &mut phdrs_buf, Some(e_phoff)) { + Ok(n) if n == phdrs_buf.len() => {} + _ => return, + } + + // Find highest PT_LOAD end (p_vaddr + p_memsz) + let mut max_load_end: u64 = 0; + for i in 0..e_phnum { + let ph = &phdrs_buf[i * e_phentsize..][..e_phentsize]; + let p_type = u32::from_le_bytes(ph[0..4].try_into().unwrap()); + if p_type != 1 { + // PT_LOAD = 1 + continue; + } + let p_vaddr = u64::from_le_bytes(ph[16..24].try_into().unwrap()); + let p_memsz = u64::from_le_bytes(ph[40..48].try_into().unwrap()); + let end = p_vaddr + p_memsz; + if end > max_load_end { + max_load_end = end; + } + } + + if max_load_end == 0 { + return; // No PT_LOAD segments + } + + // For ET_DYN (PIE/shared libs), p_vaddr is relative to base_addr. + // For ET_EXEC, p_vaddr is absolute and base_addr is 0. + let trampoline_vaddr = if e_type == 3 { + // ET_DYN + base_addr + (max_load_end as usize).next_multiple_of(PAGE_SIZE) + } else { + // ET_EXEC + (max_load_end as usize).next_multiple_of(PAGE_SIZE) + }; + + // Check if file is pre-patched by reading the last 32 bytes for magic + let (pre_patched, tramp_file_offset, tramp_vaddr, tramp_file_size) = + self.check_trampoline_magic(fd); + + // For pre-patched binaries, use the vaddr from the header instead. + let trampoline_vaddr = if pre_patched { + if e_type == 3 { + base_addr + tramp_vaddr as usize + } else { + tramp_vaddr as usize + } + } else { + trampoline_vaddr + }; + + // Insert under lock (re-check for races). + let mut cache = self.global.elf_patch_cache.lock(); + cache.entry(fd).or_insert(ElfPatchState { + pre_patched, + trampoline_file_offset: tramp_file_offset, + trampoline_file_size: tramp_file_size as usize, + trampoline_addr: trampoline_vaddr, + trampoline_cursor: 0, + trampoline_mapped: false, + trampoline_mapped_len: 0, + runtime_patches_committed: false, + }); + } + + /// Check if a file has the LITEBOX trampoline magic at its tail. + /// Returns (is_pre_patched, file_offset, vaddr, trampoline_size). + fn check_trampoline_magic(&self, fd: i32) -> (bool, u64, u64, u64) { + let header_size: usize = if cfg!(target_pointer_width = "64") { + 32 // TrampolineHeader64: magic(8) + file_offset(8) + vaddr(8) + size(8) + } else { + 20 // TrampolineHeader32: magic(8) + file_offset(4) + vaddr(4) + size(4) + }; + let Ok(stat) = self.sys_fstat(fd) else { + return (false, 0, 0, 0); + }; + let file_size = stat.st_size; + if file_size < header_size { + return (false, 0, 0, 0); + } + let mut tail = [0u8; 32]; // max header size + let tail = &mut tail[..header_size]; + match self.sys_read(fd, tail, Some(file_size - header_size)) { + Ok(n) if n == tail.len() => {} + _ => return (false, 0, 0, 0), + } + if &tail[0..8] != litebox_syscall_rewriter::TRAMPOLINE_MAGIC { + return (false, 0, 0, 0); + } + if cfg!(target_pointer_width = "64") { + // Parse 64-bit header: magic(8) | file_offset(8) | vaddr(8) | size(8) + let file_offset = u64::from_le_bytes(tail[8..16].try_into().unwrap()); + let vaddr = u64::from_le_bytes(tail[16..24].try_into().unwrap()); + let trampoline_size = u64::from_le_bytes(tail[24..32].try_into().unwrap()); + (true, file_offset, vaddr, trampoline_size) + } else { + // Parse 32-bit header: magic(8) | file_offset(4) | vaddr(4) | size(4) + let file_offset = u64::from(u32::from_le_bytes(tail[8..12].try_into().unwrap())); + let vaddr = u64::from(u32::from_le_bytes(tail[12..16].try_into().unwrap())); + let trampoline_size = u64::from(u32::from_le_bytes(tail[16..20].try_into().unwrap())); + (true, file_offset, vaddr, trampoline_size) + } + } + + /// Patch an executable segment in-place after it has been mapped. + /// + /// For pre-patched binaries: maps the trampoline from the file and writes + /// the syscall entry point. + /// For unpatched binaries: calls `patch_code_segment()` to rewrite syscall + /// instructions and places the generated stubs in the trampoline region. + /// + /// Returns `true` on success or non-fatal skip. Returns `false` when a + /// pre-patched binary's trampoline could not be set up — the caller must + /// fail the mapping because the code already contains JMPs to the + /// trampoline address. + #[allow(clippy::cast_possible_truncation)] + fn maybe_patch_exec_segment( + &self, + mapped_addr: MutPtr, + len: usize, + fd: i32, + offset: usize, + syscall_entry: usize, + ) -> bool { + // Initialize patch state if this is the first mmap for this fd. + if offset == 0 { + self.init_elf_patch_state(fd, mapped_addr.as_usize()); + } + + // This lock guards the elf_patch_cache and is held for the entire + // patching operation. In practice this is fine because the dynamic + // linker loads shared libraries sequentially. + let mut cache = self.global.elf_patch_cache.lock(); + let Some(state) = cache.get_mut(&fd) else { + return true; // No patch state — not an ELF we're tracking + }; + + if state.pre_patched { + // Pre-patched binary: map the trampoline data from the file. + if !state.trampoline_mapped && state.trampoline_file_size > 0 { + let tramp_addr = state.trampoline_addr; + let tramp_len = align_up(state.trampoline_file_size, PAGE_SIZE); + + // Allocate RW region at the trampoline address. + let alloc_result = self + .do_mmap_anonymous( + Some(tramp_addr), + tramp_len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE | MapFlags::MAP_FIXED, + ) + .or_else(|_| { + self.do_mmap_anonymous( + Some(tramp_addr), + tramp_len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, + ) + }); + let Ok(alloc_ptr) = alloc_result else { + return false; + }; + let actual_addr = alloc_ptr.as_usize(); + if actual_addr != tramp_addr { + let _ = self.sys_munmap(MutPtr::::from_usize(actual_addr), tramp_len); + return false; + } + + // Read trampoline data from the file. + let mut tramp_data = alloc::vec![0u8; state.trampoline_file_size]; + let file_off = state.trampoline_file_offset as usize; + let tramp_ptr = MutPtr::::from_usize(tramp_addr); + match self.sys_read(fd, &mut tramp_data, Some(file_off)) { + Ok(n) if n == tramp_data.len() => {} + _ => { + let _ = self.sys_munmap(tramp_ptr, tramp_len); + return false; + } + } + + // Write syscall entry point to the first 8 bytes. + if tramp_data.len() >= 8 { + tramp_data[..8].copy_from_slice(&syscall_entry.to_le_bytes()); + } + + // Write to the mapped region. + if tramp_ptr.copy_from_slice(0, &tramp_data).is_none() { + let _ = self.sys_munmap(tramp_ptr, tramp_len); + return false; + } + + // Protect as RX immediately. + if self + .sys_mprotect( + tramp_ptr, + tramp_len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ) + .is_err() + { + let _ = self.sys_munmap(tramp_ptr, tramp_len); + return false; + } + + state.trampoline_mapped = true; + state.trampoline_mapped_len = tramp_len; + } + return true; + } + + // ── Runtime patching path (unpatched binaries) ─────────────── + + // Allocate the trampoline region if not yet done. + let addr_usize = mapped_addr.as_usize(); + if !state.trampoline_mapped { + let tramp_addr = state.trampoline_addr; + + // Try MAP_FIXED first — works when ensure_space_after reserved + // PROT_NONE space (shared libraries). Falls back to a hint-based + // allocation for the ElfLoader path where no headroom is reserved. + let actual_addr = self + .do_mmap_anonymous( + Some(tramp_addr), + PAGE_SIZE, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE | MapFlags::MAP_FIXED, + ) + .or_else(|_| { + self.do_mmap_anonymous( + Some(tramp_addr), + PAGE_SIZE, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, + ) + }); + let actual_addr = match actual_addr { + Ok(ptr) => ptr.as_usize(), + Err(_) => return true, + }; + + // Verify the trampoline is within JMP rel32 range (+-2GB) of the code. + let distance = actual_addr.abs_diff(addr_usize); + if distance > 0x7FFF_0000 { + let _ = self.sys_munmap(MutPtr::::from_usize(actual_addr), PAGE_SIZE); + return true; + } + + state.trampoline_addr = actual_addr; + + // Write the 8-byte syscall entry point at the start. + let entry_ptr = MutPtr::::from_usize(actual_addr); + if entry_ptr + .copy_from_slice(0, &syscall_entry.to_le_bytes()) + .is_none() + { + let _ = self.sys_munmap(MutPtr::::from_usize(actual_addr), PAGE_SIZE); + return true; + } + state.trampoline_cursor = 8; // stubs start after the 8-byte entry + state.trampoline_mapped = true; + state.trampoline_mapped_len = PAGE_SIZE; + } + + let restore_trampoline_rx = |task: &Self, state: &ElfPatchState| { + if state.trampoline_mapped_len > 0 { + let _ = task.sys_mprotect( + MutPtr::::from_usize(state.trampoline_addr), + state.trampoline_mapped_len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + } + }; + + // Make the trampoline RW for writing stubs. + if state.trampoline_mapped_len > 0 + && self + .sys_mprotect( + MutPtr::::from_usize(state.trampoline_addr), + state.trampoline_mapped_len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + ) + .is_err() + { + return true; + } + + // Make the code segment writable for in-place patching. + if self + .sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + ) + .is_err() + { + restore_trampoline_rx(self, state); + return true; + } + + // Read the mapped code into a buffer, patch it, write back. + let Some(code_owned) = mapped_addr.to_owned_slice(len) else { + let _ = self.sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + restore_trampoline_rx(self, state); + return true; + }; + let mut code_buf = code_owned.into_vec(); + let original_code = code_buf.clone(); + + let code_vaddr = addr_usize as u64; + let trampoline_write_vaddr = (state.trampoline_addr + state.trampoline_cursor) as u64; + let syscall_entry_addr = state.trampoline_addr as u64; + + let patch_result = litebox_syscall_rewriter::patch_code_segment( + &mut code_buf, + code_vaddr, + trampoline_write_vaddr, + syscall_entry_addr, + ); + let patch_result = match patch_result { + Ok((stubs, addrs)) => { + if !addrs.is_empty() { + litebox::log_println!( + self.global.platform, + "warning: {} syscall instruction(s) could not be patched (addresses: {:?})", + addrs.len(), + addrs, + ); + } + Ok(stubs) + } + Err(e) => Err(e), + }; + match patch_result { + Ok(stubs) if !stubs.is_empty() => { + let Some(new_cursor) = state.trampoline_cursor.checked_add(stubs.len()) else { + let _ = self.sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + restore_trampoline_rx(self, state); + return true; + }; + let tramp_pages_needed = align_up(new_cursor, PAGE_SIZE); + if tramp_pages_needed > state.trampoline_mapped_len { + let extra_start = state.trampoline_addr + state.trampoline_mapped_len; + let extra_len = tramp_pages_needed - state.trampoline_mapped_len; + if self + .do_mmap_anonymous( + Some(extra_start), + extra_len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE | MapFlags::MAP_FIXED, + ) + .is_err() + { + let _ = self.sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + restore_trampoline_rx(self, state); + return true; + } + state.trampoline_mapped_len = tramp_pages_needed; + } + + // Write stubs before patching the code so rewritten jumps + // never target an uninitialized trampoline. + let tramp_write_ptr = + MutPtr::::from_usize(state.trampoline_addr + state.trampoline_cursor); + if tramp_write_ptr.copy_from_slice(0, &stubs).is_none() { + let _ = self.sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + restore_trampoline_rx(self, state); + return true; + } + + // Write patched code back to the mapped region. + if mapped_addr.copy_from_slice(0, &code_buf).is_none() { + let _ = mapped_addr.copy_from_slice(0, &original_code); + let _ = self.sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + restore_trampoline_rx(self, state); + return true; + } + state.trampoline_cursor = new_cursor; + state.runtime_patches_committed = true; + } + Ok(_) => { + // No syscalls found — no patching needed. + } + Err(_) => { + let _ = self.sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + restore_trampoline_rx(self, state); + return true; + } + } + + // Restore the code segment to RX. + let _ = self.sys_mprotect( + mapped_addr, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + restore_trampoline_rx(self, state); + true + } + + /// Finalize the ELF patching state for `fd`. + /// + /// If the fd has a trampoline region that was allocated (RW), mprotect it + /// to RX so the trampoline stubs become executable and non-writable. + /// The cache entry is removed regardless. + pub(crate) fn finalize_elf_patch(&self, fd: i32) { + let state = self.global.elf_patch_cache.lock().remove(&fd); + if let Some(state) = state + && state.trampoline_mapped + && !state.pre_patched + { + let tramp_len = state.trampoline_mapped_len; + if tramp_len > 0 { + if !state.runtime_patches_committed { + let _ = + self.sys_munmap(MutPtr::::from_usize(state.trampoline_addr), tramp_len); + return; + } + let _ = self.sys_mprotect( + MutPtr::::from_usize(state.trampoline_addr), + tramp_len, + ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, + ); + } + } + } } #[cfg(test)] diff --git a/litebox_shim_linux/src/syscalls/process.rs b/litebox_shim_linux/src/syscalls/process.rs index 70f878cde..419afb09c 100644 --- a/litebox_shim_linux/src/syscalls/process.rs +++ b/litebox_shim_linux/src/syscalls/process.rs @@ -770,6 +770,7 @@ impl Task { fs: fs.into(), files: self.files.clone(), // TODO: !CLONE_FILES support signals: self.signals.clone_for_new_task(), + suppress_elf_runtime_patch: core::cell::Cell::new(false), }, }), ) diff --git a/litebox_shim_optee/src/loader/elf.rs b/litebox_shim_optee/src/loader/elf.rs index 47859eb09..8b8a828ef 100644 --- a/litebox_shim_optee/src/loader/elf.rs +++ b/litebox_shim_optee/src/loader/elf.rs @@ -195,7 +195,13 @@ impl<'a> FileAndParsed<'a> { let file = ElfFileInMemory::new(task, elf_buf); let mut parsed = litebox_common_linux::loader::ElfParsedFile::parse(&mut &file) .map_err(ElfLoaderError::ParseError)?; - parsed.parse_trampoline(&mut &file, task.global.platform.get_syscall_entry_point())?; + match parsed.parse_trampoline(&mut &file, task.global.platform.get_syscall_entry_point()) { + Ok(()) | Err(litebox_common_linux::loader::ElfParseError::UnpatchedBinary) => { + // Unpatched binary is expected in the LVBS scenario where not + // all binaries are rewritten. Proceed without a trampoline. + } + Err(e) => return Err(ElfLoaderError::ParseError(e)), + } Ok(Self { file, parsed }) } } diff --git a/litebox_syscall_rewriter/Cargo.toml b/litebox_syscall_rewriter/Cargo.toml index 2831d6b2c..644eb55a8 100644 --- a/litebox_syscall_rewriter/Cargo.toml +++ b/litebox_syscall_rewriter/Cargo.toml @@ -3,14 +3,25 @@ name = "litebox_syscall_rewriter" version = "0.1.0" edition = "2024" +[features] +default = ["std", "anyhow", "clap"] +std = [] +anyhow = ["dep:anyhow"] +clap = ["dep:clap"] + [dependencies] -anyhow = "1.0" -clap = { version = "4.5.32", features = ["derive"] } -iced-x86 = "1.21" -memmap2 = "0.9" -object = { version = "0.36.7", default-features = false, features = ["elf", "read", "std"] } +iced-x86 = { version = "1.21", default-features = false, features = ["no_std", "decoder", "encoder", "instr_info"] } +object = { version = "0.36.7", default-features = false, features = ["elf", "read_core"] } thiserror = { version = "2.0.6", default-features = false } -zerocopy = { version = "0.8", features = ["derive"] } +zerocopy = { version = "0.8", default-features = false, features = ["derive"] } + +# Binary-only dependencies +anyhow = { version = "1.0", optional = true } +clap = { version = "4.5.32", features = ["derive"], optional = true } + +[[bin]] +name = "litebox_syscall_rewriter" +required-features = ["std", "anyhow", "clap"] [lints] workspace = true diff --git a/litebox_syscall_rewriter/src/lib.rs b/litebox_syscall_rewriter/src/lib.rs index 1440bb401..c26b28557 100644 --- a/litebox_syscall_rewriter/src/lib.rs +++ b/litebox_syscall_rewriter/src/lib.rs @@ -14,7 +14,14 @@ //! //! This crate currently only supports x86-64 (i.e., amd64) ELFs. -use std::collections::HashSet; +#![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + +use alloc::collections::BTreeSet; +use alloc::format; +use alloc::string::{String, ToString}; +use alloc::vec; +use alloc::vec::Vec; use object::read::elf::{ElfFile, ProgramHeader as _}; use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _}; @@ -27,8 +34,10 @@ use zerocopy::{FromBytes, Immutable, IntoBytes}; pub enum Error { #[error("failed to parse: {0}")] ParseError(String), - #[error("unsupported executable")] - UnsupportedObjectFile, + #[error("unsupported object file: {0}")] + UnsupportedObjectFile(String), + #[error("unsupported executable: {0}")] + UnsupportedExecutable(String), #[error("executable is already hooked with trampoline")] AlreadyHooked, #[error("no .text section found")] @@ -41,9 +50,13 @@ pub enum Error { InsufficientBytesBeforeOrAfter(u64), #[error("provided trampoline address is too large for 32-bit executable")] TrampolineAddressTooLarge, + #[error("patch failed: {0}")] + PatchError(String), } -type Result = std::result::Result; +type Result = core::result::Result; + +const BUN_FOOTER_MARKER: &[u8] = b"\n---- Bun! ----\n"; /// The magic bytes used to identify the trampoline data. /// This is checked by the loader to verify that the trampoline is valid. @@ -84,8 +97,8 @@ struct TextSectionInfo { /// The `trampoline` must be an absolute address if specified; if unspecified, it will be set to /// zeros, and it is the caller's decision to overwrite it at loading time. /// -/// If it succeeds, it produces an executable with trampoline code appended at a page-aligned -/// offset after the ELF file. The file layout is: +/// If rewriting emits trampoline stubs, the returned executable has trampoline code appended at a +/// page-aligned offset after the ELF file. The file layout is: /// `[original ELF][padding to page boundary][trampoline code][header]` /// /// The header at the end contains: @@ -94,8 +107,38 @@ struct TextSectionInfo { /// - trampoline virtual address (8 bytes for 64-bit, 4 bytes for 32-bit) /// - trampoline size (8 bytes for 64-bit, 4 bytes for 32-bit) /// -/// This layout allows loaders to read just the last 32/20 bytes to get the metadata. -pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Result> { +/// This layout allows loaders to read just the last 32/20 bytes to get the metadata. Even when +/// no syscall instructions are patched, the rewriter still appends the header and the initial +/// syscall-entry placeholder so the loader/audit path can tell the binary was processed. +/// +/// Returns a tuple of (rewritten binary, skipped syscall addresses). Skipped +/// addresses are syscall instructions that could not be patched because there +/// is not enough space around the instruction (replaced with `icebp; hlt` so +/// they trap instead of escaping to the host kernel). +pub fn hook_syscalls_in_elf( + input_binary: &[u8], + trampoline: Option, +) -> Result<(Vec, Vec)> { + if input_binary.ends_with(BUN_FOOTER_MARKER) { + return Err(Error::UnsupportedExecutable( + "Bun-packaged executable".into(), + )); + } + + // Relocatable object files (.o) must not be patched: they are linker + // input, not executable code. Rewriting instructions or appending + // trampoline data would corrupt the object file for the linker. + // Check the ELF e_type field (bytes 16..18) before doing any work. + if input_binary.len() >= 18 { + let e_type = u16::from_le_bytes([input_binary[16], input_binary[17]]); + if e_type == object::elf::ET_REL { + // ET_REL — relocatable object file + return Err(Error::UnsupportedObjectFile( + "relocatable object file".into(), + )); + } + } + // Make a single mutable, 8-byte-aligned copy of the input binary. This serves as both the // parse buffer (object::File::parse requires 8-byte alignment) and the output buffer for // in-place patching. We use a Vec to guarantee alignment, then view it as bytes. @@ -104,14 +147,26 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Res buf[..input_binary.len()].copy_from_slice(input_binary); let buf = &mut buf[..input_binary.len()]; + // Some ELF files (e.g. Node.js SEA binaries) have a program header table at an offset that + // is not 8-byte aligned, which the `object` crate rejects. Fix this by relocating the phdr + // table within our mutable copy so it sits at an 8-byte aligned offset. + fixup_phdr_alignment(buf); + // Parse the ELF and extract all metadata we need, then drop the borrow so we can mutate buf. - let (arch, dl_sysinfo_int80, text_sections, control_transfer_targets, trampoline_base_addr) = { + let ( + arch, + dl_sysinfo_int80, + text_sections, + control_transfer_targets, + trampoline_base_addr, + fork_to_vfork_patch, + ) = { let file = object::File::parse(&*buf).map_err(|e| Error::ParseError(e.to_string()))?; let arch = match file { object::File::Elf64(_) => Arch::X86_64, object::File::Elf32(_) => Arch::X86_32, - _ => return Err(Error::UnsupportedObjectFile), + _ => return Err(Error::UnsupportedObjectFile("not an ELF file".into())), }; let dl_sysinfo_int80 = if arch == Arch::X86_32 { @@ -128,7 +183,9 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Res let control_transfer_targets = get_control_transfer_targets(arch, &*buf, &text_sections)?; - let trampoline_base_addr = find_addr_for_trampoline_code(&file); + let trampoline_base_addr = find_addr_for_trampoline_code(&file)?; + + let fork_to_vfork_patch = find_fork_vfork_patch(&file, &text_sections); ( arch, @@ -136,6 +193,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Res text_sections, control_transfer_targets, trampoline_base_addr, + fork_to_vfork_patch, ) }; @@ -149,8 +207,8 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Res let trampoline = u32::try_from(trampoline).map_err(|_| Error::TrampolineAddressTooLarge)?; trampoline_data.extend_from_slice(&trampoline.to_le_bytes()); } - // Patch syscalls in-place in buf + let mut skipped_addrs = Vec::new(); let mut syscall_insns_found = false; for s in &text_sections { let section_data = section_slice_mut(buf, s)?; @@ -160,10 +218,12 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Res s.vaddr, section_data, trampoline_base_addr, + trampoline_base_addr, // entry point is at offset 0 of trampoline dl_sysinfo_int80, &mut trampoline_data, ) { - Ok(()) => { + Ok(addrs) => { + skipped_addrs.extend(addrs); syscall_insns_found = true; } Err(Error::NoSyscallInstructionsFound) => {} @@ -172,7 +232,50 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Res } if !syscall_insns_found { - return Err(Error::NoSyscallInstructionsFound); + // No syscall instructions found. Append a header-only marker so the + // loader can distinguish "checked by rewriter, nothing to patch" from + // "never processed." The trampoline_size=0 sentinel tells the loader + // to skip trampoline mapping entirely. + // Use the original input (not `buf`) to avoid emitting the phdr + // alignment fixup that was only needed for the `object` crate parser. + let mut out = input_binary.to_vec(); + if arch == Arch::X86_64 { + let header = TrampolineHeader64 { + magic: *TRAMPOLINE_MAGIC, + file_offset: 0, + vaddr: 0, + trampoline_size: 0, + }; + out.extend_from_slice(header.as_bytes()); + } else { + let header = TrampolineHeader32 { + magic: *TRAMPOLINE_MAGIC, + file_offset: 0, + vaddr: 0, + trampoline_size: 0, + }; + out.extend_from_slice(header.as_bytes()); + } + return Ok((out, skipped_addrs)); + } + + // Patch fork → vfork: overwrite the first bytes of __libc_fork with a + // JMP to __libc_vfork. This prevents glibc's fork wrapper from running + // post-fork handlers that corrupt shared state under vfork semantics. + if let Some((fork_file_offset, fork_patch_end, rel32)) = fork_to_vfork_patch { + #[allow(clippy::cast_possible_truncation)] + let off = fork_file_offset as usize; + #[allow(clippy::cast_possible_truncation)] + let patch_end = fork_patch_end as usize; + if off + 5 <= buf.len() && patch_end <= buf.len() && off + 5 <= patch_end { + buf[off] = 0xE9; // JMP rel32 + buf[off + 1..off + 5].copy_from_slice(&rel32.to_le_bytes()); + } else { + return Err(Error::ParseError(format!( + "fork→vfork patch range {off:#x}..{patch_end:#x} is invalid for buffer length {}", + buf.len(), + ))); + } } // Build output: [patched ELF][padding to page boundary][trampoline code][header] @@ -211,7 +314,7 @@ pub fn hook_syscalls_in_elf(input_binary: &[u8], trampoline: Option) -> Res }; out.extend_from_slice(header.as_bytes()); } - Ok(out) + Ok((out, skipped_addrs)) } /// (private) Get metadata for executable sections @@ -301,17 +404,24 @@ enum Arch { } /// (private) Hook all syscalls in `section`, possibly extending `trampoline_data` to do so. +/// +/// `trampoline_base_addr` is the virtual address corresponding to `trampoline_data[0]`. +/// `syscall_entry_addr` is the address of the 8-byte entry-point value that each trampoline +/// stub jumps to (via `JMP [RIP+disp32]` on x86-64 or `CALL [EAX+disp32]` on x86-32). #[allow(clippy::too_many_arguments)] fn hook_syscalls_in_section( arch: Arch, - control_transfer_targets: &HashSet, + control_transfer_targets: &BTreeSet, section_base_addr: u64, section_data: &mut [u8], trampoline_base_addr: u64, + syscall_entry_addr: u64, dl_sysinfo_int80: Option, trampoline_data: &mut Vec, -) -> Result<()> { +) -> Result> { let instructions = decode_section_instructions(arch, section_data, section_base_addr)?; + let mut found_any = false; + let mut skipped_addrs = Vec::new(); for (i, inst) in instructions.iter().enumerate() { // Forward search for `syscall` / `int 0x80` / `call DWORD PTR gs:0x10` match arch { @@ -335,6 +445,7 @@ fn hook_syscalls_in_section( } } + found_any = true; let replace_end = inst.next_ip(); let mut replace_start = None; @@ -358,82 +469,200 @@ fn hook_syscalls_in_section( } if replace_start.is_none() { - hook_syscall_and_after( + match hook_syscall_and_after( arch, control_transfer_targets, section_base_addr, section_data, trampoline_base_addr, + syscall_entry_addr, trampoline_data, &instructions, i, - )?; + ) { + Ok(()) => {} + Err(Error::InsufficientBytesBeforeOrAfter(_)) => { + // Replace the unpatchable syscall with ICEBP;HLT so it + // traps instead of escaping to the host kernel. + replace_with_trap(section_data, section_base_addr, inst); + skipped_addrs.push(inst.ip()); + } + Err(e) => return Err(e), + } continue; } let replace_start = replace_start.unwrap(); let replace_len = usize::try_from(replace_end - replace_start).unwrap(); - let target_addr = trampoline_base_addr + trampoline_data.len() as u64; + let copied_presyscall_insts_have_ip_rel_mem = arch == Arch::X86_64 + && instruction_slice_has_ip_rel_memory_operand( + instructions + .iter() + .take(i) + .skip_while(|prev_inst| prev_inst.ip() < replace_start), + ); - // Copy the original instructions to the trampoline + let target_addr = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64, + "syscall trampoline target", + )?; + + // Copy the pre-syscall instructions to the trampoline. + // When any instruction has a RIP-relative memory operand, we + // re-encode them so the displacement targets the same absolute + // address from the new trampoline location. if replace_start < inst.ip() { - trampoline_data.extend_from_slice( - §ion_data[usize::try_from(replace_start - section_base_addr).unwrap() - ..usize::try_from(inst.ip() - section_base_addr).unwrap()], - ); + if copied_presyscall_insts_have_ip_rel_mem { + let mut reencoded = Vec::new(); + let mut ok = true; + let mut encoder = iced_x86::Encoder::new(64); + for pre_inst in instructions + .iter() + .take(i) + .skip_while(|p| p.ip() < replace_start) + { + let tramp_ip = target_addr + reencoded.len() as u64; + if encoder.encode(pre_inst, tramp_ip).is_err() { + ok = false; + break; + } + let bytes = encoder.take_buffer(); + if bytes.len() != pre_inst.len() { + ok = false; + break; + } + reencoded.extend_from_slice(&bytes); + } + if !ok { + match hook_syscall_and_after( + arch, + control_transfer_targets, + section_base_addr, + section_data, + trampoline_base_addr, + syscall_entry_addr, + trampoline_data, + &instructions, + i, + ) { + Ok(()) => {} + Err(Error::InsufficientBytesBeforeOrAfter(_)) => { + replace_with_trap(section_data, section_base_addr, inst); + skipped_addrs.push(inst.ip()); + } + Err(e) => return Err(e), + } + continue; + } + trampoline_data.extend_from_slice(&reencoded); + } else { + trampoline_data.extend_from_slice( + §ion_data[usize::try_from(replace_start - section_base_addr).unwrap() + ..usize::try_from(inst.ip() - section_base_addr).unwrap()], + ); + } } let return_addr = inst.next_ip(); if arch == Arch::X86_64 { + // Reserve the SysV red zone before entering the shim so async + // guest signal delivery / interrupt handling cannot clobber + // stack locals parked below the architectural RSP. + // LEA RSP, [RSP - 0x80] = 48 8D 64 24 80 + trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x64, 0x24, 0x80]); + + // Put the address of the original JMP (call-site) into R11 so + // that SA_RESTART can rewind ctx.rip to re-enter the trampoline. + // The real `syscall` instruction clobbers R11 with RFLAGS, so + // this register is free from the guest's perspective. + // + // CONTRACT: R11 carries the call-site restart address from this + // point until the platform callback saves it to a dedicated TLS + // variable (saved_restart_addr). The platform MUST preserve R11 + // before any clobbering instructions (fsbase swap, TLS lookup). + // LEA R11, [RIP + disp32] = 4C 8D 1D + let r11_rip = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 7, + "x86_64 trampoline R11 displacement base", + )?; + let r11_disp = i64::try_from(replace_start).unwrap() - i64::try_from(r11_rip).unwrap(); + trampoline_data.extend_from_slice(&[0x4C, 0x8D, 0x1D]); // LEA R11, [RIP + disp32] + trampoline_data.extend_from_slice(&(i32::try_from(r11_disp).unwrap().to_le_bytes())); + // Put jump back location into rcx. - let jmp_back_offset = i64::try_from(return_addr).unwrap() - - i64::try_from(trampoline_base_addr + trampoline_data.len() as u64 + 7).unwrap(); + let jmp_back_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 7, + "x86_64 trampoline jump-back base", + )?; trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32] - trampoline_data - .extend_from_slice(&(i32::try_from(jmp_back_offset).unwrap().to_le_bytes())); + trampoline_data.extend_from_slice(&rel32_bytes( + return_addr, + jmp_back_base, + "x86_64 trampoline jump-back", + )?); // Add jmp [rip + offset_to_entry_point] - // Entry point is at offset 0 of trampoline_data trampoline_data.extend_from_slice(&[0xFF, 0x25]); - // disp32 points to offset 0 (entry point) from current RIP // RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4 - // We want: RIP + disp32 = trampoline_base_addr + 0 - // So: disp32 = -(trampoline_data.len() + 4) - let disp32 = -(i32::try_from(trampoline_data.len()).unwrap() + 4); - trampoline_data.extend_from_slice(&disp32.to_le_bytes()); + // We want: RIP + disp32 = syscall_entry_addr + let entry_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 4, + "x86_64 trampoline entry base", + )?; + trampoline_data.extend_from_slice(&rel32_bytes( + syscall_entry_addr, + entry_base, + "x86_64 trampoline entry", + )?); } else { // For 32-bit, use a different approach to simulate indirect call - // Entry point is at offset 0 of trampoline_data trampoline_data.push(0x50); // PUSH EAX trampoline_data.extend_from_slice(&[0xE8, 0x0, 0x0, 0x0, 0x0]); // CALL next instruction trampoline_data.push(0x58); // POP EAX (effectively store IP in EAX) trampoline_data.extend_from_slice(&[0xFF, 0x90]); // CALL [EAX + offset] - // The offset should point to the entry at offset 0 - // After PUSH(1) + CALL(5) + POP(1) + opcode(2) = 9 bytes - // EAX = base + (len_before_PUSH + 6) = base + (current_len - 9 + 6) = base + (current_len - 3) - // We want: EAX + offset = base + 0 - // So: offset = -(current_len - 3) - let disp32 = -(i32::try_from(trampoline_data.len()).unwrap() - 3); - trampoline_data.extend_from_slice(&disp32.to_le_bytes()); + // EAX = trampoline_base_addr + (trampoline_data.len() - 3) + // We want: EAX + offset = syscall_entry_addr + let call_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 - 3, + "x86 trampoline entry base", + )?; + trampoline_data.extend_from_slice(&rel32_bytes( + syscall_entry_addr, + call_base, + "x86 trampoline entry", + )?); // Note we skip `POP EAX` here as it is done by the callback `syscall_callback` // from litebox_shim_linux/src/lib.rs, which helps reduce the size of the trampoline. // Add jmp back to original after syscall - let jmp_back_offset = i64::try_from(return_addr).unwrap() - - i64::try_from(trampoline_base_addr + trampoline_data.len() as u64 + 5).unwrap(); + let jmp_back_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 5, + "x86 trampoline jump-back base", + )?; trampoline_data.push(0xE9); - trampoline_data - .extend_from_slice(&(i32::try_from(jmp_back_offset).unwrap().to_le_bytes())); + trampoline_data.extend_from_slice(&rel32_bytes( + return_addr, + jmp_back_base, + "x86 trampoline jump-back", + )?); } // Replace original instructions with jump to trampoline let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap(); section_data[replace_offset] = 0xE9; // JMP rel32 - let jump_offset = - i64::try_from(target_addr).unwrap() - i64::try_from(replace_start + 5).unwrap(); - section_data[replace_offset + 1..replace_offset + 5] - .copy_from_slice(&(i32::try_from(jump_offset).unwrap().to_le_bytes())); + let patch_base = checked_add_u64(replace_start, 5, "syscall patch jump base")?; + section_data[replace_offset + 1..replace_offset + 5].copy_from_slice(&rel32_bytes( + target_addr, + patch_base, + "syscall patch jump", + )?); // Fill remaining bytes with NOP for idx in 5..replace_len { @@ -441,23 +670,305 @@ fn hook_syscalls_in_section( } } - Ok(()) + if found_any { + Ok(skipped_addrs) + } else { + Err(Error::NoSyscallInstructionsFound) + } } -fn find_addr_for_trampoline_code(file: &object::File<'_>) -> u64 { +/// If the ELF64 program header table offset (`e_phoff`) is not 8-byte aligned, shift the table +/// forward by the necessary padding so the `object` crate can parse it. This is needed for +/// binaries like Node.js SEA executables where post-link tools append data and relocate the +/// program headers to a non-aligned offset. +/// +/// The function modifies the buffer in-place: it moves the phdr table contents and updates +/// `e_phoff` in the ELF header. Only ELF64 files are handled (ELF32 requires 4-byte alignment +/// which is always satisfied when `e_phoff` is within a valid file). +fn fixup_phdr_alignment(buf: &mut [u8]) { + // Minimum ELF header size for ELF64 + if buf.len() < 64 { + return; + } + + // Check ELF magic and class (must be ELF64) + if &buf[0..4] != b"\x7fELF" || buf[4] != 2 { + return; + } + + let e_phoff = u64::from_le_bytes(buf[32..40].try_into().unwrap()); + let e_phentsize = u64::from(u16::from_le_bytes(buf[54..56].try_into().unwrap())); + let e_phnum = u64::from(u16::from_le_bytes(buf[56..58].try_into().unwrap())); + + if e_phoff == 0 || e_phnum == 0 || e_phentsize == 0 { + return; + } + + let misalignment = e_phoff % 8; + if misalignment == 0 { + return; // already aligned + } + + let Some(phdr_size) = e_phentsize.checked_mul(e_phnum) else { + return; + }; + let Ok(old_start) = usize::try_from(e_phoff) else { + return; + }; + let Ok(phdr_size) = usize::try_from(phdr_size) else { + return; + }; + let Some(old_end) = old_start.checked_add(phdr_size) else { + return; + }; + + // Shift forward to align: new offset is the next 8-byte boundary. + let Ok(padding) = usize::try_from(8 - misalignment) else { + return; + }; + let Some(new_start) = old_start.checked_add(padding) else { + return; + }; + let Some(new_end) = new_start.checked_add(phdr_size) else { + return; + }; + + if new_end > buf.len() { + return; // not enough room + } + + // Only relocate when the overwritten bytes are padding. Otherwise this would corrupt the file + // by destroying whatever payload follows the existing program header table. + if !buf[old_end..new_end].iter().all(|&byte| byte == 0) { + return; + } + + // Move the phdr table forward (use copy_within since src and dst overlap). + buf.copy_within(old_start..old_end, new_start); + + // Update e_phoff in the ELF header. + let new_phoff = (e_phoff + padding as u64).to_le_bytes(); + buf[32..40].copy_from_slice(&new_phoff); + + // Also update the PHDR segment's p_offset if present, so it matches. + // PT_PHDR = 6, each Elf64_Phdr is e_phentsize bytes, p_type at offset 0, p_offset at offset 8. + let Ok(e_phentsize_usize) = usize::try_from(e_phentsize) else { + return; + }; + let Ok(e_phnum_usize) = usize::try_from(e_phnum) else { + return; + }; + for i in 0..e_phnum_usize { + let Some(entry_off) = new_start.checked_add(i.saturating_mul(e_phentsize_usize)) else { + break; + }; + if entry_off + 16 > buf.len() { + break; + } + let p_type = u32::from_le_bytes(buf[entry_off..entry_off + 4].try_into().unwrap()); + if p_type == object::elf::PT_PHDR { + // PT_PHDR — update p_offset to match new location + let p_offset_off = entry_off + 8; + let old_p_offset = + u64::from_le_bytes(buf[p_offset_off..p_offset_off + 8].try_into().unwrap()); + if old_p_offset == e_phoff { + let new_p_offset = (old_p_offset + padding as u64).to_le_bytes(); + buf[p_offset_off..p_offset_off + 8].copy_from_slice(&new_p_offset); + } + // The PHDR segment size should match the phdr table; no change needed. + } + } +} + +/// Find fork and vfork symbols in the ELF and compute the patch needed to +/// redirect fork -> vfork. Returns `Some((fork_file_offset, jmp_rel32))` if +/// both symbols are found, or `None` if this binary doesn't export fork. +fn find_fork_vfork_patch( + file: &object::File<'_>, + text_sections: &[TextSectionInfo], +) -> Option<(u64, u64, i32)> { + use object::ObjectSymbol as _; + + let mut fork_vaddr = None; + let mut vfork_vaddr = None; + + // Restrict this rewrite to libc-specific symbols. Plain `fork`/`vfork` names may belong to + // arbitrary DSOs or user code, and retargeting them would silently change unrelated behavior. + for sym in file.dynamic_symbols() { + if sym.kind() != object::SymbolKind::Text { + continue; + } + let Ok(name) = sym.name() else { continue }; + match name { + "__libc_fork" if fork_vaddr.is_none() => { + fork_vaddr = Some(sym.address()); + } + "__libc_vfork" | "__vfork" if vfork_vaddr.is_none() => { + vfork_vaddr = Some(sym.address()); + } + _ => {} + } + } + + for sym in file.symbols() { + if sym.kind() != object::SymbolKind::Text { + continue; + } + let Ok(name) = sym.name() else { continue }; + match name { + "__libc_fork" if fork_vaddr.is_none() => { + fork_vaddr = Some(sym.address()); + } + "__libc_vfork" | "__vfork" if vfork_vaddr.is_none() => { + vfork_vaddr = Some(sym.address()); + } + _ => {} + } + } + + let fork_vaddr = fork_vaddr?; + let vfork_vaddr = vfork_vaddr?; + if fork_vaddr == 0 || vfork_vaddr == 0 { + return None; + } + + // Convert fork's vaddr to a file offset using the text sections. + let (fork_file_offset, fork_patch_end) = text_sections.iter().find_map(|s| { + let section_end = s.vaddr + s.size; + if fork_vaddr >= s.vaddr + && fork_vaddr < section_end + && fork_vaddr + .checked_add(5) + .is_some_and(|end| end <= section_end) + { + let file_offset = s.file_offset + (fork_vaddr - s.vaddr); + let file_end = s.file_offset + s.size; + Some((file_offset, file_end)) + } else { + None + } + })?; + + // Compute the relative offset for a JMP rel32 instruction. + // JMP rel32 encodes: target = rip_after_jmp + rel32 + // rip_after_jmp = fork_vaddr + 5 (size of JMP rel32 instruction) + let rel32 = i64::try_from(vfork_vaddr) + .ok()? + .checked_sub(i64::try_from(fork_vaddr).ok()? + 5)?; + let rel32 = i32::try_from(rel32).ok()?; + + Some((fork_file_offset, fork_patch_end, rel32)) +} + +/// Replace an unpatchable syscall instruction with `ICEBP; HLT` (`F1 F4`) so +/// that reaching it traps instead of silently escaping to the host kernel. +/// +/// We avoid `UD2` (`0F 0B`) because it commonly appears in binaries to mark +/// `unreachable!()` paths. The `ICEBP; HLT` sequence is a strong, distinctive +/// indicator of "this syscall was intentionally poisoned" — `ICEBP` alone does +/// not trap on Linux in userspace, but `HLT` does (SIGILL in ring 3), and the +/// `F1` prefix makes it easy for a signal handler to distinguish an +/// intentional break from a spurious one. +/// +/// `syscall` (0F 05) and `int 0x80` (CD 80) are both 2 bytes — same size as +/// `ICEBP; HLT`. For `call DWORD PTR gs:0x10` (7 bytes), the remaining 5 +/// bytes are filled with NOPs. +fn replace_with_trap( + section_data: &mut [u8], + section_base_addr: u64, + inst: &iced_x86::Instruction, +) { + let offset = usize::try_from(inst.ip() - section_base_addr).unwrap(); + let len = inst.len(); + // ICEBP (F1) + HLT (F4): traps in userspace, easy to identify in a handler. + section_data[offset] = 0xF1; + section_data[offset + 1] = 0xF4; + // Fill any remaining bytes (e.g. 7-byte `call gs:0x10`) with NOPs. + for b in &mut section_data[offset + 2..offset + len] { + *b = 0x90; + } +} + +fn checked_add_u64(base: u64, addend: u64, context: &'static str) -> Result { + base.checked_add(addend) + .ok_or_else(|| Error::PatchError(format!("{context} address overflow"))) +} + +fn checked_sub_u64(base: u64, subtrahend: u64, context: &'static str) -> Result { + base.checked_sub(subtrahend) + .ok_or_else(|| Error::PatchError(format!("{context} address underflow"))) +} + +fn rel32_bytes(target: u64, base: u64, context: &'static str) -> Result<[u8; 4]> { + let disp = i128::from(target) - i128::from(base); + let disp = i32::try_from(disp).map_err(|_| { + Error::PatchError(format!( + "{context} displacement out of range: target {target:#x}, base {base:#x}" + )) + })?; + Ok(disp.to_le_bytes()) +} + +/// Patch a single mapped code segment in-place, returning trampoline stubs and +/// the addresses of any syscall instructions that could not be patched +/// (replaced with `ICEBP; HLT` so they trap instead of escaping to the host +/// kernel). +/// +/// This is the runtime counterpart to [`hook_syscalls_in_elf`]. Instead of +/// processing a whole ELF file, it operates on a single already-mapped code +/// region — the caller is responsible for making the region writable before +/// calling and restoring permissions afterwards. The caller must copy the +/// returned stubs to `trampoline_write_vaddr`. +pub fn patch_code_segment( + code: &mut [u8], + code_vaddr: u64, + trampoline_write_vaddr: u64, + syscall_entry_addr: u64, +) -> Result<(Vec, Vec)> { + let arch = Arch::X86_64; // runtime patching is x86-64 only + + // Build control-transfer targets for this segment. + let instructions = decode_section_instructions(arch, code, code_vaddr)?; + let mut control_transfer_targets = BTreeSet::new(); + for inst in &instructions { + let target = inst.near_branch_target(); + if target != 0 { + control_transfer_targets.insert(target); + } + } + + let mut trampoline_data = Vec::new(); + match hook_syscalls_in_section( + arch, + &control_transfer_targets, + code_vaddr, + code, + trampoline_write_vaddr, + syscall_entry_addr, + None, // dl_sysinfo_int80 — not applicable on x86-64 + &mut trampoline_data, + ) { + Ok(skipped_addrs) => Ok((trampoline_data, skipped_addrs)), + Err(Error::NoSyscallInstructionsFound) => Ok((Vec::new(), Vec::new())), + Err(e) => Err(e), + } +} + +fn find_addr_for_trampoline_code(file: &object::File<'_>) -> Result { // Find the highest virtual address among all PT_LOAD segments let max_virtual_addr = match file { object::File::Elf64(elf) => max_load_segment_end(elf), object::File::Elf32(elf) => max_load_segment_end(elf), _ => unreachable!(), - }; + } + .ok_or_else(|| Error::ParseError("no PT_LOAD segments found".into()))?; // Round up to the nearest page (assume 0x1000 page size) - max_virtual_addr.next_multiple_of(0x1000) + checked_add_u64(max_virtual_addr, 0xFFF, "trampoline base").map(|addr| addr & !0xFFF) } /// Returns the highest `p_vaddr + p_memsz` among all `PT_LOAD` segments. -fn max_load_segment_end(elf: &ElfFile<'_, Elf>) -> u64 +fn max_load_segment_end(elf: &ElfFile<'_, Elf>) -> Option where Elf::Word: Into, { @@ -465,9 +976,12 @@ where elf.elf_program_headers() .iter() .filter(|ph| ph.p_type(endian) == object::elf::PT_LOAD) - .map(|ph| ph.p_vaddr(endian).into() + ph.p_memsz(endian).into()) + .filter_map(|ph| { + ph.p_vaddr(endian) + .into() + .checked_add(ph.p_memsz(endian).into()) + }) .max() - .unwrap() } fn get_symbols(file: &object::File<'_>) -> Option { @@ -485,8 +999,8 @@ fn get_control_transfer_targets( arch: Arch, input_binary: &[u8], text_sections: &[TextSectionInfo], -) -> Result> { - let mut control_transfer_targets = HashSet::new(); +) -> Result> { + let mut control_transfer_targets = BTreeSet::new(); for s in text_sections { let section_data = section_slice(input_binary, s)?; let instructions = decode_section_instructions(arch, section_data, s.vaddr)?; @@ -595,10 +1109,11 @@ fn section_slice_mut<'a>(buf: &'a mut [u8], section: &TextSectionInfo) -> Result #[allow(clippy::too_many_arguments)] fn hook_syscall_and_after( arch: Arch, - control_transfer_targets: &HashSet, + control_transfer_targets: &BTreeSet, section_base_addr: u64, section_data: &mut [u8], trampoline_base_addr: u64, + syscall_entry_addr: u64, trampoline_data: &mut Vec, instructions: &[iced_x86::Instruction], inst_index: usize, @@ -613,7 +1128,6 @@ fn hook_syscall_and_after( && control_transfer_targets.contains(&next_inst.ip()) { // If the next instruction is a control transfer target, we don't want to cross it - println!("Skipping control transfer target at {:#x}", next_inst.ip()); break; } // Check if the instruction does control transfer @@ -639,6 +1153,7 @@ fn hook_syscall_and_after( section_base_addr, section_data, trampoline_base_addr, + syscall_entry_addr, trampoline_data, instructions, inst_index, @@ -646,36 +1161,99 @@ fn hook_syscall_and_after( } let replace_end = replace_end.unwrap(); + // This function copies post-syscall instructions to the trampoline as raw + // bytes (no re-encoding). That only works for position-independent + // instructions. If any post-syscall instruction has a RIP-relative memory + // operand, the raw bytes would reference the wrong address from the + // trampoline's location, so fall back to hook_syscall_before_and_after + // which re-encodes both sides with corrected displacements. + let copied_postsyscall_insts_have_ip_rel_mem = arch == Arch::X86_64 + && instruction_slice_has_ip_rel_memory_operand( + instructions + .iter() + .skip(inst_index + 1) + .take_while(|next_inst| next_inst.ip() < replace_end), + ); + if copied_postsyscall_insts_have_ip_rel_mem { + return hook_syscall_before_and_after( + arch, + control_transfer_targets, + section_base_addr, + section_data, + trampoline_base_addr, + syscall_entry_addr, + trampoline_data, + instructions, + inst_index, + ); + } - let target_addr = trampoline_base_addr + trampoline_data.len() as u64; + let target_addr = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64, + "syscall trampoline target", + )?; if arch == Arch::X86_64 { + // Reserve the SysV red zone before entering the shim so async guest + // signal delivery / interrupt handling cannot clobber stack locals + // parked below the architectural RSP. + // LEA RSP, [RSP - 0x80] = 48 8D 64 24 80 + trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x64, 0x24, 0x80]); + + // Put the address of the original JMP (call-site) into R11 so + // that SA_RESTART can rewind ctx.rip to re-enter the trampoline. + // + // CONTRACT: R11 carries the call-site restart address from this + // point until the platform callback saves it to a dedicated TLS + // variable (saved_restart_addr). The platform MUST preserve R11 + // before any clobbering instructions (fsbase swap, TLS lookup). + // LEA R11, [RIP + disp32] = 4C 8D 1D + let r11_rip = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 7, + "x86_64 trampoline R11 displacement base", + )?; + let r11_disp = i64::try_from(replace_start).unwrap() - i64::try_from(r11_rip).unwrap(); + trampoline_data.extend_from_slice(&[0x4C, 0x8D, 0x1D]); // LEA R11, [RIP + disp32] + trampoline_data.extend_from_slice(&(i32::try_from(r11_disp).unwrap().to_le_bytes())); + // Put jump back location into rcx, via lea rcx, [next instruction] trampoline_data.extend_from_slice(&[0x48, 0x8D, 0x0D]); // LEA RCX, [RIP + disp32] trampoline_data.extend_from_slice(&6u32.to_le_bytes()); // Add jmp [rip + offset_to_entry_point] - // Entry point is at offset 0 of trampoline_data trampoline_data.extend_from_slice(&[0xFF, 0x25]); - // disp32 points to offset 0 (entry point) from current RIP // RIP after this instruction = trampoline_base_addr + trampoline_data.len() + 4 - // We want: RIP + disp32 = trampoline_base_addr + 0 - // So: disp32 = -(trampoline_data.len() + 4) - let disp32 = -(i32::try_from(trampoline_data.len()).unwrap() + 4); - trampoline_data.extend_from_slice(&disp32.to_le_bytes()); + // We want: RIP + disp32 = syscall_entry_addr + let entry_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 4, + "x86_64 trampoline entry base", + )?; + trampoline_data.extend_from_slice(&rel32_bytes( + syscall_entry_addr, + entry_base, + "x86_64 trampoline entry", + )?); } else { // For 32-bit, use a different approach to simulate indirect call - // Entry point is at offset 0 of trampoline_data trampoline_data.push(0x50); // PUSH EAX trampoline_data.extend_from_slice(&[0xE8, 0x0, 0x0, 0x0, 0x0]); // CALL next instruction trampoline_data.push(0x58); // POP EAX (effectively store IP in EAX) trampoline_data.extend_from_slice(&[0xFF, 0x90]); // CALL [EAX + offset] - // The offset should point to the entry at offset 0 - // After PUSH(1) + CALL(5) + POP(1) + opcode(2) = 9 bytes - // EAX = base + (len_before_PUSH + 6) = base + (current_len - 9 + 6) = base + (current_len - 3) - // We want: EAX + offset = base + 0 - // So: offset = -(current_len - 3) - let disp32 = -(i32::try_from(trampoline_data.len()).unwrap() - 3); - trampoline_data.extend_from_slice(&disp32.to_le_bytes()); + // EAX = trampoline_base_addr + (trampoline_data.len() - 3) + // We want: EAX + offset = syscall_entry_addr + let call_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64, + "x86 trampoline entry base", + )?; + let call_base = checked_sub_u64(call_base, 3, "x86 trampoline entry base")?; + trampoline_data.extend_from_slice(&rel32_bytes( + syscall_entry_addr, + call_base, + "x86 trampoline entry", + )?); // Note we skip `POP EAX` here as it is done by the callback `syscall_callback` // from litebox_shim_linux/src/lib.rs, which helps reduce the size of the trampoline. } @@ -690,18 +1268,27 @@ fn hook_syscall_and_after( } // Add jmp back to original after syscall - let jmp_back_offset = i64::try_from(replace_end).unwrap() - - i64::try_from(trampoline_base_addr + trampoline_data.len() as u64 + 5).unwrap(); + let jmp_back_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 5, + "trampoline jump-back base", + )?; trampoline_data.push(0xE9); - trampoline_data.extend_from_slice(&(i32::try_from(jmp_back_offset).unwrap().to_le_bytes())); + trampoline_data.extend_from_slice(&rel32_bytes( + replace_end, + jmp_back_base, + "trampoline jump-back", + )?); // Replace original instructions with jump to trampoline let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap(); section_data[replace_offset] = 0xE9; // JMP rel32 - let jump_offset = - i64::try_from(target_addr).unwrap() - i64::try_from(replace_start + 5).unwrap(); - section_data[replace_offset + 1..replace_offset + 5] - .copy_from_slice(&(i32::try_from(jump_offset).unwrap().to_le_bytes())); + let patch_base = checked_add_u64(replace_start, 5, "syscall patch jump base")?; + section_data[replace_offset + 1..replace_offset + 5].copy_from_slice(&rel32_bytes( + target_addr, + patch_base, + "syscall patch jump", + )?); // Fill remaining bytes with NOP let replace_len = usize::try_from(replace_end - replace_start).unwrap(); @@ -712,13 +1299,22 @@ fn hook_syscall_and_after( Ok(()) } +fn instruction_slice_has_ip_rel_memory_operand<'a>( + instructions: impl IntoIterator, +) -> bool { + instructions + .into_iter() + .any(iced_x86::Instruction::is_ip_rel_memory_operand) +} + #[allow(clippy::too_many_arguments)] fn hook_syscall_before_and_after( arch: Arch, - control_transfer_targets: &HashSet, + control_transfer_targets: &BTreeSet, section_base_addr: u64, section_data: &mut [u8], trampoline_base_addr: u64, + syscall_entry_addr: u64, trampoline_data: &mut Vec, instructions: &[iced_x86::Instruction], inst_index: usize, @@ -778,7 +1374,11 @@ fn hook_syscall_before_and_after( } }; - let target_addr = trampoline_base_addr + trampoline_data.len() as u64; + let target_addr = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64, + "syscall trampoline target", + )?; let replace_start = prev_inst.ip(); let replace_len = usize::try_from(next_inst.next_ip() - replace_start).unwrap(); @@ -793,13 +1393,19 @@ fn hook_syscall_before_and_after( trampoline_data.extend_from_slice(&[0xE8, 0x0, 0x0, 0x0, 0x0]); // CALL next instruction trampoline_data.push(0x58); // POP EAX (effectively store IP in EAX) trampoline_data.extend_from_slice(&[0xFF, 0x90]); // CALL [EAX + offset] - // The offset should point to the entry at offset 0 - // After PUSH(1) + CALL(5) + POP(1) + opcode(2) = 9 bytes - // EAX = base + (len_before_PUSH + 6) = base + (current_len - 9 + 6) = base + (current_len - 3) - // We want: EAX + offset = base + 0 - // So: offset = -(current_len - 3) - let disp32 = -(i32::try_from(trampoline_data.len()).unwrap() - 3); - trampoline_data.extend_from_slice(&disp32.to_le_bytes()); + // EAX = trampoline_base_addr + (trampoline_data.len() - 3) + // We want: EAX + offset = syscall_entry_addr + let call_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64, + "x86 trampoline entry base", + )?; + let call_base = checked_sub_u64(call_base, 3, "x86 trampoline entry base")?; + trampoline_data.extend_from_slice(&rel32_bytes( + syscall_entry_addr, + call_base, + "x86 trampoline entry", + )?); // Note we skip `POP EAX` here as it is done by the callback `syscall_callback` // from litebox_shim_linux/src/lib.rs, which helps reduce the size of the trampoline. @@ -812,19 +1418,28 @@ fn hook_syscall_before_and_after( // Add jmp back to original after syscall if needed if need_jump_back { let return_addr = next_inst.next_ip(); - let jmp_back_offset = i64::try_from(return_addr).unwrap() - - i64::try_from(trampoline_base_addr + trampoline_data.len() as u64 + 5).unwrap(); + let jmp_back_base = checked_add_u64( + trampoline_base_addr, + trampoline_data.len() as u64 + 5, + "x86 trampoline jump-back base", + )?; trampoline_data.push(0xE9); - trampoline_data.extend_from_slice(&(i32::try_from(jmp_back_offset).unwrap().to_le_bytes())); + trampoline_data.extend_from_slice(&rel32_bytes( + return_addr, + jmp_back_base, + "x86 trampoline jump-back", + )?); } // Replace original instructions with jump to trampoline let replace_offset = usize::try_from(replace_start - section_base_addr).unwrap(); section_data[replace_offset] = 0xE9; // JMP rel32 - let jump_offset = - i64::try_from(target_addr).unwrap() - i64::try_from(replace_start + 5).unwrap(); - section_data[replace_offset + 1..replace_offset + 5] - .copy_from_slice(&(i32::try_from(jump_offset).unwrap().to_le_bytes())); + let patch_base = checked_add_u64(replace_start, 5, "syscall patch jump base")?; + section_data[replace_offset + 1..replace_offset + 5].copy_from_slice(&rel32_bytes( + target_addr, + patch_base, + "syscall patch jump", + )?); // Fill remaining bytes with NOP for idx in 5..replace_len { diff --git a/litebox_syscall_rewriter/src/main.rs b/litebox_syscall_rewriter/src/main.rs index 7ef8eef14..209caeaa4 100644 --- a/litebox_syscall_rewriter/src/main.rs +++ b/litebox_syscall_rewriter/src/main.rs @@ -47,10 +47,27 @@ fn main() -> anyhow::Result<()> { let mut input_binary = std::fs::File::open(&cli_args.input_binary)?; let mut input_binary_bytes = vec![]; input_binary.read_to_end(&mut input_binary_bytes)?; - let output_binary = litebox_syscall_rewriter::hook_syscalls_in_elf( + let (output_binary, skipped_addrs) = match litebox_syscall_rewriter::hook_syscalls_in_elf( &input_binary_bytes, cli_args.trampoline_addr, - )?; + ) { + Ok((output_binary, skipped_addrs)) => (output_binary, skipped_addrs), + Err(litebox_syscall_rewriter::Error::NoSyscallInstructionsFound) => { + eprintln!( + "warning: {} has no syscall instructions, copying as-is", + cli_args.input_binary.display() + ); + (input_binary_bytes.clone(), Vec::new()) + } + Err(err) => return Err(err.into()), + }; + if !skipped_addrs.is_empty() { + eprintln!( + "warning: {} unpatchable syscall instruction(s) at {:?}", + skipped_addrs.len(), + skipped_addrs, + ); + } let output_path = cli_args.output_binary.unwrap_or_else(|| { cli_args.input_binary.with_file_name( cli_args diff --git a/litebox_syscall_rewriter/tests/snapshot_tests.rs b/litebox_syscall_rewriter/tests/snapshot_tests.rs index 841dfe86b..f8c257978 100644 --- a/litebox_syscall_rewriter/tests/snapshot_tests.rs +++ b/litebox_syscall_rewriter/tests/snapshot_tests.rs @@ -6,6 +6,7 @@ fn objdump(binary: &[u8]) -> String { use std::process::Command; use tempfile::NamedTempFile; + let trampoline_range = trampoline_range(binary); let mut temp_file = NamedTempFile::new().unwrap(); temp_file.write_all(binary).unwrap(); @@ -19,16 +20,74 @@ fn objdump(binary: &[u8]) -> String { String::from_utf8_lossy(&output.stdout) .lines() .filter(|l| !l.contains("/tmp/")) - .map(str::trim_end) + .map(|line| normalize_objdump_line(line, trampoline_range.as_ref())) .collect::>() .join("\n") } +fn trampoline_range(binary: &[u8]) -> Option> { + const MAGIC: &[u8; 8] = litebox_syscall_rewriter::TRAMPOLINE_MAGIC; + + if binary.len() < 20 { + return None; + } + + match binary.get(4).copied() { + Some(2) if binary.len() >= 32 => { + let header = &binary[binary.len() - 32..]; + if &header[..8] != MAGIC { + return None; + } + let vaddr = u64::from_le_bytes(header[16..24].try_into().unwrap()); + let size = u64::from_le_bytes(header[24..32].try_into().unwrap()); + (size != 0).then_some(vaddr..vaddr.checked_add(size)?) + } + Some(1) => { + let header = &binary[binary.len() - 20..]; + if &header[..8] != MAGIC { + return None; + } + let vaddr = u64::from(u32::from_le_bytes(header[12..16].try_into().unwrap())); + let size = u64::from(u32::from_le_bytes(header[16..20].try_into().unwrap())); + (size != 0).then_some(vaddr..vaddr.checked_add(size)?) + } + _ => None, + } +} + +fn normalize_objdump_line(line: &str, trampoline_range: Option<&std::ops::Range>) -> String { + let Some(trampoline_range) = trampoline_range else { + return line.trim_end().to_owned(); + }; + let Some((address, rest)) = line.split_once(':') else { + return line.trim_end().to_owned(); + }; + let tokens: Vec<_> = rest.split_whitespace().collect(); + let Some((mnemonic_idx, mnemonic)) = tokens + .iter() + .enumerate() + .find(|(_, token)| !token.chars().all(|ch| ch.is_ascii_hexdigit())) + else { + return line.trim_end().to_owned(); + }; + if *mnemonic == "jmp" + && let Some(target) = tokens + .get(mnemonic_idx + 1) + .and_then(|token| u64::from_str_radix(token.trim_start_matches("0x"), 16).ok()) + && trampoline_range.contains(&target) + { + let offset = target - trampoline_range.start; + return format!("{address}:\t"); + } + line.trim_end().to_owned() +} + const HELLO_INPUT_64: &[u8] = include_bytes!("hello"); const HELLO_INPUT_32: &[u8] = include_bytes!("hello-32"); fn run_snapshot_test(input: &[u8], snapshot: &str) { - let output = litebox_syscall_rewriter::hook_syscalls_in_elf(input, None).unwrap(); + let (output, _skipped_addrs) = + litebox_syscall_rewriter::hook_syscalls_in_elf(input, None).unwrap(); let diff = similar::udiff::unified_diff( similar::Algorithm::Myers, &objdump(input), diff --git a/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-32-diff.snap b/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-32-diff.snap index 822b946c6..0fd4d4ae0 100644 --- a/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-32-diff.snap +++ b/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-32-diff.snap @@ -1,5 +1,6 @@ --- source: litebox_syscall_rewriter/tests/snapshot_tests.rs +assertion_line: 99 expression: diff --- --- original @@ -9,7 +10,7 @@ expression: diff 8049142: c7 85 d0 39 00 00 01 movl $0x1,0x39d0(%ebp) 8049149: 00 00 00 - 804914c: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 804914c: e9 b3 de 09 00 jmp 80e7004 <_end+0x3bc> ++ 804914c: + 8049151: 90 nop + 8049152: 90 nop 8049153: 8b 85 d0 39 00 00 mov 0x39d0(%ebp),%eax @@ -22,7 +23,7 @@ expression: diff - 8049e00: 89 d0 mov %edx,%eax - 8049e02: cd 80 int $0x80 - 8049e04: eb fa jmp 8049e00 <__libc_start_call_main+0x90> -+ 8049e00: e9 11 d2 09 00 jmp 80e7016 <_end+0x3ce> ++ 8049e00: + 8049e05: 90 nop 8049e06: 31 c0 xor %eax,%eax 8049e08: eb d0 jmp 8049dda <__libc_start_call_main+0x6a> @@ -34,7 +35,7 @@ expression: diff - 804bf8c: c7 44 24 4c 51 00 00 movl $0x51,0x4c(%esp) - 804bf93: 00 - 804bf94: cd 80 int $0x80 -+ 804bf8c: e9 96 b0 09 00 jmp 80e7027 <_end+0x3df> ++ 804bf8c: + 804bf91: 90 nop + 804bf92: 90 nop + 804bf93: 90 nop @@ -48,13 +49,13 @@ expression: diff 804bfab: 8d 8e 20 e6 fc ff lea -0x319e0(%esi),%ecx - 804bfb1: ba 2d 00 00 00 mov $0x2d,%edx - 804bfb6: cd 80 int $0x80 -+ 804bfb1: e9 8b b0 09 00 jmp 80e7041 <_end+0x3f9> ++ 804bfb1: + 804bfb6: 90 nop + 804bfb7: 90 nop 804bfb8: b8 fc 00 00 00 mov $0xfc,%eax - 804bfbd: bb 7f 00 00 00 mov $0x7f,%ebx - 804bfc2: cd 80 int $0x80 -+ 804bfbd: e9 96 b0 09 00 jmp 80e7058 <_end+0x410> ++ 804bfbd: + 804bfc2: 90 nop + 804bfc3: 90 nop 804bfc4: e8 67 ff 00 00 call 805bf30 <__tls_init_tp> @@ -66,13 +67,13 @@ expression: diff 804c082: 8d 8e 20 e6 fc ff lea -0x319e0(%esi),%ecx - 804c088: ba 2d 00 00 00 mov $0x2d,%edx - 804c08d: cd 80 int $0x80 -+ 804c088: e9 e2 af 09 00 jmp 80e706f <_end+0x427> ++ 804c088: + 804c08d: 90 nop + 804c08e: 90 nop 804c08f: b8 fc 00 00 00 mov $0xfc,%eax - 804c094: bb 7f 00 00 00 mov $0x7f,%ebx - 804c099: cd 80 int $0x80 -+ 804c094: e9 ed af 09 00 jmp 80e7086 <_end+0x43e> ++ 804c094: + 804c099: 90 nop + 804c09a: 90 nop 804c09b: e9 46 fe ff ff jmp 804bee6 <__libc_setup_tls+0x126> @@ -83,7 +84,7 @@ expression: diff 8050f61: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi 8050f68: b8 92 00 00 00 mov $0x92,%eax - 8050f6d: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8050f6d: e9 2b 61 09 00 jmp 80e709d <_end+0x455> ++ 8050f6d: + 8050f72: 90 nop + 8050f73: 90 nop 8050f74: 83 f8 fc cmp $0xfffffffc,%eax @@ -94,7 +95,7 @@ expression: diff 805118e: ba 02 00 00 00 mov $0x2,%edx 8051193: 31 f6 xor %esi,%esi - 8051195: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8051195: e9 15 5f 09 00 jmp 80e70af <_end+0x467> ++ 8051195: + 805119a: 90 nop + 805119b: 90 nop 805119c: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -105,7 +106,7 @@ expression: diff 8051202: 31 f6 xor %esi,%esi 8051204: 80 f1 80 xor $0x80,%cl - 8051207: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8051207: e9 b5 5e 09 00 jmp 80e70c1 <_end+0x479> ++ 8051207: + 805120c: 90 nop + 805120d: 90 nop 805120e: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -116,7 +117,7 @@ expression: diff 8051251: 31 f6 xor %esi,%esi 8051253: 8b 5c 24 0c mov 0xc(%esp),%ebx - 8051257: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8051257: e9 77 5e 09 00 jmp 80e70d3 <_end+0x48b> ++ 8051257: + 805125c: 90 nop + 805125d: 90 nop 805125e: 5b pop %ebx @@ -127,7 +128,7 @@ expression: diff 8051282: 8b 5c 24 0c mov 0xc(%esp),%ebx 8051286: 80 f1 81 xor $0x81,%cl - 8051289: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8051289: e9 57 5e 09 00 jmp 80e70e5 <_end+0x49d> ++ 8051289: + 805128e: 90 nop + 805128f: 90 nop 8051290: 5b pop %ebx @@ -138,7 +139,7 @@ expression: diff 805215c: c6 86 fc 35 00 00 01 movb $0x1,0x35fc(%esi) 8052163: 8d 9e f4 35 00 00 lea 0x35f4(%esi),%ebx - 8052169: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8052169: e9 89 4f 09 00 jmp 80e70f7 <_end+0x4af> ++ 8052169: + 805216e: 90 nop + 805216f: 90 nop 8052170: 8d 7c 24 0c lea 0xc(%esp),%edi @@ -149,7 +150,7 @@ expression: diff 8059916: b8 93 01 00 00 mov $0x193,%eax 805991b: 89 f9 mov %edi,%ecx - 805991d: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805991d: e9 e7 d7 08 00 jmp 80e7109 <_end+0x4c1> ++ 805991d: + 8059922: 90 nop + 8059923: 90 nop 8059924: 85 c0 test %eax,%eax @@ -159,7 +160,7 @@ expression: diff 805992d: 8d 4c 24 04 lea 0x4(%esp),%ecx 8059931: b8 09 01 00 00 mov $0x109,%eax - 8059936: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8059936: e9 e0 d7 08 00 jmp 80e711b <_end+0x4d3> ++ 8059936: + 805993b: 90 nop + 805993c: 90 nop 805993d: 85 c0 test %eax,%eax @@ -170,7 +171,7 @@ expression: diff 8059a70: f4 hlt 8059a71: 89 d0 mov %edx,%eax - 8059a73: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8059a73: e9 b5 d6 08 00 jmp 80e712d <_end+0x4e5> ++ 8059a73: + 8059a78: 90 nop + 8059a79: 90 nop 8059a7a: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -181,7 +182,7 @@ expression: diff 8059bd0: 31 c0 xor %eax,%eax 8059bd2: b8 7f 01 00 00 mov $0x17f,%eax - 8059bd7: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8059bd7: e9 63 d5 08 00 jmp 80e713f <_end+0x4f7> ++ 8059bd7: + 8059bdc: 90 nop + 8059bdd: 90 nop 8059bde: 89 c6 mov %eax,%esi @@ -192,7 +193,7 @@ expression: diff 8059dc2: 8d 54 24 2c lea 0x2c(%esp),%edx 8059dc6: b8 2c 01 00 00 mov $0x12c,%eax - 8059dcb: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8059dcb: e9 81 d3 08 00 jmp 80e7151 <_end+0x509> ++ 8059dcb: + 8059dd0: 90 nop + 8059dd1: 90 nop 8059dd2: 89 c2 mov %eax,%edx @@ -203,7 +204,7 @@ expression: diff 8059f5c: b8 06 00 00 00 mov $0x6,%eax 8059f61: 8b 5c 24 08 mov 0x8(%esp),%ebx - 8059f65: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8059f65: e9 f9 d1 08 00 jmp 80e7163 <_end+0x51b> ++ 8059f65: + 8059f6a: 90 nop + 8059f6b: 90 nop 8059f6c: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -214,7 +215,7 @@ expression: diff 8059fb9: 8b 5c 24 20 mov 0x20(%esp),%ebx 8059fbd: b8 dd 00 00 00 mov $0xdd,%eax - 8059fc2: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8059fc2: e9 ae d1 08 00 jmp 80e7175 <_end+0x52d> ++ 8059fc2: + 8059fc7: 90 nop + 8059fc8: 90 nop 8059fc9: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -225,7 +226,7 @@ expression: diff 8059ff0: b8 dd 00 00 00 mov $0xdd,%eax 8059ff5: b9 10 00 00 00 mov $0x10,%ecx - 8059ffa: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8059ffa: e9 88 d1 08 00 jmp 80e7187 <_end+0x53f> ++ 8059ffa: + 8059fff: 90 nop + 805a000: 90 nop 805a001: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -236,7 +237,7 @@ expression: diff 805a069: 8b 5c 24 20 mov 0x20(%esp),%ebx 805a06d: b8 dd 00 00 00 mov $0xdd,%eax - 805a072: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805a072: e9 22 d1 08 00 jmp 80e7199 <_end+0x551> ++ 805a072: + 805a077: 90 nop + 805a078: 90 nop 805a079: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -247,7 +248,7 @@ expression: diff 805a0a0: b8 dd 00 00 00 mov $0xdd,%eax 805a0a5: b9 10 00 00 00 mov $0x10,%ecx - 805a0aa: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805a0aa: e9 fc d0 08 00 jmp 80e71ab <_end+0x563> ++ 805a0aa: + 805a0af: 90 nop + 805a0b0: 90 nop 805a0b1: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -258,7 +259,7 @@ expression: diff 805a118: b8 27 01 00 00 mov $0x127,%eax 805a11d: bb 9c ff ff ff mov $0xffffff9c,%ebx - 805a122: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805a122: e9 96 d0 08 00 jmp 80e71bd <_end+0x575> ++ 805a122: + 805a127: 90 nop + 805a128: 90 nop 805a129: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -269,7 +270,7 @@ expression: diff 805a176: 8b 54 24 14 mov 0x14(%esp),%edx 805a17a: 8b 5c 24 0c mov 0xc(%esp),%ebx - 805a17e: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805a17e: e9 4c d0 08 00 jmp 80e71cf <_end+0x587> ++ 805a17e: + 805a183: 90 nop + 805a184: 90 nop 805a185: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -281,7 +282,7 @@ expression: diff 805a30c: b8 2d 00 00 00 mov $0x2d,%eax - 805a311: 8b 5c 24 08 mov 0x8(%esp),%ebx - 805a315: cd 80 int $0x80 -+ 805a311: e9 cb ce 08 00 jmp 80e71e1 <_end+0x599> ++ 805a311: + 805a316: 90 nop 805a317: 89 82 28 36 00 00 mov %eax,0x3628(%edx) 805a31d: 39 d8 cmp %ebx,%eax @@ -291,7 +292,7 @@ expression: diff 805a750: 8d 54 24 0c lea 0xc(%esp),%edx 805a754: b8 f2 00 00 00 mov $0xf2,%eax - 805a759: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805a759: e9 99 ca 08 00 jmp 80e71f7 <_end+0x5af> ++ 805a759: + 805a75e: 90 nop + 805a75f: 90 nop 805a760: 85 c0 test %eax,%eax @@ -302,7 +303,7 @@ expression: diff 805a959: 8b 5c 24 08 mov 0x8(%esp),%ebx 805a95d: b8 db 00 00 00 mov $0xdb,%eax - 805a962: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805a962: e9 a2 c8 08 00 jmp 80e7209 <_end+0x5c1> ++ 805a962: + 805a967: 90 nop + 805a968: 90 nop 805a969: 5b pop %ebx @@ -313,7 +314,7 @@ expression: diff 805aa29: 8b 5c 24 08 mov 0x8(%esp),%ebx 805aa2d: b8 7d 00 00 00 mov $0x7d,%eax - 805aa32: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805aa32: e9 e4 c7 08 00 jmp 80e721b <_end+0x5d3> ++ 805aa32: + 805aa37: 90 nop + 805aa38: 90 nop 805aa39: 5b pop %ebx @@ -324,7 +325,7 @@ expression: diff 805aa56: 8b 5c 24 04 mov 0x4(%esp),%ebx 805aa5a: b8 5b 00 00 00 mov $0x5b,%eax - 805aa5f: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805aa5f: e9 c9 c7 08 00 jmp 80e722d <_end+0x5e5> ++ 805aa5f: + 805aa64: 90 nop + 805aa65: 90 nop 805aa66: 89 d3 mov %edx,%ebx @@ -335,7 +336,7 @@ expression: diff 805ab29: b8 a3 00 00 00 mov $0xa3,%eax 805ab2e: 8b 5c 24 14 mov 0x14(%esp),%ebx - 805ab32: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805ab32: e9 08 c7 08 00 jmp 80e723f <_end+0x5f7> ++ 805ab32: + 805ab37: 90 nop + 805ab38: 90 nop 805ab39: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -346,7 +347,7 @@ expression: diff 805abdb: bb 41 4d 56 53 mov $0x53564d41,%ebx 805abe0: 31 c9 xor %ecx,%ecx - 805abe2: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805abe2: e9 6a c6 08 00 jmp 80e7251 <_end+0x609> ++ 805abe2: + 805abe7: 90 nop + 805abe8: 90 nop 805abe9: 83 f8 ea cmp $0xffffffea,%eax @@ -357,7 +358,7 @@ expression: diff 805ac02: 8b 5c 24 04 mov 0x4(%esp),%ebx 805ac06: b8 74 00 00 00 mov $0x74,%eax - 805ac0b: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805ac0b: e9 53 c6 08 00 jmp 80e7263 <_end+0x61b> ++ 805ac0b: + 805ac10: 90 nop + 805ac11: 90 nop 805ac12: 89 d3 mov %edx,%ebx @@ -368,7 +369,7 @@ expression: diff 805bf5e: 8d 5e 68 lea 0x68(%esi),%ebx 805bf61: b8 02 01 00 00 mov $0x102,%eax - 805bf66: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805bf66: e9 0a b3 08 00 jmp 80e7275 <_end+0x62d> ++ 805bf66: + 805bf6b: 90 nop + 805bf6c: 90 nop 805bf6d: 89 46 68 mov %eax,0x68(%esi) @@ -379,7 +380,7 @@ expression: diff 805bfa0: b9 0c 00 00 00 mov $0xc,%ecx 805bfa5: 89 5e 6c mov %ebx,0x6c(%esi) - 805bfa8: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805bfa8: e9 da b2 08 00 jmp 80e7287 <_end+0x63f> ++ 805bfa8: + 805bfad: 90 nop + 805bfae: 90 nop 805bfaf: 6a 00 push $0x0 @@ -390,7 +391,7 @@ expression: diff 805bfda: 31 d2 xor %edx,%edx 805bfdc: be 53 30 05 53 mov $0x53053053,%esi - 805bfe1: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 805bfe1: e9 b3 b2 08 00 jmp 80e7299 <_end+0x651> ++ 805bfe1: + 805bfe6: 90 nop + 805bfe7: 90 nop 805bfe8: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -403,13 +404,13 @@ expression: diff - 805c9b5: 31 db xor %ebx,%ebx - 805c9b7: 89 c8 mov %ecx,%eax - 805c9b9: cd 80 int $0x80 -+ 805c9b5: e9 f1 a8 08 00 jmp 80e72ab <_end+0x663> ++ 805c9b5: + 805c9ba: 90 nop 805c9bb: 89 c2 mov %eax,%edx - 805c9bd: 8d 1c 28 lea (%eax,%ebp,1),%ebx - 805c9c0: 89 c8 mov %ecx,%eax - 805c9c2: cd 80 int $0x80 -+ 805c9bd: e9 ff a8 08 00 jmp 80e72c1 <_end+0x679> ++ 805c9bd: + 805c9c2: 90 nop + 805c9c3: 90 nop 805c9c4: 39 c2 cmp %eax,%edx @@ -420,7 +421,7 @@ expression: diff 807afc1: f7 d1 not %ecx 807afc3: 81 e1 80 00 00 00 and $0x80,%ecx - 807afc9: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807afc9: e9 0a c3 06 00 jmp 80e72d8 <_end+0x690> ++ 807afc9: + 807afce: 90 nop + 807afcf: 90 nop 807afd0: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -431,7 +432,7 @@ expression: diff 807b172: b8 f0 00 00 00 mov $0xf0,%eax 807b177: 89 ce mov %ecx,%esi - 807b179: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807b179: e9 6c c1 06 00 jmp 80e72ea <_end+0x6a2> ++ 807b179: + 807b17e: 90 nop + 807b17f: 90 nop 807b180: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -442,7 +443,7 @@ expression: diff 807b340: b9 07 00 00 00 mov $0x7,%ecx 807b345: 89 d6 mov %edx,%esi - 807b347: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807b347: e9 b0 bf 06 00 jmp 80e72fc <_end+0x6b4> ++ 807b347: + 807b34c: 90 nop + 807b34d: 90 nop 807b34e: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -453,7 +454,7 @@ expression: diff 807b8fe: 81 e1 80 00 00 00 and $0x80,%ecx 807b904: 80 f1 81 xor $0x81,%cl - 807b907: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807b907: e9 02 ba 06 00 jmp 80e730e <_end+0x6c6> ++ 807b907: + 807b90c: 90 nop + 807b90d: 90 nop 807b90e: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -464,7 +465,7 @@ expression: diff 807bb85: b8 f0 00 00 00 mov $0xf0,%eax 807bb8a: 89 d1 mov %edx,%ecx - 807bb8c: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807bb8c: e9 8f b7 06 00 jmp 80e7320 <_end+0x6d8> ++ 807bb8c: + 807bb91: 90 nop + 807bb92: 90 nop 807bb93: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -475,7 +476,7 @@ expression: diff 807bbb5: b8 f0 00 00 00 mov $0xf0,%eax 807bbba: 89 d6 mov %edx,%esi - 807bbbc: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807bbbc: e9 71 b7 06 00 jmp 80e7332 <_end+0x6ea> ++ 807bbbc: + 807bbc1: 90 nop + 807bbc2: 90 nop 807bbc3: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -486,7 +487,7 @@ expression: diff 807bddf: b9 80 00 00 00 mov $0x80,%ecx 807bde4: 89 fb mov %edi,%ebx - 807bde6: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807bde6: e9 59 b5 06 00 jmp 80e7344 <_end+0x6fc> ++ 807bde6: + 807bdeb: 90 nop + 807bdec: 90 nop 807bded: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -497,7 +498,7 @@ expression: diff 807be90: 89 fb mov %edi,%ebx 807be92: c7 07 02 00 00 00 movl $0x2,(%edi) - 807be98: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807be98: e9 b9 b4 06 00 jmp 80e7356 <_end+0x70e> ++ 807be98: + 807be9d: 90 nop + 807be9e: 90 nop 807be9f: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -508,7 +509,7 @@ expression: diff 807bf1f: 8b 5c 24 10 mov 0x10(%esp),%ebx 807bf23: c7 03 00 00 00 00 movl $0x0,(%ebx) - 807bf29: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807bf29: e9 3a b4 06 00 jmp 80e7368 <_end+0x720> ++ 807bf29: + 807bf2e: 90 nop + 807bf2f: 90 nop 807bf30: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -519,7 +520,7 @@ expression: diff 807c11e: c1 e1 07 shl $0x7,%ecx 807c121: 80 f1 81 xor $0x81,%cl - 807c124: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c124: e9 51 b2 06 00 jmp 80e737a <_end+0x732> ++ 807c124: + 807c129: 90 nop + 807c12a: 90 nop 807c12b: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -530,7 +531,7 @@ expression: diff 807c295: 89 eb mov %ebp,%ebx 807c297: 80 f1 81 xor $0x81,%cl - 807c29a: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c29a: e9 ed b0 06 00 jmp 80e738c <_end+0x744> ++ 807c29a: + 807c29f: 90 nop + 807c2a0: 90 nop 807c2a1: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -541,7 +542,7 @@ expression: diff 807c2f4: 31 f6 xor %esi,%esi 807c2f6: 80 f1 81 xor $0x81,%cl - 807c2f9: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c2f9: e9 a0 b0 06 00 jmp 80e739e <_end+0x756> ++ 807c2f9: + 807c2fe: 90 nop + 807c2ff: 90 nop 807c300: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -552,7 +553,7 @@ expression: diff 807c385: 89 fb mov %edi,%ebx 807c387: 80 f1 81 xor $0x81,%cl - 807c38a: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c38a: e9 21 b0 06 00 jmp 80e73b0 <_end+0x768> ++ 807c38a: + 807c38f: 90 nop + 807c390: 90 nop 807c391: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -563,7 +564,7 @@ expression: diff 807c3fa: ba ff ff ff 7f mov $0x7fffffff,%edx 807c3ff: 80 f1 81 xor $0x81,%cl - 807c402: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c402: e9 bb af 06 00 jmp 80e73c2 <_end+0x77a> ++ 807c402: + 807c407: 90 nop + 807c408: 90 nop 807c409: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -574,7 +575,7 @@ expression: diff 807c6af: ba 01 00 00 00 mov $0x1,%edx 807c6b4: 80 f1 81 xor $0x81,%cl - 807c6b7: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c6b7: e9 18 ad 06 00 jmp 80e73d4 <_end+0x78c> ++ 807c6b7: + 807c6bc: 90 nop + 807c6bd: 90 nop 807c6be: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -585,7 +586,7 @@ expression: diff 807c6de: 89 fb mov %edi,%ebx 807c6e0: 80 f1 81 xor $0x81,%cl - 807c6e3: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c6e3: e9 fe ac 06 00 jmp 80e73e6 <_end+0x79e> ++ 807c6e3: + 807c6e8: 90 nop + 807c6e9: 90 nop 807c6ea: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -596,7 +597,7 @@ expression: diff 807c75d: 31 f6 xor %esi,%esi 807c75f: 80 f1 81 xor $0x81,%cl - 807c762: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 807c762: e9 91 ac 06 00 jmp 80e73f8 <_end+0x7b0> ++ 807c762: + 807c767: 90 nop + 807c768: 90 nop 807c769: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -607,7 +608,7 @@ expression: diff 808a570: 0f 47 d0 cmova %eax,%edx 808a573: b8 dc 00 00 00 mov $0xdc,%eax - 808a578: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808a578: e9 8d ce 05 00 jmp 80e740a <_end+0x7c2> ++ 808a578: + 808a57d: 90 nop + 808a57e: 90 nop 808a57f: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -618,7 +619,7 @@ expression: diff 808a706: 8b 5c 24 04 mov 0x4(%esp),%ebx 808a70a: b8 9b 00 00 00 mov $0x9b,%eax - 808a70f: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808a70f: e9 08 cd 05 00 jmp 80e741c <_end+0x7d4> ++ 808a70f: + 808a714: 90 nop + 808a715: 90 nop 808a716: 89 d3 mov %edx,%ebx @@ -629,7 +630,7 @@ expression: diff 808a732: 8b 5c 24 04 mov 0x4(%esp),%ebx 808a736: b8 9d 00 00 00 mov $0x9d,%eax - 808a73b: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808a73b: e9 ee cc 05 00 jmp 80e742e <_end+0x7e6> ++ 808a73b: + 808a740: 90 nop + 808a741: 90 nop 808a742: 89 d3 mov %edx,%ebx @@ -640,7 +641,7 @@ expression: diff 808a752: 8b 5c 24 04 mov 0x4(%esp),%ebx 808a756: b8 9f 00 00 00 mov $0x9f,%eax - 808a75b: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808a75b: e9 e0 cc 05 00 jmp 80e7440 <_end+0x7f8> ++ 808a75b: + 808a760: 90 nop + 808a761: 90 nop 808a762: 89 d3 mov %edx,%ebx @@ -651,7 +652,7 @@ expression: diff 808a772: 8b 5c 24 04 mov 0x4(%esp),%ebx 808a776: b8 a0 00 00 00 mov $0xa0,%eax - 808a77b: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808a77b: e9 d2 cc 05 00 jmp 80e7452 <_end+0x80a> ++ 808a77b: + 808a780: 90 nop + 808a781: 90 nop 808a782: 89 d3 mov %edx,%ebx @@ -662,7 +663,7 @@ expression: diff 808a799: 8b 5c 24 08 mov 0x8(%esp),%ebx 808a79d: b8 9c 00 00 00 mov $0x9c,%eax - 808a7a2: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808a7a2: e9 bd cc 05 00 jmp 80e7464 <_end+0x81c> ++ 808a7a2: + 808a7a7: 90 nop + 808a7a8: 90 nop 808a7a9: 5b pop %ebx @@ -673,7 +674,7 @@ expression: diff 808a837: b8 b7 00 00 00 mov $0xb7,%eax 808a83c: 89 f1 mov %esi,%ecx - 808a83e: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808a83e: e9 33 cc 05 00 jmp 80e7476 <_end+0x82e> ++ 808a83e: + 808a843: 90 nop + 808a844: 90 nop 808a845: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -684,7 +685,7 @@ expression: diff 808b0ac: 8b 7c 24 3c mov 0x3c(%esp),%edi 808b0b0: b8 8c 00 00 00 mov $0x8c,%eax - 808b0b5: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b0b5: e9 ce c3 05 00 jmp 80e7488 <_end+0x840> ++ 808b0b5: + 808b0ba: 90 nop + 808b0bb: 90 nop 808b0bc: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -695,7 +696,7 @@ expression: diff 808b1fe: bb 9c ff ff ff mov $0xffffff9c,%ebx 808b203: 89 ea mov %ebp,%edx - 808b205: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b205: e9 90 c2 05 00 jmp 80e749a <_end+0x852> ++ 808b205: + 808b20a: 90 nop + 808b20b: 90 nop 808b20c: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -706,7 +707,7 @@ expression: diff 808b242: 89 44 24 08 mov %eax,0x8(%esp) 808b246: b8 27 01 00 00 mov $0x127,%eax - 808b24b: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b24b: e9 5c c2 05 00 jmp 80e74ac <_end+0x864> ++ 808b24b: + 808b250: 90 nop + 808b251: 90 nop 808b252: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -717,7 +718,7 @@ expression: diff 808b2f0: b8 27 01 00 00 mov $0x127,%eax 808b2f5: 89 ea mov %ebp,%edx - 808b2f7: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b2f7: e9 c2 c1 05 00 jmp 80e74be <_end+0x876> ++ 808b2f7: + 808b2fc: 90 nop + 808b2fd: 90 nop 808b2fe: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -728,7 +729,7 @@ expression: diff 808b331: 89 44 24 08 mov %eax,0x8(%esp) 808b335: b8 27 01 00 00 mov $0x127,%eax - 808b33a: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b33a: e9 91 c1 05 00 jmp 80e74d0 <_end+0x888> ++ 808b33a: + 808b33f: 90 nop + 808b340: 90 nop 808b341: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -739,7 +740,7 @@ expression: diff 808b3c3: b8 03 00 00 00 mov $0x3,%eax 808b3c8: 8b 54 24 28 mov 0x28(%esp),%edx - 808b3cc: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b3cc: e9 11 c1 05 00 jmp 80e74e2 <_end+0x89a> ++ 808b3cc: + 808b3d1: 90 nop + 808b3d2: 90 nop 808b3d3: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -750,7 +751,7 @@ expression: diff 808b3f9: 8b 54 24 28 mov 0x28(%esp),%edx 808b3fd: b8 03 00 00 00 mov $0x3,%eax - 808b402: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b402: e9 ed c0 05 00 jmp 80e74f4 <_end+0x8ac> ++ 808b402: + 808b407: 90 nop + 808b408: 90 nop 808b409: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -761,7 +762,7 @@ expression: diff 808b483: b8 04 00 00 00 mov $0x4,%eax 808b488: 8b 54 24 28 mov 0x28(%esp),%edx - 808b48c: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b48c: e9 75 c0 05 00 jmp 80e7506 <_end+0x8be> ++ 808b48c: + 808b491: 90 nop + 808b492: 90 nop 808b493: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -772,7 +773,7 @@ expression: diff 808b4b9: 8b 54 24 28 mov 0x28(%esp),%edx 808b4bd: b8 04 00 00 00 mov $0x4,%eax - 808b4c2: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b4c2: e9 51 c0 05 00 jmp 80e7518 <_end+0x8d0> ++ 808b4c2: + 808b4c7: 90 nop + 808b4c8: 90 nop 808b4c9: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -783,7 +784,7 @@ expression: diff 808b525: 8b 6f 08 mov 0x8(%edi),%ebp 808b528: 8b 7f 04 mov 0x4(%edi),%edi - 808b52b: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b52b: e9 fa bf 05 00 jmp 80e752a <_end+0x8e2> ++ 808b52b: + 808b530: 90 nop + 808b531: 90 nop 808b532: 5d pop %ebp @@ -795,7 +796,7 @@ expression: diff 808b545: 8b 6f 08 mov 0x8(%edi),%ebp - 808b548: 8b 7f 04 mov 0x4(%edi),%edi - 808b54b: cd 80 int $0x80 -+ 808b548: e9 ef bf 05 00 jmp 80e753c <_end+0x8f4> ++ 808b548: 808b54d: 5d pop %ebp 808b54e: 5f pop %edi 808b54f: 5b pop %ebx @@ -804,7 +805,7 @@ expression: diff 808b58b: b8 27 01 00 00 mov $0x127,%eax 808b590: bb 9c ff ff ff mov $0xffffff9c,%ebx - 808b595: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b595: e9 b7 bf 05 00 jmp 80e7551 <_end+0x909> ++ 808b595: + 808b59a: 90 nop + 808b59b: 90 nop 808b59c: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -815,7 +816,7 @@ expression: diff 808b608: 8b 5c 24 10 mov 0x10(%esp),%ebx 808b60c: b8 27 01 00 00 mov $0x127,%eax - 808b611: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b611: e9 4d bf 05 00 jmp 80e7563 <_end+0x91b> ++ 808b611: + 808b616: 90 nop + 808b617: 90 nop 808b618: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -826,7 +827,7 @@ expression: diff 808b670: 8b 74 24 20 mov 0x20(%esp),%esi 808b674: 8b 7c 24 24 mov 0x24(%esp),%edi - 808b678: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b678: e9 f8 be 05 00 jmp 80e7575 <_end+0x92d> ++ 808b678: + 808b67d: 90 nop + 808b67e: 90 nop 808b67f: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -837,7 +838,7 @@ expression: diff 808b6c6: 8b 54 24 14 mov 0x14(%esp),%edx 808b6ca: 8b 5c 24 0c mov 0xc(%esp),%ebx - 808b6ce: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b6ce: e9 b4 be 05 00 jmp 80e7587 <_end+0x93f> ++ 808b6ce: + 808b6d3: 90 nop + 808b6d4: 90 nop 808b6d5: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -848,7 +849,7 @@ expression: diff 808b72a: 8d 54 24 08 lea 0x8(%esp),%edx 808b72e: b8 36 00 00 00 mov $0x36,%eax - 808b733: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b733: e9 61 be 05 00 jmp 80e7599 <_end+0x951> ++ 808b733: + 808b738: 90 nop + 808b739: 90 nop 808b73a: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -859,7 +860,7 @@ expression: diff 808b801: 8b 4c 24 0c mov 0xc(%esp),%ecx 808b805: 8b 5c 24 08 mov 0x8(%esp),%ebx - 808b809: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 808b809: e9 9d bd 05 00 jmp 80e75ab <_end+0x963> ++ 808b809: + 808b80e: 90 nop + 808b80f: 90 nop 808b810: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -870,7 +871,7 @@ expression: diff 8092201: 8d 58 1c lea 0x1c(%eax),%ebx 8092204: b8 f0 00 00 00 mov $0xf0,%eax - 8092209: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8092209: e9 af 53 05 00 jmp 80e75bd <_end+0x975> ++ 8092209: + 809220e: 90 nop + 809220f: 90 nop 8092210: 83 ec 0c sub $0xc,%esp @@ -881,7 +882,7 @@ expression: diff 8092e5f: 8d 8d ea dc fc ff lea -0x32316(%ebp),%ecx 8092e65: 89 fa mov %edi,%edx - 8092e67: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8092e67: e9 63 47 05 00 jmp 80e75cf <_end+0x987> ++ 8092e67: + 8092e6c: 90 nop + 8092e6d: 90 nop 8092e6e: 85 c0 test %eax,%eax @@ -893,7 +894,7 @@ expression: diff 80930a6: 8d 4c 24 30 lea 0x30(%esp),%ecx - 80930aa: b8 92 00 00 00 mov $0x92,%eax - 80930af: cd 80 int $0x80 -+ 80930aa: e9 32 45 05 00 jmp 80e75e1 <_end+0x999> ++ 80930aa: + 80930af: 90 nop + 80930b0: 90 nop 80930b1: 81 c4 bc 04 00 00 add $0x4bc,%esp @@ -904,7 +905,7 @@ expression: diff 809521e: 31 f6 xor %esi,%esi 8095220: 89 f8 mov %edi,%eax - 8095222: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8095222: e9 d1 23 05 00 jmp 80e75f8 <_end+0x9b0> ++ 8095222: + 8095227: 90 nop + 8095228: 90 nop 8095229: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -915,7 +916,7 @@ expression: diff 80952b6: 31 f6 xor %esi,%esi 80952b8: 89 f8 mov %edi,%eax - 80952ba: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 80952ba: e9 4b 23 05 00 jmp 80e760a <_end+0x9c2> ++ 80952ba: + 80952bf: 90 nop + 80952c0: 90 nop 80952c1: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -927,7 +928,7 @@ expression: diff 08098e30 <__restore_rt>: - 8098e30: b8 ad 00 00 00 mov $0xad,%eax - 8098e35: cd 80 int $0x80 -+ 8098e30: e9 e7 e7 04 00 jmp 80e761c <_end+0x9d4> ++ 8098e30: + 8098e35: 90 nop + 8098e36: 90 nop 8098e37: 90 nop @@ -936,7 +937,7 @@ expression: diff 8098e38: 58 pop %eax - 8098e39: b8 77 00 00 00 mov $0x77,%eax - 8098e3e: cd 80 int $0x80 -+ 8098e39: e9 f5 e7 04 00 jmp 80e7633 <_end+0x9eb> ++ 8098e39: + 8098e3e: 90 nop + 8098e3f: 90 nop @@ -947,7 +948,7 @@ expression: diff 8098eca: be 08 00 00 00 mov $0x8,%esi 8098ecf: 8d 94 24 a0 00 00 00 lea 0xa0(%esp),%edx - 8098ed6: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8098ed6: e9 6f e7 04 00 jmp 80e764a <_end+0xa02> ++ 8098ed6: + 8098edb: 90 nop + 8098edc: 90 nop 8098edd: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -958,7 +959,7 @@ expression: diff 8098f6c: be 08 00 00 00 mov $0x8,%esi 8098f71: 89 ea mov %ebp,%edx - 8098f73: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 8098f73: e9 e4 e6 04 00 jmp 80e765c <_end+0xa14> ++ 8098f73: + 8098f78: 90 nop + 8098f79: 90 nop 8098f7a: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -969,7 +970,7 @@ expression: diff 809e209: 31 f6 xor %esi,%esi 809e20b: 89 e8 mov %ebp,%eax - 809e20d: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809e20d: e9 5c 94 04 00 jmp 80e766e <_end+0xa26> ++ 809e20d: + 809e212: 90 nop + 809e213: 90 nop 809e214: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -980,7 +981,7 @@ expression: diff 809e753: b8 a6 01 00 00 mov $0x1a6,%eax 809e758: 31 d2 xor %edx,%edx - 809e75a: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809e75a: e9 21 8f 04 00 jmp 80e7680 <_end+0xa38> ++ 809e75a: + 809e75f: 90 nop + 809e760: 90 nop 809e761: 83 f8 da cmp $0xffffffda,%eax @@ -991,7 +992,7 @@ expression: diff 809e7f3: b8 f0 00 00 00 mov $0xf0,%eax 809e7f8: 31 d2 xor %edx,%edx - 809e7fa: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809e7fa: e9 93 8e 04 00 jmp 80e7692 <_end+0xa4a> ++ 809e7fa: + 809e7ff: 90 nop + 809e800: 90 nop 809e801: 83 f8 da cmp $0xffffffda,%eax @@ -1002,7 +1003,7 @@ expression: diff 809eae1: 89 54 24 08 mov %edx,0x8(%esp) 809eae5: 8d 8d dc 73 fe ff lea -0x18c24(%ebp),%ecx - 809eaeb: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809eaeb: e9 b4 8b 04 00 jmp 80e76a4 <_end+0xa5c> ++ 809eaeb: + 809eaf0: 90 nop + 809eaf1: 90 nop 809eaf2: ba 01 00 00 00 mov $0x1,%edx @@ -1013,7 +1014,7 @@ expression: diff 809eb2d: 8b 4c 24 08 mov 0x8(%esp),%ecx 809eb31: be 08 00 00 00 mov $0x8,%esi - 809eb36: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809eb36: e9 7b 8b 04 00 jmp 80e76b6 <_end+0xa6e> ++ 809eb36: + 809eb3b: 90 nop + 809eb3c: 90 nop 809eb3d: 8b 44 24 1c mov 0x1c(%esp),%eax @@ -1024,7 +1025,7 @@ expression: diff 809eb6e: 89 c3 mov %eax,%ebx 809eb70: b8 0e 01 00 00 mov $0x10e,%eax - 809eb75: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809eb75: e9 4e 8b 04 00 jmp 80e76c8 <_end+0xa80> ++ 809eb75: + 809eb7a: 90 nop + 809eb7b: 90 nop 809eb7c: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -1035,7 +1036,7 @@ expression: diff 809eb89: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi 809eb90: b8 e0 00 00 00 mov $0xe0,%eax - 809eb95: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809eb95: e9 40 8b 04 00 jmp 80e76da <_end+0xa92> ++ 809eb95: + 809eb9a: 90 nop + 809eb9b: 90 nop 809eb9c: 89 eb mov %ebp,%ebx @@ -1046,7 +1047,7 @@ expression: diff 809ebab: 89 c3 mov %eax,%ebx 809ebad: b8 0e 01 00 00 mov $0x10e,%eax - 809ebb2: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809ebb2: e9 35 8b 04 00 jmp 80e76ec <_end+0xaa4> ++ 809ebb2: + 809ebb7: 90 nop + 809ebb8: 90 nop 809ebb9: 89 c7 mov %eax,%edi @@ -1057,7 +1058,7 @@ expression: diff 809ec91: b8 af 00 00 00 mov $0xaf,%eax 809ec96: be 08 00 00 00 mov $0x8,%esi - 809ec9b: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809ec9b: e9 5e 8a 04 00 jmp 80e76fe <_end+0xab6> ++ 809ec9b: + 809eca0: 90 nop + 809eca1: 90 nop 809eca2: 89 c2 mov %eax,%edx @@ -1068,7 +1069,7 @@ expression: diff 0809f8e0 <__getpid>: 809f8e0: b8 14 00 00 00 mov $0x14,%eax - 809f8e5: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 809f8e5: e9 26 7e 04 00 jmp 80e7710 <_end+0xac8> ++ 809f8e5: + 809f8ea: 90 nop + 809f8eb: 90 nop 809f8ec: c3 ret @@ -1079,7 +1080,7 @@ expression: diff 80a009b: b8 8c 00 00 00 mov $0x8c,%eax 80a00a0: 8b 4c 24 0c mov 0xc(%esp),%ecx - 80a00a4: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 80a00a4: e9 79 76 04 00 jmp 80e7722 <_end+0xada> ++ 80a00a4: + 80a00a9: 90 nop + 80a00aa: 90 nop 80a00ab: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -1090,7 +1091,7 @@ expression: diff 80a3578: 8d 58 1c lea 0x1c(%eax),%ebx 80a357b: b8 f0 00 00 00 mov $0xf0,%eax - 80a3580: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 80a3580: e9 af 41 04 00 jmp 80e7734 <_end+0xaec> ++ 80a3580: + 80a3585: 90 nop + 80a3586: 90 nop 80a3587: eb 87 jmp 80a3510 <_dl_fixup+0xf0> @@ -1101,7 +1102,7 @@ expression: diff 80a718c: 8d 58 1c lea 0x1c(%eax),%ebx 80a718f: b8 f0 00 00 00 mov $0xf0,%eax - 80a7194: 65 ff 15 10 00 00 00 call *%gs:0x10 -+ 80a7194: e9 ad 05 04 00 jmp 80e7746 <_end+0xafe> ++ 80a7194: + 80a7199: 90 nop + 80a719a: 90 nop 80a719b: 8b 44 24 1c mov 0x1c(%esp),%eax diff --git a/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-diff.snap b/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-diff.snap index 1ae0fc2b4..aebaab30d 100644 --- a/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-diff.snap +++ b/litebox_syscall_rewriter/tests/snapshots/snapshot_tests__hello-diff.snap @@ -1,5 +1,6 @@ --- source: litebox_syscall_rewriter/tests/snapshot_tests.rs +assertion_line: 99 expression: diff --- --- original @@ -10,7 +11,7 @@ expression: diff 401222: bf 01 00 00 00 mov $0x1,%edi - 401227: b8 0e 00 00 00 mov $0xe,%eax - 40122c: 0f 05 syscall -+ 401227: e9 dc 0d 0b 00 jmp 4b2008 <_end+0xde0> ++ 401227: + 40122c: 90 nop + 40122d: 90 nop 40122e: 8b 05 0c fc 0a 00 mov 0xafc0c(%rip),%eax # 4b0e40 @@ -23,7 +24,7 @@ expression: diff - 401e78: 31 ff xor %edi,%edi - 401e7a: 89 d0 mov %edx,%eax - 401e7c: 0f 05 syscall -+ 401e78: e9 9d 01 0b 00 jmp 4b201a <_end+0xdf2> ++ 401e78: + 401e7d: 90 nop 401e7e: eb f8 jmp 401e78 <__libc_start_call_main+0x88> 401e80: 31 c0 xor %eax,%eax @@ -34,7 +35,7 @@ expression: diff 403ee0: bf 01 50 00 00 mov $0x5001,%edi - 403ee5: b8 9e 00 00 00 mov $0x9e,%eax - 403eea: 0f 05 syscall -+ 403ee5: e9 41 e1 0a 00 jmp 4b202b <_end+0xe03> ++ 403ee5: + 403eea: 90 nop + 403eeb: 90 nop 403eec: 44 89 ef mov %r13d,%edi @@ -46,7 +47,7 @@ expression: diff 4043ce: 48 89 36 mov %rsi,(%rsi) - 4043d1: 48 89 76 10 mov %rsi,0x10(%rsi) - 4043d5: 0f 05 syscall -+ 4043d1: e9 67 dc 0a 00 jmp 4b203d <_end+0xe15> ++ 4043d1: + 4043d6: 90 nop 4043d7: 85 c0 test %eax,%eax 4043d9: 74 24 je 4043ff <__libc_setup_tls+0x1df> @@ -55,7 +56,7 @@ expression: diff 4043e5: b8 01 00 00 00 mov $0x1,%eax - 4043ea: 48 8d 35 c7 d1 07 00 lea 0x7d1c7(%rip),%rsi # 4815b8 - 4043f1: 0f 05 syscall -+ 4043ea: e9 5f dc 0a 00 jmp 4b204e <_end+0xe26> ++ 4043ea: + 4043ef: 90 nop + 4043f0: 90 nop + 4043f1: 90 nop @@ -63,7 +64,7 @@ expression: diff 4043f3: bf 7f 00 00 00 mov $0x7f,%edi - 4043f8: b8 e7 00 00 00 mov $0xe7,%eax - 4043fd: 0f 05 syscall -+ 4043f8: e9 65 dc 0a 00 jmp 4b2062 <_end+0xe3a> ++ 4043f8: + 4043fd: 90 nop + 4043fe: 90 nop 4043ff: e8 dc ba 01 00 call 41fee0 <__tls_init_tp> @@ -75,7 +76,7 @@ expression: diff 4044ba: b8 01 00 00 00 mov $0x1,%eax - 4044bf: 48 8d 35 f2 d0 07 00 lea 0x7d0f2(%rip),%rsi # 4815b8 - 4044c6: 0f 05 syscall -+ 4044bf: e9 b0 db 0a 00 jmp 4b2074 <_end+0xe4c> ++ 4044bf: + 4044c4: 90 nop + 4044c5: 90 nop + 4044c6: 90 nop @@ -83,7 +84,7 @@ expression: diff 4044c8: bf 7f 00 00 00 mov $0x7f,%edi - 4044cd: b8 e7 00 00 00 mov $0xe7,%eax - 4044d2: 0f 05 syscall -+ 4044cd: e9 b6 db 0a 00 jmp 4b2088 <_end+0xe60> ++ 4044cd: + 4044d2: 90 nop + 4044d3: 90 nop 4044d4: e9 70 fe ff ff jmp 404349 <__libc_setup_tls+0x129> @@ -95,7 +96,7 @@ expression: diff 40a3e7: bf 02 00 00 00 mov $0x2,%edi - 40a3ec: 44 89 c8 mov %r9d,%eax - 40a3ef: 0f 05 syscall -+ 40a3ec: e9 a9 7c 0a 00 jmp 4b209a <_end+0xe72> ++ 40a3ec: 40a3f1: 48 83 f8 fc cmp $0xfffffffffffffffc,%rax 40a3f5: 74 e9 je 40a3e0 <__libc_message_impl+0x150> 40a3f7: 45 31 c9 xor %r9d,%r9d @@ -105,7 +106,7 @@ expression: diff 40a5cf: be 80 00 00 00 mov $0x80,%esi - 40a5d4: b8 ca 00 00 00 mov $0xca,%eax - 40a5d9: 0f 05 syscall -+ 40a5d4: e9 d1 7a 0a 00 jmp 4b20aa <_end+0xe82> ++ 40a5d4: + 40a5d9: 90 nop + 40a5da: 90 nop 40a5db: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -117,7 +118,7 @@ expression: diff 40a635: b8 ca 00 00 00 mov $0xca,%eax - 40a63a: 40 80 f6 80 xor $0x80,%sil - 40a63e: 0f 05 syscall -+ 40a63a: e9 7d 7a 0a 00 jmp 4b20bc <_end+0xe94> ++ 40a63a: + 40a63f: 90 nop 40a640: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 40a646: 76 d6 jbe 40a61e <__lll_lock_wait+0xe> @@ -128,7 +129,7 @@ expression: diff 40a67c: be 81 00 00 00 mov $0x81,%esi - 40a681: b8 ca 00 00 00 mov $0xca,%eax - 40a686: 0f 05 syscall -+ 40a681: e9 47 7a 0a 00 jmp 4b20cd <_end+0xea5> ++ 40a681: + 40a686: 90 nop + 40a687: 90 nop 40a688: c3 ret @@ -140,7 +141,7 @@ expression: diff 40a69b: ba 01 00 00 00 mov $0x1,%edx - 40a6a0: b8 ca 00 00 00 mov $0xca,%eax - 40a6a5: 0f 05 syscall -+ 40a6a0: e9 3a 7a 0a 00 jmp 4b20df <_end+0xeb7> ++ 40a6a0: + 40a6a5: 90 nop + 40a6a6: 90 nop 40a6a7: c3 ret @@ -152,7 +153,7 @@ expression: diff 40bbdb: c6 05 3e 4c 0a 00 01 movb $0x1,0xa4c3e(%rip) # 4b0820 <__malloc_initialized> - 40bbe2: b8 3e 01 00 00 mov $0x13e,%eax - 40bbe7: 0f 05 syscall -+ 40bbe2: e9 0a 65 0a 00 jmp 4b20f1 <_end+0xec9> ++ 40bbe2: + 40bbe7: 90 nop + 40bbe8: 90 nop 40bbe9: 48 8d 5d d0 lea -0x30(%rbp),%rbx @@ -164,7 +165,7 @@ expression: diff 4181de: 66 90 xchg %ax,%ax - 4181e0: b8 e4 00 00 00 mov $0xe4,%eax - 4181e5: 0f 05 syscall -+ 4181e0: e9 1e 9f 09 00 jmp 4b2103 <_end+0xedb> ++ 4181e0: + 4181e5: 90 nop + 4181e6: 90 nop 4181e7: 85 c0 test %eax,%eax @@ -176,7 +177,7 @@ expression: diff 418249: 89 d0 mov %edx,%eax - 41824b: 0f 05 syscall - 41824d: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax -+ 41824b: e9 c5 9e 09 00 jmp 4b2115 <_end+0xeed> ++ 41824b: + 418250: 90 nop + 418251: 90 nop + 418252: 90 nop @@ -189,7 +190,7 @@ expression: diff 418260: f3 0f 1e fa endbr64 - 418264: b8 05 00 00 00 mov $0x5,%eax - 418269: 0f 05 syscall -+ 418264: e9 c4 9e 09 00 jmp 4b212d <_end+0xf05> ++ 418264: + 418269: 90 nop + 41826a: 90 nop 41826b: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -201,7 +202,7 @@ expression: diff 418290: f3 0f 1e fa endbr64 - 418294: b8 03 00 00 00 mov $0x3,%eax - 418299: 0f 05 syscall -+ 418294: e9 a6 9e 09 00 jmp 4b213f <_end+0xf17> ++ 418294: + 418299: 90 nop + 41829a: 90 nop 41829b: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -213,7 +214,7 @@ expression: diff 4182f9: 74 25 je 418320 <__fcntl64_nocancel+0x60> - 4182fb: b8 48 00 00 00 mov $0x48,%eax - 418300: 0f 05 syscall -+ 4182fb: e9 51 9e 09 00 jmp 4b2151 <_end+0xf29> ++ 4182fb: + 418300: 90 nop + 418301: 90 nop 418302: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -225,7 +226,7 @@ expression: diff 418324: be 10 00 00 00 mov $0x10,%esi - 418329: b8 48 00 00 00 mov $0x48,%eax - 41832e: 0f 05 syscall -+ 418329: e9 35 9e 09 00 jmp 4b2163 <_end+0xf3b> ++ 418329: + 41832e: 90 nop + 41832f: 90 nop 418330: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -237,7 +238,7 @@ expression: diff 41837e: 74 20 je 4183a0 <__fcntl64_nocancel_adjusted+0x40> - 418380: b8 48 00 00 00 mov $0x48,%eax - 418385: 0f 05 syscall -+ 418380: e9 f0 9d 09 00 jmp 4b2175 <_end+0xf4d> ++ 418380: + 418385: 90 nop + 418386: 90 nop 418387: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -249,7 +250,7 @@ expression: diff 4183a4: be 10 00 00 00 mov $0x10,%esi - 4183a9: b8 48 00 00 00 mov $0x48,%eax - 4183ae: 0f 05 syscall -+ 4183a9: e9 d9 9d 09 00 jmp 4b2187 <_end+0xf5f> ++ 4183a9: + 4183ae: 90 nop + 4183af: 90 nop 4183b0: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -261,7 +262,7 @@ expression: diff 41841a: 48 89 fe mov %rdi,%rsi - 41841d: bf 9c ff ff ff mov $0xffffff9c,%edi - 418422: 0f 05 syscall -+ 41841d: e9 77 9d 09 00 jmp 4b2199 <_end+0xf71> ++ 41841d: + 418422: 90 nop + 418423: 90 nop 418424: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -274,7 +275,7 @@ expression: diff - 418480: f3 0f 1e fa endbr64 - 418484: 31 c0 xor %eax,%eax - 418486: 0f 05 syscall -+ 418480: e9 26 9d 09 00 jmp 4b21ab <_end+0xf83> ++ 418480: + 418485: 90 nop + 418486: 90 nop + 418487: 90 nop @@ -287,7 +288,7 @@ expression: diff 4184b0: f3 0f 1e fa endbr64 - 4184b4: b8 0c 00 00 00 mov $0xc,%eax - 4184b9: 0f 05 syscall -+ 4184b4: e9 05 9d 09 00 jmp 4b21be <_end+0xf96> ++ 4184b4: + 4184b9: 90 nop + 4184ba: 90 nop 4184bb: 48 89 05 96 83 09 00 mov %rax,0x98396(%rip) # 4b0858 <__curbrk> @@ -299,7 +300,7 @@ expression: diff 41871a: 48 8d 95 f0 ef ff ff lea -0x1010(%rbp),%rdx - 418721: b8 cc 00 00 00 mov $0xcc,%eax - 418726: 0f 05 syscall -+ 418721: e9 aa 9a 09 00 jmp 4b21d0 <_end+0xfa8> ++ 418721: + 418726: 90 nop + 418727: 90 nop 418728: 85 c0 test %eax,%eax @@ -311,7 +312,7 @@ expression: diff 418b40: f3 0f 1e fa endbr64 - 418b44: b8 1c 00 00 00 mov $0x1c,%eax - 418b49: 0f 05 syscall -+ 418b44: e9 99 96 09 00 jmp 4b21e2 <_end+0xfba> ++ 418b44: + 418b49: 90 nop + 418b4a: 90 nop 418b4b: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -323,7 +324,7 @@ expression: diff 418b92: 48 89 df mov %rbx,%rdi - 418b95: b8 09 00 00 00 mov $0x9,%eax - 418b9a: 0f 05 syscall -+ 418b95: e9 5a 96 09 00 jmp 4b21f4 <_end+0xfcc> ++ 418b95: + 418b9a: 90 nop + 418b9b: 90 nop 418b9c: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -335,7 +336,7 @@ expression: diff 418bed: b8 09 00 00 00 mov $0x9,%eax - 418bf2: 41 83 ca 40 or $0x40,%r10d - 418bf6: 0f 05 syscall -+ 418bf2: e9 0f 96 09 00 jmp 4b2206 <_end+0xfde> ++ 418bf2: + 418bf7: 90 nop 418bf8: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 418bfe: 76 a4 jbe 418ba4 <__mmap64+0x34> @@ -346,7 +347,7 @@ expression: diff 418c30: f3 0f 1e fa endbr64 - 418c34: b8 0a 00 00 00 mov $0xa,%eax - 418c39: 0f 05 syscall -+ 418c34: e9 de 95 09 00 jmp 4b2217 <_end+0xfef> ++ 418c34: + 418c39: 90 nop + 418c3a: 90 nop 418c3b: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -358,7 +359,7 @@ expression: diff 418c60: f3 0f 1e fa endbr64 - 418c64: b8 0b 00 00 00 mov $0xb,%eax - 418c69: 0f 05 syscall -+ 418c64: e9 c0 95 09 00 jmp 4b2229 <_end+0x1001> ++ 418c64: + 418c69: 90 nop + 418c6a: 90 nop 418c6b: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -370,7 +371,7 @@ expression: diff 418d47: 45 31 c0 xor %r8d,%r8d - 418d4a: b8 19 00 00 00 mov $0x19,%eax - 418d4f: 0f 05 syscall -+ 418d4a: e9 ec 94 09 00 jmp 4b223b <_end+0x1013> ++ 418d4a: + 418d4f: 90 nop + 418d50: 90 nop 418d51: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -382,7 +383,7 @@ expression: diff 418e23: bf 41 4d 56 53 mov $0x53564d41,%edi - 418e28: b8 9d 00 00 00 mov $0x9d,%eax - 418e2d: 0f 05 syscall -+ 418e28: e9 20 94 09 00 jmp 4b224d <_end+0x1025> ++ 418e28: + 418e2d: 90 nop + 418e2e: 90 nop 418e2f: 83 f8 ea cmp $0xffffffea,%eax @@ -394,7 +395,7 @@ expression: diff 418e50: f3 0f 1e fa endbr64 - 418e54: b8 63 00 00 00 mov $0x63,%eax - 418e59: 0f 05 syscall -+ 418e54: e9 06 94 09 00 jmp 4b225f <_end+0x1037> ++ 418e54: + 418e59: 90 nop + 418e5a: 90 nop 418e5b: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -406,7 +407,7 @@ expression: diff 41e494: 48 8d 9d e0 ef ff ff lea -0x1020(%rbp),%rbx - 41e49b: 48 89 da mov %rbx,%rdx - 41e49e: 0f 05 syscall -+ 41e49b: e9 d1 3d 09 00 jmp 4b2271 <_end+0x1049> ++ 41e49b: 41e4a0: 85 c0 test %eax,%eax 41e4a2: 7e 5c jle 41e500 <_dl_get_origin+0xa0> 41e4a4: 0f b6 95 e0 ef ff ff movzbl -0x1020(%rbp),%edx @@ -416,7 +417,7 @@ expression: diff 41e6db: 48 8d b5 d0 f6 ff ff lea -0x930(%rbp),%rsi - 41e6e2: b8 14 00 00 00 mov $0x14,%eax - 41e6e7: 0f 05 syscall -+ 41e6e2: e9 9a 3b 09 00 jmp 4b2281 <_end+0x1059> ++ 41e6e2: + 41e6e7: 90 nop + 41e6e8: 90 nop 41e6e9: 48 81 c4 38 09 00 00 add $0x938,%rsp @@ -428,7 +429,7 @@ expression: diff 41ff24: 48 8d bb d0 02 00 00 lea 0x2d0(%rbx),%rdi - 41ff2b: b8 da 00 00 00 mov $0xda,%eax - 41ff30: 0f 05 syscall -+ 41ff2b: e9 63 23 09 00 jmp 4b2293 <_end+0x106b> ++ 41ff2b: + 41ff30: 90 nop + 41ff31: 90 nop 41ff32: 89 83 d0 02 00 00 mov %eax,0x2d0(%rbx) @@ -440,7 +441,7 @@ expression: diff 41ff81: 66 0f 6c c0 punpcklqdq %xmm0,%xmm0 - 41ff85: 0f 11 83 d8 02 00 00 movups %xmm0,0x2d8(%rbx) - 41ff8c: 0f 05 syscall -+ 41ff85: e9 1b 23 09 00 jmp 4b22a5 <_end+0x107d> ++ 41ff85: + 41ff8a: 90 nop + 41ff8b: 90 nop + 41ff8c: 90 nop @@ -454,7 +455,7 @@ expression: diff 41fff4: 48 89 df mov %rbx,%rdi - 41fff7: b8 4e 01 00 00 mov $0x14e,%eax - 41fffc: 0f 05 syscall -+ 41fff7: e9 bd 22 09 00 jmp 4b22b9 <_end+0x1091> ++ 41fff7: + 41fffc: 90 nop + 41fffd: 90 nop 41fffe: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -466,7 +467,7 @@ expression: diff 421344: bf 02 50 00 00 mov $0x5002,%edi - 421349: b8 9e 00 00 00 mov $0x9e,%eax - 42134e: 0f 05 syscall -+ 421349: e9 7d 0f 09 00 jmp 4b22cb <_end+0x10a3> ++ 421349: + 42134e: 90 nop + 42134f: 90 nop 421350: 89 c7 mov %eax,%edi @@ -478,7 +479,7 @@ expression: diff 4213a5: 48 89 e5 mov %rsp,%rbp - 4213a8: 48 8d 75 f8 lea -0x8(%rbp),%rsi - 4213ac: 0f 05 syscall -+ 4213a8: e9 30 0f 09 00 jmp 4b22dd <_end+0x10b5> ++ 4213a8: + 4213ad: 90 nop 4213ae: 48 85 c0 test %rax,%rax 4213b1: 74 15 je 4213c8 <_dl_cet_setup_features+0x38> @@ -490,7 +491,7 @@ expression: diff - 4213f7: bf 03 50 00 00 mov $0x5003,%edi - 4213fc: 89 d0 mov %edx,%eax - 4213fe: 0f 05 syscall -+ 4213f7: e9 f2 0e 09 00 jmp 4b22ee <_end+0x10c6> ++ 4213f7: + 4213fc: 90 nop + 4213fd: 90 nop + 4213fe: 90 nop @@ -505,13 +506,13 @@ expression: diff - 421455: 31 ff xor %edi,%edi - 421457: 89 f0 mov %esi,%eax - 421459: 0f 05 syscall -+ 421455: e9 a8 0e 09 00 jmp 4b2302 <_end+0x10da> ++ 421455: + 42145a: 90 nop 42145b: 48 89 c2 mov %rax,%rdx - 42145e: 48 8d 3c 18 lea (%rax,%rbx,1),%rdi - 421462: 89 f0 mov %esi,%eax - 421464: 0f 05 syscall -+ 42145e: e9 b0 0e 09 00 jmp 4b2313 <_end+0x10eb> ++ 42145e: + 421463: 90 nop + 421464: 90 nop + 421465: 90 nop @@ -524,7 +525,7 @@ expression: diff 421481: 48 89 de mov %rbx,%rsi - 421484: b8 09 00 00 00 mov $0x9,%eax - 421489: 0f 05 syscall -+ 421484: e9 9d 0e 09 00 jmp 4b2326 <_end+0x10fe> ++ 421484: + 421489: 90 nop + 42148a: 90 nop 42148b: 31 d2 xor %edx,%edx @@ -536,7 +537,7 @@ expression: diff 444c16: 48 8d 35 b3 0a 04 00 lea 0x40ab3(%rip),%rsi # 4856d0 - 444c1d: b8 0e 00 00 00 mov $0xe,%eax - 444c22: 0f 05 syscall -+ 444c1d: e9 16 d7 06 00 jmp 4b2338 <_end+0x1110> ++ 444c1d: + 444c22: 90 nop + 444c23: 90 nop 444c24: 31 c0 xor %eax,%eax @@ -548,7 +549,7 @@ expression: diff 444c63: bf 02 00 00 00 mov $0x2,%edi - 444c68: b8 0e 00 00 00 mov $0xe,%eax - 444c6d: 0f 05 syscall -+ 444c68: e9 dd d6 06 00 jmp 4b234a <_end+0x1122> ++ 444c68: + 444c6d: 90 nop + 444c6e: 90 nop 444c6f: 48 8b 45 d8 mov -0x28(%rbp),%rax @@ -560,7 +561,7 @@ expression: diff 444ca8: 89 de mov %ebx,%esi - 444caa: b8 ea 00 00 00 mov $0xea,%eax - 444caf: 0f 05 syscall -+ 444caa: e9 ad d6 06 00 jmp 4b235c <_end+0x1134> ++ 444caa: + 444caf: 90 nop + 444cb0: 90 nop 444cb1: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -571,7 +572,7 @@ expression: diff 444cbe: 66 90 xchg %ax,%ax - 444cc0: b8 ba 00 00 00 mov $0xba,%eax - 444cc5: 0f 05 syscall -+ 444cc0: e9 a9 d6 06 00 jmp 4b236e <_end+0x1146> ++ 444cc0: + 444cc5: 90 nop + 444cc6: 90 nop 444cc7: 89 c3 mov %eax,%ebx @@ -581,7 +582,7 @@ expression: diff 444cd3: 89 c7 mov %eax,%edi - 444cd5: b8 ea 00 00 00 mov $0xea,%eax - 444cda: 0f 05 syscall -+ 444cd5: e9 a6 d6 06 00 jmp 4b2380 <_end+0x1158> ++ 444cd5: + 444cda: 90 nop + 444cdb: 90 nop 444cdc: 89 c3 mov %eax,%ebx @@ -593,7 +594,7 @@ expression: diff 444d78: 4c 89 fa mov %r15,%rdx - 444d7b: 48 8d 35 4e 09 04 00 lea 0x4094e(%rip),%rsi # 4856d0 - 444d82: 0f 05 syscall -+ 444d7b: e9 12 d6 06 00 jmp 4b2392 <_end+0x116a> ++ 444d7b: + 444d80: 90 nop + 444d81: 90 nop + 444d82: 90 nop @@ -607,7 +608,7 @@ expression: diff 444dc4: bf 02 00 00 00 mov $0x2,%edi - 444dc9: b8 0e 00 00 00 mov $0xe,%eax - 444dce: 0f 05 syscall -+ 444dc9: e9 d8 d5 06 00 jmp 4b23a6 <_end+0x117e> ++ 444dc9: + 444dce: 90 nop + 444dcf: 90 nop 444dd0: 48 8b 45 c8 mov -0x38(%rbp),%rax @@ -619,7 +620,7 @@ expression: diff 444e08: 89 de mov %ebx,%esi - 444e0a: b8 ea 00 00 00 mov $0xea,%eax - 444e0f: 0f 05 syscall -+ 444e0a: e9 a9 d5 06 00 jmp 4b23b8 <_end+0x1190> ++ 444e0a: + 444e0f: 90 nop + 444e10: 90 nop 444e11: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -629,7 +630,7 @@ expression: diff 444e1e: eb 8a jmp 444daa <__pthread_kill+0x8a> - 444e20: b8 ba 00 00 00 mov $0xba,%eax - 444e25: 0f 05 syscall -+ 444e20: e9 a5 d5 06 00 jmp 4b23ca <_end+0x11a2> ++ 444e20: + 444e25: 90 nop + 444e26: 90 nop 444e27: 89 c3 mov %eax,%ebx @@ -639,7 +640,7 @@ expression: diff 444e33: 89 c7 mov %eax,%edi - 444e35: b8 ea 00 00 00 mov $0xea,%eax - 444e3a: 0f 05 syscall -+ 444e35: e9 a2 d5 06 00 jmp 4b23dc <_end+0x11b4> ++ 444e35: + 444e3a: 90 nop + 444e3b: 90 nop 444e3c: 41 89 c6 mov %eax,%r14d @@ -651,7 +652,7 @@ expression: diff 445107: f7 d6 not %esi - 445109: 81 e6 80 00 00 00 and $0x80,%esi - 44510f: 0f 05 syscall -+ 445109: e9 e0 d2 06 00 jmp 4b23ee <_end+0x11c6> ++ 445109: + 44510e: 90 nop + 44510f: 90 nop + 445110: 90 nop @@ -664,7 +665,7 @@ expression: diff 4452e4: 48 89 df mov %rbx,%rdi - 4452e7: b8 ca 00 00 00 mov $0xca,%eax - 4452ec: 0f 05 syscall -+ 4452e7: e9 15 d1 06 00 jmp 4b2401 <_end+0x11d9> ++ 4452e7: + 4452ec: 90 nop + 4452ed: 90 nop 4452ee: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -676,7 +677,7 @@ expression: diff 4454ff: be 07 00 00 00 mov $0x7,%esi - 445504: b8 ca 00 00 00 mov $0xca,%eax - 445509: 0f 05 syscall -+ 445504: e9 0a cf 06 00 jmp 4b2413 <_end+0x11eb> ++ 445504: + 445509: 90 nop + 44550a: 90 nop 44550b: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -688,7 +689,7 @@ expression: diff 445aa9: 81 e6 80 00 00 00 and $0x80,%esi - 445aaf: 40 80 f6 81 xor $0x81,%sil - 445ab3: 0f 05 syscall -+ 445aaf: e9 71 c9 06 00 jmp 4b2425 <_end+0x11fd> ++ 445aaf: + 445ab4: 90 nop 445ab5: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 445abb: 0f 87 0e 02 00 00 ja 445ccf <__pthread_mutex_unlock_full+0x3bf> @@ -699,7 +700,7 @@ expression: diff 445cfd: 4c 89 c7 mov %r8,%rdi - 445d00: b8 ca 00 00 00 mov $0xca,%eax - 445d05: 0f 05 syscall -+ 445d00: e9 31 c7 06 00 jmp 4b2436 <_end+0x120e> ++ 445d00: + 445d05: 90 nop + 445d06: 90 nop 445d07: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -711,7 +712,7 @@ expression: diff 445d29: 4c 89 c7 mov %r8,%rdi - 445d2c: b8 ca 00 00 00 mov $0xca,%eax - 445d31: 0f 05 syscall -+ 445d2c: e9 17 c7 06 00 jmp 4b2448 <_end+0x1220> ++ 445d2c: + 445d31: 90 nop + 445d32: 90 nop 445d33: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -723,7 +724,7 @@ expression: diff 44600f: 48 89 df mov %rbx,%rdi - 446012: b8 ca 00 00 00 mov $0xca,%eax - 446017: 0f 05 syscall -+ 446012: e9 43 c4 06 00 jmp 4b245a <_end+0x1232> ++ 446012: + 446017: 90 nop + 446018: 90 nop 446019: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -735,7 +736,7 @@ expression: diff 4460b0: 48 89 df mov %rbx,%rdi - 4460b3: b8 ca 00 00 00 mov $0xca,%eax - 4460b8: 0f 05 syscall -+ 4460b3: e9 b4 c3 06 00 jmp 4b246c <_end+0x1244> ++ 4460b3: + 4460b8: 90 nop + 4460b9: 90 nop 4460ba: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -747,7 +748,7 @@ expression: diff 446142: be 81 00 00 00 mov $0x81,%esi - 446147: b8 ca 00 00 00 mov $0xca,%eax - 44614c: 0f 05 syscall -+ 446147: e9 32 c3 06 00 jmp 4b247e <_end+0x1256> ++ 446147: + 44614c: 90 nop + 44614d: 90 nop 44614e: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -759,7 +760,7 @@ expression: diff 4462e3: c1 e6 07 shl $0x7,%esi - 4462e6: 40 80 f6 81 xor $0x81,%sil - 4462ea: 0f 05 syscall -+ 4462e6: e9 a5 c1 06 00 jmp 4b2490 <_end+0x1268> ++ 4462e6: + 4462eb: 90 nop 4462ec: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 4462f2: 0f 86 2e ff ff ff jbe 446226 <___pthread_rwlock_rdlock+0x46> @@ -770,7 +771,7 @@ expression: diff 446437: 40 80 f6 81 xor $0x81,%sil - 44643b: b8 ca 00 00 00 mov $0xca,%eax - 446440: 0f 05 syscall -+ 44643b: e9 61 c0 06 00 jmp 4b24a1 <_end+0x1279> ++ 44643b: + 446440: 90 nop + 446441: 90 nop 446442: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -782,7 +783,7 @@ expression: diff 44648a: b8 ca 00 00 00 mov $0xca,%eax - 44648f: 40 80 f6 81 xor $0x81,%sil - 446493: 0f 05 syscall -+ 44648f: e9 1f c0 06 00 jmp 4b24b3 <_end+0x128b> ++ 44648f: + 446494: 90 nop 446495: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 44649b: 76 83 jbe 446420 <___pthread_rwlock_unlock+0x50> @@ -793,7 +794,7 @@ expression: diff 446511: 40 80 f6 81 xor $0x81,%sil - 446515: b8 ca 00 00 00 mov $0xca,%eax - 44651a: 0f 05 syscall -+ 446515: e9 aa bf 06 00 jmp 4b24c4 <_end+0x129c> ++ 446515: + 44651a: 90 nop + 44651b: 90 nop 44651c: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -805,7 +806,7 @@ expression: diff 446577: b8 ca 00 00 00 mov $0xca,%eax - 44657c: 40 80 f6 81 xor $0x81,%sil - 446580: 0f 05 syscall -+ 44657c: e9 55 bf 06 00 jmp 4b24d6 <_end+0x12ae> ++ 44657c: + 446581: 90 nop 446582: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 446588: 0f 86 6c ff ff ff jbe 4464fa <___pthread_rwlock_unlock+0x12a> @@ -816,7 +817,7 @@ expression: diff 446855: 40 80 f6 81 xor $0x81,%sil - 446859: b8 ca 00 00 00 mov $0xca,%eax - 44685e: 0f 05 syscall -+ 446859: e9 89 bc 06 00 jmp 4b24e7 <_end+0x12bf> ++ 446859: + 44685e: 90 nop + 44685f: 90 nop 446860: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -828,7 +829,7 @@ expression: diff 446880: 40 80 f6 81 xor $0x81,%sil - 446884: b8 ca 00 00 00 mov $0xca,%eax - 446889: 0f 05 syscall -+ 446884: e9 70 bc 06 00 jmp 4b24f9 <_end+0x12d1> ++ 446884: + 446889: 90 nop + 44688a: 90 nop 44688b: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -840,7 +841,7 @@ expression: diff 446924: 40 80 f6 81 xor $0x81,%sil - 446928: b8 ca 00 00 00 mov $0xca,%eax - 44692d: 0f 05 syscall -+ 446928: e9 de bb 06 00 jmp 4b250b <_end+0x12e3> ++ 446928: + 44692d: 90 nop + 44692e: 90 nop 44692f: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -852,7 +853,7 @@ expression: diff 446a0b: 41 ba 08 00 00 00 mov $0x8,%r10d - 446a11: b8 0e 00 00 00 mov $0xe,%eax - 446a16: 0f 05 syscall -+ 446a11: e9 07 bb 06 00 jmp 4b251d <_end+0x12f5> ++ 446a11: + 446a16: 90 nop + 446a17: 90 nop 446a18: 89 c2 mov %eax,%edx @@ -864,7 +865,7 @@ expression: diff 45ba2c: 48 0f 47 d0 cmova %rax,%rdx - 45ba30: b8 d9 00 00 00 mov $0xd9,%eax - 45ba35: 0f 05 syscall -+ 45ba30: e9 fa 6a 05 00 jmp 4b252f <_end+0x1307> ++ 45ba30: + 45ba35: 90 nop + 45ba36: 90 nop 45ba37: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -876,7 +877,7 @@ expression: diff 45bb50: f3 0f 1e fa endbr64 - 45bb54: b8 27 00 00 00 mov $0x27,%eax - 45bb59: 0f 05 syscall -+ 45bb54: e9 e8 69 05 00 jmp 4b2541 <_end+0x1319> ++ 45bb54: + 45bb59: 90 nop + 45bb5a: 90 nop 45bb5b: c3 ret @@ -888,7 +889,7 @@ expression: diff 45bba0: f3 0f 1e fa endbr64 - 45bba4: b8 8f 00 00 00 mov $0x8f,%eax - 45bba9: 0f 05 syscall -+ 45bba4: e9 aa 69 05 00 jmp 4b2553 <_end+0x132b> ++ 45bba4: + 45bba9: 90 nop + 45bbaa: 90 nop 45bbab: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -900,7 +901,7 @@ expression: diff 45bbd0: f3 0f 1e fa endbr64 - 45bbd4: b8 91 00 00 00 mov $0x91,%eax - 45bbd9: 0f 05 syscall -+ 45bbd4: e9 8c 69 05 00 jmp 4b2565 <_end+0x133d> ++ 45bbd4: + 45bbd9: 90 nop + 45bbda: 90 nop 45bbdb: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -912,7 +913,7 @@ expression: diff 45bc00: f3 0f 1e fa endbr64 - 45bc04: b8 92 00 00 00 mov $0x92,%eax - 45bc09: 0f 05 syscall -+ 45bc04: e9 6e 69 05 00 jmp 4b2577 <_end+0x134f> ++ 45bc04: + 45bc09: 90 nop + 45bc0a: 90 nop 45bc0b: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -924,7 +925,7 @@ expression: diff 45bc30: f3 0f 1e fa endbr64 - 45bc34: b8 93 00 00 00 mov $0x93,%eax - 45bc39: 0f 05 syscall -+ 45bc34: e9 50 69 05 00 jmp 4b2589 <_end+0x1361> ++ 45bc34: + 45bc39: 90 nop + 45bc3a: 90 nop 45bc3b: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -936,7 +937,7 @@ expression: diff 45bc60: f3 0f 1e fa endbr64 - 45bc64: b8 90 00 00 00 mov $0x90,%eax - 45bc69: 0f 05 syscall -+ 45bc64: e9 32 69 05 00 jmp 4b259b <_end+0x1373> ++ 45bc64: + 45bc69: 90 nop + 45bc6a: 90 nop 45bc6b: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax @@ -948,7 +949,7 @@ expression: diff 45bd0d: 48 8b bd 08 ff ff ff mov -0xf8(%rbp),%rdi - 45bd14: b8 4f 00 00 00 mov $0x4f,%eax - 45bd19: 0f 05 syscall -+ 45bd14: e9 94 68 05 00 jmp 4b25ad <_end+0x1385> ++ 45bd14: + 45bd19: 90 nop + 45bd1a: 90 nop 45bd1b: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -960,7 +961,7 @@ expression: diff 45c510: f3 0f 1e fa endbr64 - 45c514: b8 08 00 00 00 mov $0x8,%eax - 45c519: 0f 05 syscall -+ 45c514: e9 a6 60 05 00 jmp 4b25bf <_end+0x1397> ++ 45c514: + 45c519: 90 nop + 45c51a: 90 nop 45c51b: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -972,7 +973,7 @@ expression: diff 45c5a9: bf 9c ff ff ff mov $0xffffff9c,%edi - 45c5ae: b8 01 01 00 00 mov $0x101,%eax - 45c5b3: 0f 05 syscall -+ 45c5ae: e9 1e 60 05 00 jmp 4b25d1 <_end+0x13a9> ++ 45c5ae: + 45c5b3: 90 nop + 45c5b4: 90 nop 45c5b5: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -984,7 +985,7 @@ expression: diff 45c619: bf 9c ff ff ff mov $0xffffff9c,%edi - 45c61e: b8 01 01 00 00 mov $0x101,%eax - 45c623: 0f 05 syscall -+ 45c61e: e9 c0 5f 05 00 jmp 4b25e3 <_end+0x13bb> ++ 45c61e: + 45c623: 90 nop + 45c624: 90 nop 45c625: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -996,7 +997,7 @@ expression: diff 45c6b9: 74 51 je 45c70c <__libc_openat64+0x8c> - 45c6bb: b8 01 01 00 00 mov $0x101,%eax - 45c6c0: 0f 05 syscall -+ 45c6bb: e9 35 5f 05 00 jmp 4b25f5 <_end+0x13cd> ++ 45c6bb: + 45c6c0: 90 nop + 45c6c1: 90 nop 45c6c2: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1008,7 +1009,7 @@ expression: diff 45c72d: 8b 7d a8 mov -0x58(%rbp),%edi - 45c730: b8 01 01 00 00 mov $0x101,%eax - 45c735: 0f 05 syscall -+ 45c730: e9 d2 5e 05 00 jmp 4b2607 <_end+0x13df> ++ 45c730: + 45c735: 90 nop + 45c736: 90 nop 45c737: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1020,7 +1021,7 @@ expression: diff 45c79d: 31 c0 xor %eax,%eax - 45c79f: 0f 05 syscall - 45c7a1: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax -+ 45c79f: e9 75 5e 05 00 jmp 4b2619 <_end+0x13f1> ++ 45c79f: + 45c7a4: 90 nop + 45c7a5: 90 nop + 45c7a6: 90 nop @@ -1034,7 +1035,7 @@ expression: diff - 45c7d3: 8b 7d f8 mov -0x8(%rbp),%edi - 45c7d6: 31 c0 xor %eax,%eax - 45c7d8: 0f 05 syscall -+ 45c7d3: e9 59 5e 05 00 jmp 4b2631 <_end+0x1409> ++ 45c7d3: + 45c7d8: 90 nop + 45c7d9: 90 nop 45c7da: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1046,7 +1047,7 @@ expression: diff 45c85b: 74 13 je 45c870 <__libc_write+0x20> - 45c85d: b8 01 00 00 00 mov $0x1,%eax - 45c862: 0f 05 syscall -+ 45c85d: e9 e1 5d 05 00 jmp 4b2643 <_end+0x141b> ++ 45c85d: + 45c862: 90 nop + 45c863: 90 nop 45c864: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1058,7 +1059,7 @@ expression: diff 45c893: 8b 7d f8 mov -0x8(%rbp),%edi - 45c896: b8 01 00 00 00 mov $0x1,%eax - 45c89b: 0f 05 syscall -+ 45c896: e9 ba 5d 05 00 jmp 4b2655 <_end+0x142d> ++ 45c896: + 45c89b: 90 nop + 45c89c: 90 nop 45c89d: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1070,7 +1071,7 @@ expression: diff 45c920: 74 26 je 45c948 <__openat64_nocancel+0x58> - 45c922: b8 01 01 00 00 mov $0x101,%eax - 45c927: 0f 05 syscall -+ 45c922: e9 40 5d 05 00 jmp 4b2667 <_end+0x143f> ++ 45c922: + 45c927: 90 nop + 45c928: 90 nop 45c929: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1082,7 +1083,7 @@ expression: diff 45c984: 49 89 ca mov %rcx,%r10 - 45c987: b8 11 00 00 00 mov $0x11,%eax - 45c98c: 0f 05 syscall -+ 45c987: e9 ed 5c 05 00 jmp 4b2679 <_end+0x1451> ++ 45c987: + 45c98c: 90 nop + 45c98d: 90 nop 45c98e: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1094,7 +1095,7 @@ expression: diff 45c9c0: f3 0f 1e fa endbr64 - 45c9c4: b8 01 00 00 00 mov $0x1,%eax - 45c9c9: 0f 05 syscall -+ 45c9c4: e9 c2 5c 05 00 jmp 4b268b <_end+0x1463> ++ 45c9c4: + 45c9c9: 90 nop + 45c9ca: 90 nop 45c9cb: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1106,7 +1107,7 @@ expression: diff 45ca13: 48 8d 55 d0 lea -0x30(%rbp),%rdx - 45ca17: b8 10 00 00 00 mov $0x10,%eax - 45ca1c: 0f 05 syscall -+ 45ca17: e9 81 5c 05 00 jmp 4b269d <_end+0x1475> ++ 45ca17: + 45ca1c: 90 nop + 45ca1d: 90 nop 45ca1e: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1119,7 +1120,7 @@ expression: diff - 45cabb: b8 2e 01 00 00 mov $0x12e,%eax - 45cac0: 31 ff xor %edi,%edi - 45cac2: 0f 05 syscall -+ 45cabb: e9 ef 5b 05 00 jmp 4b26af <_end+0x1487> ++ 45cabb: + 45cac0: 90 nop + 45cac1: 90 nop + 45cac2: 90 nop @@ -1133,7 +1134,7 @@ expression: diff 45ffa0: 48 8d 78 1c lea 0x1c(%rax),%rdi - 45ffa4: b8 ca 00 00 00 mov $0xca,%eax - 45ffa9: 0f 05 syscall -+ 45ffa4: e9 1a 27 05 00 jmp 4b26c3 <_end+0x149b> ++ 45ffa4: + 45ffa9: 90 nop + 45ffaa: 90 nop 45ffab: 48 8d 3d 6e ab 04 00 lea 0x4ab6e(%rip),%rdi # 4aab20 <_dl_load_lock> @@ -1145,7 +1146,7 @@ expression: diff 46306a: be 80 00 00 00 mov $0x80,%esi - 46306f: 44 89 c8 mov %r9d,%eax - 463072: 0f 05 syscall -+ 46306f: e9 61 f6 04 00 jmp 4b26d5 <_end+0x14ad> ++ 46306f: 463074: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 46307a: 76 dc jbe 463058 <__thread_gscope_wait+0x88> 46307c: 83 f8 f5 cmp $0xfffffff5,%eax @@ -1155,7 +1156,7 @@ expression: diff 46310a: be 80 00 00 00 mov $0x80,%esi - 46310f: 44 89 c8 mov %r9d,%eax - 463112: 0f 05 syscall -+ 46310f: e9 d1 f5 04 00 jmp 4b26e5 <_end+0x14bd> ++ 46310f: 463114: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 46311a: 76 dc jbe 4630f8 <__thread_gscope_wait+0x128> 46311c: 83 f8 f5 cmp $0xfffffff5,%eax @@ -1165,7 +1166,7 @@ expression: diff 00000000004669d0 <__restore_rt>: - 4669d0: 48 c7 c0 0f 00 00 00 mov $0xf,%rax - 4669d7: 0f 05 syscall -+ 4669d0: e9 20 bd 04 00 jmp 4b26f5 <_end+0x14cd> ++ 4669d0: + 4669d5: 90 nop + 4669d6: 90 nop + 4669d7: 90 nop @@ -1179,7 +1180,7 @@ expression: diff 466aad: 41 ba 08 00 00 00 mov $0x8,%r10d - 466ab3: b8 0d 00 00 00 mov $0xd,%eax - 466ab8: 0f 05 syscall -+ 466ab3: e9 51 bc 04 00 jmp 4b2709 <_end+0x14e1> ++ 466ab3: + 466ab8: 90 nop + 466ab9: 90 nop 466aba: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax @@ -1191,7 +1192,7 @@ expression: diff 46cb16: be 80 00 00 00 mov $0x80,%esi - 46cb1b: 44 89 c0 mov %r8d,%eax - 46cb1e: 0f 05 syscall -+ 46cb1b: e9 fb 5b 04 00 jmp 4b271b <_end+0x14f3> ++ 46cb1b: 46cb20: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 46cb26: 77 0d ja 46cb35 <__pthread_disable_asynccancel+0x65> 46cb28: 8b 0f mov (%rdi),%ecx @@ -1201,7 +1202,7 @@ expression: diff 46ccdf: 44 31 c6 xor %r8d,%esi - 46cce2: 45 31 c0 xor %r8d,%r8d - 46cce5: 0f 05 syscall -+ 46cce2: e9 44 5a 04 00 jmp 4b272b <_end+0x1503> ++ 46cce2: 46cce7: 85 c0 test %eax,%eax 46cce9: 7f 27 jg 46cd12 <__futex_abstimed_wait64+0x62> 46cceb: 83 f8 ea cmp $0xffffffea,%eax @@ -1211,7 +1212,7 @@ expression: diff 46cd89: 44 89 e2 mov %r12d,%edx - 46cd8c: b8 ca 00 00 00 mov $0xca,%eax - 46cd91: 0f 05 syscall -+ 46cd8c: e9 aa 59 04 00 jmp 4b273b <_end+0x1513> ++ 46cd8c: + 46cd91: 90 nop + 46cd92: 90 nop 46cd93: 48 89 c3 mov %rax,%rbx @@ -1223,7 +1224,7 @@ expression: diff 46ce17: 44 89 e2 mov %r12d,%edx - 46ce1a: b8 ca 00 00 00 mov $0xca,%eax - 46ce1f: 0f 05 syscall -+ 46ce1a: e9 2e 59 04 00 jmp 4b274d <_end+0x1525> ++ 46ce1a: + 46ce1f: 90 nop + 46ce20: 90 nop 46ce21: 44 89 ef mov %r13d,%edi @@ -1235,7 +1236,7 @@ expression: diff 46ce6c: 31 d2 xor %edx,%edx - 46ce6e: b8 ca 00 00 00 mov $0xca,%eax - 46ce73: 0f 05 syscall -+ 46ce6e: e9 ec 58 04 00 jmp 4b275f <_end+0x1537> ++ 46ce6e: + 46ce73: 90 nop + 46ce74: 90 nop 46ce75: 83 f8 da cmp $0xffffffda,%eax @@ -1247,7 +1248,7 @@ expression: diff 46f344: 41 89 ca mov %ecx,%r10d - 46f347: b8 06 01 00 00 mov $0x106,%eax - 46f34c: 0f 05 syscall -+ 46f347: e9 25 34 04 00 jmp 4b2771 <_end+0x1549> ++ 46f347: + 46f34c: 90 nop + 46f34d: 90 nop 46f34e: 3d 00 f0 ff ff cmp $0xfffff000,%eax @@ -1259,7 +1260,7 @@ expression: diff 472975: 48 8d 78 1c lea 0x1c(%rax),%rdi - 472979: b8 ca 00 00 00 mov $0xca,%eax - 47297e: 0f 05 syscall -+ 472979: e9 05 fe 03 00 jmp 4b2783 <_end+0x155b> ++ 472979: + 47297e: 90 nop + 47297f: 90 nop 472980: eb 8c jmp 47290e <_dl_fixup+0x10e> @@ -1271,7 +1272,7 @@ expression: diff 476c10: 48 8d 78 1c lea 0x1c(%rax),%rdi - 476c14: b8 ca 00 00 00 mov $0xca,%eax - 476c19: 0f 05 syscall -+ 476c14: e9 7c bb 03 00 jmp 4b2795 <_end+0x156d> ++ 476c14: + 476c19: 90 nop + 476c1a: 90 nop 476c1b: 48 83 7d 98 00 cmpq $0x0,-0x68(%rbp) @@ -1283,7 +1284,7 @@ expression: diff 476e4a: 48 8d 78 1c lea 0x1c(%rax),%rdi - 476e4e: b8 ca 00 00 00 mov $0xca,%eax - 476e53: 0f 05 syscall -+ 476e4e: e9 54 b9 03 00 jmp 4b27a7 <_end+0x157f> ++ 476e4e: + 476e53: 90 nop + 476e54: 90 nop 476e55: 48 83 7d 98 00 cmpq $0x0,-0x68(%rbp)