Index: lld/COFF/Chunks.h =================================================================== --- lld/COFF/Chunks.h +++ lld/COFF/Chunks.h @@ -42,6 +42,9 @@ // Mask for permissions (discardable, writable, readable, executable, etc). const uint32_t permMask = 0xFE000000; +// Mask for access bits (readable, writable, executable). +const uint32_t accessMask = 0xE0000000; + // Mask for section types (code, data, bss). const uint32_t typeMask = 0x000000E0; Index: lld/COFF/Config.h =================================================================== --- lld/COFF/Config.h +++ lld/COFF/Config.h @@ -80,7 +80,15 @@ // Global configuration. struct Configuration { - enum ManifestKind { SideBySide, Embed, No }; + enum ManifestKind { SideBySide, Embed, No }; + + // Used for applying section attributes specified via /section. + struct SectionAttributes { + uint32_t enableMask = 0; // Bit set for every attibute to enable. + uint32_t disableMask = 0; // Bit set for every attribute to disable. + uint32_t accessBits = 0; // Set access bits to apply as-is. + }; + bool is64() { return machine == AMD64 || machine == ARM64; } llvm::COFF::MachineTypes machine = IMAGE_FILE_MACHINE_UNKNOWN; @@ -153,8 +161,8 @@ // Used for /merge:from=to (e.g. /merge:.rdata=.text) std::map merge; - // Used for /section=.name,{DEKPRSW} to set section attributes. - std::map section; + // Used for /section=.name,[!]{DEKPRSW} to set section attributes. + std::map section; // Options for manifest files. ManifestKind manifest = No; Index: lld/COFF/DriverUtils.cpp =================================================================== --- lld/COFF/DriverUtils.cpp +++ lld/COFF/DriverUtils.cpp @@ -166,36 +166,66 @@ } } -static uint32_t parseSectionAttributes(StringRef s) { - uint32_t ret = 0; +// Parse the section attributes modification for [!]{DEKPRSW}. +// MSVC link.exe behavior is reproduced for compatibility. +static void parseSectionAttributes(StringRef s, Configuration::SectionAttributes &attrs) { + bool enableMaskActive = true; + uint32_t activeMask = attrs.enableMask; + uint32_t inactiveMask = attrs.disableMask; + for (char c : s.lower()) { switch (c) { case 'd': - ret |= IMAGE_SCN_MEM_DISCARDABLE; + activeMask |= IMAGE_SCN_MEM_DISCARDABLE; break; case 'e': - ret |= IMAGE_SCN_MEM_EXECUTE; + activeMask |= IMAGE_SCN_MEM_EXECUTE; break; + // Unlike other flags, K & P are applied when used w/ !. case 'k': - ret |= IMAGE_SCN_MEM_NOT_CACHED; + inactiveMask |= IMAGE_SCN_MEM_NOT_CACHED; break; case 'p': - ret |= IMAGE_SCN_MEM_NOT_PAGED; + inactiveMask |= IMAGE_SCN_MEM_NOT_PAGED; break; case 'r': - ret |= IMAGE_SCN_MEM_READ; + activeMask |= IMAGE_SCN_MEM_READ; break; case 's': - ret |= IMAGE_SCN_MEM_SHARED; + activeMask |= IMAGE_SCN_MEM_SHARED; break; case 'w': - ret |= IMAGE_SCN_MEM_WRITE; + activeMask |= IMAGE_SCN_MEM_WRITE; + break; + case '!': + // Although it is undocumented, '!' may be repeated, effectively flipping + // between enabling or disabling flags. + enableMaskActive = !enableMaskActive; + std::swap(activeMask, inactiveMask); break; default: fatal("/section: invalid argument: " + s); } + + // If we enabled a disabled attribute (or vice-versa), override the previous one. + inactiveMask &= ~activeMask; + } + + // We started w/ enableMask active, so make sure we apply changes correctly. + if (!enableMaskActive) + std::swap(activeMask, inactiveMask); + attrs.enableMask = activeMask; + attrs.disableMask = inactiveMask; + + // Collapse enable/disable access masks into access field. + if (attrs.enableMask & accessMask) { + attrs.accessBits = attrs.enableMask & accessMask; + attrs.enableMask &= ~accessMask; + } + if (attrs.disableMask & accessMask) { + attrs.accessBits &= ~attrs.disableMask; + attrs.disableMask &= ~accessMask; } - return ret; } // Parses /section option argument. @@ -204,7 +234,7 @@ std::tie(name, attrs) = s.split(','); if (name.empty() || attrs.empty()) fatal("/section: invalid argument: " + s); - config->section[name] = parseSectionAttributes(attrs); + parseSectionAttributes(attrs, config->section[name]); } // Parses /aligncomm option argument. Index: lld/COFF/Writer.h =================================================================== --- lld/COFF/Writer.h +++ lld/COFF/Writer.h @@ -44,6 +44,7 @@ void addChunk(Chunk *c); void insertChunkAtStart(Chunk *c); void merge(OutputSection *other); + uint32_t getPermissions(); void setPermissions(uint32_t c); uint64_t getRVA() { return header.VirtualAddress; } uint64_t getFileOff() { return header.PointerToRawData; } Index: lld/COFF/Writer.cpp =================================================================== --- lld/COFF/Writer.cpp +++ lld/COFF/Writer.cpp @@ -299,6 +299,10 @@ chunks.insert(chunks.begin(), c); } +uint32_t OutputSection::getPermissions() { + return header.Characteristics & ~permMask; +} + void OutputSection::setPermissions(uint32_t c) { header.Characteristics &= ~permMask; header.Characteristics |= c; @@ -1735,10 +1739,23 @@ void Writer::setSectionPermissions() { for (auto &p : config->section) { StringRef name = p.first; - uint32_t perm = p.second; + Configuration::SectionAttributes & attrs = p.second; for (OutputSection *sec : outputSections) - if (sec->name == name) + if (sec->name == name) { + uint32_t perm = sec->getPermissions(); + + // MSVC link.exe gives priority to enabling attributes. + perm &= ~attrs.disableMask; + perm |= attrs.enableMask; + + // Access bits are set as-is, if present. + if (attrs.accessBits) { + perm &= ~accessMask; + perm |= attrs.accessBits; + } + sec->setPermissions(perm); + } } } Index: lld/test/COFF/section.test =================================================================== --- lld/test/COFF/section.test +++ lld/test/COFF/section.test @@ -15,6 +15,26 @@ # RUN: /section:.foo,s %t.obj # RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=S %s +# RUN: lld-link /out:%t.exe /entry:main /subsystem:console /force \ +# RUN: /section:.foo,!k %t.obj +# RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=K %s + +# RUN: lld-link /out:%t.exe /entry:main /subsystem:console /force \ +# RUN: /section:.foo,!p %t.obj +# RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=P %s + +# RUN: lld-link /out:%t.exe /entry:main /subsystem:console /force \ +# RUN: /section:.foo,rw %t.obj /section:.foo,r +# RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=R %s + +# RUN: lld-link /out:%t.exe /entry:main /subsystem:console /force \ +# RUN: /section:.foo,rw %t.obj /section:.foo,!w +# RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=R %s + +# RUN: lld-link /out:%t.exe /entry:main /subsystem:console /force \ +# RUN: /section:.foo,re %t.obj /section:.foo,!e!w +# RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=W %s + # R: Characteristics [ # R-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA # R-NEXT: IMAGE_SCN_MEM_READ @@ -35,6 +55,16 @@ # S-NEXT: IMAGE_SCN_MEM_SHARED # S-NEXT: ] +# K: Characteristics [ +# K-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA +# K-NEXT: IMAGE_SCN_MEM_NOT_CACHED +# K-NEXT: ] + +# P: Characteristics [ +# P-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA +# P-NEXT: IMAGE_SCN_MEM_NOT_PAGED +# P-NEXT: ] + --- !COFF header: Machine: IMAGE_FILE_MACHINE_AMD64