| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // PE (Portable Executable) file writing |
| // http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx |
| |
| #include "l.h" |
| #include "../ld/lib.h" |
| #include "../ld/pe.h" |
| #include "../ld/dwarf.h" |
| |
| // DOS stub that prints out |
| // "This program cannot be run in DOS mode." |
| static char dosstub[] = |
| { |
| 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x04, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, |
| 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, |
| 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, |
| 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, |
| 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, |
| 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, |
| 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, |
| 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, |
| 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, |
| 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| }; |
| |
| // Note: currently only up to 8 chars plus \0. |
| static char *symlabels[] = { |
| "symtab", "esymtab", "pclntab", "epclntab" |
| }; |
| |
| static Sym *rsrcsym; |
| |
| static char symnames[256]; |
| static int nextsymoff; |
| |
| int32 PESECTHEADR; |
| int32 PEFILEHEADR; |
| |
| static int pe64; |
| static int nsect; |
| static int nextsectoff; |
| static int nextfileoff; |
| static int textsect; |
| |
| static IMAGE_FILE_HEADER fh; |
| static IMAGE_OPTIONAL_HEADER oh; |
| static PE64_IMAGE_OPTIONAL_HEADER oh64; |
| static IMAGE_SECTION_HEADER sh[16]; |
| static IMAGE_DATA_DIRECTORY* dd; |
| |
| #define set(n, v) (pe64 ? (oh64.n = v) : (oh.n = v)) |
| #define put(v) (pe64 ? vputl(v) : lputl(v)) |
| |
| typedef struct Imp Imp; |
| struct Imp { |
| Sym* s; |
| uvlong off; |
| Imp* next; |
| }; |
| |
| typedef struct Dll Dll; |
| struct Dll { |
| char* name; |
| uvlong nameoff; |
| uvlong thunkoff; |
| Imp* ms; |
| Dll* next; |
| }; |
| |
| static Dll* dr; |
| |
| static Sym *dexport[1024]; |
| static int nexport; |
| |
| static IMAGE_SECTION_HEADER* |
| addpesection(char *name, int sectsize, int filesize) |
| { |
| IMAGE_SECTION_HEADER *h; |
| |
| if(nsect == 16) { |
| diag("too many sections"); |
| errorexit(); |
| } |
| h = &sh[nsect++]; |
| strncpy((char*)h->Name, name, sizeof(h->Name)); |
| h->VirtualSize = sectsize; |
| h->VirtualAddress = nextsectoff; |
| nextsectoff = rnd(nextsectoff+sectsize, PESECTALIGN); |
| h->PointerToRawData = nextfileoff; |
| if(filesize > 0) { |
| h->SizeOfRawData = rnd(filesize, PEFILEALIGN); |
| nextfileoff += h->SizeOfRawData; |
| } |
| return h; |
| } |
| |
| static void |
| chksectoff(IMAGE_SECTION_HEADER *h, vlong off) |
| { |
| if(off != h->PointerToRawData) { |
| diag("%s.PointerToRawData = %#llux, want %#llux", (char *)h->Name, (vlong)h->PointerToRawData, off); |
| errorexit(); |
| } |
| } |
| |
| static void |
| chksectseg(IMAGE_SECTION_HEADER *h, Segment *s) |
| { |
| if(s->vaddr-PEBASE != h->VirtualAddress) { |
| diag("%s.VirtualAddress = %#llux, want %#llux", (char *)h->Name, (vlong)h->VirtualAddress, (vlong)(s->vaddr-PEBASE)); |
| errorexit(); |
| } |
| if(s->fileoff != h->PointerToRawData) { |
| diag("%s.PointerToRawData = %#llux, want %#llux", (char *)h->Name, (vlong)h->PointerToRawData, (vlong)(s->fileoff)); |
| errorexit(); |
| } |
| } |
| |
| void |
| peinit(void) |
| { |
| int32 l; |
| |
| switch(thechar) { |
| // 64-bit architectures |
| case '6': |
| pe64 = 1; |
| l = sizeof(oh64); |
| dd = oh64.DataDirectory; |
| break; |
| // 32-bit architectures |
| default: |
| l = sizeof(oh); |
| dd = oh.DataDirectory; |
| break; |
| } |
| |
| PEFILEHEADR = rnd(sizeof(dosstub)+sizeof(fh)+l+sizeof(sh), PEFILEALIGN); |
| PESECTHEADR = rnd(PEFILEHEADR, PESECTALIGN); |
| nextsectoff = PESECTHEADR; |
| nextfileoff = PEFILEHEADR; |
| } |
| |
| static void |
| pewrite(void) |
| { |
| cseek(0); |
| cwrite(dosstub, sizeof dosstub); |
| strnput("PE", 4); |
| // TODO: This code should not assume that the |
| // memory representation is little-endian or |
| // that the structs are packed identically to |
| // their file representation. |
| cwrite(&fh, sizeof fh); |
| if(pe64) |
| cwrite(&oh64, sizeof oh64); |
| else |
| cwrite(&oh, sizeof oh); |
| cwrite(sh, nsect * sizeof sh[0]); |
| } |
| |
| static void |
| strput(char *s) |
| { |
| int n; |
| |
| for(n=0; *s; n++) |
| cput(*s++); |
| cput('\0'); |
| n++; |
| // string must be padded to even size |
| if(n%2) |
| cput('\0'); |
| } |
| |
| static Dll* |
| initdynimport(void) |
| { |
| Imp *m; |
| Dll *d; |
| Sym *s, *dynamic; |
| |
| dr = nil; |
| m = nil; |
| for(s = allsym; s != S; s = s->allsym) { |
| if(!s->reachable || !s->dynimpname || s->dynexport) |
| continue; |
| for(d = dr; d != nil; d = d->next) { |
| if(strcmp(d->name,s->dynimplib) == 0) { |
| m = mal(sizeof *m); |
| break; |
| } |
| } |
| if(d == nil) { |
| d = mal(sizeof *d); |
| d->name = s->dynimplib; |
| d->next = dr; |
| dr = d; |
| m = mal(sizeof *m); |
| } |
| m->s = s; |
| m->next = d->ms; |
| d->ms = m; |
| } |
| |
| dynamic = lookup(".windynamic", 0); |
| dynamic->reachable = 1; |
| dynamic->type = SWINDOWS; |
| for(d = dr; d != nil; d = d->next) { |
| for(m = d->ms; m != nil; m = m->next) { |
| m->s->type = SWINDOWS | SSUB; |
| m->s->sub = dynamic->sub; |
| dynamic->sub = m->s; |
| m->s->value = dynamic->size; |
| dynamic->size += PtrSize; |
| } |
| dynamic->size += PtrSize; |
| } |
| |
| return dr; |
| } |
| |
| static void |
| addimports(IMAGE_SECTION_HEADER *datsect) |
| { |
| IMAGE_SECTION_HEADER *isect; |
| uvlong n, oftbase, ftbase; |
| vlong startoff, endoff; |
| Imp *m; |
| Dll *d; |
| Sym* dynamic; |
| |
| startoff = cpos(); |
| dynamic = lookup(".windynamic", 0); |
| |
| // skip import descriptor table (will write it later) |
| n = 0; |
| for(d = dr; d != nil; d = d->next) |
| n++; |
| cseek(startoff + sizeof(IMAGE_IMPORT_DESCRIPTOR) * (n + 1)); |
| |
| // write dll names |
| for(d = dr; d != nil; d = d->next) { |
| d->nameoff = cpos() - startoff; |
| strput(d->name); |
| } |
| |
| // write function names |
| for(d = dr; d != nil; d = d->next) { |
| for(m = d->ms; m != nil; m = m->next) { |
| m->off = nextsectoff + cpos() - startoff; |
| wputl(0); // hint |
| strput(m->s->dynimpname); |
| } |
| } |
| |
| // write OriginalFirstThunks |
| oftbase = cpos() - startoff; |
| n = cpos(); |
| for(d = dr; d != nil; d = d->next) { |
| d->thunkoff = cpos() - n; |
| for(m = d->ms; m != nil; m = m->next) |
| put(m->off); |
| put(0); |
| } |
| |
| // add pe section and pad it at the end |
| n = cpos() - startoff; |
| isect = addpesection(".idata", n, n); |
| isect->Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA| |
| IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE; |
| chksectoff(isect, startoff); |
| strnput("", isect->SizeOfRawData - n); |
| endoff = cpos(); |
| |
| // write FirstThunks (allocated in .data section) |
| ftbase = dynamic->value - datsect->VirtualAddress - PEBASE; |
| cseek(datsect->PointerToRawData + ftbase); |
| for(d = dr; d != nil; d = d->next) { |
| for(m = d->ms; m != nil; m = m->next) |
| put(m->off); |
| put(0); |
| } |
| |
| // finally write import descriptor table |
| cseek(startoff); |
| for(d = dr; d != nil; d = d->next) { |
| lputl(isect->VirtualAddress + oftbase + d->thunkoff); |
| lputl(0); |
| lputl(0); |
| lputl(isect->VirtualAddress + d->nameoff); |
| lputl(datsect->VirtualAddress + ftbase + d->thunkoff); |
| } |
| lputl(0); //end |
| lputl(0); |
| lputl(0); |
| lputl(0); |
| lputl(0); |
| |
| // update data directory |
| dd[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = isect->VirtualAddress; |
| dd[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = isect->VirtualSize; |
| dd[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress = dynamic->value - PEBASE; |
| dd[IMAGE_DIRECTORY_ENTRY_IAT].Size = dynamic->size; |
| |
| cseek(endoff); |
| } |
| |
| static int |
| scmp(const void *p1, const void *p2) |
| { |
| Sym *s1, *s2; |
| |
| s1 = *(Sym**)p1; |
| s2 = *(Sym**)p2; |
| return strcmp(s1->dynimpname, s2->dynimpname); |
| } |
| |
| static void |
| initdynexport(void) |
| { |
| Sym *s; |
| |
| nexport = 0; |
| for(s = allsym; s != S; s = s->allsym) { |
| if(!s->reachable || !s->dynimpname || !s->dynexport) |
| continue; |
| if(nexport+1 > sizeof(dexport)/sizeof(dexport[0])) { |
| diag("pe dynexport table is full"); |
| errorexit(); |
| } |
| |
| dexport[nexport] = s; |
| nexport++; |
| } |
| |
| qsort(dexport, nexport, sizeof dexport[0], scmp); |
| } |
| |
| void |
| addexports(void) |
| { |
| IMAGE_SECTION_HEADER *sect; |
| IMAGE_EXPORT_DIRECTORY e; |
| int size, i, va, va_name, va_addr, va_na, v; |
| |
| size = sizeof e + 10*nexport + strlen(outfile) + 1; |
| for(i=0; i<nexport; i++) |
| size += strlen(dexport[i]->dynimpname) + 1; |
| |
| if (nexport == 0) |
| return; |
| |
| sect = addpesection(".edata", size, size); |
| sect->Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ; |
| chksectoff(sect, cpos()); |
| va = sect->VirtualAddress; |
| dd[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress = va; |
| dd[IMAGE_DIRECTORY_ENTRY_EXPORT].Size = sect->VirtualSize; |
| |
| va_name = va + sizeof e + nexport*4; |
| va_addr = va + sizeof e; |
| va_na = va + sizeof e + nexport*8; |
| |
| e.Characteristics = 0; |
| e.MajorVersion = 0; |
| e.MinorVersion = 0; |
| e.NumberOfFunctions = nexport; |
| e.NumberOfNames = nexport; |
| e.Name = va + sizeof e + nexport*10; // Program names. |
| e.Base = 1; |
| e.AddressOfFunctions = va_addr; |
| e.AddressOfNames = va_name; |
| e.AddressOfNameOrdinals = va_na; |
| // put IMAGE_EXPORT_DIRECTORY |
| for (i=0; i<sizeof(e); i++) |
| cput(((char*)&e)[i]); |
| // put EXPORT Address Table |
| for(i=0; i<nexport; i++) |
| lputl(dexport[i]->value - PEBASE); |
| // put EXPORT Name Pointer Table |
| v = e.Name + strlen(outfile)+1; |
| for(i=0; i<nexport; i++) { |
| lputl(v); |
| v += strlen(dexport[i]->dynimpname)+1; |
| } |
| // put EXPORT Ordinal Table |
| for(i=0; i<nexport; i++) |
| wputl(i); |
| // put Names |
| strnput(outfile, strlen(outfile)+1); |
| for(i=0; i<nexport; i++) |
| strnput(dexport[i]->dynimpname, strlen(dexport[i]->dynimpname)+1); |
| strnput("", sect->SizeOfRawData - size); |
| } |
| |
| void |
| dope(void) |
| { |
| Sym *rel; |
| |
| /* relocation table */ |
| rel = lookup(".rel", 0); |
| rel->reachable = 1; |
| rel->type = SELFROSECT; |
| |
| initdynimport(); |
| initdynexport(); |
| } |
| |
| /* |
| * For more than 8 characters section names, name contains a slash (/) that is |
| * followed by an ASCII representation of a decimal number that is an offset into |
| * the string table. |
| * reference: pecoff_v8.docx Page 24. |
| * <http://www.microsoft.com/whdc/system/platform/firmware/PECOFFdwn.mspx> |
| */ |
| IMAGE_SECTION_HEADER* |
| newPEDWARFSection(char *name, vlong size) |
| { |
| IMAGE_SECTION_HEADER *h; |
| char s[8]; |
| |
| if(size == 0) |
| return nil; |
| |
| if(nextsymoff+strlen(name)+1 > sizeof(symnames)) { |
| diag("pe string table is full"); |
| errorexit(); |
| } |
| |
| strcpy(&symnames[nextsymoff], name); |
| sprint(s, "/%d\0", nextsymoff+4); |
| nextsymoff += strlen(name); |
| symnames[nextsymoff] = 0; |
| nextsymoff ++; |
| h = addpesection(s, size, size); |
| h->Characteristics = IMAGE_SCN_MEM_READ| |
| IMAGE_SCN_MEM_DISCARDABLE; |
| |
| return h; |
| } |
| |
| static void |
| addsymtable(void) |
| { |
| IMAGE_SECTION_HEADER *h; |
| int i, size; |
| Sym *s; |
| |
| fh.NumberOfSymbols = sizeof(symlabels)/sizeof(symlabels[0]); |
| size = nextsymoff + 4 + 18*fh.NumberOfSymbols; |
| h = addpesection(".symtab", size, size); |
| h->Characteristics = IMAGE_SCN_MEM_READ| |
| IMAGE_SCN_MEM_DISCARDABLE; |
| chksectoff(h, cpos()); |
| fh.PointerToSymbolTable = cpos(); |
| |
| // put COFF symbol table |
| for (i=0; i<fh.NumberOfSymbols; i++) { |
| s = rlookup(symlabels[i], 0); |
| strnput(s->name, 8); |
| lputl(datoff(s->value)); |
| wputl(textsect); |
| wputl(0x0308); // "array of structs" |
| cput(2); // storage class: external |
| cput(0); // no aux entries |
| } |
| |
| // put COFF string table |
| lputl(nextsymoff + 4); |
| for (i=0; i<nextsymoff; i++) |
| cput(symnames[i]); |
| strnput("", h->SizeOfRawData - size); |
| } |
| |
| void |
| setpersrc(Sym *sym) |
| { |
| if(rsrcsym != nil) |
| diag("too many .rsrc sections"); |
| |
| rsrcsym = sym; |
| } |
| |
| void |
| addpersrc(void) |
| { |
| IMAGE_SECTION_HEADER *h; |
| uchar *p; |
| uint32 val; |
| Reloc *r; |
| |
| if(rsrcsym == nil) |
| return; |
| |
| h = addpesection(".rsrc", rsrcsym->size, rsrcsym->size); |
| h->Characteristics = IMAGE_SCN_MEM_READ| |
| IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_INITIALIZED_DATA; |
| chksectoff(h, cpos()); |
| // relocation |
| for(r=rsrcsym->r; r<rsrcsym->r+rsrcsym->nr; r++) { |
| p = rsrcsym->p + r->off; |
| val = h->VirtualAddress + r->add; |
| // 32-bit little-endian |
| p[0] = val; |
| p[1] = val>>8; |
| p[2] = val>>16; |
| p[3] = val>>24; |
| } |
| cwrite(rsrcsym->p, rsrcsym->size); |
| strnput("", h->SizeOfRawData - rsrcsym->size); |
| |
| // update data directory |
| dd[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = h->VirtualAddress; |
| dd[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = h->VirtualSize; |
| } |
| |
| static void |
| addexcept(IMAGE_SECTION_HEADER *text) |
| { |
| IMAGE_SECTION_HEADER *pdata, *xdata; |
| vlong startoff; |
| uvlong n; |
| Sym *sym; |
| |
| if(thechar != '6') |
| return; |
| |
| // write unwind info |
| sym = lookup("runtime.sigtramp", 0); |
| startoff = cpos(); |
| lputl(9); // version=1, flags=UNW_FLAG_EHANDLER, rest 0 |
| lputl(sym->value - PEBASE); |
| lputl(0); |
| |
| n = cpos() - startoff; |
| xdata = addpesection(".xdata", n, n); |
| xdata->Characteristics = IMAGE_SCN_MEM_READ| |
| IMAGE_SCN_CNT_INITIALIZED_DATA; |
| chksectoff(xdata, startoff); |
| strnput("", xdata->SizeOfRawData - n); |
| |
| // write a function table entry for the whole text segment |
| startoff = cpos(); |
| lputl(text->VirtualAddress); |
| lputl(text->VirtualAddress + text->VirtualSize); |
| lputl(xdata->VirtualAddress); |
| |
| n = cpos() - startoff; |
| pdata = addpesection(".pdata", n, n); |
| pdata->Characteristics = IMAGE_SCN_MEM_READ| |
| IMAGE_SCN_CNT_INITIALIZED_DATA; |
| chksectoff(pdata, startoff); |
| strnput("", pdata->SizeOfRawData - n); |
| |
| dd[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress = pdata->VirtualAddress; |
| dd[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size = pdata->VirtualSize; |
| } |
| |
| void |
| asmbpe(void) |
| { |
| IMAGE_SECTION_HEADER *t, *d; |
| |
| switch(thechar) { |
| default: |
| diag("unknown PE architecture"); |
| errorexit(); |
| case '6': |
| fh.Machine = IMAGE_FILE_MACHINE_AMD64; |
| break; |
| case '8': |
| fh.Machine = IMAGE_FILE_MACHINE_I386; |
| break; |
| } |
| |
| t = addpesection(".text", segtext.len, segtext.len); |
| t->Characteristics = IMAGE_SCN_CNT_CODE| |
| IMAGE_SCN_CNT_INITIALIZED_DATA| |
| IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_READ; |
| chksectseg(t, &segtext); |
| textsect = nsect; |
| |
| d = addpesection(".data", segdata.len, segdata.filelen); |
| d->Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA| |
| IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE; |
| chksectseg(d, &segdata); |
| |
| if(!debug['s']) |
| dwarfaddpeheaders(); |
| |
| cseek(nextfileoff); |
| addimports(d); |
| addexports(); |
| addsymtable(); |
| addpersrc(); |
| addexcept(t); |
| |
| fh.NumberOfSections = nsect; |
| fh.TimeDateStamp = time(0); |
| fh.Characteristics = IMAGE_FILE_RELOCS_STRIPPED| |
| IMAGE_FILE_EXECUTABLE_IMAGE|IMAGE_FILE_DEBUG_STRIPPED; |
| if (pe64) { |
| fh.SizeOfOptionalHeader = sizeof(oh64); |
| fh.Characteristics |= IMAGE_FILE_LARGE_ADDRESS_AWARE; |
| set(Magic, 0x20b); // PE32+ |
| } else { |
| fh.SizeOfOptionalHeader = sizeof(oh); |
| fh.Characteristics |= IMAGE_FILE_32BIT_MACHINE; |
| set(Magic, 0x10b); // PE32 |
| oh.BaseOfData = d->VirtualAddress; |
| } |
| set(MajorLinkerVersion, 3); |
| set(MinorLinkerVersion, 0); |
| set(SizeOfCode, t->SizeOfRawData); |
| set(SizeOfInitializedData, d->SizeOfRawData); |
| set(SizeOfUninitializedData, 0); |
| set(AddressOfEntryPoint, entryvalue()-PEBASE); |
| set(BaseOfCode, t->VirtualAddress); |
| set(ImageBase, PEBASE); |
| set(SectionAlignment, PESECTALIGN); |
| set(FileAlignment, PEFILEALIGN); |
| set(MajorOperatingSystemVersion, 4); |
| set(MinorOperatingSystemVersion, 0); |
| set(MajorImageVersion, 1); |
| set(MinorImageVersion, 0); |
| set(MajorSubsystemVersion, 4); |
| set(MinorSubsystemVersion, 0); |
| set(SizeOfImage, nextsectoff); |
| set(SizeOfHeaders, PEFILEHEADR); |
| if(strcmp(headstring, "windowsgui") == 0) |
| set(Subsystem, IMAGE_SUBSYSTEM_WINDOWS_GUI); |
| else |
| set(Subsystem, IMAGE_SUBSYSTEM_WINDOWS_CUI); |
| |
| // Disable stack growth as we don't want Windows to |
| // fiddle with the thread stack limits, which we set |
| // ourselves to circumvent the stack checks in the |
| // Windows exception dispatcher. |
| // Commit size must be strictly less than reserve |
| // size otherwise reserve will be rounded up to a |
| // larger size, as verified with VMMap. |
| set(SizeOfStackReserve, 0x00010000); |
| set(SizeOfStackCommit, 0x0000ffff); |
| set(SizeOfHeapReserve, 0x00100000); |
| set(SizeOfHeapCommit, 0x00001000); |
| set(NumberOfRvaAndSizes, 16); |
| |
| pewrite(); |
| } |