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/Errc.h"
20 #include "llvm/Support/JamCRC.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 uint32_t getCRC32(StringRef Data
) {
45 CRC
.update(ArrayRef
<char>(Data
.data(), Data
.size()));
46 // The CRC32 value needs to be complemented because the JamCRC dosn't
47 // finalize the CRC32 value. It also dosn't negate the initial CRC32 value
48 // but it starts by default at 0xFFFFFFFF which is the complement of zero.
52 static std::vector
<uint8_t> createGnuDebugLinkSectionContents(StringRef File
) {
53 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> LinkTargetOrErr
=
54 MemoryBuffer::getFile(File
);
56 error("'" + File
+ "': " + LinkTargetOrErr
.getError().message());
57 auto LinkTarget
= std::move(*LinkTargetOrErr
);
58 uint32_t CRC32
= getCRC32(LinkTarget
->getBuffer());
60 StringRef FileName
= sys::path::filename(File
);
61 size_t CRCPos
= alignTo(FileName
.size() + 1, 4);
62 std::vector
<uint8_t> Data(CRCPos
+ 4);
63 memcpy(Data
.data(), FileName
.data(), FileName
.size());
64 support::endian::write32le(Data
.data() + CRCPos
, CRC32
);
68 static void addGnuDebugLink(Object
&Obj
, StringRef DebugLinkFile
) {
69 uint32_t StartRVA
= getNextRVA(Obj
);
71 std::vector
<Section
> Sections
;
73 Sec
.setOwnedContents(createGnuDebugLinkSectionContents(DebugLinkFile
));
74 Sec
.Name
= ".gnu_debuglink";
75 Sec
.Header
.VirtualSize
= Sec
.getContents().size();
76 Sec
.Header
.VirtualAddress
= StartRVA
;
77 Sec
.Header
.SizeOfRawData
= alignTo(Sec
.Header
.VirtualSize
,
78 Obj
.IsPE
? Obj
.PeHeader
.FileAlignment
: 1);
79 // Sec.Header.PointerToRawData is filled in by the writer.
80 Sec
.Header
.PointerToRelocations
= 0;
81 Sec
.Header
.PointerToLinenumbers
= 0;
82 // Sec.Header.NumberOfRelocations is filled in by the writer.
83 Sec
.Header
.NumberOfLinenumbers
= 0;
84 Sec
.Header
.Characteristics
= IMAGE_SCN_CNT_INITIALIZED_DATA
|
85 IMAGE_SCN_MEM_READ
| IMAGE_SCN_MEM_DISCARDABLE
;
86 Sections
.push_back(Sec
);
87 Obj
.addSections(Sections
);
90 static Error
handleArgs(const CopyConfig
&Config
, Object
&Obj
) {
91 // Perform the actual section removals.
92 Obj
.removeSections([&Config
](const Section
&Sec
) {
93 // Contrary to --only-keep-debug, --only-section fully removes sections that
95 if (!Config
.OnlySection
.empty() &&
96 !is_contained(Config
.OnlySection
, Sec
.Name
))
99 if (Config
.StripDebug
|| Config
.StripAll
|| Config
.StripAllGNU
||
100 Config
.DiscardMode
== DiscardType::All
|| Config
.StripUnneeded
) {
101 if (isDebugSection(Sec
) &&
102 (Sec
.Header
.Characteristics
& IMAGE_SCN_MEM_DISCARDABLE
) != 0)
106 if (is_contained(Config
.ToRemove
, Sec
.Name
))
112 if (Config
.OnlyKeepDebug
) {
113 // For --only-keep-debug, we keep all other sections, but remove their
114 // content. The VirtualSize field in the section header is kept intact.
115 Obj
.truncateSections([](const Section
&Sec
) {
116 return !isDebugSection(Sec
) && Sec
.Name
!= ".buildid" &&
117 ((Sec
.Header
.Characteristics
&
118 (IMAGE_SCN_CNT_CODE
| IMAGE_SCN_CNT_INITIALIZED_DATA
)) != 0);
122 // StripAll removes all symbols and thus also removes all relocations.
123 if (Config
.StripAll
|| Config
.StripAllGNU
)
124 for (Section
&Sec
: Obj
.getMutableSections())
127 // If we need to do per-symbol removals, initialize the Referenced field.
128 if (Config
.StripUnneeded
|| Config
.DiscardMode
== DiscardType::All
||
129 !Config
.SymbolsToRemove
.empty())
130 if (Error E
= Obj
.markSymbols())
133 // Actually do removals of symbols.
134 Obj
.removeSymbols([&](const Symbol
&Sym
) {
135 // For StripAll, all relocations have been stripped and we remove all
137 if (Config
.StripAll
|| Config
.StripAllGNU
)
140 if (is_contained(Config
.SymbolsToRemove
, Sym
.Name
)) {
141 // Explicitly removing a referenced symbol is an error.
143 reportError(Config
.OutputFilename
,
144 createStringError(llvm::errc::invalid_argument
,
145 "not stripping symbol '%s' because it is "
146 "named in a relocation",
147 Sym
.Name
.str().c_str()));
151 if (!Sym
.Referenced
) {
152 // With --strip-unneeded, GNU objcopy removes all unreferenced local
153 // symbols, and any unreferenced undefined external.
154 // With --strip-unneeded-symbol we strip only specific unreferenced
155 // local symbol instead of removing all of such.
156 if (Sym
.Sym
.StorageClass
== IMAGE_SYM_CLASS_STATIC
||
157 Sym
.Sym
.SectionNumber
== 0)
158 if (Config
.StripUnneeded
||
159 is_contained(Config
.UnneededSymbolsToRemove
, Sym
.Name
))
162 // GNU objcopy keeps referenced local symbols and external symbols
163 // if --discard-all is set, similar to what --strip-unneeded does,
164 // but undefined local symbols are kept when --discard-all is set.
165 if (Config
.DiscardMode
== DiscardType::All
&&
166 Sym
.Sym
.StorageClass
== IMAGE_SYM_CLASS_STATIC
&&
167 Sym
.Sym
.SectionNumber
!= 0)
174 if (!Config
.AddGnuDebugLink
.empty())
175 addGnuDebugLink(Obj
, Config
.AddGnuDebugLink
);
177 if (Config
.AllowBrokenLinks
|| !Config
.BuildIdLinkDir
.empty() ||
178 Config
.BuildIdLinkInput
|| Config
.BuildIdLinkOutput
||
179 !Config
.SplitDWO
.empty() || !Config
.SymbolsPrefix
.empty() ||
180 !Config
.AllocSectionsPrefix
.empty() || !Config
.AddSection
.empty() ||
181 !Config
.DumpSection
.empty() || !Config
.KeepSection
.empty() ||
182 !Config
.SymbolsToGlobalize
.empty() || !Config
.SymbolsToKeep
.empty() ||
183 !Config
.SymbolsToLocalize
.empty() || !Config
.SymbolsToWeaken
.empty() ||
184 !Config
.SymbolsToKeepGlobal
.empty() || !Config
.SectionsToRename
.empty() ||
185 !Config
.SetSectionFlags
.empty() || !Config
.SymbolsToRename
.empty() ||
186 Config
.ExtractDWO
|| Config
.KeepFileSymbols
|| Config
.LocalizeHidden
||
187 Config
.PreserveDates
|| Config
.StripDWO
|| Config
.StripNonAlloc
||
188 Config
.StripSections
|| Config
.Weaken
|| Config
.DecompressDebugSections
||
189 Config
.DiscardMode
== DiscardType::Locals
||
190 !Config
.SymbolsToAdd
.empty() || Config
.EntryExpr
) {
191 return createStringError(llvm::errc::invalid_argument
,
192 "option not supported by llvm-objcopy for COFF");
195 return Error::success();
198 Error
executeObjcopyOnBinary(const CopyConfig
&Config
, COFFObjectFile
&In
,
200 COFFReader
Reader(In
);
201 Expected
<std::unique_ptr
<Object
>> ObjOrErr
= Reader
.create();
203 return createFileError(Config
.InputFilename
, ObjOrErr
.takeError());
204 Object
*Obj
= ObjOrErr
->get();
205 assert(Obj
&& "Unable to deserialize COFF object");
206 if (Error E
= handleArgs(Config
, *Obj
))
207 return createFileError(Config
.InputFilename
, std::move(E
));
208 COFFWriter
Writer(*Obj
, Out
);
209 if (Error E
= Writer
.write())
210 return createFileError(Config
.OutputFilename
, std::move(E
));
211 return Error::success();
214 } // end namespace coff
215 } // end namespace objcopy
216 } // end namespace llvm