Skip to content

Conversation

@Il-Capitano
Copy link
Contributor

-z execute-only-report checks that all executable sections have either the SHF_AARCH64_PURECODE or SHF_ARM_PURECODE section flag set on AArch64 and ARM respectively.

@llvmbot
Copy link
Member

llvmbot commented Feb 26, 2025

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-elf

Author: Csanád Hajdú (Il-Capitano)

Changes

-z execute-only-report checks that all executable sections have either the SHF_AARCH64_PURECODE or SHF_ARM_PURECODE section flag set on AArch64 and ARM respectively.


Full diff: https://github.com/llvm/llvm-project/pull/128883.diff

6 Files Affected:

  • (modified) lld/ELF/Config.h (+1)
  • (modified) lld/ELF/Driver.cpp (+11-4)
  • (modified) lld/ELF/Writer.cpp (+34)
  • (added) lld/test/ELF/aarch64-execute-only-report.s (+40)
  • (added) lld/test/ELF/arm-execute-only-report.s (+36)
  • (added) lld/test/ELF/execute-only-report-error.s (+18)
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index f132b11b20c63..b5872b85efd3a 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -229,6 +229,7 @@ struct Config {
   StringRef zCetReport = "none";
   StringRef zPauthReport = "none";
   StringRef zGcsReport = "none";
+  StringRef zExecuteOnlyReport = "none";
   bool ltoBBAddrMap;
   llvm::StringRef ltoBasicBlockSections;
   std::pair<llvm::StringRef, llvm::StringRef> thinLTOObjectSuffixReplace;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 70a293875f27b..8cf588ef27b09 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -406,6 +406,11 @@ static void checkOptions(Ctx &ctx) {
       ErrAlways(ctx) << "-z gcs only supported on AArch64";
   }
 
+  if (ctx.arg.emachine != EM_AARCH64 && ctx.arg.emachine != EM_ARM &&
+      ctx.arg.zExecuteOnlyReport != "none")
+    ErrAlways(ctx)
+        << "-z execute-only-report only supported on AArch64 and ARM";
+
   if (ctx.arg.emachine != EM_PPC64) {
     if (ctx.arg.tocOptimize)
       ErrAlways(ctx) << "--toc-optimize is only supported on PowerPC64 targets";
@@ -1620,10 +1625,12 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
       ErrAlways(ctx) << errPrefix << pat.takeError() << ": " << kv.first;
   }
 
-  auto reports = {std::make_pair("bti-report", &ctx.arg.zBtiReport),
-                  std::make_pair("cet-report", &ctx.arg.zCetReport),
-                  std::make_pair("gcs-report", &ctx.arg.zGcsReport),
-                  std::make_pair("pauth-report", &ctx.arg.zPauthReport)};
+  auto reports = {
+      std::make_pair("bti-report", &ctx.arg.zBtiReport),
+      std::make_pair("cet-report", &ctx.arg.zCetReport),
+      std::make_pair("execute-only-report", &ctx.arg.zExecuteOnlyReport),
+      std::make_pair("gcs-report", &ctx.arg.zGcsReport),
+      std::make_pair("pauth-report", &ctx.arg.zPauthReport)};
   for (opt::Arg *arg : args.filtered(OPT_z)) {
     std::pair<StringRef, StringRef> option =
         StringRef(arg->getValue()).split('=');
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index a2c49343e5c8d..c747a5b5e1239 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -64,6 +64,7 @@ template <class ELFT> class Writer {
   void sortOrphanSections();
   void finalizeSections();
   void checkExecuteOnly();
+  void checkExecuteOnlyReport();
   void setReservedSymbolSections();
 
   SmallVector<std::unique_ptr<PhdrEntry>, 0> createPhdrs(Partition &part);
@@ -323,6 +324,7 @@ template <class ELFT> void Writer<ELFT>::run() {
   // finalizeSections does that.
   finalizeSections();
   checkExecuteOnly();
+  checkExecuteOnlyReport();
 
   // If --compressed-debug-sections is specified, compress .debug_* sections.
   // Do it right now because it changes the size of output sections.
@@ -2177,6 +2179,38 @@ template <class ELFT> void Writer<ELFT>::checkExecuteOnly() {
                             "data and code";
 }
 
+// Check which input sections of RX output sections don't have the
+// SHF_AARCH64_PURECODE or SHF_ARM_PURECODE flag set.
+template <class ELFT> void Writer<ELFT>::checkExecuteOnlyReport() {
+  if ((ctx.arg.emachine != EM_AARCH64 && ctx.arg.emachine != EM_ARM) ||
+      ctx.arg.zExecuteOnlyReport == "none")
+    return;
+
+  auto reportUnless = [&](bool cond) -> ELFSyncStream {
+    if (cond)
+      return {ctx, DiagLevel::None};
+    if (ctx.arg.zExecuteOnlyReport == "error")
+      return {ctx, DiagLevel::Err};
+    if (ctx.arg.zExecuteOnlyReport == "warning")
+      return {ctx, DiagLevel::Warn};
+    return {ctx, DiagLevel::None};
+  };
+
+  uint64_t purecodeFlag =
+      ctx.arg.emachine == EM_AARCH64 ? SHF_AARCH64_PURECODE : SHF_ARM_PURECODE;
+  StringRef purecodeFlagName = ctx.arg.emachine == EM_AARCH64
+                                   ? "SHF_AARCH64_PURECODE"
+                                   : "SHF_ARM_PURECODE";
+  SmallVector<InputSection *, 0> storage;
+  for (OutputSection *osec : ctx.outputSections)
+    if (osec->getPhdrFlags() == (PF_R | PF_X))
+      for (InputSection *sec : getInputSections(*osec, storage))
+        if (sec->file && sec->file->getName() != "<internal>")
+          reportUnless(sec->flags & purecodeFlag)
+              << "-z execute-only-report: " << sec << " does not have "
+              << purecodeFlagName << " flag set";
+}
+
 // The linker is expected to define SECNAME_start and SECNAME_end
 // symbols for a few sections. This function defines them.
 template <class ELFT> void Writer<ELFT>::addStartEndSymbols() {
diff --git a/lld/test/ELF/aarch64-execute-only-report.s b/lld/test/ELF/aarch64-execute-only-report.s
new file mode 100644
index 0000000000000..e56eed819d12b
--- /dev/null
+++ b/lld/test/ELF/aarch64-execute-only-report.s
@@ -0,0 +1,40 @@
+// REQUIRES: aarch64
+
+// RUN: llvm-mc --triple=aarch64 --filetype=obj -o %t.o %s
+// RUN: ld.lld --defsym absolute=0xf0000000 -z execute-only-report=none --fatal-warnings %t.o -o /dev/null
+// RUN: ld.lld --defsym absolute=0xf0000000 -z execute-only-report=warning %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck --check-prefix=WARNING %s
+// RUN: ld.lld --defsym absolute=0xf0000000 --execute-only -z execute-only-report=warning %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck --check-prefix=WARNING %s
+// RUN: not ld.lld --defsym absolute=0xf0000000 -z execute-only-report=error %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck --check-prefix=ERROR %s
+// RUN: not ld.lld --defsym absolute=0xf0000000 --execute-only -z execute-only-report=error %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck --check-prefix=ERROR %s
+
+// WARNING-NOT: warning: -z execute-only-report: {{.*}}.o:(.text) does not have SHF_AARCH64_PURECODE flag set
+// WARNING-NOT: warning: -z execute-only-report: {{.*}}.o:(.text.foo) does not have SHF_AARCH64_PURECODE flag set
+// WARNING: warning: -z execute-only-report: {{.*}}.o:(.text.bar) does not have SHF_AARCH64_PURECODE flag set
+// WARNING-NOT: warning: -z execute-only-report: <internal>:({{.*}}) does not have SHF_AARCH64_PURECODE flag set
+
+// ERROR-NOT: error: -z execute-only-report: {{.*}}.o:(.text) does not have SHF_AARCH64_PURECODE flag set
+// ERROR-NOT: error: -z execute-only-report: {{.*}}.o:(.text.foo) does not have SHF_AARCH64_PURECODE flag set
+// ERROR: error: -z execute-only-report: {{.*}}.o:(.text.bar) does not have SHF_AARCH64_PURECODE flag set
+// ERROR-NOT: error: -z execute-only-report: <internal>:({{.*}}) does not have SHF_AARCH64_PURECODE flag set
+
+.section .text,"axy",@progbits,unique,0
+.globl _start
+_start:
+  bl foo
+  bl bar
+  bl absolute
+  ret
+
+.section .text.foo,"axy",@progbits,unique,0
+.globl foo
+foo:
+  ret
+
+.section .text.bar,"ax",@progbits,unique,0
+.globl bar
+bar:
+  ret
diff --git a/lld/test/ELF/arm-execute-only-report.s b/lld/test/ELF/arm-execute-only-report.s
new file mode 100644
index 0000000000000..1676a8f654552
--- /dev/null
+++ b/lld/test/ELF/arm-execute-only-report.s
@@ -0,0 +1,36 @@
+// REQUIRES: arm
+
+// RUN: llvm-mc --triple=armv7 --filetype=obj -o %t.o %s
+// RUN: ld.lld --defsym absolute=0xf0000000 -z execute-only-report=none --fatal-warnings %t.o -o /dev/null
+// RUN: ld.lld --defsym absolute=0xf0000000 -z execute-only-report=warning %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck --check-prefix=WARNING %s
+// RUN: not ld.lld --defsym absolute=0xf0000000 -z execute-only-report=error %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck --check-prefix=ERROR %s
+
+// WARNING-NOT: warning: -z execute-only-report: {{.*}}.o:(.text) does not have SHF_ARM_PURECODE flag set
+// WARNING-NOT: warning: -z execute-only-report: {{.*}}.o:(.text.foo) does not have SHF_ARM_PURECODE flag set
+// WARNING: warning: -z execute-only-report: {{.*}}.o:(.text.bar) does not have SHF_ARM_PURECODE flag set
+// WARNING-NOT: warning: -z execute-only-report: <internal>:({{.*}}) does not have SHF_ARM_PURECODE flag set
+
+// ERROR-NOT: error: -z execute-only-report: {{.*}}.o:(.text) does not have SHF_ARM_PURECODE flag set
+// ERROR-NOT: error: -z execute-only-report: {{.*}}.o:(.text.foo) does not have SHF_ARM_PURECODE flag set
+// ERROR: error: -z execute-only-report: {{.*}}.o:(.text.bar) does not have SHF_ARM_PURECODE flag set
+// ERROR-NOT: error: -z execute-only-report: <internal>:({{.*}}) does not have SHF_ARM_PURECODE flag set
+
+.section .text,"axy",%progbits,unique,0
+.globl _start
+_start:
+  bl foo
+  bl bar
+  bl absolute
+  bx lr
+
+.section .text.foo,"axy",%progbits,unique,0
+.globl foo
+foo:
+  bx lr
+
+.section .text.bar,"ax",%progbits,unique,0
+.globl bar
+bar:
+  bx lr
diff --git a/lld/test/ELF/execute-only-report-error.s b/lld/test/ELF/execute-only-report-error.s
new file mode 100644
index 0000000000000..91afd4edfc0d2
--- /dev/null
+++ b/lld/test/ELF/execute-only-report-error.s
@@ -0,0 +1,18 @@
+// REQUIRES: x86
+
+// RUN: llvm-mc --triple=x86_64 --filetype=obj -o %t.o %s
+// RUN: not ld.lld -z execute-only-report=warning %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck %s
+// RUN: not ld.lld -z execute-only-report=error %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck %s
+
+// CHECK: error: -z execute-only-report only supported on AArch64 and ARM
+
+// RUN: not ld.lld -z execute-only-report=foo %t.o -o /dev/null 2>&1 \
+// RUN:     | FileCheck --check-prefix=INVALID %s
+
+// INVALID: error: -z execute-only-report= parameter foo is not recognized
+
+.globl _start
+_start:
+  ret

@Il-Capitano
Copy link
Contributor Author

@MaskRay @smithp35 Can I ask you to review this, please?

// finalizeSections does that.
finalizeSections();
checkExecuteOnly();
checkExecuteOnlyReport();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move into checkExecuteOnly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't do this yet, because I think that checkExecuteOnly and checkExecuteOnlyReport aside from the similar naming do different things and they should be separate.

Copy link
Collaborator

@smithp35 smithp35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a couple of suggestions over what MaskRay has already mentioned.

Copy link
Member

@MaskRay MaskRay Mar 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that the error message is not conventional. It should be
unknown -z execute-only-report= value: foo to match a few other options.

I am updating it. Sorry for making you rebase the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, let me know when it's done and I'll rebase.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7e8a06c

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rebased and updated the test.

`-z execute-only-report` checks that all executable sections have either
the SHF_AARCH64_PURECODE or SHF_ARM_PURECODE section flag set on AArch64
and ARM respectively.
@Il-Capitano Il-Capitano force-pushed the lld-z-execute-only-report branch from 38cf844 to ddaf54d Compare March 1, 2025 18:32
@MaskRay MaskRay merged commit 9b7b7d6 into llvm:main Mar 1, 2025
11 checks passed
@Il-Capitano Il-Capitano deleted the lld-z-execute-only-report branch March 1, 2025 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants