Skip to content

Commit

Permalink
Support building against macOS universal binaries (#1166)
Browse files Browse the repository at this point in the history
FIXES: #1087

A "universal binary" is a fat binary which contains multiple builds for
different architectures. When Postgres is packaged in this format it's
necessary to slice out the build for the current architecture before
trying to parse it. The program terminates early if the universal binary
doesn't support the current architecture.

The list of supported architectures in `slice_arch32` is limited by
[`object`][object-arch], which supports fewer than [Rust][rust-arch].

The universal binary test fixture was simply chosen because of it's
small size relative to other binaries in Postgres.

[^1]: gimli-rs/object#454 (comment)

[rust-arch]: http://doc.rust-lang.org/1.68.2/std/env/consts/constant.ARCH.html
[object-arch]: https://docs.rs/object/latest/object/read/macho/trait.FatArch.html#method.architecture
  • Loading branch information
clowder authored Jun 16, 2023
1 parent efff208 commit 16c9d5c
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 3 deletions.
87 changes: 84 additions & 3 deletions cargo-pgrx/src/command/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use crate::profile::CargoProfile;
use crate::CommandExecute;
use cargo_toml::Manifest;
use eyre::{eyre, WrapErr};
use object::Object;
use object::read::macho::{FatArch, FatHeader};
use object::{Architecture, FileKind, Object};
use once_cell::sync::OnceCell;
use owo_colors::OwoColorize;
use pgrx_pg_config::{cargo::PgrxManifestExt, get_target_dir, PgConfig, Pgrx};
Expand All @@ -25,6 +26,7 @@ use std::process::Stdio;
extern crate alloc;
use crate::manifest::{get_package_manifest, pg_config_and_version};
use alloc::vec::Vec;
use std::env;

// An apparent bug in `glibc` 2.17 prevents us from safely dropping this
// otherwise users find issues such as https://github.com/tcdi/pgrx/issues/572
Expand Down Expand Up @@ -301,7 +303,7 @@ pub(crate) fn generate_schema(

let lib_so_data = std::fs::read(&lib_so).wrap_err("couldn't read extension shared object")?;
let lib_so_obj_file =
object::File::parse(&*lib_so_data).wrap_err("couldn't parse extension shared object")?;
parse_object(&*lib_so_data).wrap_err("couldn't parse extension shared object")?;
let lib_so_exports =
lib_so_obj_file.exports().wrap_err("couldn't get exports from extension shared object")?;

Expand Down Expand Up @@ -496,7 +498,7 @@ fn create_stub(
}

let postmaster_obj_file =
object::File::parse(&*postmaster_bin_data).wrap_err("couldn't parse postmaster")?;
parse_object(&*postmaster_bin_data).wrap_err("couldn't parse postmaster")?;
let postmaster_exports = postmaster_obj_file
.exports()
.wrap_err("couldn't get exports from extension shared object")?;
Expand Down Expand Up @@ -556,3 +558,82 @@ fn create_stub(

Ok(postmaster_stub_built)
}

fn parse_object(data: &[u8]) -> object::Result<object::File> {
let kind = object::FileKind::parse(&*data)?;

match kind {
FileKind::MachOFat32 => {
let arch = env::consts::ARCH;

match slice_arch32(&*data, arch) {
Some(slice) => parse_object(&*slice),
None => {
panic!("Failed to slice architecture '{arch}' from universal binary.")
}
}
}
_ => object::File::parse(&*data),
}
}

fn slice_arch32<'a>(data: &'a [u8], arch: &str) -> Option<&'a [u8]> {
let target = match arch {
"x86" => Architecture::I386,
"x86_64" => Architecture::X86_64,
"arm" => Architecture::Arm,
"aarch64" => Architecture::Aarch64,
"mips" => Architecture::Mips,
"powerpc" => Architecture::PowerPc,
"powerpc64" => Architecture::PowerPc64,
_ => Architecture::Unknown,
};

let candidates = FatHeader::parse_arch32(&*data).ok()?;
let architecture = candidates.iter().find(|a| a.architecture() == target)?;

architecture.data(&*data).ok()
}

#[cfg(test)]
mod tests {
use crate::command::schema::*;
use pgrx_pg_config::PgConfigSelector;

#[test]
fn test_parse_managed_postmasters() {
let pgrx = Pgrx::from_config().unwrap();
let mut results = pgrx
.iter(PgConfigSelector::All)
.map(|pg_config| {
let fixture_path = pg_config.unwrap().postmaster_path().unwrap();
let bin = std::fs::read(fixture_path).unwrap();

parse_object(&bin).is_ok()
})
.peekable();

assert!(results.peek().is_some());
assert!(results.all(|r| r));
}

#[test]
fn test_parse_universal_binary_slice() {
let root_path = env!("CARGO_MANIFEST_DIR");
let fixture_path = format!("{root_path}/tests/fixtures/macos-universal-binary");
let bin = std::fs::read(fixture_path).unwrap();

let slice = slice_arch32(&bin, "aarch64")
.expect("Failed to slice architecture 'aarch64' from universal binary.");
assert!(parse_object(&slice).is_ok());
}

#[test]
fn test_slice_unknown_architecture() {
let root_path = env!("CARGO_MANIFEST_DIR");
let fixture_path = format!("{root_path}/tests/fixtures/macos-universal-binary");
let bin = std::fs::read(fixture_path).unwrap();

assert!(slice_arch32(&bin, "foo").is_none());
}
}
Binary file added cargo-pgrx/tests/fixtures/macos-universal-binary
Binary file not shown.

0 comments on commit 16c9d5c

Please sign in to comment.