#!/bin/sh set -eu : "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" "${CPU_AR:?}" TMP=${TMPDIR:-/tmp}/libmcpu-static-archive-link-$$ mkdir -p "$TMP/lib" "$TMP/first" "$TMP/second" "$TMP/order" trap 'rm -rf "$TMP"' EXIT HUP INT TERM cat > "$TMP/main-entry.s" <<'EOS' .global _life .extern entry .text _start_life: LDI r64@0x00000, 40 CALL entry MOV r64@0x00018, r64@0x00000 LDI r32@0x00030, 0 EXIT r32@0x00030 EOS cat > "$TMP/helper.s" <<'EOS' .global helper .text helper: LDI r64@0x00008, 2 IADD r64@0x00000, r64@0x00000, r64@0x00008 RET EOS cat > "$TMP/entry.s" <<'EOS' .global entry .extern helper .text entry: CALL helper RET EOS cat > "$TMP/unused.s" <<'EOS' .global unused .extern missing_forever .text unused: CALL missing_forever RET EOS "$CPU_AS" -o "$TMP/main-entry.o" "$TMP/main-entry.s" "$CPU_AS" -o "$TMP/helper.o" "$TMP/helper.s" "$CPU_AS" -o "$TMP/entry.o" "$TMP/entry.s" "$CPU_AS" -o "$TMP/unused.o" "$TMP/unused.s" # The archive intentionally has no symbol-index member. helper.o precedes # entry.o: entry.o is extracted first, introduces an undefined helper, and a # repeated scan must then reach the earlier helper.o. unused.o must never be # extracted, otherwise its missing_forever reference would break the link. "$CPU_AR" rc "$TMP/lib/libchain.a" \ "$TMP/helper.o" "$TMP/entry.o" "$TMP/unused.o" "$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/lib" -lchain \ -o "$TMP/static-chain" "$TMP/main-entry.o" [ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/static-chain"`" = 42 ] # ABI-v3 also falls back to a static archive when no libchain.so exists. # Extracted members are part of the output image and therefore add no NEEDED. "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ -L"$TMP/lib" -lchain -o "$TMP/dynamic-chain" "$TMP/main-entry.o" [ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/dynamic-chain"`" = 42 ] "$CPU_READELF" -d "$TMP/dynamic-chain" > "$TMP/dynamic-chain.dynamic" [ "`grep -c 'NEEDED' "$TMP/dynamic-chain.dynamic" || true`" -eq 0 ] # ABI-v3 ET_DYN output follows the same static-archive fallback rule. The # selected members become part of the shared object and do not add NEEDED. cat > "$TMP/wrapper.s" <<'EOS' .global wrapper .extern entry .text wrapper: CALL entry RET EOS "$CPU_AS" -o "$TMP/wrapper.o" "$TMP/wrapper.s" "$CPU_LD" --shared --soname=libwrapper.so.1 \ -L"$TMP/lib" -lchain -o "$TMP/libwrapper.so" "$TMP/wrapper.o" "$CPU_READELF" -d "$TMP/libwrapper.so" > "$TMP/libwrapper.dynamic" grep -q 'SONAME.*\[libwrapper.so.1\]' "$TMP/libwrapper.dynamic" [ "`grep -c 'NEEDED' "$TMP/libwrapper.dynamic" || true`" -eq 0 ] # A weak undefined symbol does not cause archive extraction. Its PTR64 # relocation therefore remains the established NULL weak result. cat > "$TMP/weak-main.s" <<'EOS' .global _life .weak optional .data optional_ptr: .ptr optional .text _start_life: LDI r32@0x00030, 0 EXIT r32@0x00030 EOS cat > "$TMP/optional.s" <<'EOS' .global optional .text optional: RET EOS "$CPU_AS" -o "$TMP/weak-main.o" "$TMP/weak-main.s" "$CPU_AS" -o "$TMP/optional.o" "$TMP/optional.s" "$CPU_AR" rcs "$TMP/lib/liboptional.a" "$TMP/optional.o" "$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/lib" -loptional \ -o "$TMP/weak-main" "$TMP/weak-main.o" [ "`"$CPU_RUN" --int-data=64@16 "$TMP/weak-main"`" = 0 ] # Static archive order across -l operands follows traditional one-pass library # semantics. A later archive can satisfy references introduced by an earlier # one, but an already-scanned archive is not implicitly revisited. cat > "$TMP/order-main.s" <<'EOS' .global _life .extern a1 .text _start_life: LDI r64@0x00000, 40 CALL a1 MOV r64@0x00018, r64@0x00000 LDI r32@0x00030, 0 EXIT r32@0x00030 EOS cat > "$TMP/a1.s" <<'EOS' .global a1 .extern b1 .text a1: CALL b1 RET EOS cat > "$TMP/a2.s" <<'EOS' .global a2 .text a2: LDI r64@0x00008, 2 IADD r64@0x00000, r64@0x00000, r64@0x00008 RET EOS cat > "$TMP/b1.s" <<'EOS' .global b1 .extern a2 .text b1: CALL a2 RET EOS "$CPU_AS" -o "$TMP/order-main.o" "$TMP/order-main.s" "$CPU_AS" -o "$TMP/a1.o" "$TMP/a1.s" "$CPU_AS" -o "$TMP/a2.o" "$TMP/a2.s" "$CPU_AS" -o "$TMP/b1.o" "$TMP/b1.s" "$CPU_AR" rcs "$TMP/order/liba.a" "$TMP/a1.o" "$TMP/a2.o" "$CPU_AR" rcs "$TMP/order/libb.a" "$TMP/b1.o" if "$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/order" \ -la -lb -o "$TMP/order-fail" "$TMP/order-main.o" \ >/dev/null 2>"$TMP/order-fail.err"; then echo "previous archive was unexpectedly rescanned across -l operands" >&2 exit 1 fi grep -q 'undefined symbol: a2' "$TMP/order-fail.err" "$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/order" \ -la -lb -la -o "$TMP/order-ok" "$TMP/order-main.o" [ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/order-ok"`" = 42 ] # Prepare two providers for -lpick: a static one returns 42, a shared one 43. cat > "$TMP/pick-main.s" <<'EOS' .global _life .extern pick .text _start_life: LDI r64@0x00000, 40 CALL pick MOV r64@0x00018, r64@0x00000 LDI r32@0x00030, 0 EXIT r32@0x00030 EOS cat > "$TMP/pick-static.s" <<'EOS' .global pick .text pick: LDI r64@0x00008, 2 IADD r64@0x00000, r64@0x00000, r64@0x00008 RET EOS cat > "$TMP/pick-shared.s" <<'EOS' .global pick .text pick: LDI r64@0x00008, 3 IADD r64@0x00000, r64@0x00000, r64@0x00008 RET EOS "$CPU_AS" -o "$TMP/pick-main.o" "$TMP/pick-main.s" "$CPU_AS" -o "$TMP/pick-static.o" "$TMP/pick-static.s" "$CPU_AS" -o "$TMP/pick-shared.o" "$TMP/pick-shared.s" "$CPU_AR" rcs "$TMP/first/libpick.a" "$TMP/pick-static.o" "$CPU_LD" --shared --soname=libpick.so.7 \ -o "$TMP/second/libpick.so.7.0.0" "$TMP/pick-shared.o" ln -s libpick.so.7.0.0 "$TMP/second/libpick.so.7" ln -s libpick.so.7 "$TMP/second/libpick.so" # Search is directory-major. The .a in the first -L directory wins over a # .so in a later directory. "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ -L"$TMP/first" -L"$TMP/second" -lpick \ -o "$TMP/pick-first-static" "$TMP/pick-main.o" [ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-first-static"`" = 42 ] "$CPU_READELF" -d "$TMP/pick-first-static" > "$TMP/pick-first-static.dynamic" [ "`grep -c 'NEEDED' "$TMP/pick-first-static.dynamic" || true`" -eq 0 ] # Reversing -L order selects the shared object and records its SONAME. "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ -L"$TMP/second" -L"$TMP/first" -lpick \ -o "$TMP/pick-first-shared" "$TMP/pick-main.o" "$CPU_READELF" -d "$TMP/pick-first-shared" > "$TMP/pick-first-shared.dynamic" grep -q 'NEEDED.*\[libpick.so.7\]' "$TMP/pick-first-shared.dynamic" [ "`"$CPU_RUN" --library-path="$TMP/second" \ --int-reg=r64@0x00018 "$TMP/pick-first-shared"`" = 43 ] # Within one directory ABI-v3 prefers .so over .a. cp "$TMP/first/libpick.a" "$TMP/second/libpick.a" "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ -L"$TMP/second" -lpick -o "$TMP/pick-same-dir" "$TMP/pick-main.o" "$CPU_READELF" -d "$TMP/pick-same-dir" | grep -q 'NEEDED.*\[libpick.so.7\]' # ABI-v1 never selects .so for -l. It uses the archive even when both exist. "$CPU_LD" --stack-size=4096 --no-heap \ -L"$TMP/second" -lpick -o "$TMP/pick-abi1" "$TMP/pick-main.o" [ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-abi1"`" = 42 ] # With only a shared provider available, ABI-v1 -l must fail rather than # silently introducing a dynamic dependency into a static executable. rm -f "$TMP/second/libpick.a" if "$CPU_LD" --stack-size=4096 --no-heap \ -L"$TMP/second" -lpick -o "$TMP/pick-abi1-bad" "$TMP/pick-main.o" \ >/dev/null 2>&1; then echo "ABI-v1 unexpectedly selected a shared library" >&2 exit 1 fi "$CPU_LD" --help 2>&1 | grep -q 'ABI-v1 and -r search .a' "$CPU_LD" --help 2>&1 | grep -q 'ABI-v3 searches .so then .a' exit 0