350 lines · cpp
1//===- COFFObjcopy.cpp ----------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/ObjCopy/COFF/COFFObjcopy.h"10#include "COFFObject.h"11#include "COFFReader.h"12#include "COFFWriter.h"13#include "llvm/ObjCopy/COFF/COFFConfig.h"14#include "llvm/ObjCopy/CommonConfig.h"15 16#include "llvm/ADT/StringExtras.h"17#include "llvm/Object/Binary.h"18#include "llvm/Object/COFF.h"19#include "llvm/Support/CRC.h"20#include "llvm/Support/Errc.h"21#include "llvm/Support/Path.h"22#include <cassert>23 24namespace llvm {25namespace objcopy {26namespace coff {27 28using namespace object;29using namespace COFF;30 31static bool isDebugSection(const Section &Sec) {32 return Sec.Name.starts_with(".debug");33}34 35static uint64_t getNextRVA(const Object &Obj) {36 if (Obj.getSections().empty())37 return 0;38 const Section &Last = Obj.getSections().back();39 return alignTo(Last.Header.VirtualAddress + Last.Header.VirtualSize,40 Obj.IsPE ? Obj.PeHeader.SectionAlignment : 1);41}42 43static Expected<std::vector<uint8_t>>44createGnuDebugLinkSectionContents(StringRef File) {45 ErrorOr<std::unique_ptr<MemoryBuffer>> LinkTargetOrErr =46 MemoryBuffer::getFile(File);47 if (!LinkTargetOrErr)48 return createFileError(File, LinkTargetOrErr.getError());49 auto LinkTarget = std::move(*LinkTargetOrErr);50 uint32_t CRC32 = llvm::crc32(arrayRefFromStringRef(LinkTarget->getBuffer()));51 52 StringRef FileName = sys::path::filename(File);53 size_t CRCPos = alignTo(FileName.size() + 1, 4);54 std::vector<uint8_t> Data(CRCPos + 4);55 memcpy(Data.data(), FileName.data(), FileName.size());56 support::endian::write32le(Data.data() + CRCPos, CRC32);57 return Data;58}59 60// Adds named section with given contents to the object.61static void addSection(Object &Obj, StringRef Name, ArrayRef<uint8_t> Contents,62 uint32_t Characteristics) {63 bool NeedVA = Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ |64 IMAGE_SCN_MEM_WRITE);65 66 Section Sec;67 Sec.setOwnedContents(Contents);68 Sec.Name = Name;69 Sec.Header.VirtualSize = NeedVA ? Sec.getContents().size() : 0u;70 Sec.Header.VirtualAddress = NeedVA ? getNextRVA(Obj) : 0u;71 Sec.Header.SizeOfRawData =72 NeedVA ? alignTo(Sec.Header.VirtualSize,73 Obj.IsPE ? Obj.PeHeader.FileAlignment : 1)74 : Sec.getContents().size();75 // Sec.Header.PointerToRawData is filled in by the writer.76 Sec.Header.PointerToRelocations = 0;77 Sec.Header.PointerToLinenumbers = 0;78 // Sec.Header.NumberOfRelocations is filled in by the writer.79 Sec.Header.NumberOfLinenumbers = 0;80 Sec.Header.Characteristics = Characteristics;81 82 Obj.addSections(Sec);83}84 85static Error addGnuDebugLink(Object &Obj, StringRef DebugLinkFile) {86 Expected<std::vector<uint8_t>> Contents =87 createGnuDebugLinkSectionContents(DebugLinkFile);88 if (!Contents)89 return Contents.takeError();90 91 addSection(Obj, ".gnu_debuglink", *Contents,92 IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |93 IMAGE_SCN_MEM_DISCARDABLE);94 95 return Error::success();96}97 98static uint32_t flagsToCharacteristics(SectionFlag AllFlags, uint32_t OldChar) {99 // Need to preserve alignment flags.100 const uint32_t PreserveMask =101 IMAGE_SCN_ALIGN_1BYTES | IMAGE_SCN_ALIGN_2BYTES | IMAGE_SCN_ALIGN_4BYTES |102 IMAGE_SCN_ALIGN_8BYTES | IMAGE_SCN_ALIGN_16BYTES |103 IMAGE_SCN_ALIGN_32BYTES | IMAGE_SCN_ALIGN_64BYTES |104 IMAGE_SCN_ALIGN_128BYTES | IMAGE_SCN_ALIGN_256BYTES |105 IMAGE_SCN_ALIGN_512BYTES | IMAGE_SCN_ALIGN_1024BYTES |106 IMAGE_SCN_ALIGN_2048BYTES | IMAGE_SCN_ALIGN_4096BYTES |107 IMAGE_SCN_ALIGN_8192BYTES;108 109 // Setup new section characteristics based on the flags provided in command110 // line.111 uint32_t NewCharacteristics = (OldChar & PreserveMask) | IMAGE_SCN_MEM_READ;112 113 if ((AllFlags & SectionFlag::SecAlloc) && !(AllFlags & SectionFlag::SecLoad))114 NewCharacteristics |= IMAGE_SCN_CNT_UNINITIALIZED_DATA;115 if (AllFlags & SectionFlag::SecNoload)116 NewCharacteristics |= IMAGE_SCN_LNK_REMOVE;117 if (!(AllFlags & SectionFlag::SecReadonly))118 NewCharacteristics |= IMAGE_SCN_MEM_WRITE;119 if (AllFlags & SectionFlag::SecDebug)120 NewCharacteristics |=121 IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_DISCARDABLE;122 if (AllFlags & SectionFlag::SecCode)123 NewCharacteristics |= IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE;124 if (AllFlags & SectionFlag::SecData)125 NewCharacteristics |= IMAGE_SCN_CNT_INITIALIZED_DATA;126 if (AllFlags & SectionFlag::SecShare)127 NewCharacteristics |= IMAGE_SCN_MEM_SHARED;128 if (AllFlags & SectionFlag::SecExclude)129 NewCharacteristics |= IMAGE_SCN_LNK_REMOVE;130 131 return NewCharacteristics;132}133 134static Error dumpSection(Object &O, StringRef SectionName, StringRef FileName) {135 for (const coff::Section &Section : O.getSections()) {136 if (Section.Name != SectionName)137 continue;138 139 ArrayRef<uint8_t> Contents = Section.getContents();140 141 std::unique_ptr<FileOutputBuffer> Buffer;142 if (auto B = FileOutputBuffer::create(FileName, Contents.size()))143 Buffer = std::move(*B);144 else145 return B.takeError();146 147 llvm::copy(Contents, Buffer->getBufferStart());148 if (Error E = Buffer->commit())149 return E;150 151 return Error::success();152 }153 return createStringError(object_error::parse_failed, "section '%s' not found",154 SectionName.str().c_str());155}156 157static Error handleArgs(const CommonConfig &Config,158 const COFFConfig &COFFConfig, Object &Obj) {159 for (StringRef Op : Config.DumpSection) {160 auto [Section, File] = Op.split('=');161 if (Error E = dumpSection(Obj, Section, File))162 return E;163 }164 165 // Perform the actual section removals.166 Obj.removeSections([&Config](const Section &Sec) {167 // Contrary to --only-keep-debug, --only-section fully removes sections that168 // aren't mentioned.169 if (!Config.OnlySection.empty() && !Config.OnlySection.matches(Sec.Name))170 return true;171 172 if (Config.StripDebug || Config.StripAll || Config.StripAllGNU ||173 Config.DiscardMode == DiscardType::All || Config.StripUnneeded) {174 if (isDebugSection(Sec) &&175 (Sec.Header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0)176 return true;177 }178 179 if (Config.ToRemove.matches(Sec.Name))180 return true;181 182 return false;183 });184 185 if (Config.OnlyKeepDebug) {186 const data_directory *DebugDir =187 Obj.DataDirectories.size() > DEBUG_DIRECTORY188 ? &Obj.DataDirectories[DEBUG_DIRECTORY]189 : nullptr;190 // For --only-keep-debug, we keep all other sections, but remove their191 // content. The VirtualSize field in the section header is kept intact.192 Obj.truncateSections([DebugDir](const Section &Sec) {193 return !isDebugSection(Sec) && Sec.Name != ".buildid" &&194 !(DebugDir && DebugDir->Size > 0 &&195 DebugDir->RelativeVirtualAddress >= Sec.Header.VirtualAddress &&196 DebugDir->RelativeVirtualAddress <197 Sec.Header.VirtualAddress + Sec.Header.SizeOfRawData) &&198 ((Sec.Header.Characteristics &199 (IMAGE_SCN_CNT_CODE | IMAGE_SCN_CNT_INITIALIZED_DATA)) != 0);200 });201 }202 203 // StripAll removes all symbols and thus also removes all relocations.204 if (Config.StripAll || Config.StripAllGNU)205 for (Section &Sec : Obj.getMutableSections())206 Sec.Relocs.clear();207 208 // If we need to do per-symbol removals, initialize the Referenced field.209 if (Config.StripUnneeded || Config.DiscardMode == DiscardType::All ||210 !Config.SymbolsToRemove.empty())211 if (Error E = Obj.markSymbols())212 return E;213 214 for (Symbol &Sym : Obj.getMutableSymbols()) {215 auto I = Config.SymbolsToRename.find(Sym.Name);216 if (I != Config.SymbolsToRename.end())217 Sym.Name = I->getValue();218 }219 220 auto ToRemove = [&](const Symbol &Sym) -> Expected<bool> {221 // For StripAll, all relocations have been stripped and we remove all222 // symbols.223 if (Config.StripAll || Config.StripAllGNU)224 return true;225 226 if (Config.SymbolsToRemove.matches(Sym.Name)) {227 // Explicitly removing a referenced symbol is an error.228 if (Sym.Referenced)229 return createStringError(230 llvm::errc::invalid_argument,231 "'" + Config.OutputFilename + "': not stripping symbol '" +232 Sym.Name.str() + "' because it is named in a relocation");233 return true;234 }235 236 if (!Sym.Referenced) {237 // With --strip-unneeded, GNU objcopy removes all unreferenced local238 // symbols, and any unreferenced undefined external.239 // With --strip-unneeded-symbol we strip only specific unreferenced240 // local symbol instead of removing all of such.241 if (Sym.Sym.StorageClass == IMAGE_SYM_CLASS_STATIC ||242 Sym.Sym.SectionNumber == 0)243 if (Config.StripUnneeded ||244 Config.UnneededSymbolsToRemove.matches(Sym.Name))245 return true;246 247 // GNU objcopy keeps referenced local symbols and external symbols248 // if --discard-all is set, similar to what --strip-unneeded does,249 // but undefined local symbols are kept when --discard-all is set.250 if (Config.DiscardMode == DiscardType::All &&251 Sym.Sym.StorageClass == IMAGE_SYM_CLASS_STATIC &&252 Sym.Sym.SectionNumber != 0)253 return true;254 }255 256 return false;257 };258 259 // Actually do removals of symbols.260 if (Error Err = Obj.removeSymbols(ToRemove))261 return Err;262 263 if (!Config.SetSectionFlags.empty())264 for (Section &Sec : Obj.getMutableSections()) {265 const auto It = Config.SetSectionFlags.find(Sec.Name);266 if (It != Config.SetSectionFlags.end())267 Sec.Header.Characteristics = flagsToCharacteristics(268 It->second.NewFlags, Sec.Header.Characteristics);269 }270 271 for (const NewSectionInfo &NewSection : Config.AddSection) {272 uint32_t Characteristics;273 const auto It = Config.SetSectionFlags.find(NewSection.SectionName);274 if (It != Config.SetSectionFlags.end())275 Characteristics = flagsToCharacteristics(It->second.NewFlags, 0);276 else277 Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_ALIGN_1BYTES;278 279 addSection(Obj, NewSection.SectionName,280 ArrayRef(reinterpret_cast<const uint8_t *>(281 NewSection.SectionData->getBufferStart()),282 NewSection.SectionData->getBufferSize()),283 Characteristics);284 }285 286 for (const NewSectionInfo &NewSection : Config.UpdateSection) {287 auto It = llvm::find_if(Obj.getMutableSections(), [&](auto &Sec) {288 return Sec.Name == NewSection.SectionName;289 });290 if (It == Obj.getMutableSections().end())291 return createStringError(errc::invalid_argument,292 "could not find section with name '%s'",293 NewSection.SectionName.str().c_str());294 size_t ContentSize = It->getContents().size();295 if (!ContentSize)296 return createStringError(297 errc::invalid_argument,298 "section '%s' cannot be updated because it does not have contents",299 NewSection.SectionName.str().c_str());300 if (ContentSize < NewSection.SectionData->getBufferSize())301 return createStringError(302 errc::invalid_argument,303 "new section cannot be larger than previous section");304 It->setOwnedContents({NewSection.SectionData->getBufferStart(),305 NewSection.SectionData->getBufferEnd()});306 }307 308 if (!Config.AddGnuDebugLink.empty())309 if (Error E = addGnuDebugLink(Obj, Config.AddGnuDebugLink))310 return E;311 312 if (COFFConfig.Subsystem || COFFConfig.MajorSubsystemVersion ||313 COFFConfig.MinorSubsystemVersion) {314 if (!Obj.IsPE)315 return createStringError(316 errc::invalid_argument,317 "'" + Config.OutputFilename +318 "': unable to set subsystem on a relocatable object file");319 if (COFFConfig.Subsystem)320 Obj.PeHeader.Subsystem = *COFFConfig.Subsystem;321 if (COFFConfig.MajorSubsystemVersion)322 Obj.PeHeader.MajorSubsystemVersion = *COFFConfig.MajorSubsystemVersion;323 if (COFFConfig.MinorSubsystemVersion)324 Obj.PeHeader.MinorSubsystemVersion = *COFFConfig.MinorSubsystemVersion;325 }326 327 return Error::success();328}329 330Error executeObjcopyOnBinary(const CommonConfig &Config,331 const COFFConfig &COFFConfig, COFFObjectFile &In,332 raw_ostream &Out) {333 COFFReader Reader(In);334 Expected<std::unique_ptr<Object>> ObjOrErr = Reader.create();335 if (!ObjOrErr)336 return createFileError(Config.InputFilename, ObjOrErr.takeError());337 Object *Obj = ObjOrErr->get();338 assert(Obj && "Unable to deserialize COFF object");339 if (Error E = handleArgs(Config, COFFConfig, *Obj))340 return createFileError(Config.InputFilename, std::move(E));341 COFFWriter Writer(*Obj, Out);342 if (Error E = Writer.write())343 return createFileError(Config.OutputFilename, std::move(E));344 return Error::success();345}346 347} // end namespace coff348} // end namespace objcopy349} // end namespace llvm350