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,13 @@ // Global configuration. struct Configuration { - enum ManifestKind { SideBySide, Embed, No }; + enum ManifestKind { SideBySide, Embed, No }; + + // Used for applying section attribute mask. + // Enable/Disable bits are set for every attribute to enable or disable, respectively. + // Access bits (readable, writable, executable) are applied as-is. + struct AttributeMask { uint32_t enable = 0, disable = 0, access = 0; }; + bool is64() { return machine == AMD64 || machine == ARM64; } llvm::COFF::MachineTypes machine = IMAGE_FILE_MACHINE_UNKNOWN; @@ -153,8 +159,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,58 @@ } } -static uint32_t parseSectionAttributes(StringRef s) { - uint32_t ret = 0; +// Parse the section attribute mask for [!]{DEKPRSW}. +// MSVC link.exe behavior is reproduced for compatibility. +static Configuration::AttributeMask parseSectionAttributeMask(StringRef s, Configuration::AttributeMask &mask) { + uint32_t *current = &mask.enable, *other = &mask.disable; for (char c : s.lower()) { switch (c) { case 'd': - ret |= IMAGE_SCN_MEM_DISCARDABLE; + *current |= IMAGE_SCN_MEM_DISCARDABLE; break; case 'e': - ret |= IMAGE_SCN_MEM_EXECUTE; + *current |= IMAGE_SCN_MEM_EXECUTE; break; + // Unlike other flags, K & P are applied when used w/ !. case 'k': - ret |= IMAGE_SCN_MEM_NOT_CACHED; + *other |= IMAGE_SCN_MEM_NOT_CACHED; break; case 'p': - ret |= IMAGE_SCN_MEM_NOT_PAGED; + *other |= IMAGE_SCN_MEM_NOT_PAGED; break; case 'r': - ret |= IMAGE_SCN_MEM_READ; + *current |= IMAGE_SCN_MEM_READ; break; case 's': - ret |= IMAGE_SCN_MEM_SHARED; + *current |= IMAGE_SCN_MEM_SHARED; break; case 'w': - ret |= IMAGE_SCN_MEM_WRITE; + *current |= IMAGE_SCN_MEM_WRITE; + break; + case '!': + // Although it is undocumented, '!' may be repeated, effectively flipping + // between enabling or disabling flags. + std::swap(current, other); break; default: fatal("/section: invalid argument: " + s); } + + // If we enabled a disabled attribute (or vice-versa), override the previous one. + *other &= ~*current; + } + + // Collapse enable/disable access masks into access field. + if (mask.enable & accessMask) { + mask.access = mask.enable & accessMask; + mask.enable &= ~accessMask; + } + if (mask.disable & accessMask) { + mask.access &= ~mask.disable; + mask.disable &= ~accessMask; } - return ret; + + return mask; } // Parses /section option argument. @@ -204,7 +226,7 @@ std::tie(name, attrs) = s.split(','); if (name.empty() || attrs.empty()) fatal("/section: invalid argument: " + s); - config->section[name] = parseSectionAttributes(attrs); + parseSectionAttributeMask(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::AttributeMask mask = 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 &= ~mask.disable; + perm |= mask.enable; + + // Access bits are set as-is, if present. + if (mask.access) { + perm &= ~accessMask; + perm |= mask.access; + } + 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