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 }