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-exception 6// 7//===----------------------------------------------------------------------===// 8 9#include "COFFObjcopy.h" 10#include "Buffer.h" 11#include "CopyConfig.h" 12#include "Object.h" 13#include "Reader.h" 14#include "Writer.h" 15#include "llvm-objcopy.h" 16 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.startswith(".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 std::vector<uint8_t> createGnuDebugLinkSectionContents(StringRef File) { 44 ErrorOr<std::unique_ptr<MemoryBuffer>> LinkTargetOrErr = 45 MemoryBuffer::getFile(File); 46 if (!LinkTargetOrErr) 47 error("'" + File + "': " + LinkTargetOrErr.getError().message()); 48 auto LinkTarget = std::move(*LinkTargetOrErr); 49 uint32_t CRC32 = llvm::crc32(arrayRefFromStringRef(LinkTarget->getBuffer())); 50 51 StringRef FileName = sys::path::filename(File); 52 size_t CRCPos = alignTo(FileName.size() + 1, 4); 53 std::vector<uint8_t> Data(CRCPos + 4); 54 memcpy(Data.data(), FileName.data(), FileName.size()); 55 support::endian::write32le(Data.data() + CRCPos, CRC32); 56 return Data; 57} 58 59// Adds named section with given contents to the object. 60static void addSection(Object &Obj, StringRef Name, ArrayRef<uint8_t> Contents, 61 uint32_t Characteristics) { 62 bool NeedVA = Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | 63 IMAGE_SCN_MEM_WRITE); 64 65 Section Sec; 66 Sec.setOwnedContents(Contents); 67 Sec.Name = Name; 68 Sec.Header.VirtualSize = NeedVA ? Sec.getContents().size() : 0u; 69 Sec.Header.VirtualAddress = NeedVA ? getNextRVA(Obj) : 0u; 70 Sec.Header.SizeOfRawData = 71 NeedVA ? alignTo(Sec.Header.VirtualSize, 72 Obj.IsPE ? Obj.PeHeader.FileAlignment : 1) 73 : Sec.getContents().size(); 74 // Sec.Header.PointerToRawData is filled in by the writer. 75 Sec.Header.PointerToRelocations = 0; 76 Sec.Header.PointerToLinenumbers = 0; 77 // Sec.Header.NumberOfRelocations is filled in by the writer. 78 Sec.Header.NumberOfLinenumbers = 0; 79 Sec.Header.Characteristics = Characteristics; 80 81 Obj.addSections(Sec); 82} 83 84static void addGnuDebugLink(Object &Obj, StringRef DebugLinkFile) { 85 std::vector<uint8_t> Contents = 86 createGnuDebugLinkSectionContents(DebugLinkFile); 87 addSection(Obj, ".gnu_debuglink", Contents, 88 IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | 89 IMAGE_SCN_MEM_DISCARDABLE); 90} 91 92static Error handleArgs(const CopyConfig &Config, Object &Obj) { 93 // Perform the actual section removals. 94 Obj.removeSections([&Config](const Section &Sec) { 95 // Contrary to --only-keep-debug, --only-section fully removes sections that 96 // aren't mentioned. 97 if (!Config.OnlySection.empty() && !Config.OnlySection.matches(Sec.Name)) 98 return true; 99 100 if (Config.StripDebug || Config.StripAll || Config.StripAllGNU || 101 Config.DiscardMode == DiscardType::All || Config.StripUnneeded) { 102 if (isDebugSection(Sec) && 103 (Sec.Header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0) 104 return true; 105 } 106 107 if (Config.ToRemove.matches(Sec.Name)) 108 return true; 109 110 return false; 111 }); 112 113 if (Config.OnlyKeepDebug) { 114 // For --only-keep-debug, we keep all other sections, but remove their 115 // content. The VirtualSize field in the section header is kept intact. 116 Obj.truncateSections([](const Section &Sec) { 117 return !isDebugSection(Sec) && Sec.Name != ".buildid" && 118 ((Sec.Header.Characteristics & 119 (IMAGE_SCN_CNT_CODE | IMAGE_SCN_CNT_INITIALIZED_DATA)) != 0); 120 }); 121 } 122 123 // StripAll removes all symbols and thus also removes all relocations. 124 if (Config.StripAll || Config.StripAllGNU) 125 for (Section &Sec : Obj.getMutableSections()) 126 Sec.Relocs.clear(); 127 128 // If we need to do per-symbol removals, initialize the Referenced field. 129 if (Config.StripUnneeded || Config.DiscardMode == DiscardType::All || 130 !Config.SymbolsToRemove.empty()) 131 if (Error E = Obj.markSymbols()) 132 return E; 133 134 for (Symbol &Sym : Obj.getMutableSymbols()) { 135 auto I = Config.SymbolsToRename.find(Sym.Name); 136 if (I != Config.SymbolsToRename.end()) 137 Sym.Name = I->getValue(); 138 } 139 140 // Actually do removals of symbols. 141 Obj.removeSymbols([&](const Symbol &Sym) { 142 // For StripAll, all relocations have been stripped and we remove all 143 // symbols. 144 if (Config.StripAll || Config.StripAllGNU) 145 return true; 146 147 if (Config.SymbolsToRemove.matches(Sym.Name)) { 148 // Explicitly removing a referenced symbol is an error. 149 if (Sym.Referenced) 150 reportError(Config.OutputFilename, 151 createStringError(llvm::errc::invalid_argument, 152 "not stripping symbol '%s' because it is " 153 "named in a relocation", 154 Sym.Name.str().c_str())); 155 return true; 156 } 157 158 if (!Sym.Referenced) { 159 // With --strip-unneeded, GNU objcopy removes all unreferenced local 160 // symbols, and any unreferenced undefined external. 161 // With --strip-unneeded-symbol we strip only specific unreferenced 162 // local symbol instead of removing all of such. 163 if (Sym.Sym.StorageClass == IMAGE_SYM_CLASS_STATIC || 164 Sym.Sym.SectionNumber == 0) 165 if (Config.StripUnneeded || 166 Config.UnneededSymbolsToRemove.matches(Sym.Name)) 167 return true; 168 169 // GNU objcopy keeps referenced local symbols and external symbols 170 // if --discard-all is set, similar to what --strip-unneeded does, 171 // but undefined local symbols are kept when --discard-all is set. 172 if (Config.DiscardMode == DiscardType::All && 173 Sym.Sym.StorageClass == IMAGE_SYM_CLASS_STATIC && 174 Sym.Sym.SectionNumber != 0) 175 return true; 176 } 177 178 return false; 179 }); 180 181 for (const auto &Flag : Config.AddSection) { 182 StringRef SecName, FileName; 183 std::tie(SecName, FileName) = Flag.split("="); 184 185 auto BufOrErr = MemoryBuffer::getFile(FileName); 186 if (!BufOrErr) 187 return createFileError(FileName, errorCodeToError(BufOrErr.getError())); 188 auto Buf = std::move(*BufOrErr); 189 190 addSection( 191 Obj, SecName, 192 makeArrayRef(reinterpret_cast<const uint8_t *>(Buf->getBufferStart()), 193 Buf->getBufferSize()), 194 IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_ALIGN_1BYTES); 195 } 196 197 if (!Config.AddGnuDebugLink.empty()) 198 addGnuDebugLink(Obj, Config.AddGnuDebugLink); 199 200 if (Config.AllowBrokenLinks || !Config.BuildIdLinkDir.empty() || 201 Config.BuildIdLinkInput || Config.BuildIdLinkOutput || 202 !Config.SplitDWO.empty() || !Config.SymbolsPrefix.empty() || 203 !Config.AllocSectionsPrefix.empty() || !Config.DumpSection.empty() || 204 !Config.KeepSection.empty() || Config.NewSymbolVisibility || 205 !Config.SymbolsToGlobalize.empty() || !Config.SymbolsToKeep.empty() || 206 !Config.SymbolsToLocalize.empty() || !Config.SymbolsToWeaken.empty() || 207 !Config.SymbolsToKeepGlobal.empty() || !Config.SectionsToRename.empty() || 208 !Config.SetSectionAlignment.empty() || !Config.SetSectionFlags.empty() || 209 Config.ExtractDWO || Config.KeepFileSymbols || Config.LocalizeHidden || 210 Config.PreserveDates || Config.StripDWO || Config.StripNonAlloc || 211 Config.StripSections || Config.Weaken || Config.DecompressDebugSections || 212 Config.DiscardMode == DiscardType::Locals || 213 !Config.SymbolsToAdd.empty() || Config.EntryExpr) { 214 return createStringError(llvm::errc::invalid_argument, 215 "option not supported by llvm-objcopy for COFF"); 216 } 217 218 return Error::success(); 219} 220 221Error executeObjcopyOnBinary(const CopyConfig &Config, COFFObjectFile &In, 222 Buffer &Out) { 223 COFFReader Reader(In); 224 Expected<std::unique_ptr<Object>> ObjOrErr = Reader.create(); 225 if (!ObjOrErr) 226 return createFileError(Config.InputFilename, ObjOrErr.takeError()); 227 Object *Obj = ObjOrErr->get(); 228 assert(Obj && "Unable to deserialize COFF object"); 229 if (Error E = handleArgs(Config, *Obj)) 230 return createFileError(Config.InputFilename, std::move(E)); 231 COFFWriter Writer(*Obj, Out); 232 if (Error E = Writer.write()) 233 return createFileError(Config.OutputFilename, std::move(E)); 234 return Error::success(); 235} 236 237} // end namespace coff 238} // end namespace objcopy 239} // end namespace llvm 240