From 39a616fdcc7a1ec996dec1ff63469829b8b27862 Mon Sep 17 00:00:00 2001 From: quic-areg Date: Fri, 9 Jan 2026 13:22:36 -0800 Subject: [PATCH] Support z separate/no-separate code `-z separate-code` puts the text segment in its own pages disjoint from any other segments. --- include/eld/Config/GeneralOptions.h | 10 +++++ include/eld/Driver/GnuLinkerOptions.td | 4 +- include/eld/Input/ZOption.h | 2 + include/eld/Object/ScriptMemoryRegion.h | 2 +- lib/Config/GeneralOptions.cpp | 6 +++ lib/LayoutMap/LayoutInfo.cpp | 2 +- lib/LinkerWrapper/GnuLdDriver.cpp | 4 ++ lib/Support/StringUtils.cpp | 2 +- lib/Target/CreateProgramHeaders.hpp | 25 +++++++++++- .../PartialLinkEXIDX/PartialLinkEXIDX.test | 2 +- test/lld/ELF/separate-segments.s | 40 +++++++++++++++++++ 11 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 test/lld/ELF/separate-segments.s diff --git a/include/eld/Config/GeneralOptions.h b/include/eld/Config/GeneralOptions.h index 66dcee806..49818606c 100644 --- a/include/eld/Config/GeneralOptions.h +++ b/include/eld/Config/GeneralOptions.h @@ -49,6 +49,8 @@ class GeneralOptions { typedef llvm::StringMap AddressMapType; + enum class SeparateSegmentKind { None, Code }; + enum StripSymbolMode { KeepAllSymbols, StripTemporaries, @@ -654,6 +656,12 @@ class GeneralOptions { void setROSegment(bool R = false) { Rosegment = R; } + SeparateSegmentKind getSeparateSegmentKind() const { + return SeparateSegments; + } + + void setSeparateSegmentKind(SeparateSegmentKind K) { SeparateSegments = K; } + bool verifyLink() const { return Verify; } void setVerifyLink(bool V = true) { Verify = V; } @@ -1214,6 +1222,8 @@ class GeneralOptions { bool Savetemps = false; // -save-temps std::optional SaveTempsDir; // -save-temps= bool Rosegment = false; // merge read only with readonly/execute segments. + SeparateSegmentKind SeparateSegments = + SeparateSegmentKind::None; // -z separate-code std::vector UnparsedLTOOptions; // Unparsed -flto-options, to pass to plugin. uint32_t LTOOptions = 0; // -flto-options diff --git a/include/eld/Driver/GnuLinkerOptions.td b/include/eld/Driver/GnuLinkerOptions.td index fdbc0d209..4151c3a0a 100644 --- a/include/eld/Driver/GnuLinkerOptions.td +++ b/include/eld/Driver/GnuLinkerOptions.td @@ -872,7 +872,9 @@ defm dash_z "\t\t\t-z=now : Enables immediate binding\n" "\t\t\t-z=nocopyreloc : Disables Copy Relocation" "\t\t\t-z=text : Do not permit relocations in read-only segments (default)\n" - "\t\t\t-z=notext : Allow relocations in read-only segments\n">, + "\t\t\t-z=notext : Allow relocations in read-only segments\n" + "\t\t\t-z=separate-code : Create separate code segment\n" + "\t\t\t-z=no-separate-code : Do not create separate code segment\n">, MetaVarName<"">, Group; def no_align_segments : Flag<["--"], "no-align-segments">, diff --git a/include/eld/Input/ZOption.h b/include/eld/Input/ZOption.h index 4a33cf8dc..d9f352340 100644 --- a/include/eld/Input/ZOption.h +++ b/include/eld/Input/ZOption.h @@ -41,6 +41,8 @@ class ZOption { NoDelete, NoExecStack, NoGnuStack, + SeparateCode, + NoseparateCode, NoRelro, Now, Origin, diff --git a/include/eld/Object/ScriptMemoryRegion.h b/include/eld/Object/ScriptMemoryRegion.h index 99b83e239..bf30d1af0 100644 --- a/include/eld/Object/ScriptMemoryRegion.h +++ b/include/eld/Object/ScriptMemoryRegion.h @@ -51,7 +51,7 @@ class ScriptMemoryRegion { // We used to have only one function called getAddr and there // was no way to differentiate between a call to virtual address - // or physical address. Now we need this seperate to support + // or physical address. Now we need this separate to support // ALIGN_WITH_INPUT uint64_t getVirtualAddr(OutputSectionEntry *O); diff --git a/lib/Config/GeneralOptions.cpp b/lib/Config/GeneralOptions.cpp index 4d9c158f3..a54254eec 100644 --- a/lib/Config/GeneralOptions.cpp +++ b/lib/Config/GeneralOptions.cpp @@ -95,6 +95,12 @@ bool GeneralOptions::addZOption(const ZOption &POption) { case ZOption::NoGnuStack: NoGnuStack = true; break; + case eld::ZOption::SeparateCode: + setSeparateSegmentKind(SeparateSegmentKind::Code); + break; + case eld::ZOption::NoseparateCode: + setSeparateSegmentKind(SeparateSegmentKind::None); + break; case ZOption::Global: BGlobal = true; break; diff --git a/lib/LayoutMap/LayoutInfo.cpp b/lib/LayoutMap/LayoutInfo.cpp index 3f566785e..78d4b48ea 100644 --- a/lib/LayoutMap/LayoutInfo.cpp +++ b/lib/LayoutMap/LayoutInfo.cpp @@ -133,7 +133,7 @@ bool LayoutInfo::isSectionDetailedInfoAvailable(ELFSection *Section) { if (Section->isIgnore()) return false; - // These sections are handled seperately and they dont follow + // These sections are handled separately and they dont follow // the same path of merging switch (Section->getKind()) { case LDFileFormat::Discard: diff --git a/lib/LinkerWrapper/GnuLdDriver.cpp b/lib/LinkerWrapper/GnuLdDriver.cpp index 480ceb6a5..d841380c6 100644 --- a/lib/LinkerWrapper/GnuLdDriver.cpp +++ b/lib/LinkerWrapper/GnuLdDriver.cpp @@ -728,6 +728,10 @@ bool GnuLdDriver::processOptions(llvm::opt::InputArgList &Args) { ZKind = eld::ZOption::NoExecStack; else if (ZOpt == "nognustack") ZKind = eld::ZOption::NoGnuStack; + else if (ZOpt == "separate-code") + ZKind = eld::ZOption::SeparateCode; + else if (ZOpt == "noseparate-code") + ZKind = eld::ZOption::NoseparateCode; else if (ZOpt == "execstack") ZKind = eld::ZOption::ExecStack; else if (ZOpt == "nodelete") { diff --git a/lib/Support/StringUtils.cpp b/lib/Support/StringUtils.cpp index ede21aee6..30675ed40 100644 --- a/lib/Support/StringUtils.cpp +++ b/lib/Support/StringUtils.cpp @@ -20,7 +20,7 @@ std::string ReplaceString(std::string &Subject, const std::string &Search, return Subject; } -/// \brief Split a string with multiple names seperated by comma. +/// \brief Split a string with multiple names separated by comma. /// \returns a vector of strings. std::vector split(const std::string &S, char Seperator) { std::vector Output; diff --git a/lib/Target/CreateProgramHeaders.hpp b/lib/Target/CreateProgramHeaders.hpp index 1e0b2b200..5f578eff1 100644 --- a/lib/Target/CreateProgramHeaders.hpp +++ b/lib/Target/CreateProgramHeaders.hpp @@ -178,6 +178,25 @@ bool GNULDBackend::createProgramHdrs() { reset_state(); + auto SeparateKind = config().options().getSeparateSegmentKind(); + bool ShouldSeparate = + !linkerScriptHasSectionsCommand && + (SeparateKind != GeneralOptions::SeparateSegmentKind::None); + + auto ShouldPageAlignSegment = [&](bool IsExec) { + if (config().options().alignSegmentsToPage()) + return true; + if (!ShouldSeparate) + return false; + if (!prevOut) + return false; + auto PrevSegIt = _segmentsForSection.find(prevOut); + if (PrevSegIt == _segmentsForSection.end() || PrevSegIt->second.empty()) + return false; + bool PrevExec = (PrevSegIt->second.front()->flag() & llvm::ELF::PF_X); + return PrevExec != IsExec; + }; + if (load_ehdr) setNeedEhdr(); @@ -359,6 +378,7 @@ bool GNULDBackend::createProgramHdrs() { } cur_flag = getSegmentFlag(cur->getFlags()); + bool CurIsExec = cur_flag & llvm::ELF::PF_X; if (linkerScriptHasMemoryCommand && (*out)->epilog().hasRegion()) cur_mem_region = (*out) @@ -445,7 +465,8 @@ bool GNULDBackend::createProgramHdrs() { } if (isCurAlloc && (createPT_LOAD || last_section_needs_new_segment)) { - if (config().options().alignSegmentsToPage()) + bool ShouldPageAlign = ShouldPageAlignSegment(CurIsExec); + if (ShouldPageAlign) alignAddress(vma, segAlign); if (cur->isFixedAddr() && (vma != cur->addr())) { config().raise(Diag::cannot_set_at_address) << cur->name(); @@ -483,7 +504,7 @@ bool GNULDBackend::createProgramHdrs() { // FIXME : remove this option alignSegmentsToPage // Handle the case without any linker scripts, - if (config().options().alignSegmentsToPage()) + if (ShouldPageAlign) alignAddress(pma, segAlign); cur->setPaddr(pma); diff --git a/test/ARM/standalone/PartialLinkEXIDX/PartialLinkEXIDX.test b/test/ARM/standalone/PartialLinkEXIDX/PartialLinkEXIDX.test index a6026be60..e5683d984 100644 --- a/test/ARM/standalone/PartialLinkEXIDX/PartialLinkEXIDX.test +++ b/test/ARM/standalone/PartialLinkEXIDX/PartialLinkEXIDX.test @@ -1,6 +1,6 @@ #---PartialEXIDX.test--------------------------- PartialLink -----------------# #BEGIN_COMMENT -#Linker should not create seperate exidx sections even if link order is set. +#Linker should not create separate exidx sections even if link order is set. #END_COMMENT #START_TEST RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.1.o diff --git a/test/lld/ELF/separate-segments.s b/test/lld/ELF/separate-segments.s new file mode 100644 index 000000000..219ee2e0d --- /dev/null +++ b/test/lld/ELF/separate-segments.s @@ -0,0 +1,40 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o + +## -z noseparate-code. All PT_LOAD can have overlapping p_offset +## ranges at runtime. +# RUN: %link -pie --no-align-segments --rosegment %t.o -o %t +# RUN: llvm-readelf -l %t | FileCheck --check-prefix=NONE %s +# NONE: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000200 0x000200 R E 0x1000 +# NONE-NEXT: LOAD 0x000200 0x0000000000000200 0x0000000000000200 0x000044 0x000044 R 0x1000 +# NONE-NEXT: LOAD 0x000244 0x0000000000000244 0x0000000000000244 0x000001 0x000001 R E 0x1000 +# NONE-NEXT: LOAD 0x000245 0x0000000000000245 0x0000000000000245 0x000001 0x000001 R 0x1000 +# NONE-NEXT: LOAD 0x000248 0x0000000000000248 0x0000000000000248 0x0000f1 0x0000f1 RW 0x1000 + +## -z separate-code makes text segment (RX) separate. +## The two RW can have overlapping p_offset ranges at runtime. +# RUN: %link -pie --no-align-segments --rosegment %t.o -z separate-code -o %t +# RUN: llvm-readelf -l %t | FileCheck --check-prefix=CODE %s +# CODE: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000200 0x000200 R E 0x1000 +# CODE-NEXT: LOAD 0x001000 0x0000000000001000 0x0000000000001000 0x000044 0x000044 R 0x1000 +# CODE-NEXT: LOAD 0x002000 0x0000000000002000 0x0000000000002000 0x000001 0x000001 R E 0x1000 +# CODE-NEXT: LOAD 0x003000 0x0000000000003000 0x0000000000003000 0x000001 0x000001 R 0x1000 +# CODE-NEXT: LOAD 0x003008 0x0000000000003008 0x0000000000003008 0x0000f1 0x0000f1 RW 0x1000 + +## TODO: +## -z separate-loadable-segments makes all segments separate. +# %link -pie --no-align-segments --rosegment %t.o -z separate-loadable-segments -o %t +# llvm-readelf -l %t | FileCheck --check-prefix=ALL %s +# ALL: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000200 0x000200 R E 0x1000 +# ALL-NEXT: LOAD 0x001000 0x0000000000001000 0x0000000000001000 0x000044 0x000044 R 0x1000 +# ALL-NEXT: LOAD 0x002000 0x0000000000002000 0x0000000000002000 0x000001 0x000001 R E 0x1000 +# ALL-NEXT: LOAD 0x003000 0x0000000000003000 0x0000000000003000 0x000001 0x000001 R 0x1000 +# ALL-NEXT: LOAD 0x004000 0x0000000000004000 0x0000000000004000 0x0000f1 0x0000f1 RW 0x1000 + +nop + +.section .rodata,"a" +.byte 0 + +.data +.byte 0