diff --git a/scripts/003-normalize-llvm-psp-archives.sh b/scripts/003-normalize-llvm-psp-archives.sh new file mode 100755 index 0000000..96013b8 --- /dev/null +++ b/scripts/003-normalize-llvm-psp-archives.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Normalize the core PSPSDK archives before psp-packages and psplink build +# against them. The final normalization step runs again after packages install +# their own archives. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +"${SCRIPT_DIR}/006-normalize-llvm-psp-archives.sh" diff --git a/scripts/004-psplinkusb-extra.sh b/scripts/004-psplinkusb-extra.sh index a261aff..9ae1d94 100755 --- a/scripts/004-psplinkusb-extra.sh +++ b/scripts/004-psplinkusb-extra.sh @@ -11,6 +11,97 @@ else cd $REPO_FOLDER && git fetch origin && git reset --hard origin/${BRANCH_NAME} || { exit 1; } fi +## The clang-built SDK archives are normalized to rustc-compatible double-float +## metadata. Keep psplinkusb's PSP-side object metadata consistent with those +## archives so BFD ld does not warn while linking these helper PRX/ELF files. +## Do this after compilation instead of changing CFLAGS, because psplinkusb +## contains real float/double code and should keep its original codegen. +NORMALIZE_OBJECTS="$PWD/normalize-psp-object-metadata.sh" +cat > "$NORMALIZE_OBJECTS" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +[[ "$#" -eq 0 ]] && exit 0 + +perl - "$@" <<'PERL' +use strict; +use warnings; + +for my $path (@ARGV) { + open(my $fh, "+<:raw", $path) or die "open $path: $!"; + local $/; + my $data = <$fh>; + next unless length($data) >= 52; + next unless substr($data, 0, 4) eq "\x7fELF"; + next unless unpack("C", substr($data, 4, 1)) == 1; # ELFCLASS32 + next unless unpack("C", substr($data, 5, 1)) == 1; # ELFDATA2LSB + next unless unpack("v", substr($data, 18, 2)) == 8; # EM_MIPS + + my $flags = unpack("V", substr($data, 36, 4)); + substr($data, 36, 4) = pack("V", $flags & 0xf000ffff); + + my $shoff = unpack("V", substr($data, 32, 4)); + my $shentsize = unpack("v", substr($data, 46, 2)); + my $shnum = unpack("v", substr($data, 48, 2)); + my $shstrndx = unpack("v", substr($data, 50, 2)); + if ($shoff && $shentsize && $shstrndx < $shnum) { + my $shstr = $shoff + $shstrndx * $shentsize; + if ($shstr + 24 <= length($data)) { + my $shstr_off = unpack("V", substr($data, $shstr + 16, 4)); + my $shstr_size = unpack("V", substr($data, $shstr + 20, 4)); + my $shstr_end = $shstr_off + $shstr_size; + if ($shstr_end <= length($data)) { + for (my $i = 0; $i < $shnum; $i++) { + my $sh = $shoff + $i * $shentsize; + next if $sh + 24 > length($data); + my $name_off = unpack("V", substr($data, $sh, 4)); + my $name_start = $shstr_off + $name_off; + next if $name_start >= $shstr_end; + my $name_end = index($data, "\0", $name_start); + next if $name_end < 0 || $name_end > $shstr_end; + my $name = substr($data, $name_start, $name_end - $name_start); + + if ($name eq ".MIPS.abiflags") { + my $off = unpack("V", substr($data, $sh + 16, 4)); + my $size = unpack("V", substr($data, $sh + 20, 4)); + next if $size < 24 || $off + $size > length($data); + substr($data, $off + 7, 1) = "\x01"; + substr($data, $off + 8, 4) = pack("V", 0); + substr($data, $off + 12, 4) = pack("V", 0); + substr($data, $off + 16, 4) = pack("V", 1); + } + + if ($name eq ".gnu.attributes") { + my $off = unpack("V", substr($data, $sh + 16, 4)); + my $size = unpack("V", substr($data, $sh + 20, 4)); + next if $off + $size > length($data); + my $attrs = substr($data, $off, $size); + $attrs =~ s/\x04\x02/\x04\x01/g; + substr($data, $off, $size) = $attrs; + } + } + } + } + } + + seek($fh, 0, 0) or die "seek $path: $!"; + print {$fh} $data or die "write $path: $!"; + truncate($fh, length($data)) or die "truncate $path: $!"; + close($fh) or die "close $path: $!"; +} +PERL +EOF +chmod +x "$NORMALIZE_OBJECTS" +export PSPLINKUSB_NORMALIZE_OBJECTS="$NORMALIZE_OBJECTS" + +while IFS= read -r MAKEFILE; do + perl -0pi -e 's/(\nPSPSDK=\$\(shell psp-config --pspsdk-path\)\ninclude \$\(PSPSDK\)\/lib\/build[^ \n]*\.mak\n)/$1\n.PHONY: normalize-psp-objects\n\$(TARGET).elf: | normalize-psp-objects\nnormalize-psp-objects: \$(OBJS) \$(EXPORT_OBJ)\n\t\$(PSPLINKUSB_NORMALIZE_OBJECTS) \$(OBJS) \$(EXPORT_OBJ)\n/s' "$MAKEFILE" +done < <(find . -name Makefile -type f -exec grep -l '\$(PSPSDK)/lib/build' {} +) + +for MAKEFILE in libpsplink/Makefile libpsplink_driver/Makefile libusbhostfs/Makefile libusbhostfs_driver/Makefile; do + perl -0pi -e 's/\n\$\((TARGET)\): \$\((OBJS)\)/\n.PHONY: normalize-psp-objects\nnormalize-psp-objects: \$(OBJS)\n\t\$(PSPLINKUSB_NORMALIZE_OBJECTS) \$(OBJS)\n\n\$(TARGET): \$(OBJS) | normalize-psp-objects/' "$MAKEFILE" +done + ## Determine the maximum number of processes that Make can work with. PROC_NR=$(getconf _NPROCESSORS_ONLN) OSVER=$(uname) diff --git a/scripts/006-normalize-llvm-psp-archives.sh b/scripts/006-normalize-llvm-psp-archives.sh new file mode 100755 index 0000000..f417dc5 --- /dev/null +++ b/scripts/006-normalize-llvm-psp-archives.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ -z "${PSPDEV:-}" ]]; then + echo "ERROR: PSPDEV must be set before normalizing PSP archives." + exit 1 +fi + +ARCHIVE_DIRS=() +add_archive_dir() { + local dir="$1" + if [[ -d "${dir}" ]]; then + ARCHIVE_DIRS+=("${dir}") + fi +} + +add_archive_dir "${PSPDEV}/psp/lib" +add_archive_dir "${PSPDEV}/psp/sdk/lib" +if [[ -d "${PSPDEV}/lib/gcc/psp" ]]; then + while IFS= read -r dir; do + add_archive_dir "${dir}" + done < <(find "${PSPDEV}/lib/gcc/psp" -mindepth 1 -maxdepth 1 -type d | sort) +fi + +if [[ "${#ARCHIVE_DIRS[@]}" -eq 0 ]]; then + echo "No PSP archive directories found under ${PSPDEV}; skipping archive normalization." + exit 0 +fi + +find_tool() { + local tool="$1" + if command -v "${tool}" >/dev/null 2>&1; then + command -v "${tool}" + return 0 + fi + + if command -v brew >/dev/null 2>&1; then + local brew_llvm + brew_llvm="$(brew --prefix llvm 2>/dev/null || true)" + if [[ -n "${brew_llvm}" && -x "${brew_llvm}/bin/${tool}" ]]; then + echo "${brew_llvm}/bin/${tool}" + return 0 + fi + fi + + for prefix in /opt/homebrew/opt/llvm /usr/local/opt/llvm; do + if [[ -x "${prefix}/bin/${tool}" ]]; then + echo "${prefix}/bin/${tool}" + return 0 + fi + done + + echo "ERROR: ${tool} not found. Install LLVM before running this step." >&2 + return 1 +} + +LLVM_AR="$(find_tool llvm-ar)" +LLVM_RANLIB="$(find_tool llvm-ranlib)" +LLVM_READELF="$(find_tool llvm-readelf)" +LLVM_OBJCOPY="$(find_tool llvm-objcopy)" + +if ! command -v perl >/dev/null 2>&1; then + echo "ERROR: perl not found. A system Perl is required for ELF metadata normalization." + exit 1 +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/pspdev-normalize-archives.XXXXXX")" +trap 'rm -rf "${WORK_DIR}"' EXIT + +PATCH_ELF="${WORK_DIR}/patch-mips-psp-elf.pl" +cat > "${PATCH_ELF}" <<'PERL' +use strict; +use warnings; + +for my $path (@ARGV) { + open(my $fh, "+<:raw", $path) or die "open $path: $!"; + local $/; + my $data = <$fh>; + next unless length($data) >= 52; + next unless substr($data, 0, 4) eq "\x7fELF"; + next unless unpack("C", substr($data, 4, 1)) == 1; # ELFCLASS32 + next unless unpack("C", substr($data, 5, 1)) == 1; # ELFDATA2LSB + next unless unpack("v", substr($data, 18, 2)) == 8; # EM_MIPS + + # Clang's MIPS objects can carry machine-extension and single-float metadata + # that rust-lld rejects when linked with rustc's current mipsel-sony-psp + # objects. Match the metadata shape emitted by rustc and the legacy PSP SDK: + # MIPS2 O32, no machine extension, hard-float double ABI. + my $flags = unpack("V", substr($data, 36, 4)); + substr($data, 36, 4) = pack("V", $flags & 0xf000ffff); + + my $shoff = unpack("V", substr($data, 32, 4)); + my $shentsize = unpack("v", substr($data, 46, 2)); + my $shnum = unpack("v", substr($data, 48, 2)); + my $shstrndx = unpack("v", substr($data, 50, 2)); + if ($shoff && $shentsize && $shstrndx < $shnum) { + my $shstr = $shoff + $shstrndx * $shentsize; + if ($shstr + 24 <= length($data)) { + my $shstr_off = unpack("V", substr($data, $shstr + 16, 4)); + my $shstr_size = unpack("V", substr($data, $shstr + 20, 4)); + my $shstr_end = $shstr_off + $shstr_size; + if ($shstr_end <= length($data)) { + for (my $i = 0; $i < $shnum; $i++) { + my $sh = $shoff + $i * $shentsize; + next if $sh + 24 > length($data); + my $name_off = unpack("V", substr($data, $sh, 4)); + my $name_start = $shstr_off + $name_off; + next if $name_start >= $shstr_end; + my $name_end = index($data, "\0", $name_start); + next if $name_end < 0 || $name_end > $shstr_end; + my $name = substr($data, $name_start, $name_end - $name_start); + + if ($name eq ".MIPS.abiflags") { + my $off = unpack("V", substr($data, $sh + 16, 4)); + my $size = unpack("V", substr($data, $sh + 20, 4)); + next if $size < 24 || $off + $size > length($data); + substr($data, $off + 7, 1) = "\x01"; # Val_GNU_MIPS_ABI_FP_DOUBLE + substr($data, $off + 8, 4) = pack("V", 0); # ISA extension: none + substr($data, $off + 12, 4) = pack("V", 0); # ASEs: none + substr($data, $off + 16, 4) = pack("V", 1); # FLAGS1: odd single-precision regs + } + + if ($name eq ".gnu.attributes") { + my $off = unpack("V", substr($data, $sh + 16, 4)); + my $size = unpack("V", substr($data, $sh + 20, 4)); + next if $off + $size > length($data); + + my $attrs = substr($data, $off, $size); + # GNU MIPS Tag_GNU_MIPS_ABI_FP is encoded as tag 4 followed + # by a small uleb128 value. Clang emits value 2 here while + # the SDK/rustc-compatible .MIPS.abiflags metadata says + # double-float. Keep both metadata sources in sync. + $attrs =~ s/\x04\x02/\x04\x01/g; + substr($data, $off, $size) = $attrs; + } + } + } + } + } + + seek($fh, 0, 0) or die "seek $path: $!"; + print {$fh} $data or die "write $path: $!"; + truncate($fh, length($data)) or die "truncate $path: $!"; + close($fh) or die "close $path: $!"; +} +PERL + +normalize_archive() { + local archive="$1" + local name + name="$(basename "${archive}")" + local archive_dir + archive_dir="$(mktemp -d "${WORK_DIR}/${name%.a}.XXXXXX")" + + ( + cd "${archive_dir}" + "${LLVM_AR}" x "${archive}" + ) + + local member_list="${WORK_DIR}/${name%.a}.members" + find "${archive_dir}" -type f | sort > "${member_list}" + if [[ ! -s "${member_list}" ]]; then + echo "Skipping empty PSP archive: ${archive#${PSPDEV}/}" + return + fi + + local changed=0 + while IFS= read -r obj; do + local file_symbols + file_symbols="$("${LLVM_READELF}" -s "${obj}" 2>/dev/null | awk '$4 == "FILE" && $5 == "LOCAL" && $8 != "" { print $8 }' | sort -u || true)" + if [[ -n "${file_symbols}" ]]; then + changed=1 + local strip_args=() + while IFS= read -r sym; do + strip_args+=("--strip-symbol=${sym}") + done <<< "${file_symbols}" + "${LLVM_OBJCOPY}" "${strip_args[@]}" "${obj}" + fi + done < "${member_list}" + + while IFS= read -r obj; do + perl "${PATCH_ELF}" "${obj}" + done < "${member_list}" + + rm -f "${archive}" + local members=() + while IFS= read -r obj; do + members+=("${obj#${archive_dir}/}") + done < "${member_list}" + ( + cd "${archive_dir}" + "${LLVM_AR}" rc "${archive}" "${members[@]}" + ) + "${LLVM_RANLIB}" "${archive}" + + if [[ "${changed}" -eq 1 ]]; then + echo "Normalized PSP archive symbols and metadata: ${archive#${PSPDEV}/}" + else + echo "Normalized PSP archive metadata: ${archive#${PSPDEV}/}" + fi +} + +for dir in "${ARCHIVE_DIRS[@]}"; do + for archive in "${dir}"/*.a; do + [[ -e "${archive}" ]] || continue + normalize_archive "${archive}" + done +done