// 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 }; static LSym *rsrcsym; static char* strtbl; static int strtblnextoff; static int strtblsize; int32 PESECTHEADR; int32 PEFILEHEADR; static int pe64; static int nsect; static int nextsectoff; static int nextfileoff; static int textsect; static int datasect; 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 { LSym* 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 LSym *dexport[1024]; static int nexport; typedef struct COFFSym COFFSym; struct COFFSym { LSym* sym; int strtbloff; int sect; vlong value; }; static COFFSym* coffsym; static int ncoffsym; 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; // some mingw libs depend on this symbol, for example, FindPESectionByName xdefine("__image_base__", SDATA, PEBASE); xdefine("_image_base__", SDATA, PEBASE); } 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; LSym *s, *dynamic; dr = nil; m = nil; for(s = ctxt->allsym; s != S; s = s->allsym) { if(!s->reachable || s->type != SDYNIMPORT) 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 = linklookup(ctxt, ".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; LSym* dynamic; startoff = cpos(); dynamic = linklookup(ctxt, ".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->extname); } } // 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) { LSym *s1, *s2; s1 = *(LSym**)p1; s2 = *(LSym**)p2; return strcmp(s1->extname, s2->extname); } static void initdynexport(void) { LSym *s; nexport = 0; for(s = ctxt->allsym; s != S; s = s->allsym) { if(!s->reachable || !(s->cgoexport & CgoExportDynamic)) 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; iextname) + 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; ivalue - PEBASE); // put EXPORT Name Pointer Table v = e.Name + strlen(outfile)+1; for(i=0; iextname)+1; } // put EXPORT Ordinal Table for(i=0; iextname, strlen(dexport[i]->extname)+1); strnput("", sect->SizeOfRawData - size); } void dope(void) { LSym *rel; /* relocation table */ rel = linklookup(ctxt, ".rel", 0); rel->reachable = 1; rel->type = SELFROSECT; initdynimport(); initdynexport(); } static int strtbladd(char *name) { int newsize, thisoff; newsize = strtblnextoff + strlen(name) + 1; if(newsize > strtblsize) { strtblsize = 2 * (newsize + (1<<18)); strtbl = realloc(strtbl, strtblsize); } thisoff = strtblnextoff+4; // first string starts at offset=4 strcpy(&strtbl[strtblnextoff], name); strtblnextoff += strlen(name); strtbl[strtblnextoff] = 0; strtblnextoff++; return thisoff; } /* * 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. * */ IMAGE_SECTION_HEADER* newPEDWARFSection(char *name, vlong size) { IMAGE_SECTION_HEADER *h; char s[8]; int off; if(size == 0) return nil; off = strtbladd(name); sprint(s, "/%d\0", off); h = addpesection(s, size, size); h->Characteristics = IMAGE_SCN_MEM_READ| IMAGE_SCN_MEM_DISCARDABLE; return h; } static void addsym(LSym *s, char *name, int type, vlong addr, vlong size, int ver, LSym *gotype) { COFFSym *cs; USED(name); USED(addr); USED(size); USED(ver); USED(gotype); if(s == nil) return; if(s->sect == nil) return; switch(type) { default: return; case 'D': case 'B': case 'T': break; } if(coffsym) { cs = &coffsym[ncoffsym]; cs->sym = s; if(strlen(s->name) > 8) cs->strtbloff = strtbladd(s->name); if(s->value >= segdata.vaddr) { cs->value = s->value - segdata.vaddr; cs->sect = datasect; } else if(s->value >= segtext.vaddr) { cs->value = s->value - segtext.vaddr; cs->sect = textsect; } else { cs->value = 0; cs->sect = 0; diag("addsym %#llx", addr); } } ncoffsym++; } static void addsymtable(void) { IMAGE_SECTION_HEADER *h; int i, size; COFFSym *s; if(!debug['s']) { genasmsym(addsym); coffsym = mal(ncoffsym * sizeof coffsym[0]); ncoffsym = 0; genasmsym(addsym); } size = strtblnextoff + 4 + 18*ncoffsym; h = addpesection(".symtab", size, size); h->Characteristics = IMAGE_SCN_MEM_READ| IMAGE_SCN_MEM_DISCARDABLE; chksectoff(h, cpos()); fh.PointerToSymbolTable = cpos(); fh.NumberOfSymbols = ncoffsym; // put COFF symbol table for (i=0; istrtbloff == 0) strnput(s->sym->name, 8); else { lputl(0); lputl(s->strtbloff); } lputl(s->value); wputl(s->sect); wputl(0x0308); // "array of structs" cput(2); // storage class: external cput(0); // no aux entries } // put COFF string table lputl(strtblnextoff + 4); for (i=0; iSizeOfRawData - size); } void setpersrc(LSym *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; rr+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; } 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); datasect = nsect; if(!debug['s']) dwarfaddpeheaders(); cseek(nextfileoff); addimports(d); addexports(); addsymtable(); addpersrc(); 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. // Go code would be OK with 64k stacks, but we need larger stacks for cgo. // That default stack reserve size affects only the main thread, // for other threads we specify stack size in runtime explicitly // (runtime knows whether cgo is enabled or not). // If you change stack reserve sizes here, // change STACKSIZE in runtime/cgo/gcc_windows_{386,amd64}.c as well. if(!iscgo) { set(SizeOfStackReserve, 0x00010000); set(SizeOfStackCommit, 0x0000ffff); } else { set(SizeOfStackReserve, pe64 ? 0x00200000 : 0x00100000); // account for 2 guard pages set(SizeOfStackCommit, (pe64 ? 0x00200000 : 0x00100000) - 0x2000); } set(SizeOfHeapReserve, 0x00100000); set(SizeOfHeapCommit, 0x00001000); set(NumberOfRvaAndSizes, 16); pewrite(); }