summaryrefslogtreecommitdiff
path: root/tests/tst-static-archive-link-runner.sh
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
committerkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
commit065da3509019b15dd0e678072e6be629e5330bef (patch)
tree69c934c9e786ac048b4fbe8f4c4c5571661521f4 /tests/tst-static-archive-link-runner.sh
parentd130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff)
downloadlibmcpu-0.4.0.tar.xz
Version 0.4.00.4.0
Diffstat (limited to 'tests/tst-static-archive-link-runner.sh')
-rwxr-xr-xtests/tst-static-archive-link-runner.sh263
1 files changed, 263 insertions, 0 deletions
diff --git a/tests/tst-static-archive-link-runner.sh b/tests/tst-static-archive-link-runner.sh
new file mode 100755
index 0000000..596fa16
--- /dev/null
+++ b/tests/tst-static-archive-link-runner.sh
@@ -0,0 +1,263 @@
+#!/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