LLVM 23.0.0git
MachOBuilder.h
Go to the documentation of this file.
1//===------------ MachOBuilder.h -- Build MachO Objects ---------*- C++ -*-===//
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// Build MachO object files for interaction with the ObjC runtime and debugger.
10//
11//===----------------------------------------------------------------------===//
12
13#ifndef LLVM_EXECUTIONENGINE_ORC_MACHOBUILDER_H
14#define LLVM_EXECUTIONENGINE_ORC_MACHOBUILDER_H
15
17#include "llvm/Support/Endian.h"
19
20#include <list>
21#include <map>
22#include <vector>
23
24namespace llvm {
25namespace orc {
26
27template <typename MachOStruct>
28size_t writeMachOStruct(MutableArrayRef<char> Buf, size_t Offset, MachOStruct S,
29 bool SwapStruct) {
30 if (SwapStruct)
32 assert(Offset + sizeof(MachOStruct) <= Buf.size() && "Buffer overflow");
33 memcpy(&Buf[Offset], reinterpret_cast<const char *>(&S), sizeof(MachOStruct));
34 return Offset + sizeof(MachOStruct);
35}
36
37/// Base type for MachOBuilder load command wrappers.
39 virtual ~MachOBuilderLoadCommandBase() = default;
40 virtual size_t size() const = 0;
41 virtual size_t write(MutableArrayRef<char> Buf, size_t Offset,
42 bool SwapStruct) = 0;
43};
44
45/// MachOBuilder load command wrapper type.
46template <MachO::LoadCommandType LCType> struct MachOBuilderLoadCommandImplBase;
47
48#define HANDLE_LOAD_COMMAND(Name, Value, LCStruct) \
49 template <> \
50 struct MachOBuilderLoadCommandImplBase<MachO::Name> \
51 : public MachO::LCStruct, public MachOBuilderLoadCommandBase { \
52 using CmdStruct = LCStruct; \
53 MachOBuilderLoadCommandImplBase() { \
54 memset(&rawStruct(), 0, sizeof(CmdStruct)); \
55 cmd = Value; \
56 cmdsize = sizeof(CmdStruct); \
57 } \
58 template <typename... ArgTs> \
59 MachOBuilderLoadCommandImplBase(ArgTs &&...Args) \
60 : CmdStruct{Value, sizeof(CmdStruct), std::forward<ArgTs>(Args)...} {} \
61 CmdStruct &rawStruct() { return static_cast<CmdStruct &>(*this); } \
62 size_t size() const override { return cmdsize; } \
63 size_t write(MutableArrayRef<char> Buf, size_t Offset, \
64 bool SwapStruct) override { \
65 return writeMachOStruct(Buf, Offset, rawStruct(), SwapStruct); \
66 } \
67 };
68
69#include "llvm/BinaryFormat/MachO.def"
70
71#undef HANDLE_LOAD_COMMAND
72
73template <MachO::LoadCommandType LCType>
75 : public MachOBuilderLoadCommandImplBase<LCType> {
76public:
78
79 template <typename... ArgTs>
80 MachOBuilderLoadCommand(ArgTs &&...Args)
81 : MachOBuilderLoadCommandImplBase<LCType>(std::forward<ArgTs>(Args)...) {}
82};
83
84template <>
86 : public MachOBuilderLoadCommandImplBase<MachO::LC_UUID> {
89 memcpy(uuid, UUID, sizeof(uuid));
90 }
91
92 MachOBuilderLoadCommand(const std::array<uint8_t, 16> &UUID)
94 memcpy(uuid, UUID.data(), sizeof(uuid));
95 }
96};
97
98template <MachO::LoadCommandType LCType>
100 : public MachOBuilderLoadCommandImplBase<LCType> {
101
103 uint32_t CurrentVersion,
104 uint32_t CompatibilityVersion)
106 MachO::dylib{24, Timestamp, CurrentVersion, CompatibilityVersion}),
107 Name(std::move(Name)) {
108 this->cmdsize += (this->Name.size() + 1 + 3) & ~0x3;
109 }
110
112 bool SwapStruct) override {
113 Offset = writeMachOStruct(Buf, Offset, this->rawStruct(), SwapStruct);
114 strcpy(Buf.data() + Offset, Name.data());
115 return Offset + ((Name.size() + 1 + 3) & ~0x3);
116 }
117
118 std::string Name;
119};
120
121template <>
126
127template <>
128struct MachOBuilderLoadCommand<MachO::LC_LOAD_DYLIB>
129 : public MachOBuilderDylibLoadCommand<MachO::LC_LOAD_DYLIB> {
131};
132
133template <>
134struct MachOBuilderLoadCommand<MachO::LC_LOAD_WEAK_DYLIB>
135 : public MachOBuilderDylibLoadCommand<MachO::LC_LOAD_WEAK_DYLIB> {
137};
138
139template <>
141 : public MachOBuilderLoadCommandImplBase<MachO::LC_RPATH> {
144 cmdsize += (this->Path.size() + 1 + 3) & ~0x3;
145 }
146
148 bool SwapStruct) override {
149 Offset = writeMachOStruct(Buf, Offset, rawStruct(), SwapStruct);
150 strcpy(Buf.data() + Offset, Path.data());
151 return Offset + ((Path.size() + 1 + 3) & ~0x3);
152 }
153
154 std::string Path;
155};
156
157// Builds MachO objects.
158template <typename MachOTraits> class MachOBuilder {
159private:
160 struct SymbolContainer {
161 size_t SymbolIndexBase = 0;
162 std::vector<typename MachOTraits::NList> Symbols;
163 };
164
165 struct StringTableEntry {
166 StringRef S;
167 size_t Offset;
168 };
169
170 using StringTable = std::vector<StringTableEntry>;
171
172 static bool swapStruct() {
173 return MachOTraits::Endianness != llvm::endianness::native;
174 }
175
176public:
177 using StringId = size_t;
178
179 struct Section;
180
181 // Points to either an nlist entry (as a (symbol-container, index) pair), or
182 // a section.
184 public:
185 RelocTarget(const Section &S) : S(&S), Idx(~0U) {}
186 RelocTarget(SymbolContainer &SC, size_t Idx) : SC(&SC), Idx(Idx) {}
187
188 bool isSymbol() { return Idx != ~0U; }
189
191 assert(isSymbol() && "Target is not a symbol");
192 return SC->SymbolIndexBase + Idx;
193 }
194
196 assert(!isSymbol() && "Target is not a section");
197 return S->SectionNumber;
198 }
199
200 typename MachOTraits::NList &nlist() {
201 assert(isSymbol() && "Target is not a symbol");
202 return SC->Symbols[Idx];
203 }
204
205 private:
206 union {
207 const Section *S;
208 SymbolContainer *SC;
209 };
210 size_t Idx;
211 };
212
215
216 Reloc(int32_t Offset, RelocTarget Target, bool PCRel, unsigned Length,
217 unsigned Type)
218 : Target(Target) {
219 assert(Type < 16 && "Relocation type out of range");
220 r_address = Offset; // Will slide to account for sec addr during layout
221 r_symbolnum = 0;
222 r_pcrel = PCRel;
224 r_extern = Target.isSymbol();
225 r_type = Type;
226 }
227
229 return static_cast<MachO::relocation_info &>(*this);
230 }
231 };
232
234 const char *Data = nullptr;
235 size_t Size = 0;
236 };
237
238 struct Section : public MachOTraits::Section, public RelocTarget {
241 size_t SectionNumber = 0;
242 SymbolContainer SC;
243 std::vector<Reloc> Relocs;
244
246 : RelocTarget(*this), Builder(Builder) {
247 memset(&rawStruct(), 0, sizeof(typename MachOTraits::Section));
248 assert(SecName.size() <= 16 && "SecName too long");
249 assert(SegName.size() <= 16 && "SegName too long");
250 memcpy(this->sectname, SecName.data(), SecName.size());
251 memcpy(this->segname, SegName.data(), SegName.size());
252 }
253
255 uint16_t Desc) {
256 StringId SI = Builder.addString(Name);
257 typename MachOTraits::NList Sym;
258 Sym.n_strx = SI;
259 Sym.n_type = Type | MachO::N_SECT;
260 Sym.n_sect = MachO::NO_SECT; // Will be filled in later.
261 Sym.n_desc = Desc;
262 Sym.n_value = Offset;
263 SC.Symbols.push_back(Sym);
264 return {SC, SC.Symbols.size() - 1};
265 }
266
267 void addReloc(int32_t Offset, RelocTarget Target, bool PCRel,
268 unsigned Length, unsigned Type) {
269 Relocs.push_back({Offset, Target, PCRel, Length, Type});
270 }
271
272 auto &rawStruct() {
273 return static_cast<typename MachOTraits::Section &>(*this);
274 }
275 };
276
277 struct Segment : public MachOBuilderLoadCommand<MachOTraits::SegmentCmd> {
279 std::vector<std::unique_ptr<Section>> Sections;
280
282 : MachOBuilderLoadCommand<MachOTraits::SegmentCmd>(), Builder(Builder) {
283 assert(SegName.size() <= 16 && "SegName too long");
284 memcpy(this->segname, SegName.data(), SegName.size());
285 this->maxprot =
287 this->initprot = this->maxprot;
288 }
289
291 Sections.push_back(std::make_unique<Section>(Builder, SecName, SegName));
292 return *Sections.back();
293 }
294
296 bool SwapStruct) override {
298 Buf, Offset, SwapStruct);
299 for (auto &Sec : Sections)
300 Offset = writeMachOStruct(Buf, Offset, Sec->rawStruct(), SwapStruct);
301 return Offset;
302 }
303 };
304
305 MachOBuilder(size_t PageSize) : PageSize(PageSize) {
306 memset((char *)&Header, 0, sizeof(Header));
307 Header.magic = MachOTraits::Magic;
308 }
309
310 template <MachO::LoadCommandType LCType, typename... ArgTs>
312 static_assert(LCType != MachOTraits::SegmentCmd,
313 "Use addSegment to add segment load command");
314 auto LC = std::make_unique<MachOBuilderLoadCommand<LCType>>(
315 std::forward<ArgTs>(Args)...);
316 auto &Tmp = *LC;
317 LoadCommands.push_back(std::move(LC));
318 return Tmp;
319 }
320
322 if (Strings.empty() && !Str.empty())
323 addString("");
324 return Strings.insert(std::make_pair(Str, Strings.size())).first->second;
325 }
326
328 Segments.push_back(Segment(*this, SegName));
329 return Segments.back();
330 }
331
333 uint16_t Desc, typename MachOTraits::UIntPtr Value) {
334 StringId SI = addString(Name);
335 typename MachOTraits::NList Sym;
336 Sym.n_strx = SI;
337 Sym.n_type = Type;
338 Sym.n_sect = Sect;
339 Sym.n_desc = Desc;
340 Sym.n_value = Value;
341 SC.Symbols.push_back(Sym);
342 return {SC, SC.Symbols.size() - 1};
343 }
344
345 // Call to perform layout on the MachO. Returns the total size of the
346 // resulting file.
347 // This method will automatically insert some load commands (e.g.
348 // LC_SYMTAB) and fill in load command fields.
349 size_t layout() {
350
351 // Build symbol table and add LC_SYMTAB command.
352 makeStringTable();
354 if (!StrTab.empty())
356
357 // Lay out header, segment load command, and other load commands.
358 size_t Offset = sizeof(Header);
359 for (auto &Seg : Segments) {
360 Seg.cmdsize +=
361 Seg.Sections.size() * sizeof(typename MachOTraits::Section);
362 Seg.nsects = Seg.Sections.size();
363 Offset += Seg.cmdsize;
364 }
365 for (auto &LC : LoadCommands)
366 Offset += LC->size();
367
368 Header.sizeofcmds = Offset - sizeof(Header);
369
370 // Lay out content, set segment / section addrs and offsets.
371 size_t SegVMAddr = 0;
372 for (auto &Seg : Segments) {
373 Seg.vmaddr = SegVMAddr;
374 Seg.fileoff = Offset;
375 for (auto &Sec : Seg.Sections) {
376 Offset = alignTo(Offset, 1ULL << Sec->align);
377 if (Sec->Content.Size)
378 Sec->offset = Offset;
379 Sec->size = Sec->Content.Size;
380 Sec->addr = SegVMAddr + Sec->offset - Seg.fileoff;
381 Offset += Sec->Content.Size;
382 }
383 size_t SegContentSize = Offset - Seg.fileoff;
384 Seg.filesize = SegContentSize;
385 Seg.vmsize = Header.filetype == MachO::MH_OBJECT
386 ? SegContentSize
387 : alignTo(SegContentSize, PageSize);
388 SegVMAddr += Seg.vmsize;
389 }
390
391 // Set string table offsets for non-section symbols.
392 for (auto &Sym : SC.Symbols)
393 Sym.n_strx = StrTab[Sym.n_strx].Offset;
394
395 // Number sections, set symbol section numbers and string table offsets,
396 // count relocations.
397 size_t NumSymbols = SC.Symbols.size();
398 size_t SectionNumber = 0;
399 for (auto &Seg : Segments) {
400 for (auto &Sec : Seg.Sections) {
401 ++SectionNumber;
402 Sec->SectionNumber = SectionNumber;
403 Sec->SC.SymbolIndexBase = NumSymbols;
404 NumSymbols += Sec->SC.Symbols.size();
405 for (auto &Sym : Sec->SC.Symbols) {
406 Sym.n_sect = SectionNumber;
407 Sym.n_strx = StrTab[Sym.n_strx].Offset;
408 Sym.n_value += Sec->addr;
409 }
410 }
411 }
412
413 // Handle relocations
414 bool OffsetAlignedForRelocs = false;
415 for (auto &Seg : Segments) {
416 for (auto &Sec : Seg.Sections) {
417 if (!Sec->Relocs.empty()) {
418 if (!OffsetAlignedForRelocs) {
420 OffsetAlignedForRelocs = true;
421 }
422 Sec->reloff = Offset;
423 Sec->nreloc = Sec->Relocs.size();
424 Offset += Sec->Relocs.size() * sizeof(MachO::relocation_info);
425 for (auto &R : Sec->Relocs)
426 R.r_symbolnum = R.Target.isSymbol() ? R.Target.getSymbolNum()
427 : R.Target.getSectionId();
428 }
429 }
430 }
431
432 // Calculate offset to start of nlist and update symtab command.
433 if (NumSymbols > 0) {
434 Offset = alignTo(Offset, sizeof(typename MachOTraits::NList));
435 SymTabLC->symoff = Offset;
436 SymTabLC->nsyms = NumSymbols;
437
438 // Calculate string table bounds and update symtab command.
439 if (!StrTab.empty()) {
440 Offset += NumSymbols * sizeof(typename MachOTraits::NList);
441 size_t StringTableSize =
442 StrTab.back().Offset + StrTab.back().S.size() + 1;
443
444 SymTabLC->stroff = Offset;
445 SymTabLC->strsize = StringTableSize;
446 Offset += StringTableSize;
447 }
448 }
449
450 return Offset;
451 }
452
454 size_t Offset = 0;
455 Offset = writeHeader(Buffer, Offset);
456 Offset = writeSegments(Buffer, Offset);
457 Offset = writeLoadCommands(Buffer, Offset);
458 Offset = writeSectionContent(Buffer, Offset);
459 Offset = writeRelocations(Buffer, Offset);
460 Offset = writeSymbols(Buffer, Offset);
461 Offset = writeStrings(Buffer, Offset);
462 }
463
464 typename MachOTraits::Header Header;
465
466private:
467 void makeStringTable() {
468 if (Strings.empty())
469 return;
470
471 StrTab.resize(Strings.size());
472 for (auto &[Str, Idx] : Strings)
473 StrTab[Idx] = {Str, 0};
474 size_t Offset = 0;
475 for (auto &Elem : StrTab) {
476 Elem.Offset = Offset;
477 Offset += Elem.S.size() + 1;
478 }
479 }
480
481 size_t writeHeader(MutableArrayRef<char> Buf, size_t Offset) {
482 Header.ncmds = Segments.size() + LoadCommands.size();
483 return writeMachOStruct(Buf, Offset, Header, swapStruct());
484 }
485
486 size_t writeSegments(MutableArrayRef<char> Buf, size_t Offset) {
487 for (auto &Seg : Segments)
488 Offset = Seg.write(Buf, Offset, swapStruct());
489 return Offset;
490 }
491
492 size_t writeLoadCommands(MutableArrayRef<char> Buf, size_t Offset) {
493 for (auto &LC : LoadCommands)
494 Offset = LC->write(Buf, Offset, swapStruct());
495 return Offset;
496 }
497
498 size_t writeSectionContent(MutableArrayRef<char> Buf, size_t Offset) {
499 for (auto &Seg : Segments) {
500 for (auto &Sec : Seg.Sections) {
501 if (!Sec->Content.Data) {
502 assert(Sec->Relocs.empty() &&
503 "Cant' have relocs for zero-fill segment");
504 continue;
505 }
506 while (Offset != Sec->offset)
507 Buf[Offset++] = '\0';
508
509 assert(Offset + Sec->Content.Size <= Buf.size() && "Buffer overflow");
510 memcpy(&Buf[Offset], Sec->Content.Data, Sec->Content.Size);
511 Offset += Sec->Content.Size;
512 }
513 }
514 return Offset;
515 }
516
517 size_t writeRelocations(MutableArrayRef<char> Buf, size_t Offset) {
518 for (auto &Seg : Segments) {
519 for (auto &Sec : Seg.Sections) {
520 if (!Sec->Relocs.empty()) {
521 while (Offset % sizeof(MachO::relocation_info))
522 Buf[Offset++] = '\0';
523 }
524 for (auto &R : Sec->Relocs) {
525 assert(Offset + sizeof(MachO::relocation_info) <= Buf.size() &&
526 "Buffer overflow");
527 memcpy(&Buf[Offset], reinterpret_cast<const char *>(&R.rawStruct()),
528 sizeof(MachO::relocation_info));
529 Offset += sizeof(MachO::relocation_info);
530 }
531 }
532 }
533 return Offset;
534 }
535
536 size_t writeSymbols(MutableArrayRef<char> Buf, size_t Offset) {
537
538 // Count symbols.
539 size_t NumSymbols = SC.Symbols.size();
540 for (auto &Seg : Segments)
541 for (auto &Sec : Seg.Sections)
542 NumSymbols += Sec->SC.Symbols.size();
543
544 // If none then return.
545 if (NumSymbols == 0)
546 return Offset;
547
548 // Align to nlist entry size.
549 while (Offset % sizeof(typename MachOTraits::NList))
550 Buf[Offset++] = '\0';
551
552 // Write non-section symbols.
553 for (auto &Sym : SC.Symbols)
554 Offset = writeMachOStruct(Buf, Offset, Sym, swapStruct());
555
556 // Write section symbols.
557 for (auto &Seg : Segments) {
558 for (auto &Sec : Seg.Sections) {
559 for (auto &Sym : Sec->SC.Symbols) {
560 Offset = writeMachOStruct(Buf, Offset, Sym, swapStruct());
561 }
562 }
563 }
564 return Offset;
565 }
566
567 size_t writeStrings(MutableArrayRef<char> Buf, size_t Offset) {
568 for (auto &Elem : StrTab) {
569 assert(Offset + Elem.S.size() + 1 <= Buf.size() && "Buffer overflow");
570 memcpy(&Buf[Offset], Elem.S.data(), Elem.S.size());
571 Offset += Elem.S.size();
572 Buf[Offset++] = '\0';
573 }
574 return Offset;
575 }
576
577 size_t PageSize;
578 std::list<Segment> Segments;
579 std::vector<std::unique_ptr<MachOBuilderLoadCommandBase>> LoadCommands;
580 SymbolContainer SC;
581
582 // Maps strings to their "id" (addition order).
583 std::map<StringRef, size_t> Strings;
584 StringTable StrTab;
585};
586
599
600} // namespace orc
601} // namespace llvm
602
603#endif // LLVM_EXECUTIONENGINE_ORC_MACHOBUILDER_H
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
std::pair< llvm::MachO::Target, std::string > UUID
size_t size() const
size - Get the array size.
Definition ArrayRef.h:142
MutableArrayRef - Represent a mutable reference to an array (0 or more elements consecutively in memo...
Definition ArrayRef.h:298
StringRef - Represent a constant reference to a string, i.e.
Definition StringRef.h:55
constexpr size_t size() const
size - Get the string size.
Definition StringRef.h:143
constexpr const char * data() const
data - Get a pointer to the start of the string (which may not be null terminated).
Definition StringRef.h:137
Target - Wrapper for Target specific information.
The instances of the Type class are immutable: once they are created, they are never changed.
Definition Type.h:46
LLVM Value Representation.
Definition Value.h:75
RelocTarget(SymbolContainer &SC, size_t Idx)
MachOBuilderLoadCommand< LCType > & addLoadCommand(ArgTs &&...Args)
void write(MutableArrayRef< char > Buffer)
RelocTarget addSymbol(StringRef Name, uint8_t Type, uint8_t Sect, uint16_t Desc, typename MachOTraits::UIntPtr Value)
StringId addString(StringRef Str)
MachOBuilder(size_t PageSize)
Segment & addSegment(StringRef SegName)
MachOTraits::Header Header
@ VM_PROT_EXECUTE
Definition MachO.h:554
@ VM_PROT_READ
Definition MachO.h:554
@ VM_PROT_WRITE
Definition MachO.h:554
@ MH_OBJECT
Definition MachO.h:43
void swapStruct(fat_header &mh)
Definition MachO.h:1197
@ MH_MAGIC_64
Definition MachO.h:32
LoadCommandType
Definition MachO.h:98
size_t writeMachOStruct(MutableArrayRef< char > Buf, size_t Offset, MachOStruct S, bool SwapStruct)
This is an optimization pass for GlobalISel generic memory operations.
@ Offset
Definition DWP.cpp:532
@ Length
Definition DWP.cpp:532
Op::Description Desc
constexpr uint64_t alignTo(uint64_t Size, Align A)
Returns a multiple of A needed to store Size bytes.
Definition Alignment.h:144
OutputIt move(R &&Range, OutputIt Out)
Provide wrappers to std::move which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1917
endianness
Definition bit.h:71
Implement std::hash so that hash_code can be used in STL containers.
Definition BitVector.h:870
static constexpr uint32_t Magic
MachO::nlist_64 NList
MachO::relocation_info Relocation
static constexpr llvm::endianness Endianness
static constexpr MachO::LoadCommandType SymTabCmd
static constexpr MachO::LoadCommandType SegmentCmd
MachO::section_64 Section
MachO::mach_header_64 Header
MachOBuilderDylibLoadCommand(std::string Name, uint32_t Timestamp, uint32_t CurrentVersion, uint32_t CompatibilityVersion)
size_t write(MutableArrayRef< char > Buf, size_t Offset, bool SwapStruct) override
Base type for MachOBuilder load command wrappers.
virtual size_t size() const =0
virtual ~MachOBuilderLoadCommandBase()=default
virtual size_t write(MutableArrayRef< char > Buf, size_t Offset, bool SwapStruct)=0
MachOBuilder load command wrapper type.
MachOBuilderDylibLoadCommand(std::string Name, uint32_t Timestamp, uint32_t CurrentVersion, uint32_t CompatibilityVersion)
MachOBuilderDylibLoadCommand(std::string Name, uint32_t Timestamp, uint32_t CurrentVersion, uint32_t CompatibilityVersion)
MachOBuilderDylibLoadCommand(std::string Name, uint32_t Timestamp, uint32_t CurrentVersion, uint32_t CompatibilityVersion)
size_t write(MutableArrayRef< char > Buf, size_t Offset, bool SwapStruct) override
MachOBuilderLoadCommand(const std::array< uint8_t, 16 > &UUID)
MachOBuilderLoadCommand(ArgTs &&...Args)
Reloc(int32_t Offset, RelocTarget Target, bool PCRel, unsigned Length, unsigned Type)
MachO::relocation_info & rawStruct()
void addReloc(int32_t Offset, RelocTarget Target, bool PCRel, unsigned Length, unsigned Type)
Section(MachOBuilder &Builder, StringRef SecName, StringRef SegName)
RelocTarget addSymbol(int32_t Offset, StringRef Name, uint8_t Type, uint16_t Desc)
Segment(MachOBuilder &Builder, StringRef SegName)
Section & addSection(StringRef SecName, StringRef SegName)
std::vector< std::unique_ptr< Section > > Sections
size_t write(MutableArrayRef< char > Buf, size_t Offset, bool SwapStruct) override