From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 3B9F81FF0EC for ; Sat, 24 Oct 2026 07:13:46 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id BC3B1215F0; Sat, 24 Oct 2026 07:13:45 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; t=1792818822; cv=none; d=google.com; s=arc-20260327; b=roBI12Vo7Q+lRQTDTkdf123A34C9qSdllCI4pg4C1FWc+oQ9625oUc0UubWla/rgTF ItJetI10DlnyFEQRGs7Y0SSh1M/28v+IxmtFByBBI8Qzl9dzELNTi76zPAaz3Marmygp GQMVBbAjRMmjhddiQMm9VyFj55iyFSpSSLT0+yDGB0YZdTM0r42P/Kg9w041dOnafhd8 EuHkjDP2MypiAC6lHC2RQhLsAThgZsO18lQDOY3BFSW97HKAuj3lFjvQKzH7x4Klp/oK YbVgu8GbFtM88wW0hjVTubeGlXN6s/v5rZaVIHPe8fVShYBlP26ewOHZ7FQH/q1fZrZc cLlA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=content-transfer-encoding:to:subject:message-id:date:references :in-reply-to:from:mime-version:dkim-signature; bh=WsApJQDKQo6VVgr/W/+SJOKUuyc5sDW7PKnCalZQJWA=; fh=qUdnlOw4HMavIAa4vJfUpEe/QUA6LBLHlg5B+iT7exQ=; b=HplxNmrk/g4FkzIv+0egJBqTJlvzo1DWHC/E+3ea4+dd+iPQ9FU2J1d9cb5TVVUNUi ux38sXwFWkv7Ye9c9L7qREI3VJtaJr9/J+TvU4zLKXlesGs12SW0grKXHW7/56WbSSZj ILH5KsnCFxwanl1XybPO+Dck7UTGPzqTbOBkN6ODmzXX0C6XPO4LlmWVMITM+dgMBscf /cCSmFPqxUAcX4sbbYgNSy3kEy06LV1U/OoaVIoOkPlZnnnU8tk3k4GvdAqDCrkhPCv8 0kgYq9F+0eeDDF/dbnWKHf9jLPD590dygq4a6/l1d/hkLXhhUfB0DfylQ2KvR8yxT2BO 8iwg==; darn=lists.proxmox.com ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792818822; x=1793423622; darn=lists.proxmox.com; h=content-transfer-encoding:content-type:to:subject:message-id:date :references:in-reply-to:from:mime-version:from:to:cc:subject:date :message-id:reply-to:content-type; bh=WsApJQDKQo6VVgr/W/+SJOKUuyc5sDW7PKnCalZQJWA=; b=XMKO7FHGdHZDccACT0Ig+JZb8z+96vmAY/ejvnEklblpRrJ/ozAT4vNif4nZd1NSbO REoKpVuyHMUPO2X8nG7ZdppVr3XnK+TyPf4xFEdMsojYleqS/3S1MFGxsJmiDbCG/AYL 0vh0mlXb4zacoFF4sgLcig0D90r3LqQDJHm4lMZRL/QnI4eRnt9tql5EX3DOukXWdZJ4 L53FlQBMmTgLraUixYD/kyiKQsD6QI0WEs/3fHzDTfmG0dmP17Es/uZ/jMfzDqrwgZGZ mIFx273kafebC9EhoDBG3RubfYivhVXrHH6tvISUYlZehXRpb/Dxr+2ucpwZNLY0Grn8 QhpQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792818822; x=1793423622; h=content-transfer-encoding:content-type:to:subject:message-id:date :references:in-reply-to:from:mime-version:x-gm-gg:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to:content-type; bh=WsApJQDKQo6VVgr/W/+SJOKUuyc5sDW7PKnCalZQJWA=; b=uLLHRLY2HwcLjPF6pAkHkLZdtRDzevVpL1mVrjzv9yl9UulLAbxuiQEEJFYhTCDcUG 8/rREALDa4mk03WmsEu7wio6CCtlBUuo3z6YiK+ektabU20pyiV889AnNCCKgTkQXGbQ oCPIib44aSnCQr/rCJgbZbQno75e0dfQprqlgUXX8WbxT6T5NrAhmkjZZn2rg66r5Sxc WUUOhmkdBCCmZ44lVY0ZATYKHumSAg8q+jNFdV1rBuokCfGcSooax83jmExnlR5gGKTR /ds7mz2OX9gWew4i67I/L4SGb1hKWU10tctSwGp4MFSa24TVvxinFlsxQJM2d8B5zy78 OYdg== X-Gm-Message-State: AFq9FYI5awxqq2wf2HeY+UewBE3qn4gbtQmADbe/xL2q3kMPnsA1Wwfd aZ43enBlUbOX6lOhPnYDN5IyFQU5ss1lFcaJsL5/nf3EX+8FH6SHbVHg3PT0ivgNw3S0P3qFxnB eu9U5b4sQhOFqIm2kxQlcEgCGrA1luFRrxQ== X-Gm-Gg: AYBFou1w2Hr8/QtJaKtMoH6Su/NRH0es5bR3NEPnERyK4MaNAJPi8sat5iSRa2p0swA /18eG1A0vBStRa005+0Pg/EdBYILyySY62YiaxlzoJPSFQzjsYgkv8jWweKtN3r45IcEgw5nBVJ g/WmgtcSEwAJ3Kl1ZC+Jlgel1Py1eHkA2LbgiOS9msuGz8Gbyz2iGe9dbDtWlOtkdGiJVSREtIT jkF7YA3WACZGxHyo1k1hgnLbI4InOesfTVmhfq9VMdDnxNx9ARdI3XDuMMJNbLbOJZ3pCy4Mwt4 O6UyapAWIuRjQIjemnzv3sIESFzMonAHzmpPasDwTu2F1/VScrNBt+dJeFbvJgPKr+Smp0aURFz 7DRrOnaDXCZkDPMLW4whNjC2BtJCZjQiJjw== X-Received: by 2002:aa7:db4d:0:b0:6b3:c382:364a with SMTP id 4fb4d7f45d1cf-6b442ff307cmr947301a12.35.1792818822165; Fri, 23 Oct 2026 22:13:42 -0700 (PDT) MIME-Version: 1.0 From: =?UTF-8?B?57WQ5Y+L?= In-Reply-To: References: Date: Fri, 23 Oct 2026 22:13:41 -0700 X-Gm-Features: AclHuK_UuGQqpGeEQ7J3GSIyHo7KdEcvL4UwB_WlGeuKbODRhpjaRmRZR-vLPGU Message-ID: Subject: [PATCH pve-kernel v2 RESEND 1/2] d/scripts: correctly compare namespaced ABI exports To: pve-devel@lists.proxmox.com Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Spam-Level: 3 Message-ID-Hash: RNB5QVWCSXCVYTX7Y6F7TEYTVCKGBDZT X-Message-ID-Hash: RNB5QVWCSXCVYTX7Y6F7TEYTVCKGBDZT X-MailFrom: miagetegorann@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: abi-generate emits a different field order for namespaced exports. Parse both layouts explicitly so the module name does not become part of the symbol name. Otherwise, building hv_vmbus into vmlinux falsely reports three existing namespaced exports as missing. Compare the actual export type and namespace, preserving hash and missing symbol checks and symbol/module blacklists. Reject added or changed namespace requirements, malformed entries and duplicate symbols. Fix the export-type diagnostic's printf argument separator. Add 12 ABI regression tests, including end-to-end abi-generate output, relocation, hash changes, GPL restrictions, namespaces and skip handling. Document the check and test command, and ignore Python bytecode caches. All 12 tests pass under WSL2 Ubuntu 24.04. Rechecking the existing 7.0.14-26-pve build artifacts with SKIPABI=3D0 preserves all 32,726 symbols= , their CRCs and export types, with only the expected 47 VMBus relocations. Signed-off-by: =E7=B5=90=E5=8F=8B --- .gitignore | 1 + README | 8 ++ debian/scripts/abi-check | 56 ++++++++++--- debian/tests/test_abi_check.py | 140 +++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 debian/tests/test_abi_check.py diff --git a/.gitignore b/.gitignore index 295de11..aa1d4e1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.deb *.dsc *.prepared +__pycache__/ /abi-tmp* /config*.org /proxmox-kernel-[0-9]*/ diff --git a/README b/README index 9dd855a..00564c8 100644 --- a/README +++ b/README @@ -167,6 +167,14 @@ variable in the Makefile, rebuilding the packages and running 'make abiupdate' (the 'abiupdate' target in 'Makefile' contains the steps for consistently updating the repository). +The ABI check reads both ordinary and namespaced exports. Moving an export +between a module and vmlinux is reported without failing the check. Missin= g +symbols, hash changes and stricter export or namespace requirements fail i= t +unless an applicable blacklist or the ABI skip mode permits them. + +Run the ABI checker regression tests on a Debian build host with +'python3 -B -m unittest discover -s debian/tests -v'. + The source package-name is proxmox-kernel-MAJ.MIN, the version of the pack= age defaults to MAJ.MIN.PATCH-KREL - but this can be adapted to contain a suff= ix for test-packages (e.g. ~test01). The version in the Makefile needs to be = equal diff --git a/debian/scripts/abi-check b/debian/scripts/abi-check index 9987d04..ff000c3 100755 --- a/debian/scripts/abi-check +++ b/debian/scripts/abi-check @@ -89,17 +89,35 @@ sub is_ignored($$) { return 0; } +# abi-generate emits four fields for ordinary exports, and five fields for +# namespaced exports (namespace, type, hash, symbol, module). In the latte= r +# format the trailing module must not become part of the symbol name. +sub parse_symbol { + my ($line, $file, $number) =3D @_; + chomp $line; + $line =3D~ s/\r$//; + + if ($line =3D~ /^(EXPORT_SYMBOL(?:_[A-Z0-9_]+)?)\s+(\S+)\s+(0x[0-9a-f]+)\s+(\S+)$/) { + return ($4, $1, $2, $3, ''); + } + if ($line =3D~ /^(\S+)\s+(EXPORT_SYMBOL(?:_[A-Z0-9_]+)?)\s+(0x[0-9a-f]+)\s+(\S+)\s+(\S+)$/= ) { + return ($4, $2, $5, $3, $1); + } + die "EE: $file:$number: malformed ABI entry: '$line'\n"; +} + # Read new syms first print " Reading new symbols ($abistr)..."; $count =3D 0; open(my $NEW_FH, '<', $abinew) or die "Could not open $abinew - $!"; while (<$NEW_FH>) { - chomp; - m/^(\S+)\s(.+)\s(0x[0-9a-f]+)\s(.+)$/; - $symbols{$4}{'type'} =3D $1; - $symbols{$4}{'loc'} =3D $2; - $symbols{$4}{'hash'} =3D $3; - $module_syms{$2} =3D 0; + my ($sym, $type, $loc, $hash, $namespace) =3D parse_symbol($_, $abinew= , $.); + die "EE: $abinew:$.: duplicate symbol '$sym'\n" if defined($symbols{$sym}{'type'}); + $symbols{$sym}{'type'} =3D $type; + $symbols{$sym}{'loc'} =3D $loc; + $symbols{$sym}{'hash'} =3D $hash; + $symbols{$sym}{'namespace'} =3D $namespace; + $module_syms{$loc} =3D 0; $count++; } close($NEW_FH); @@ -110,11 +128,12 @@ print " Reading old symbols..."; $count =3D 0; open(my $OLD_FH, '<', $abiold) or die "Could not open $abiold - $!"; while (<$OLD_FH>) { - chomp; - m/^(\S+)\s(.+)\s(0x[0-9a-f]+)\s(.+)$/; - $symbols{$4}{'old_type'} =3D $1; - $symbols{$4}{'old_loc'} =3D $2; - $symbols{$4}{'old_hash'} =3D $3; + my ($sym, $type, $loc, $hash, $namespace) =3D parse_symbol($_, $abiold= , $.); + die "EE: $abiold:$.: duplicate symbol '$sym'\n" if defined($symbols{$sym}{'old_type'}); + $symbols{$sym}{'old_type'} =3D $type; + $symbols{$sym}{'old_loc'} =3D $loc; + $symbols{$sym}{'old_hash'} =3D $hash; + $symbols{$sym}{'old_namespace'} =3D $namespace; $count++; } close($OLD_FH); @@ -158,6 +177,7 @@ $count =3D 0; my $moved =3D 0; my $changed_type =3D 0; my $changed_hash =3D 0; +my $changed_namespace =3D 0; for my $sym (keys(%symbols)) { if (!defined($symbols{$sym}{'old_type'}) or !defined($symbols{$sym}{'type'})) { next; @@ -172,12 +192,21 @@ for my $sym (keys(%symbols)) { # Changes to export type are only bad if new type isn't # EXPORT_SYMBOL. Changing things to GPL are bad. if ($symbols{$sym}{'type'} ne $symbols{$sym}{'old_type'}) { - printf(" TYPE : %-40s : %s =3D> %s%s\n", $sym, $symbols{$sym}{'old_typ= e'}. + printf(" TYPE : %-40s : %s =3D> %s%s\n", $sym, $symbols{$sym}{'old_typ= e'}, $symbols{$sym}{'type'}, is_ignored($symbols{$sym}{'loc'}, $sym) ? " (ignored)" : ""); $changed_type++ if $symbols{$sym}{'type'} ne "EXPORT_SYMBOL" and !is_ignored($symbols{$sym}{'loc'}, $sym); } + # Adding or changing a namespace requires consumers to change their imports. + # Removing a namespace relaxes that requirement and is compatible. + if ($symbols{$sym}{'namespace'} ne $symbols{$sym}{'old_namespace'}) { + printf(" NS : %-40s : %s =3D> %s%s\n", $sym, + $symbols{$sym}{'old_namespace'} || '(none)', $symbols{$sym}{'namespace'} || '(none)', + is_ignored($symbols{$sym}{'loc'}, $sym) ? " (ignored)" : ""); + $changed_namespace++ if $symbols{$sym}{'namespace'} ne '' && !is_ignored($symbols{$sym}{'loc'}, $sym); + } + # Changes to the hash are always bad if ($symbols{$sym}{'hash'} ne $symbols{$sym}{'old_hash'}) { printf(" HASH : %-40s : %s =3D> %s%s\n", $sym, $symbols{$sym}{'old_has= h'}, @@ -191,8 +220,9 @@ for my $sym (keys(%symbols)) { print "WW: $moved symbols changed location\n" if $moved; print "$EE $changed_type symbols changed export type and weren't ignored\n" if $changed_type; print "$EE $changed_hash symbols changed hash and weren't ignored\n" if $changed_hash; +print "$EE $changed_namespace symbols changed namespace and weren't ignored\n" if $changed_namespace; -$errors++ if $changed_hash or $changed_type; +$errors++ if $changed_hash or $changed_type or $changed_namespace; if ($changed_hash) { print "II: Module hash change summary...\n"; for my $mod (sort { $module_syms{$b} <=3D> $module_syms{$a} } keys %module_syms) { diff --git a/debian/tests/test_abi_check.py b/debian/tests/test_abi_check.p= y new file mode 100644 index 0000000..928c64a --- /dev/null +++ b/debian/tests/test_abi_check.py @@ -0,0 +1,140 @@ +#!/usr/bin/python3 + +"""Check ABI compatibility using both formats emitted by abi-generate.""" + +from pathlib import Path +import subprocess +import tempfile +import unittest + + +ROOT =3D Path(__file__).resolve().parents[2] +CHECKER =3D ROOT / "debian/scripts/abi-check" +GENERATOR =3D ROOT / "debian/scripts/abi-generate" + + +class AbiCheckTests(unittest.TestCase): + def setUp(self): + temporary =3D tempfile.TemporaryDirectory(prefix=3D"kernel-abi-tes= t-") + self.addCleanup(temporary.cleanup) + self.directory =3D Path(temporary.name) + + def check(self, old, new, *, skip=3DFalse, blacklist=3D""): + (self.directory / "abi-prev-test").write_text(old) + (self.directory / "abi-test").write_text(new) + (self.directory / "abi-blacklist").write_text(blacklist) + return subprocess.run( + ["perl", str(CHECKER), "abi-test", "abi-prev-test", str(int(skip))], + cwd=3Dself.directory, text=3DTrue, stdout=3Dsubprocess.PIPE, + stderr=3Dsubprocess.STDOUT, check=3DFalse, + ) + + def test_namespaced_and_ordinary_exports_can_move_to_vmlinux(self): + old =3D ( + "EXPORT_SYMBOL drivers/hv/hv_vmbus 0x12345678\tvmbus_open\n" + "module:mshv_vtl EXPORT_SYMBOL_GPL 0x853b6fd2\thv_post_message\tdrivers/hv/hv_vmbus\n" + ) + result =3D self.check(old, old.replace("drivers/hv/hv_vmbus", "vml= inux")) + self.assertEqual(result.returncode, 0, result.stdout) + self.assertIn("2 symbols changed location", result.stdout) + self.assertIn("found 0 missing symbols", result.stdout) + self.assertIn("found 0 new symbols", result.stdout) + + def test_namespaced_hash_change_is_an_error(self): + old =3D "DMA_BUF EXPORT_SYMBOL_GPL 0x12345678\tdma_buf_get\tvmlinu= x\n" + result =3D self.check(old, old.replace("0x12345678", "0x87654321")= ) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("1 symbols changed hash", result.stdout) + self.assertRegex(result.stdout, r"vmlinux\s+: 1") + + def test_namespaced_export_restriction_is_an_error(self): + old =3D "DMA_BUF EXPORT_SYMBOL 0x12345678\tdma_buf_get\tvmlinux\n" + result =3D self.check(old, old.replace("EXPORT_SYMBOL ", "EXPORT_SYMBOL_GPL ")) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("EXPORT_SYMBOL =3D> EXPORT_SYMBOL_GPL", result.stdou= t) + self.assertIn("1 symbols changed export type", result.stdout) + + def test_ordinary_exports_keep_hash_and_type_checks(self): + old =3D "EXPORT_SYMBOL vmlinux 0x12345678\tordinary\n" + for new in ( + old.replace("0x12345678", "0x87654321"), + old.replace("EXPORT_SYMBOL ", "EXPORT_SYMBOL_GPL "), + ): + with self.subTest(new=3Dnew): + result =3D self.check(old, new) + self.assertNotEqual(result.returncode, 0, result.stdout) + + def test_namespace_added_or_changed_requires_consumer_changes(self): + new =3D "DMA_BUF EXPORT_SYMBOL 0x12345678\tdma_buf_get\tvmlinux\n" + for old in ( + "EXPORT_SYMBOL vmlinux 0x12345678\tdma_buf_get\n", + new.replace("DMA_BUF", "OLD_NAMESPACE"), + ): + with self.subTest(old=3Dold): + result =3D self.check(old, new) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("1 symbols changed namespace", result.stdout= ) + + def test_namespace_and_gpl_restrictions_can_be_removed(self): + old =3D "DMA_BUF EXPORT_SYMBOL_GPL 0x12345678\tdma_buf_get\tvmlinu= x\n" + new =3D "EXPORT_SYMBOL vmlinux 0x12345678\tdma_buf_get\n" + result =3D self.check(old, new) + self.assertEqual(result.returncode, 0, result.stdout) + self.assertIn("DMA_BUF =3D> (none)", result.stdout) + + def test_namespaced_symbol_and_module_blacklists_still_apply(self): + old =3D "DMA_BUF EXPORT_SYMBOL 0x12345678\tdma_buf_get\tvmlinux\n" + new =3D old.replace("0x12345678", "0x87654321").replace("DMA_BUF", "NEW_NAMESPACE") + for blacklist in ("dma_buf_get\n", "M: vmlinux\n"): + with self.subTest(blacklist=3Dblacklist): + changed =3D self.check(old, new, blacklist=3Dblacklist) + self.assertEqual(changed.returncode, 0, changed.stdout) + missing =3D self.check(old, "", blacklist=3Dblacklist) + self.assertEqual(missing.returncode, 0, missing.stdout) + + def test_missing_namespaced_symbol_is_still_an_error(self): + old =3D "DMA_BUF EXPORT_SYMBOL 0x12345678\tdma_buf_get\tvmlinux\n" + result =3D self.check(old, "") + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("found 1 missing symbols", result.stdout) + + def test_skip_flag_still_reports_abi_errors(self): + old =3D "DMA_BUF EXPORT_SYMBOL_GPL 0x12345678\tdma_buf_get\tvmlinu= x\n" + result =3D self.check(old, old.replace("0x12345678", "0x87654321"), skip=3DTrue) + self.assertEqual(result.returncode, 0, result.stdout) + self.assertIn("WW: 1 symbols changed hash", result.stdout) + + def test_malformed_entries_are_rejected_even_when_skipped(self): + valid =3D "EXPORT_SYMBOL vmlinux 0x12345678\tordinary\n" + for old, new in ((valid, valid + "broken\n"), (valid + "broken\n", valid)): + with self.subTest(old=3Dold, new=3Dnew): + result =3D self.check(old, new, skip=3DTrue) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("malformed ABI entry", result.stdout) + + def test_duplicate_entries_are_rejected(self): + valid =3D "EXPORT_SYMBOL vmlinux 0x12345678\tordinary\n" + for old, new in ((valid, valid * 2), (valid * 2, valid)): + with self.subTest(old=3Dold, new=3Dnew): + result =3D self.check(old, new) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("duplicate symbol 'ordinary'", result.stdout= ) + + def test_real_generator_output_preserves_namespaced_relocations(self): + symvers =3D ( + "0x12345678\tvmbus_open\tdrivers/hv/hv_vmbus\tEXPORT_SYMBOL\t\= n" + "0x853b6fd2\thv_post_message\tdrivers/hv/hv_vmbus\tEXPORT_SYMB= OL_GPL\tmodule:mshv_vtl\n" + ) + for name, content in (("abi-prev-test", symvers), ("abi-test", symvers.replace("drivers/hv/hv_vmbus", "vmlinux"))): + source =3D self.directory / "Module.symvers" + source.write_text(content) + subprocess.run(["bash", str(GENERATOR), str(source), str(self.directory / name)], check=3DTrue) + old =3D (self.directory / "abi-prev-test").read_text() + new =3D (self.directory / "abi-test").read_text() + result =3D self.check(old, new) + self.assertEqual(result.returncode, 0, result.stdout) + self.assertIn("2 symbols changed location", result.stdout) + + +if __name__ =3D=3D "__main__": + unittest.main() --=20 2.56.0.windows.1