1 /**
2 Copyright: Copyright (c) 2019 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module vox.be.make_exe;
7 
8 import std.path;
9 import std.stdio;
10 import std.conv;
11 
12 import vox.all;
13 import vox.be.pecoff;
14 import vox.be.elf64;
15 
16 //version = print_info;
17 
18 void pass_create_executable(ref CompilationContext context, CompilePassPerModule[] subPasses)
19 {
20 	//writefln("%s", context.targetOs);
21 	final switch(context.targetOs) {
22 		case TargetOs.windows: make_pe_exe(&context); break;
23 		case TargetOs.linux: make_elf_exe(&context); break;
24 		case TargetOs.macos: context.internal_error("MacOS exe write is not implemented");
25 	}
26 }
27 
28 void make_elf_exe(CompilationContext* context)
29 {
30 	enum SECTION_ALIGNMENT = 4096;
31 	Elf64Executable executable;
32 	executable.context = context;
33 	executable.fileHeader.file_type = ElfObjectFileType.ET_EXEC;
34 	executable.fileHeader.machine = ElfMachineType.x86_64;
35 
36 	Elf64ProgramHeader[NUM_BUILTIN_SECTIONS] segmentBuf;
37 	Arena!Elf64ProgramHeader segmentArena;
38 	segmentArena.setBuffer(cast(ubyte[])segmentBuf);
39 	ObjectSection*[NUM_BUILTIN_SECTIONS] sectionBuf;
40 	Arena!(ObjectSection*) sectionArena;
41 	sectionArena.setBuffer(cast(ubyte[])sectionBuf);
42 	executable.assignSectionAddresses(segmentArena, sectionArena);
43 
44 	// fix all references between symbols
45 	foreach (ref SourceFileInfo file; context.files.data) {
46 		linkModule(*context, file.mod.objectSymIndex);
47 	}
48 
49 	if (context.printSymbols) context.objSymTab.dump(context);
50 
51 	if (context.entryPoint is null) {
52 		context.unrecoverable_error(TokenIndex(), "No entry point set. Need 'main' function");
53 	}
54 
55 	ObjectSymbol* entryPoint = context.objSymTab.getSymbol(context.entryPoint.backendData.objectSymIndex);
56 	ObjectSection* entryPointSection = context.objSymTab.getSection(entryPoint.sectionIndex);
57 	executable.fileHeader.entry = to!uint(entryPointSection.sectionAddress + entryPoint.sectionOffset);
58 
59 	executable.write(context.binaryBuffer);
60 }
61 
62 void make_pe_exe(CompilationContext* context)
63 {
64 	// Code section
65 	Section textSection = Section(SectionType.text, ".text");
66 	textSection.header.Characteristics =
67 		SectionFlags.SCN_CNT_CODE |
68 		SectionFlags.SCN_MEM_EXECUTE |
69 		SectionFlags.SCN_MEM_READ;
70 
71 	// Import table section
72 	Section idataSection = Section(SectionType.idata, ".idata");
73 	idataSection.header.Characteristics =
74 		SectionFlags.SCN_CNT_INITIALIZED_DATA |
75 		SectionFlags.SCN_MEM_WRITE |
76 		SectionFlags.SCN_MEM_READ;
77 
78 	// Static data section
79 	Section dataSection = Section(SectionType.data, ".data");
80 	dataSection.header.Characteristics =
81 		SectionFlags.SCN_MEM_WRITE |
82 		SectionFlags.SCN_MEM_READ;
83 
84 	// Readonly static data section
85 	Section rdataSection = Section(SectionType.data, ".rdata");
86 	rdataSection.header.Characteristics =
87 		SectionFlags.SCN_CNT_INITIALIZED_DATA |
88 		SectionFlags.SCN_MEM_READ;
89 
90 	// ---------------------------------------------------------
91 
92 	ImportSection importSection;
93 	importSection.section = &idataSection;
94 
95 	CoffImportSectionSize impSize = calcImportSize(context);
96 	ubyte[] importBuffer = context.importBuffer.voidPut(impSize.totalSectionBytes);
97 	auto importMapping = CoffImportSectionMapping(importBuffer, impSize);
98 
99 	textSection.data = context.codeBuffer.data;
100 	idataSection.data = importMapping.sectionData;
101 	dataSection.data = context.staticDataBuffer.data;
102 	dataSection.setSectionZeroLength(context.zeroDataLength);
103 	if (dataSection.data.length > 0) dataSection.header.Characteristics |= SectionFlags.SCN_CNT_INITIALIZED_DATA;
104 	if (context.zeroDataLength > 0) dataSection.header.Characteristics |= SectionFlags.SCN_CNT_UNINITIALIZED_DATA;
105 	rdataSection.data = context.roStaticDataBuffer.data;
106 
107 	// ---------------------------------------------------------
108 
109 	// Exe gen
110 	Section*[4] sectionsBuf;
111 	Arena!(Section*) sections;
112 	sections.setBuffer(cast(ubyte[])sectionsBuf);
113 
114 	void addSection(Section* section)
115 	{
116 		if (section.totalSize == 0) return;
117 		sections.put(section);
118 	}
119 
120 	addSection(&textSection);
121 	addSection(&idataSection);
122 	addSection(&dataSection);
123 	addSection(&rdataSection);
124 
125 	auto fileParams = FileParameters(DEFAULT_SECTION_ALIGNMENT, context.sectionAlignemnt);
126 	if (fileParams.fileAlignment < 512) fileParams.sectionAlignment = fileParams.fileAlignment;
127 
128 	CoffExecutable executable = CoffExecutable(fileParams, context);
129 	executable.sections = sections.data;
130 
131 	// fills header.VirtualAddress
132 	executable.windowsSubsystem = context.windowsSubsystem;
133 	executable.fixup();
134 
135 	// fill sectionAddress, uses VirtualAddress
136 	context.objSymTab.getSection(context.builtinSections[ObjectSectionType.code]).sectionAddress = textSection.header.VirtualAddress;
137 	context.objSymTab.getSection(context.builtinSections[ObjectSectionType.imports]).sectionAddress = idataSection.header.VirtualAddress;
138 	context.objSymTab.getSection(context.builtinSections[ObjectSectionType.rw_data]).sectionAddress = dataSection.header.VirtualAddress;
139 	context.objSymTab.getSection(context.builtinSections[ObjectSectionType.ro_data]).sectionAddress = rdataSection.header.VirtualAddress;
140 
141 	// uses sectionAddress
142 	if (importMapping.sectionData.length > 0) {
143 		fillImports(importMapping, context);
144 	}
145 
146 	// fix all references between symbols
147 	foreach (ref SourceFileInfo file; context.files.data) {
148 		linkModule(*context, file.mod.objectSymIndex);
149 	}
150 
151 	if (context.printSymbols) context.objSymTab.dump(context);
152 
153 	if (context.entryPoint is null)
154 	{
155 		context.unrecoverable_error(TokenIndex(), "No entry point set. Need 'main' function");
156 	}
157 
158 	ObjectSymbol* entryPoint = context.objSymTab.getSymbol(context.entryPoint.backendData.objectSymIndex);
159 	ObjectSection* entryPointSection = context.objSymTab.getSection(entryPoint.sectionIndex);
160 	executable.entryPointAddress = to!uint(entryPointSection.sectionAddress + entryPoint.sectionOffset);
161 
162 	/*
163 	writeln("Code");
164 	printHex(textSection.data, 16);
165 	writeln("Imports");
166 	printHex(idataSection.data, 16);
167 	writeln("data");
168 	printHex(dataSection.data, 16);
169 	writeln;*/
170 
171 	executable.write(context.binaryBuffer);
172 	//writeln(textSection.header);
173 	//writeln(idataSection.header);
174 	//writeln(dataSection.header);
175 	//printHex(sink.data, 16);
176 	//writefln("Writing to '%s'", context.outputFilename.absolutePath);
177 	//std.file.write(context.outputFilename, sink.data);
178 
179 	//context.objSymTab.print_dd64_debug_info(&context);
180 }
181 
182 // Walks all imported symbols and calculates import section size
183 CoffImportSectionSize calcImportSize(CompilationContext* context)
184 {
185 	CoffImportSectionSize impSize;
186 	LinkIndex modIndex = context.objSymTab.firstModule;
187 	while (modIndex.isDefined)
188 	{
189 		ObjectModule* mod = context.objSymTab.getModule(modIndex);
190 
191 		if (mod.isImported)
192 		{
193 			++impSize.numLibs;
194 			string libName = context.idMap.get(mod.id);
195 			impSize.totalDllNamesBytes = alignValue(
196 				impSize.totalDllNamesBytes + libName.length + 1, 2);
197 
198 			size_t numImportedFunctions;
199 			LinkIndex symIndex = mod.firstSymbol;
200 			while (symIndex.isDefined)
201 			{
202 				ObjectSymbol* sym = context.objSymTab.getSymbol(symIndex);
203 
204 				// Only add referenced symbols to import table
205 				if (sym.isReferenced)
206 				{
207 					string symName = context.idString(sym.id);
208 					++numImportedFunctions;
209 					impSize.totalDllHintsBytes = alignValue(
210 						impSize.totalDllHintsBytes +
211 						HintNameEntry.Hint.sizeof +
212 						symName.length + 1, 2);
213 				}
214 
215 				symIndex = sym.nextSymbol;
216 			}
217 
218 			size_t numTableEntries = numImportedFunctions + 1; // include null entry
219 			impSize.totalImportEntries += numTableEntries;
220 		}
221 		modIndex = mod.nextModule;
222 	}
223 	return impSize;
224 }
225 
226 struct CoffImportSectionSize
227 {
228 	size_t numLibs;
229 	size_t importDirectoryTableBytes() { return (numLibs + 1) * ImportDirectoryTableEntry.sizeof; }
230 	size_t totalImportEntries; // num of entries for both IAT and ILT, with null entries
231 	size_t totalTableBytes() { return totalImportEntries * IAT_ILT_ENTRY_BYTES; }
232 	size_t totalDllNamesBytes; // with leading zero and 2 byte alignment padding
233 	size_t totalDllHintsBytes; // with leading zero and 2 byte alignment padding
234 	size_t totalSectionBytes() {
235 		// without check importDirectoryTableBytes is adding 1 to numLibs, resulting in non-zero size for empty idata section
236 		if (numLibs == 0) return 0;
237 		return importDirectoryTableBytes + totalTableBytes * 2 + totalDllHintsBytes + totalDllNamesBytes;
238 	}
239 }
240 
241 /// A set of slices on top of single memory buffer
242 struct CoffImportSectionMapping
243 {
244 	this(ubyte[] _sectionBuffer, CoffImportSectionSize _importSizes)
245 	{
246 		this.importSizes = _importSizes;
247 		this.sectionData = _sectionBuffer[0..importSizes.totalSectionBytes];
248 		assert(sectionData.length == importSizes.totalSectionBytes);
249 
250 		if (sectionData.length == 0) return;
251 
252 		size_t dirsEnd = importSizes.importDirectoryTableBytes;
253 		directories = cast(ImportDirectoryTableEntry[])(sectionData[0..dirsEnd]);
254 
255 		ilt_rva = cast(uint)(dirsEnd);
256 		size_t ILT_end = cast(uint)(ilt_rva + importSizes.totalTableBytes);
257 		ILTs = cast(ImportLookupEntry[])(sectionData[ilt_rva..ILT_end]);
258 
259 		iat_rva = cast(uint)(ILT_end);
260 		size_t IAT_end = cast(uint)(iat_rva + importSizes.totalTableBytes);
261 		IATs = cast(ImportLookupEntry[])(sectionData[iat_rva..IAT_end]);
262 
263 		str_rva = cast(uint)IAT_end;
264 		stringData = sectionData[str_rva..$];
265 	}
266 
267 	ubyte[] sectionData;
268 
269 	// initial info
270 	CoffImportSectionSize importSizes;
271 
272 	// dir entries
273 	ImportDirectoryTableEntry[] directories; // includes null entry
274 	// import lookup tables (ILTs)
275 	ImportLookupEntry[] ILTs; // includes null entry
276 	uint ilt_rva;
277 	// import address tables (IATs)
278 	ImportLookupEntry[] IATs; // includes null entry
279 	uint iat_rva;
280 	// list of (lib name + hint/names)
281 	ubyte[] stringData;
282 	uint str_rva;
283 }
284 
285 void fillImports(ref CoffImportSectionMapping mapping, CompilationContext* context)
286 {
287 	ObjectSection* importSection = context.objSymTab.getSection(context.builtinSections[ObjectSectionType.imports]);
288 	uint sectionRVA = cast(uint)importSection.sectionAddress; // TODO check
289 
290 	// here, sink already has reserved space for Directory entries and IA, IL tables
291 	// we will write strings and set address at the same time. Relative to section start
292 	size_t tableIndex;
293 	size_t moduleIndex;
294 	immutable uint str_rva = sectionRVA + mapping.str_rva;
295 	immutable uint ilt_rva = sectionRVA + mapping.ilt_rva;
296 	immutable uint iat_rva = sectionRVA + mapping.iat_rva;
297 
298 	Arena!ubyte strSink;
299 	strSink.setBuffer(cast(ubyte[])mapping.stringData);
300 
301 	LinkIndex modIndex = context.objSymTab.firstModule;
302 	while (modIndex.isDefined)
303 	{
304 		ObjectModule* mod = context.objSymTab.getModule(modIndex);
305 
306 		if (mod.isImported)
307 		{
308 			mapping.directories[moduleIndex].importLookupTableRVA = cast(uint)(ilt_rva + tableIndex * IAT_ILT_ENTRY_BYTES);
309 			mapping.directories[moduleIndex].importAddressTableRVA = cast(uint)(iat_rva + tableIndex * IAT_ILT_ENTRY_BYTES);
310 			mapping.directories[moduleIndex].nameRVA = cast(uint)(str_rva + strSink.length);
311 
312 			string libName = context.idMap.get(mod.id);
313 			auto pre1 = strSink.length;
314 			strSink.writeStringAligned2(libName);
315 			version(print_info) writefln("write '%s' len %s sink %s", libName, strSink.length - pre1, strSink.length);
316 
317 			LinkIndex symIndex = mod.firstSymbol;
318 			while (symIndex.isDefined)
319 			{
320 				ObjectSymbol* sym = context.objSymTab.getSymbol(symIndex);
321 
322 				// Only add referenced symbols to import table
323 				if (sym.isReferenced)
324 				{
325 					string symName = context.idString(sym.id);
326 
327 					uint hintRVA = cast(uint)(str_rva + strSink.length);
328 					auto hint = ImportLookupEntry.fromHintNameTableRVA(hintRVA);
329 
330 					mapping.ILTs[tableIndex] = hint;
331 					mapping.IATs[tableIndex] = hint;
332 
333 					uint sectionOffset = cast(uint)(mapping.iat_rva + tableIndex * IAT_ILT_ENTRY_BYTES);
334 					sym.sectionOffset = sectionOffset;
335 					sym.length = IAT_ILT_ENTRY_BYTES;
336 
337 					auto pre2 = strSink.length;
338 					HintNameEntry(0, symName).write(strSink);
339 					version(print_info) writefln("write '%s' len %s RVA %x", symName, strSink.length - pre2, sectionOffset);
340 
341 					++tableIndex;
342 				}
343 
344 				symIndex = sym.nextSymbol;
345 			}
346 
347 			// account for null entry
348 			++tableIndex;
349 			++moduleIndex;
350 		}
351 		modIndex = mod.nextModule;
352 	}
353 
354 	assert(strSink.length == mapping.stringData.length);
355 }
356 
357 struct CoffExecutable
358 {
359 	FileParameters params;
360 	CompilationContext* context;
361 	uint entryPointAddress;
362 	WindowsSubsystem windowsSubsystem;
363 
364 	DosHeader dosHeader;
365 	DosStub dosStub;
366 	PeSignature peSignature;
367 	CoffFileHeader coffFileHeader;
368 	OptionalHeader optionalHeader;
369 	Section*[] sections;
370 
371 	uint unalignedHeadersSize;
372 
373 	void fixup()
374 	{
375 		calculateFileSizes();
376 		fixupInMemorySizes();
377 		collectCodeInfo();
378 		fixupInvariants();
379 	}
380 
381 	private void calculateFileSizes()
382 	{
383 		uint fileSize = 0;
384 		fileSize += DosHeader.sizeof;
385 		fileSize += DosStub.sizeof;
386 		fileSize += PeSignature.sizeof;
387 		fileSize += CoffFileHeader.sizeof;
388 		fileSize += OptionalHeader.sizeof;
389 		fileSize += SectionHeader.sizeof * sections.length;
390 
391 		unalignedHeadersSize = fileSize;
392 		optionalHeader.SizeOfHeaders = alignValue(unalignedHeadersSize, params.fileAlignment);
393 		uint imageFileSize = optionalHeader.SizeOfHeaders;
394 
395 		foreach (Section* section; sections)
396 		{
397 			// size in a file
398 			uint sectionFileSize = alignValue(section.initializedSize, params.fileAlignment);
399 			section.header.SizeOfRawData = sectionFileSize;
400 
401 			// position in a file
402 			if (section.header.SizeOfRawData == 0)
403 				// "When a section contains only uninitialized data, this field should be zero."
404 				section.header.PointerToRawData = 0;
405 			else
406 				section.header.PointerToRawData = imageFileSize;
407 
408 			imageFileSize += sectionFileSize;
409 		}
410 		version(print_info) writefln("Image file size is %s bytes", imageFileSize);
411 	}
412 
413 	private void fixupInMemorySizes()
414 	{
415 		uint headersInMemorySize = alignValue(unalignedHeadersSize, params.sectionAlignment);
416 		uint imageVirtualSize = headersInMemorySize;
417 
418 		foreach (Section* section; sections)
419 		{
420 			// position in memory after load
421 			section.header.VirtualAddress = imageVirtualSize;
422 			uint sectionVirtualSize = alignValue(section.totalSize, params.sectionAlignment);
423 			// size in memory after load, need not be aligned
424 			section.header.VirtualSize = section.totalSize;
425 			imageVirtualSize += sectionVirtualSize;
426 		}
427 
428 		optionalHeader.SizeOfImage = imageVirtualSize;
429 	}
430 
431 	private void collectCodeInfo()
432 	{
433 		bool codeSectionDetected = false;
434 		bool importSectionDetected = false;
435 
436 		foreach (Section* section; sections)
437 		{
438 			if (section.isCodeSection)
439 			{
440 				if (!codeSectionDetected)
441 				{
442 					codeSectionDetected = true;
443 					optionalHeader.BaseOfCode = section.header.VirtualAddress;
444 					version(print_info) writefln("First code section. BaseOfCode is %s", optionalHeader.BaseOfCode);
445 				}
446 				optionalHeader.SizeOfCode += section.header.SizeOfRawData;
447 				version(print_info) writefln("Code section %s", section.header);
448 			}
449 			else if (section.isImportSection)
450 			{
451 				if (!importSectionDetected)
452 				{
453 					importSectionDetected = true;
454 					optionalHeader.ImportTable.VirtualAddress = section.header.VirtualAddress;
455 				}
456 			}
457 
458 			optionalHeader.SizeOfInitializedData += section.header.SizeOfRawData;
459 		}
460 		version(print_info) writefln("Total code size is %sB", optionalHeader.SizeOfCode);
461 	}
462 
463 	private void fixupInvariants()
464 	{
465 		// COFF Header
466 		coffFileHeader.Machine = MachineType.amd64;
467 		coffFileHeader.NumberOfSections = cast(ushort)sections.length;
468 		coffFileHeader.TimeDateStamp = 0;
469 		coffFileHeader.PointerToSymbolTable = 0;
470 		coffFileHeader.NumberOfSymbols = 0;
471 		coffFileHeader.Characteristics =
472 			CoffFlags.RELOCS_STRIPPED | // TODO. remove when relocations are implemented
473 			CoffFlags.EXECUTABLE_IMAGE |
474 			CoffFlags.LARGE_ADDRESS_AWARE;
475 
476 
477 		// Optional Header (Image Only)
478 		optionalHeader.MajorLinkerVersion = 1;
479 		optionalHeader.SizeOfUninitializedData = 0; // FIXUP
480 		optionalHeader.SectionAlignment = params.sectionAlignment;
481 		optionalHeader.FileAlignment = params.fileAlignment;
482 		optionalHeader.MajorOperatingSystemVersion = 6;
483 		optionalHeader.MinorOperatingSystemVersion = 0;
484 		optionalHeader.MajorImageVersion = 0;
485 		optionalHeader.MinorImageVersion = 0;
486 		optionalHeader.MajorSubsystemVersion = 6;
487 		optionalHeader.MinorSubsystemVersion = 0;
488 		optionalHeader.CheckSum = 0;
489 		optionalHeader.Subsystem = windowsSubsystem;
490 		optionalHeader.DllCharacteristics = 0;
491 		optionalHeader.SizeOfStackReserve = 0x100000;
492 		optionalHeader.SizeOfStackCommit = 0x1000;
493 		optionalHeader.SizeOfHeapReserve = 0x100000;
494 		optionalHeader.SizeOfHeapCommit = 0x1000;
495 
496 		// Section headers
497 		foreach (section; sections)
498 		{
499 			section.header.PointerToRelocations = 0;
500 			section.header.NumberOfRelocations = 0;
501 		}
502 	}
503 
504 	void write(ref Arena!ubyte sink)
505 	{
506 		optionalHeader.AddressOfEntryPoint = entryPointAddress;
507 
508 		// DOS Header
509 		dosHeader.write(sink);
510 		// DOS Stub
511 		dosStub.write(sink);
512 		// PE signature
513 		peSignature.write(sink);
514 		// COFF Header
515 		coffFileHeader.write(sink);
516 		// Optional Header (Image Only)
517 		optionalHeader.write(sink);
518 		// Section Headers
519 		foreach (section; sections)
520 		{
521 			section.header.Name[0..section.name.length] = section.name;
522 			section.header.Name[section.name.length..$] = '\0';
523 			section.header.write(sink);
524 		}
525 		uint headersPadding = paddingSize(unalignedHeadersSize, params.fileAlignment);
526 		sink.pad(headersPadding);
527 
528 		// Section Datas
529 		foreach (section; sections)
530 		{
531 			version(print_info) writefln("%s RVA %x\t%s len\t%s bytes", section.name, sink.length, section.header.VirtualAddress, section.data.length);
532 			sink.put(section.data);
533 			size_t sectionPadding = section.header.SizeOfRawData - section.data.length;
534 			sink.pad(sectionPadding);
535 		}
536 	}
537 }
538 
539 struct Elf64Executable
540 {
541 	CompilationContext* context;
542 	Elf64Header fileHeader;
543 	// 0 address will not work without relocations
544 	ulong baseAddress = 0x400000;
545 
546 	Elf64ProgramHeader[] segments;
547 	ObjectSection*[] sections;
548 
549 	void assignSectionAddresses(ref Arena!Elf64ProgramHeader segmentBuf, ref Arena!(ObjectSection*) sectionBuf)
550 	{
551 		// gather non-empty sections and calc offsets
552 		uint fileOffset = Elf64Header.sizeof;
553 
554 		foreach (LinkIndex sectionIndex; context.builtinSections)
555 		{
556 			ObjectSection* section = context.objSymTab.getSection(sectionIndex);
557 			if (section.totalLength == 0) continue; // skip empty sections
558 
559 			Elf64ProgramHeader segment;
560 			segment.type = Elf64SegmentType.PT_LOAD;
561 			if (section.flag_read) segment.flags |= Elf64SegmentAttributes.READ;
562 			if (section.flag_write) segment.flags |= Elf64SegmentAttributes.WRITE;
563 			if (section.flag_execute) segment.flags |= Elf64SegmentAttributes.EXECUTE;
564 
565 			fileOffset += Elf64ProgramHeader.sizeof;
566 
567 			segmentBuf.put(segment);
568 			sectionBuf.put(section);
569 		}
570 
571 		segments = segmentBuf.data;
572 		sections = sectionBuf.data;
573 
574 		fileHeader.phnum = to!ushort(segments.length);
575 
576 		ulong memoryOffset = baseAddress;
577 
578 		foreach(i, ObjectSection* section; sections) {
579 			Elf64ProgramHeader* segment = &segments[i];
580 			segment.alignment = section.alignment;
581 
582 			// Memory offset
583 			// A.1. align the data in memory
584 			memoryOffset = alignValue(memoryOffset, section.alignment); // add padding
585 			segment.vaddr = memoryOffset;
586 			section.sectionAddress = memoryOffset;
587 			// Memory size
588 			// A.2. then add the data (including zero-initialized data)
589 			segment.memsz = section.totalLength;
590 			memoryOffset += section.totalLength;
591 
592 			if (section.initDataLength > 0)
593 			{
594 				// File offset
595 				// We don't want to add padding to the file if there will be no data
596 				// B.1. align the data in the file
597 				fileOffset = alignValue(fileOffset, section.alignment); // add padding
598 				segment.offset = fileOffset;
599 				// File size
600 				// B.2. then set the in-memory size (only initialized data)
601 				segment.filesz = section.initDataLength;
602 				fileOffset += section.initDataLength;
603 			}
604 		}
605 	}
606 
607 	void write(ref Arena!ubyte sink)
608 	{
609 		// ELF64 header
610 		sink.put(fileHeader);
611 
612 		// Segment table
613 		foreach (ref segment; segments) {
614 			sink.put(segment);
615 		}
616 
617 		// Segments
618 		foreach (i, ref segment; segments) {
619 			ObjectSection* section = sections[i];
620 			sink.padUntilAligned(segment.alignment);
621 			sink.put(section.buffer.data);
622 		}
623 	}
624 }