d10f745d70b458137254ef66449d98b555fc57e8
[chise/xemacs-chise.git.1] / src / unexnt.c
1 /* unexec for GNU Emacs on Windows NT.
2    Copyright (C) 1994 Free Software Foundation, Inc.
3
4 This file is part of XEmacs.
5
6 XEmacs is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
9 later version.
10
11 XEmacs is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with XEmacs; see the file COPYING.  If not, write to the Free
18 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20
21    Geoff Voelker (voelker@cs.washington.edu) 8-12-94 */
22
23 /* Adapted for XEmacs by David Hobley <david@spook-le0.cia.com.au> */
24
25 /* The linkers that come with MSVC >= 4.0 merge .bss into .data and reorder
26  * uninitialised data so that the .data section looks like:
27  *
28  *      crt0 initialised data
29  *      emacs initialised data
30  *              <my_edata>
31  *      library initialised data
32  *              <start of bss part of .data>
33  *      emacs static uninitialised data
34  *      library static uninitialised data
35  *      emacs global uninitialised data
36  *              <my_ebss>
37  *      library global uninitialised data
38  *
39  * This means that we can't use the normal my_ebss in lastfile.c trick to
40  * differentiate between unitialised data that belongs to emacs and
41  * uninitialised data that belongs to system libraries. This is bad because
42  * we do want to initialise the emacs data, but we don't want to initialise
43  * the system library data.
44  *
45  * To solve this problem using MSVC >= 5.0 we use a pragma directive to tell
46  * the compiler to put emacs's data (both initialised and uninitialised) in
47  * a separate section in the executable, and we only dump that section. This
48  * means that all files that define initialized data must include config.h
49  * to pick up the pragma. We don't try to make any part of that section
50  * read-only.
51  *
52  * This pragma directive isn't supported by the MSVC 4.x compiler. Instead,
53  * we dump crt0 initialised data and library static uninitialised data in
54  * addition to the emacs data. This is wrong, but we appear to be able to
55  * get away with it. A proper fix might involve the introduction of a static
56  * version of my_ebss in lastfile.c and a new firstfile.c file.  jhar */
57
58 #include <config.h>
59 #include <stdlib.h>     /* _fmode */
60 #include <stdio.h>
61 #include <fcntl.h>
62 #include <windows.h>
63
64 #include "nt.h"
65 #include "ntheap.h"
66
67 /* From IMAGEHLP.H which is not installed by default by MSVC < 5 */
68 /* The IMAGEHLP.DLL library is not distributed by default with Windows95 */
69 typedef PIMAGE_NT_HEADERS
70 (__stdcall * pfnCheckSumMappedFile_t) (LPVOID BaseAddress, DWORD FileLength,
71                                        LPDWORD HeaderSum, LPDWORD CheckSum);
72
73
74 #if 0
75 extern BOOL ctrl_c_handler (unsigned long type);
76 #endif
77
78 /* Sync with FSF Emacs 19.34.6
79    note: struct file_data is now defined in nt.h */
80
81 enum {
82   HEAP_UNINITIALIZED = 1,
83   HEAP_UNLOADED,
84   HEAP_LOADED
85 };
86
87 /* Basically, our "initialized" flag.  */
88 int heap_state = HEAP_UNINITIALIZED;
89
90 /* So we can find our heap in the file to recreate it.  */
91 unsigned long heap_index_in_executable = UNINIT_LONG;
92
93 void get_section_info (file_data *p_file);
94 void copy_executable_and_dump_data_section (file_data *, file_data *);
95 void dump_bss_and_heap (file_data *p_infile, file_data *p_outfile);
96
97 /* Cached info about the .data section in the executable.  */
98 PUCHAR data_start_va = UNINIT_PTR;
99 DWORD  data_start_file = UNINIT_LONG;
100 DWORD  data_size = UNINIT_LONG;
101
102 /* Cached info about the .bss section in the executable.  */
103 PUCHAR bss_start = UNINIT_PTR;
104 DWORD  bss_size = UNINIT_LONG;
105
106 #ifdef HAVE_NTGUI
107 HINSTANCE hinst = NULL;
108 HINSTANCE hprevinst = NULL;
109 LPSTR lpCmdLine = "";
110 int nCmdShow = 0;
111 #endif /* HAVE_NTGUI */
112
113 /* Startup code for running on NT.  When we are running as the dumped
114    version, we need to bootstrap our heap and .bss section into our
115    address space before we can actually hand off control to the startup
116    code supplied by NT (primarily because that code relies upon malloc ()).  */
117
118 /* **********************
119    Hackers please remember, this _start() thingy is *not* called neither
120    when dumping portably, nor when running from temacs! Do not put
121    significant XEmacs initialization here!
122    ********************** */
123
124 void
125 _start (void)
126 {
127   extern void mainCRTStartup (void);
128
129   /* Cache system info, e.g., the NT page size.  */
130   cache_system_info ();
131
132   /* If we're a dumped version of emacs then we need to recreate
133      our heap and play tricks with our .bss section.  Do this before
134      start up.  (WARNING:  Do not put any code before this section
135      that relies upon malloc () and runs in the dumped version.  It
136      won't work.)  */
137   if (heap_state == HEAP_UNLOADED) 
138     {
139       char executable_path[MAX_PATH];
140
141       if (GetModuleFileName (NULL, executable_path, MAX_PATH) == 0) 
142         {
143           exit (1);
144         }
145
146       /* #### This is super-bogus. When I rename xemacs.exe,
147          the renamed file still loads its heap from xemacs.exe --kkm */
148 #if 0
149       {
150         /* To allow profiling, make sure executable_path names the .exe
151            file, not the file created by the profiler */
152         char *p = strrchr (executable_path, '\\');
153         strcpy (p+1, PATH_PROGNAME ".exe");
154       }
155 #endif
156
157       recreate_heap (executable_path);
158       heap_state = HEAP_LOADED;
159     }
160
161   /* #### This is bogus, too. _fmode is set to different values
162      when we run `xemacs' and `temacs run-emacs'. The sooner we
163      hit and fix all the weirdities this causes us, the better --kkm */
164 #if 0
165   /* The default behavior is to treat files as binary and patch up
166      text files appropriately, in accordance with the MSDOS code.  */
167   _fmode = O_BINARY;
168 #endif
169
170 #if 0
171   /* This prevents ctrl-c's in shells running while we're suspended from
172      having us exit.  */
173   SetConsoleCtrlHandler ((PHANDLER_ROUTINE) ctrl_c_handler, TRUE);
174 #endif
175
176   /* Invoke the NT CRT startup routine now that our housecleaning
177      is finished.  */
178 #ifdef HAVE_NTGUI
179   /* determine WinMain args like crt0.c does */
180   hinst = GetModuleHandle(NULL);
181   lpCmdLine = GetCommandLine();
182   nCmdShow = SW_SHOWDEFAULT;
183 #endif
184   mainCRTStartup ();
185 }
186
187 /* Dump out .data and .bss sections into a new executable.  */
188 void
189 unexec (char *new_name, char *old_name, void *start_data, void *start_bss,
190         void *entry_address)
191 {
192   file_data in_file, out_file;
193   char out_filename[MAX_PATH], in_filename[MAX_PATH];
194   unsigned long size;
195   char *ptr;
196   HINSTANCE hImagehelp;
197   
198   /* Make sure that the input and output filenames have the
199      ".exe" extension...patch them up if they don't.  */
200   strcpy (in_filename, old_name);
201   ptr = in_filename + strlen (in_filename) - 4;
202   if (strcmp (ptr, ".exe"))
203     strcat (in_filename, ".exe");
204
205   strcpy (out_filename, new_name);
206   ptr = out_filename + strlen (out_filename) - 4;
207   if (strcmp (ptr, ".exe"))
208     strcat (out_filename, ".exe");
209
210   printf ("Dumping from %s\n", in_filename);
211   printf ("          to %s\n", out_filename);
212
213   /* We need to round off our heap to NT's allocation unit (64KB).  */
214   round_heap (get_allocation_unit ());
215
216   /* Open the undumped executable file.  */
217   if (!open_input_file (&in_file, in_filename))
218     {
219       printf ("Failed to open %s (%d)...bailing.\n", 
220               in_filename, GetLastError ());
221       exit (1);
222     }
223
224   /* Get the interesting section info, like start and size of .bss...  */
225   get_section_info (&in_file);
226
227   /* The size of the dumped executable is the size of the original
228      executable plus the size of the heap and the size of the .bss section.  */
229   heap_index_in_executable = (unsigned long)
230     round_to_next ((unsigned char *) in_file.size, get_allocation_unit ());
231   size = heap_index_in_executable + get_committed_heap_size () + bss_size;
232   if (!open_output_file (&out_file, out_filename, size))
233     {
234       printf ("Failed to open %s (%d)...bailing.\n", 
235               out_filename, GetLastError ());
236       exit (1);
237     }
238
239   /* Set the flag (before dumping).  */
240   heap_state = HEAP_UNLOADED;
241
242   copy_executable_and_dump_data_section (&in_file, &out_file);
243   dump_bss_and_heap (&in_file, &out_file);
244
245   /* Patch up header fields; profiler is picky about this. */
246   hImagehelp = LoadLibrary ("imagehlp.dll");
247   if (hImagehelp)
248   {
249     PIMAGE_DOS_HEADER dos_header;
250     PIMAGE_NT_HEADERS nt_header;
251
252     DWORD  headersum;
253     DWORD  checksum;
254     pfnCheckSumMappedFile_t pfnCheckSumMappedFile;
255
256     dos_header = (PIMAGE_DOS_HEADER) out_file.file_base;
257     nt_header = (PIMAGE_NT_HEADERS) ((char *) dos_header + dos_header->e_lfanew);
258
259     nt_header->OptionalHeader.CheckSum = 0;
260 #if 0
261     nt_header->FileHeader.TimeDateStamp = time (NULL);
262     dos_header->e_cp = size / 512;
263     nt_header->OptionalHeader.SizeOfImage = size;
264 #endif
265
266     pfnCheckSumMappedFile =
267       (pfnCheckSumMappedFile_t) GetProcAddress (hImagehelp,
268                                                 "CheckSumMappedFile");
269     if (pfnCheckSumMappedFile)
270       {
271 #if 0
272         nt_header->FileHeader.TimeDateStamp = time (NULL);
273 #endif
274         pfnCheckSumMappedFile (out_file.file_base,
275                                out_file.size,
276                                &headersum,
277                                &checksum);
278         nt_header->OptionalHeader.CheckSum = checksum;
279       }
280     FreeLibrary (hImagehelp);
281   }
282
283   close_file_data (&in_file);
284   close_file_data (&out_file);
285 }
286
287 /* Routines to manipulate NT executable file sections.  */
288
289 #ifndef DUMP_SEPARATE_SECTION
290 static void
291 get_bss_info_from_map_file (file_data *p_infile, PUCHAR *p_bss_start, 
292                             DWORD *p_bss_size)
293 {
294   int n, start, len;
295   char map_filename[MAX_PATH];
296   char buffer[256];
297   FILE *map;
298
299   /* Overwrite the .exe extension on the executable file name with
300      the .map extension.  */
301   strcpy (map_filename, p_infile->name);
302   n = strlen (map_filename) - 3;
303   strcpy (&map_filename[n], "map");
304
305   map = fopen (map_filename, "r");
306   if (!map)
307     {
308       printf ("Failed to open map file %s, error %d...bailing out.\n",
309               map_filename, GetLastError ());
310       exit (-1);
311     }
312
313   while (fgets (buffer, sizeof (buffer), map))
314     {
315       if (!(strstr (buffer, ".bss") && strstr (buffer, "DATA")))
316         continue;
317       n = sscanf (buffer, " %*d:%x %x", &start, &len);
318       if (n != 2)
319         {
320           printf ("Failed to scan the .bss section line:\n%s", buffer);
321           exit (-1);
322         }
323       break;
324     }
325   *p_bss_start = (PUCHAR) start;
326   *p_bss_size = (DWORD) len;
327 }
328 #endif
329
330 /* Flip through the executable and cache the info necessary for dumping.  */
331 static void
332 get_section_info (file_data *p_infile)
333 {
334   PIMAGE_DOS_HEADER dos_header;
335   PIMAGE_NT_HEADERS nt_header;
336   PIMAGE_SECTION_HEADER section, data_section;
337   unsigned char *ptr;
338   int i;
339   
340   dos_header = (PIMAGE_DOS_HEADER) p_infile->file_base;
341   if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) 
342     {
343       printf ("Unknown EXE header in %s...bailing.\n", p_infile->name);
344       exit (1);
345     }
346   nt_header = (PIMAGE_NT_HEADERS) (((unsigned long) dos_header) + 
347                                    dos_header->e_lfanew);
348   if (nt_header == NULL) 
349     {
350       printf ("Failed to find IMAGE_NT_HEADER in %s...bailing.\n", 
351              p_infile->name);
352       exit (1);
353     }
354
355   /* Check the NT header signature ...  */
356   if (nt_header->Signature != IMAGE_NT_SIGNATURE) 
357     {
358       printf ("Invalid IMAGE_NT_SIGNATURE 0x%x in %s...bailing.\n",
359               nt_header->Signature, p_infile->name);
360     }
361
362   /* Flip through the sections for .data and .bss ...  */
363   section = (PIMAGE_SECTION_HEADER) IMAGE_FIRST_SECTION (nt_header);
364   for (i = 0; i < nt_header->FileHeader.NumberOfSections; i++) 
365     {
366 #ifndef DUMP_SEPARATE_SECTION
367       if (!strcmp (section->Name, ".bss")) 
368         {
369           extern int my_ebss;           /* From lastfile.c  */
370
371           ptr = (char *) nt_header->OptionalHeader.ImageBase +
372             section->VirtualAddress;
373           bss_start = ptr;
374           bss_size = (char*)&my_ebss - (char*)bss_start;
375         }
376
377       if (!strcmp (section->Name, ".data")) 
378 #else
379       if (!strcmp (section->Name, "xdata"))
380 #endif
381         {
382           extern char my_edata[];       /* From lastfile.c  */
383
384           /* The .data section.  */
385           data_section = section;
386           ptr = (char *) nt_header->OptionalHeader.ImageBase +
387             section->VirtualAddress;
388           data_start_va = ptr;
389           data_start_file = section->PointerToRawData;
390
391 #ifndef DUMP_SEPARATE_SECTION
392           /* Write only the part of the section that contains emacs data. */
393           data_size = my_edata - data_start_va;
394 #else
395           /* Write back the full section.  */
396           data_size = section->SizeOfRawData;
397
398           /* This code doesn't know how to grow the raw size of a section. */
399           if (section->SizeOfRawData < section->Misc.VirtualSize)
400             {
401               printf ("The emacs data section is smaller than expected"
402                       "...bailing.\n");
403               exit (1);
404             }
405 #endif
406         }
407       section++;
408     }
409
410 #ifndef DUMP_SEPARATE_SECTION
411   if (bss_start == UNINIT_PTR)
412     {
413       /* Starting with MSVC 4.0, the .bss section has been eliminated
414          and appended virtually to the end of the .data section.  Our
415          only hint about where the .bss section starts in the address
416          comes from the SizeOfRawData field in the .data section
417          header.  Unfortunately, this field is only approximate, as it
418          is a rounded number and is typically rounded just beyond the
419          start of the .bss section.  To find the start and size of the
420          .bss section exactly, we have to peek into the map file.  */
421       extern int my_ebss;
422
423       get_bss_info_from_map_file (p_infile, &ptr, &bss_size);
424       bss_start = ptr + nt_header->OptionalHeader.ImageBase
425         + data_section->VirtualAddress;
426       bss_size = (char*)&my_ebss - (char*)bss_start;
427     }
428 #else
429   bss_size = 0;
430 #endif
431 }
432
433
434 /* The dump routines.  */
435
436 #ifdef DEBUG_XEMACS
437 #define DUMP_MSG(x) printf x
438 #else
439 #define DUMP_MSG(x)
440 #endif
441
442 static void
443 copy_executable_and_dump_data_section (file_data *p_infile,
444                                        file_data *p_outfile)
445 {
446   unsigned char *data_file, *data_va;
447   unsigned long size, index;
448
449   /* Get a pointer to where the raw data should go in the executable file.  */
450   data_file = (char *) p_outfile->file_base + data_start_file;
451
452   /* Get a pointer to the raw data in our address space.  */
453   data_va = data_start_va;
454
455   size = (DWORD) data_file - (DWORD) p_outfile->file_base;
456   DUMP_MSG (("Copying executable up to data section...\n"));
457   DUMP_MSG (("\t0x%08x Offset in input file.\n", 0));
458   DUMP_MSG (("\t0x%08x Offset in output file.\n", 0));
459   DUMP_MSG (("\t0x%08x Size in bytes.\n", size));
460   memcpy (p_outfile->file_base, p_infile->file_base, size);
461
462   size = data_size;
463   DUMP_MSG (("Dumping data section...\n"));
464   DUMP_MSG (("\t0x%08x Address in process.\n", data_va));
465   DUMP_MSG (("\t0x%08x Offset in output file.\n", 
466              (char*)data_file - p_outfile->file_base));
467   DUMP_MSG (("\t0x%08x Size in bytes.\n", size));
468   memcpy (data_file, data_va, size);
469
470   index = (DWORD) data_file + size - (DWORD) p_outfile->file_base;
471   size = p_infile->size - index;
472   DUMP_MSG (("Copying rest of executable...\n"));
473   DUMP_MSG (("\t0x%08x Offset in input file.\n", index));
474   DUMP_MSG (("\t0x%08x Offset in output file.\n", index));
475   DUMP_MSG (("\t0x%08x Size in bytes.\n", size));
476   memcpy ((char *) p_outfile->file_base + index, 
477           (char *) p_infile->file_base + index, size);
478 }
479
480 static void
481 dump_bss_and_heap (file_data *p_infile, file_data *p_outfile)
482 {
483     unsigned char *heap_data;
484     unsigned long size, index;
485
486     DUMP_MSG (("Dumping heap onto end of executable...\n"));
487
488     index = heap_index_in_executable;
489     size = get_committed_heap_size ();
490     heap_data = get_heap_start ();
491
492     DUMP_MSG (("\t0x%08x Heap start in process.\n", heap_data));
493     DUMP_MSG (("\t0x%08x Heap offset in executable.\n", index));
494     DUMP_MSG (("\t0x%08x Heap size in bytes.\n", size));
495
496     memcpy ((PUCHAR) p_outfile->file_base + index, heap_data, size);
497
498 #ifndef DUMP_SEPARATE_SECTION
499     DUMP_MSG (("Dumping bss onto end of executable...\n"));
500     
501     index += size;
502     size = bss_size;
503
504     DUMP_MSG (("\t0x%08x BSS start in process.\n", bss_start));
505     DUMP_MSG (("\t0x%08x BSS offset in executable.\n", index));
506     DUMP_MSG (("\t0x%08x BSS size in bytes.\n", size));
507     memcpy ((char *) p_outfile->file_base + index, bss_start, size);
508 #endif
509 }
510
511 #undef DUMP_MSG
512
513 /* Reload and remap routines.  */
514
515
516 /* Load the dumped .bss section into the .bss area of our address space.  */
517 /* Already done if the .bss  was part of a separate emacs data section */
518 void
519 read_in_bss (char *filename)
520 {
521 #ifndef DUMP_SEPARATE_SECTION
522   HANDLE file;
523   unsigned long index, n_read;
524
525   file = CreateFile (filename, GENERIC_READ, FILE_SHARE_READ, NULL,
526                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
527   if (file == INVALID_HANDLE_VALUE)
528     abort ();
529   
530   /* Seek to where the .bss section is tucked away after the heap...  */
531   index = heap_index_in_executable + get_committed_heap_size ();
532   if (SetFilePointer (file, index, NULL, FILE_BEGIN) == 0xFFFFFFFF) 
533     abort ();
534
535   /* Ok, read in the saved .bss section and initialize all 
536      uninitialized variables.  */
537   if (!ReadFile (file, bss_start, bss_size, &n_read, NULL))
538     abort ();
539
540   CloseHandle (file);
541 #endif
542 }
543
544 /* Map the heap dumped into the executable file into our address space.  */
545 void 
546 map_in_heap (char *filename)
547 {
548   HANDLE file;
549   HANDLE file_mapping;
550   void  *file_base;
551   unsigned long size, upper_size, n_read;
552
553   file = CreateFile (filename, GENERIC_READ, FILE_SHARE_READ, NULL,
554                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
555   if (file == INVALID_HANDLE_VALUE) 
556     abort ();
557
558   size = GetFileSize (file, &upper_size);
559   file_mapping = CreateFileMapping (file, NULL, PAGE_WRITECOPY, 
560                                     0, size, NULL);
561   if (!file_mapping) 
562     abort ();
563
564   size = get_committed_heap_size ();
565   file_base = MapViewOfFileEx (file_mapping, FILE_MAP_COPY, 0, 
566                                heap_index_in_executable, size,
567                                get_heap_start ());
568   if (file_base != 0) 
569     {
570       return;
571     }
572
573   /* If we don't succeed with the mapping, then copy from the 
574      data into the heap.  */
575
576   CloseHandle (file_mapping);
577
578   if (VirtualAlloc (get_heap_start (), get_committed_heap_size (),
579                     MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE) == NULL)
580     abort ();
581
582   /* Seek to the location of the heap data in the executable.  */
583   if (SetFilePointer (file, heap_index_in_executable,
584                       NULL, FILE_BEGIN) == 0xFFFFFFFF)
585     abort ();
586
587   /* Read in the data.  */
588   if (!ReadFile (file, get_heap_start (), 
589                  get_committed_heap_size (), &n_read, NULL))
590     abort ();
591
592   CloseHandle (file);
593 }