1 //===- COFFObjcopy.cpp ----------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "COFFObjcopy.h"
11 #include "CopyConfig.h"
15 #include "llvm-objcopy.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"
28 using namespace object
;
31 static bool isDebugSection(const Section
&Sec
) {
32 return Sec
.Name
.startswith(".debug");
35 static uint64_t getNextRVA(const Object
&Obj
) {
36 if (Obj
.getSections().empty())
38 const Section
&Last
= Obj
.getSections().back();
39 return alignTo(Last
.Header
.VirtualAddress
+ Last
.Header
.VirtualSize
,
40 Obj
.IsPE
? Obj
.PeHeader
.SectionAlignment
: 1);
43 static std::vector
<uint8_t> createGnuDebugLinkSectionContents(StringRef File
) {
44 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> LinkTargetOrErr
=
45 MemoryBuffer::getFile(File
);
47 error("'" + File
+ "': " + LinkTargetOrErr
.getError().message());
48 auto LinkTarget
= std::move(*LinkTargetOrErr
);
49 uint32_t CRC32
= llvm::crc32(arrayRefFromStringRef(LinkTarget
->getBuffer()));
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
);
59 // Adds named section with given contents to the object.
60 static 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
|
66 Sec
.setOwnedContents(Contents
);
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
;
84 static 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
);
92 static 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
97 if (!Config
.OnlySection
.empty() && !Config
.OnlySection
.matches(Sec
.Name
))
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)
107 if (Config
.ToRemove
.matches(Sec
.Name
))
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);
123 // StripAll removes all symbols and thus also removes all relocations.
124 if (Config
.StripAll
|| Config
.StripAllGNU
)
125 for (Section
&Sec
: Obj
.getMutableSections())
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())
134 // Actually do removals of symbols.
135 Obj
.removeSymbols([&](const Symbol
&Sym
) {
136 // For StripAll, all relocations have been stripped and we remove all
138 if (Config
.StripAll
|| Config
.StripAllGNU
)
141 if (Config
.SymbolsToRemove
.matches(Sym
.Name
)) {
142 // Explicitly removing a referenced symbol is an error.
144 reportError(Config
.OutputFilename
,
145 createStringError(llvm::errc::invalid_argument
,
146 "not stripping symbol '%s' because it is "
147 "named in a relocation",
148 Sym
.Name
.str().c_str()));
152 if (!Sym
.Referenced
) {
153 // With --strip-unneeded, GNU objcopy removes all unreferenced local
154 // symbols, and any unreferenced undefined external.
155 // With --strip-unneeded-symbol we strip only specific unreferenced
156 // local symbol instead of removing all of such.
157 if (Sym
.Sym
.StorageClass
== IMAGE_SYM_CLASS_STATIC
||
158 Sym
.Sym
.SectionNumber
== 0)
159 if (Config
.StripUnneeded
||
160 Config
.UnneededSymbolsToRemove
.matches(Sym
.Name
))
163 // GNU objcopy keeps referenced local symbols and external symbols
164 // if --discard-all is set, similar to what --strip-unneeded does,
165 // but undefined local symbols are kept when --discard-all is set.
166 if (Config
.DiscardMode
== DiscardType::All
&&
167 Sym
.Sym
.StorageClass
== IMAGE_SYM_CLASS_STATIC
&&
168 Sym
.Sym
.SectionNumber
!= 0)
175 for (const auto &Flag
: Config
.AddSection
) {
176 StringRef SecName
, FileName
;
177 std::tie(SecName
, FileName
) = Flag
.split("=");
179 auto BufOrErr
= MemoryBuffer::getFile(FileName
);
181 return createFileError(FileName
, errorCodeToError(BufOrErr
.getError()));
182 auto Buf
= std::move(*BufOrErr
);
186 makeArrayRef(reinterpret_cast<const uint8_t *>(Buf
->getBufferStart()),
187 Buf
->getBufferSize()),
188 IMAGE_SCN_CNT_INITIALIZED_DATA
| IMAGE_SCN_ALIGN_1BYTES
);
191 if (!Config
.AddGnuDebugLink
.empty())
192 addGnuDebugLink(Obj
, Config
.AddGnuDebugLink
);
194 if (Config
.AllowBrokenLinks
|| !Config
.BuildIdLinkDir
.empty() ||
195 Config
.BuildIdLinkInput
|| Config
.BuildIdLinkOutput
||
196 !Config
.SplitDWO
.empty() || !Config
.SymbolsPrefix
.empty() ||
197 !Config
.AllocSectionsPrefix
.empty() || !Config
.DumpSection
.empty() ||
198 !Config
.KeepSection
.empty() || Config
.NewSymbolVisibility
||
199 !Config
.SymbolsToGlobalize
.empty() || !Config
.SymbolsToKeep
.empty() ||
200 !Config
.SymbolsToLocalize
.empty() || !Config
.SymbolsToWeaken
.empty() ||
201 !Config
.SymbolsToKeepGlobal
.empty() || !Config
.SectionsToRename
.empty() ||
202 !Config
.SetSectionAlignment
.empty() || !Config
.SetSectionFlags
.empty() ||
203 !Config
.SymbolsToRename
.empty() || Config
.ExtractDWO
||
204 Config
.KeepFileSymbols
|| Config
.LocalizeHidden
|| Config
.PreserveDates
||
205 Config
.StripDWO
|| Config
.StripNonAlloc
|| Config
.StripSections
||
206 Config
.Weaken
|| Config
.DecompressDebugSections
||
207 Config
.DiscardMode
== DiscardType::Locals
||
208 !Config
.SymbolsToAdd
.empty() || Config
.EntryExpr
) {
209 return createStringError(llvm::errc::invalid_argument
,
210 "option not supported by llvm-objcopy for COFF");
213 return Error::success();
216 Error
executeObjcopyOnBinary(const CopyConfig
&Config
, COFFObjectFile
&In
,
218 COFFReader
Reader(In
);
219 Expected
<std::unique_ptr
<Object
>> ObjOrErr
= Reader
.create();
221 return createFileError(Config
.InputFilename
, ObjOrErr
.takeError());
222 Object
*Obj
= ObjOrErr
->get();
223 assert(Obj
&& "Unable to deserialize COFF object");
224 if (Error E
= handleArgs(Config
, *Obj
))
225 return createFileError(Config
.InputFilename
, std::move(E
));
226 COFFWriter
Writer(*Obj
, Out
);
227 if (Error E
= Writer
.write())
228 return createFileError(Config
.OutputFilename
, std::move(E
));
229 return Error::success();
232 } // end namespace coff
233 } // end namespace objcopy
234 } // end namespace llvm