diff options
Diffstat (limited to 'src/cmd/ld/pe.c')
-rw-r--r-- | src/cmd/ld/pe.c | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/src/cmd/ld/pe.c b/src/cmd/ld/pe.c new file mode 100644 index 000000000..334c9959f --- /dev/null +++ b/src/cmd/ld/pe.c @@ -0,0 +1,593 @@ +// 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 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 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; + + if(nextsymoff == 0) + return; + + size = nextsymoff + 4 + 18; + h = addpesection(".symtab", size, size); + h->Characteristics = IMAGE_SCN_MEM_READ| + IMAGE_SCN_MEM_DISCARDABLE; + chksectoff(h, cpos()); + fh.PointerToSymbolTable = cpos(); + fh.NumberOfSymbols = 1; + strnput("", 18); // one empty symbol + // put symbol string table + lputl(size); + 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; +} + +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); + + 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(); + + 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, 1); + 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); + set(SizeOfStackReserve, 0x0040000); + set(SizeOfStackCommit, 0x00001000); + set(SizeOfHeapReserve, 0x00100000); + set(SizeOfHeapCommit, 0x00001000); + set(NumberOfRvaAndSizes, 16); + + pewrite(); +} |