From a187d0accb38a997bf915eba953d579b4c41109b Mon Sep 17 00:00:00 2001 From: Jessica Clarke Date: Fri, 10 Apr 2026 23:41:39 +0100 Subject: [PATCH 1/2] [ELF][Cheri] Support (/path/to/)libfoo.a:bar.o c18n policy syntax This allows for matching only input files pulled in from a static library. In theory extending this to support :(/path/to/)bar.o to mean only files not from static libraries (i.e. on the command line or pulled in by linker scripts) could be useful, but the priority of the various combinations becomes less clear, as static libraries (by virtue of being ar archives) don't permit directories and sidestep that question; specifically, there is the question of whether :bar.o or /path/to/bar.o (without an explicit empty scope) is higher priority. --- lld/ELF/Compartments.cpp | 45 ++++++++--- lld/test/ELF/cheri/riscv/c18n/static-lib.s | 92 ++++++++++++++++++++++ 2 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 lld/test/ELF/cheri/riscv/c18n/static-lib.s diff --git a/lld/ELF/Compartments.cpp b/lld/ELF/Compartments.cpp index c9e6874699e08..b12e055a0b903 100644 --- a/lld/ELF/Compartments.cpp +++ b/lld/ELF/Compartments.cpp @@ -479,29 +479,48 @@ void assignSectionsToCompartments() { assert(s->file->compartment == 0); StringRef path = s->file->getName(); - Expected c = fileLookup.findMatch(path); - if (!c) { - error("input file " + path + " " + toString(c.takeError())); - valid = false; - continue; - } - - // If the path is not a basename, try to match on the basename. - if (c->isDefault() && llvm::sys::path::has_parent_path(path)) { - c = fileLookup.findMatch(llvm::sys::path::filename(path)); + StringRef scope = s->file->archiveName; + Twine scopedPath = scope.empty() ? path : scope + ":" + path; + Expected c = *defaultCompart; + handleAllErrors(c.takeError()); + auto tryMatch = [&](StringRef s) -> bool { + llvm::errs() << "tryMatch: " << s << "\n"; + c = fileLookup.findMatch(s); if (!c) { - error("input file " + path + " " + toString(c.takeError())); + error("input file " + scopedPath + " " + toString(c.takeError())); valid = false; - continue; + return false; } + return true; + }; + + // First, prioritise explicitly-scoped matches. + if (!scope.empty()) { + if (!tryMatch(scopedPath.str())) + continue; + + // If the archive path is not a basename, try to match with a scope of + // just the basename. + if (c->isDefault() && llvm::sys::path::has_parent_path(scope) && + !tryMatch((llvm::sys::path::filename(scope) + ":" + path).str())) + continue; } + // Next, prioritise matches with a full path. + if (c->isDefault() && !tryMatch(path)) + continue; + + // If the path is not a basename, try to match on the basename. + if (c->isDefault() && llvm::sys::path::has_parent_path(path) && + !tryMatch(llvm::sys::path::filename(path))) + continue; + if (c->isDefault()) continue; s->file->compartment = c->getNumber(); if (config->verboseCompartmentalization) - message("info: input file " + s->file->getName() + " matched to " + + message("info: input file " + scopedPath + " matched to " + compartmentName(*c)); } diff --git a/lld/test/ELF/cheri/riscv/c18n/static-lib.s b/lld/test/ELF/cheri/riscv/c18n/static-lib.s new file mode 100644 index 0000000000000..73ea2b8c9418c --- /dev/null +++ b/lld/test/ELF/cheri/riscv/c18n/static-lib.s @@ -0,0 +1,92 @@ +# REQUIRES: riscv + +# RUN: rm -rf %t && split-file %s %t + +# RUN: %riscv64_cheri_purecap_llvm-mc -filetype=obj %t/a.s -o %t/a.o +# RUN: %riscv64_cheri_purecap_llvm-mc -filetype=obj %t/b.s -o %t/b.o +# RUN: llvm-ar crs %t/libb.a %t/b.o +# RUN: ld.lld -o %t/scoped %t/a.o %t/libb.a \ +# RUN: --compartment-policy=%t/scoped.json \ +# RUN: --compartment-policy=%t/unscoped.json +# RUN: ld.lld -o %t/unscoped %t/a.o %t/libb.a \ +# RUN: --compartment-policy=%t/unscoped.json +# RUN: llvm-readelf -l %t/scoped | FileCheck %s --check-prefix=SCOPED +# RUN: llvm-readelf -l %t/unscoped | FileCheck %s --check-prefix=UNSCOPED + +# SCOPED-LABEL: Program Headers: +# SCOPED-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align +# SCOPED-NEXT: PHDR 0x000040 0x0000000000010040 0x0000000000010040 0x0001f8 0x0001f8 R 0x8 +# SCOPED-NEXT: LOAD 0x000000 0x0000000000010000 0x0000000000010000 0x000278 0x000278 R 0x1000 +# SCOPED-NEXT: LOAD 0x000278 0x0000000000011278 0x0000000000011278 0x000028 0x000028 R E 0x1000 +# SCOPED-NEXT: LOAD 0x0002a0 0x00000000000122a0 0x00000000000122a0 0x000010 0x000010 RW 0x1000 +# SCOPED-NEXT: C18N_NAME 0x000278 0x0000000000011278 0x0000000000000001 0x000004 0x000004 0x4 +# SCOPED-NEXT: [Compartment: scoped] +# SCOPED-NEXT: C18N_NAME 0x000280 0x0000000000011280 0x0000000000000008 0x000030 0x001030 0x10 +# SCOPED-NEXT: [Compartment: unscoped] +# SCOPED-NEXT: CHERI_PCC 0x000278 0x0000000000011278 0x0000000000011278 0x000004 0x000004 R E 0x4 +# SCOPED-NEXT: CHERI_PCC 0x000280 0x0000000000011280 0x0000000000011280 0x000030 0x001030 R E 0x10 +# SCOPED-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0 + +# SCOPED-LABEL: Section to Segment mapping: +# SCOPED-NEXT: Segment Sections... +# SCOPED-NEXT: 00 +# SCOPED-NEXT: 01 .c18nstrtab __cap_relocs +# SCOPED-NEXT: 02 .text.scoped .pad.cheri.pcc.scoped .text.unscoped .iplt.unscoped +# SCOPED-NEXT: 03 .got.plt.unscoped +# SCOPED-NEXT: 04 .text.scoped +# SCOPED-NEXT: 05 .text.unscoped .iplt.unscoped .got.plt.unscoped +# SCOPED-NEXT: 06 .text.scoped +# SCOPED-NEXT: 07 .text.unscoped .iplt.unscoped .got.plt.unscoped +# SCOPED-NEXT: 08 +# SCOPED-NEXT: None .pad.cheri.pcc.unscoped .comment .symtab .shstrtab .strtab + +# UNSCOPED-LABEL: Program Headers: +# UNSCOPED-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align +# UNSCOPED-NEXT: PHDR 0x000040 0x0000000000010040 0x0000000000010040 0x000150 0x000150 R 0x8 +# UNSCOPED-NEXT: LOAD 0x000000 0x0000000000010000 0x0000000000010000 0x00019a 0x00019a R 0x1000 +# UNSCOPED-NEXT: LOAD 0x00019c 0x000000000001119c 0x000000000001119c 0x00000c 0x00000c R E 0x1000 +# UNSCOPED-NEXT: C18N_NAME 0x00019c 0x000000000001119c 0x0000000000000001 0x00000c 0x00000c 0x4 +# UNSCOPED-NEXT: [Compartment: unscoped] +# UNSCOPED-NEXT: CHERI_PCC 0x00019c 0x000000000001119c 0x000000000001119c 0x00000c 0x00000c R E 0x4 +# UNSCOPED-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0 + +# UNSCOPED-LABEL: Section to Segment mapping: +# UNSCOPED-NEXT: Segment Sections... +# UNSCOPED-NEXT: 00 +# UNSCOPED-NEXT: 01 .c18nstrtab +# UNSCOPED-NEXT: 02 .text.unscoped +# UNSCOPED-NEXT: 03 .text.unscoped +# UNSCOPED-NEXT: 04 .text.unscoped +# UNSCOPED-NEXT: 05 +# UNSCOPED-NEXT: None .pad.cheri.pcc.unscoped .comment .symtab .shstrtab .strtab + +#--- a.s + +call foo + +#--- b.s + +.global foo +.type foo, %function +foo: + ret + +#--- scoped.json + +{ + "compartments": { + "scoped": { + "files": ["libb.a:b.o"] + } + } +} + +#--- unscoped.json + +{ + "compartments": { + "unscoped": { + "files": ["a.o", "b.o"] + } + } +} From 02c727778e5c637b7b561907f10764a22fc1b5cf Mon Sep 17 00:00:00 2001 From: John Baldwin Date: Fri, 16 Oct 2026 16:09:00 -0400 Subject: [PATCH 2/2] squash! [ELF][Cheri] Support (/path/to/)libfoo.a:bar.o c18n policy syntax [ELF][Cheri] Require archive names in c18n policy syntax For object files which are archive members, require the path of a file specification to name the archive (static library) rather than the individual object file. An archive name can be followed by an optional suffix (prefixed with a colon) with a glob pattern for archive members. --- lld/ELF/Compartments.cpp | 48 ++++++++---- lld/test/ELF/cheri/riscv/c18n/static-lib.s | 85 +++++++++------------- 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/lld/ELF/Compartments.cpp b/lld/ELF/Compartments.cpp index b12e055a0b903..7d87133dbe92e 100644 --- a/lld/ELF/Compartments.cpp +++ b/lld/ELF/Compartments.cpp @@ -480,7 +480,12 @@ void assignSectionsToCompartments() { StringRef path = s->file->getName(); StringRef scope = s->file->archiveName; - Twine scopedPath = scope.empty() ? path : scope + ":" + path; + StringRef scopedPath = path; + std::string scopedStr; + if (!scope.empty()) { + scopedStr = (scope + ":" + path).str(); + scopedPath = scopedStr; + } Expected c = *defaultCompart; handleAllErrors(c.takeError()); auto tryMatch = [&](StringRef s) -> bool { @@ -494,26 +499,41 @@ void assignSectionsToCompartments() { return true; }; - // First, prioritise explicitly-scoped matches. + // When matching, give precedence to full-path matches for the archive + // name (for archive members) or file name (for standalone input files). + + // For archive members, require the pattern to include the name of the + // archive with an optional colon-prefixed suffix of the member name. if (!scope.empty()) { - if (!tryMatch(scopedPath.str())) + // First, try to match a fully scoped :. + if (!tryMatch(scopedPath)) + continue; + + // Second, match on just the archive name. + if (c->isDefault() && !tryMatch(scope)) continue; // If the archive path is not a basename, try to match with a scope of // just the basename. - if (c->isDefault() && llvm::sys::path::has_parent_path(scope) && - !tryMatch((llvm::sys::path::filename(scope) + ":" + path).str())) - continue; - } + if (c->isDefault() && llvm::sys::path::has_parent_path(scope)) { + StringRef baseScope = llvm::sys::path::filename(scope); + if (!tryMatch((baseScope + ":" + path).str())) + continue; - // Next, prioritise matches with a full path. - if (c->isDefault() && !tryMatch(path)) - continue; + // Try to match just the archive basename. + if (c->isDefault() && !tryMatch(baseScope)) + continue; + } + } else { + // Prioritise matches with a full path. + if (c->isDefault() && !tryMatch(path)) + continue; - // If the path is not a basename, try to match on the basename. - if (c->isDefault() && llvm::sys::path::has_parent_path(path) && - !tryMatch(llvm::sys::path::filename(path))) - continue; + // If the path is not a basename, try to match on the basename. + if (c->isDefault() && llvm::sys::path::has_parent_path(path) && + !tryMatch(llvm::sys::path::filename(path))) + continue; + } if (c->isDefault()) continue; diff --git a/lld/test/ELF/cheri/riscv/c18n/static-lib.s b/lld/test/ELF/cheri/riscv/c18n/static-lib.s index 73ea2b8c9418c..7dd5ca4258db5 100644 --- a/lld/test/ELF/cheri/riscv/c18n/static-lib.s +++ b/lld/test/ELF/cheri/riscv/c18n/static-lib.s @@ -5,60 +5,37 @@ # RUN: %riscv64_cheri_purecap_llvm-mc -filetype=obj %t/a.s -o %t/a.o # RUN: %riscv64_cheri_purecap_llvm-mc -filetype=obj %t/b.s -o %t/b.o # RUN: llvm-ar crs %t/libb.a %t/b.o -# RUN: ld.lld -o %t/scoped %t/a.o %t/libb.a \ -# RUN: --compartment-policy=%t/scoped.json \ -# RUN: --compartment-policy=%t/unscoped.json -# RUN: ld.lld -o %t/unscoped %t/a.o %t/libb.a \ -# RUN: --compartment-policy=%t/unscoped.json -# RUN: llvm-readelf -l %t/scoped | FileCheck %s --check-prefix=SCOPED -# RUN: llvm-readelf -l %t/unscoped | FileCheck %s --check-prefix=UNSCOPED +# RUN: ld.lld -o %t/fully-scoped %t/a.o %t/libb.a \ +# RUN: --compartment-policy=%t/fully-scoped.json +# RUN: ld.lld -o %t/scope-only %t/a.o %t/libb.a \ +# RUN: --compartment-policy=%t/scope-only.json +# RUN: not ld.lld -o %t/unscoped %t/a.o %t/libb.a \ +# RUN: --compartment-policy=%t/unscoped.json 2>&1 | \ +# RUN: FileCheck %s --check-prefix=UNSCOPED +# RUN: llvm-readelf -l %t/fully-scoped | FileCheck %s --check-prefix=SCOPED +# RUN: llvm-readelf -l %t/scope-only | FileCheck %s --check-prefix=SCOPED # SCOPED-LABEL: Program Headers: # SCOPED-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align -# SCOPED-NEXT: PHDR 0x000040 0x0000000000010040 0x0000000000010040 0x0001f8 0x0001f8 R 0x8 -# SCOPED-NEXT: LOAD 0x000000 0x0000000000010000 0x0000000000010000 0x000278 0x000278 R 0x1000 -# SCOPED-NEXT: LOAD 0x000278 0x0000000000011278 0x0000000000011278 0x000028 0x000028 R E 0x1000 -# SCOPED-NEXT: LOAD 0x0002a0 0x00000000000122a0 0x00000000000122a0 0x000010 0x000010 RW 0x1000 -# SCOPED-NEXT: C18N_NAME 0x000278 0x0000000000011278 0x0000000000000001 0x000004 0x000004 0x4 -# SCOPED-NEXT: [Compartment: scoped] -# SCOPED-NEXT: C18N_NAME 0x000280 0x0000000000011280 0x0000000000000008 0x000030 0x001030 0x10 -# SCOPED-NEXT: [Compartment: unscoped] -# SCOPED-NEXT: CHERI_PCC 0x000278 0x0000000000011278 0x0000000000011278 0x000004 0x000004 R E 0x4 -# SCOPED-NEXT: CHERI_PCC 0x000280 0x0000000000011280 0x0000000000011280 0x000030 0x001030 R E 0x10 +# SCOPED-NEXT: PHDR 0x000040 0x0000000000010040 0x0000000000010040 0x000150 0x000150 R 0x8 +# SCOPED-NEXT: LOAD 0x000000 0x0000000000010000 0x0000000000010000 0x000195 0x000195 R 0x1000 +# SCOPED-NEXT: LOAD 0x000198 0x0000000000011198 0x0000000000011198 0x00000c 0x00000c R E 0x1000 +# SCOPED-NEXT: C18N_NAME 0x000198 0x0000000000011198 0x0000000000000001 0x00000c 0x00000c 0x4 +# SCOPED-NEXT: [Compartment: one] +# SCOPED-NEXT: CHERI_PCC 0x000198 0x0000000000011198 0x0000000000011198 0x00000c 0x00000c R E 0x4 # SCOPED-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0 # SCOPED-LABEL: Section to Segment mapping: # SCOPED-NEXT: Segment Sections... # SCOPED-NEXT: 00 -# SCOPED-NEXT: 01 .c18nstrtab __cap_relocs -# SCOPED-NEXT: 02 .text.scoped .pad.cheri.pcc.scoped .text.unscoped .iplt.unscoped -# SCOPED-NEXT: 03 .got.plt.unscoped -# SCOPED-NEXT: 04 .text.scoped -# SCOPED-NEXT: 05 .text.unscoped .iplt.unscoped .got.plt.unscoped -# SCOPED-NEXT: 06 .text.scoped -# SCOPED-NEXT: 07 .text.unscoped .iplt.unscoped .got.plt.unscoped -# SCOPED-NEXT: 08 -# SCOPED-NEXT: None .pad.cheri.pcc.unscoped .comment .symtab .shstrtab .strtab +# SCOPED-NEXT: 01 .c18nstrtab +# SCOPED-NEXT: 02 .text.one +# SCOPED-NEXT: 03 .text.one +# SCOPED-NEXT: 04 .text.one +# SCOPED-NEXT: 05 +# SCOPED-NEXT: None .pad.cheri.pcc.one .comment .symtab .shstrtab .strtab -# UNSCOPED-LABEL: Program Headers: -# UNSCOPED-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align -# UNSCOPED-NEXT: PHDR 0x000040 0x0000000000010040 0x0000000000010040 0x000150 0x000150 R 0x8 -# UNSCOPED-NEXT: LOAD 0x000000 0x0000000000010000 0x0000000000010000 0x00019a 0x00019a R 0x1000 -# UNSCOPED-NEXT: LOAD 0x00019c 0x000000000001119c 0x000000000001119c 0x00000c 0x00000c R E 0x1000 -# UNSCOPED-NEXT: C18N_NAME 0x00019c 0x000000000001119c 0x0000000000000001 0x00000c 0x00000c 0x4 -# UNSCOPED-NEXT: [Compartment: unscoped] -# UNSCOPED-NEXT: CHERI_PCC 0x00019c 0x000000000001119c 0x000000000001119c 0x00000c 0x00000c R E 0x4 -# UNSCOPED-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0 - -# UNSCOPED-LABEL: Section to Segment mapping: -# UNSCOPED-NEXT: Segment Sections... -# UNSCOPED-NEXT: 00 -# UNSCOPED-NEXT: 01 .c18nstrtab -# UNSCOPED-NEXT: 02 .text.unscoped -# UNSCOPED-NEXT: 03 .text.unscoped -# UNSCOPED-NEXT: 04 .text.unscoped -# UNSCOPED-NEXT: 05 -# UNSCOPED-NEXT: None .pad.cheri.pcc.unscoped .comment .symtab .shstrtab .strtab +# UNSCOPED: error: no output sections for compartment one #--- a.s @@ -71,22 +48,32 @@ call foo foo: ret -#--- scoped.json +#--- fully-scoped.json { "compartments": { - "scoped": { + "one": { "files": ["libb.a:b.o"] } } } +#--- scope-only.json + +{ + "compartments": { + "one": { + "files": ["libb.a"] + } + } +} + #--- unscoped.json { "compartments": { - "unscoped": { - "files": ["a.o", "b.o"] + "one": { + "files": ["b.o"] } } }