2001-07-24 Miguel de Icaza <miguel@ximian.com>
[mono.git] / mono / metadata / image.c
1 /*
2  * image.c: Routines for manipulating an image stored in an
3  * extended PE/COFF file.
4  * 
5  * Author:
6  *   Miguel de Icaza (miguel@ximian.com)
7  *
8  * (C) 2001 Ximian, Inc.  http://www.ximian.com
9  *
10  * TODO:
11  *   Implement big-endian versions of the reading routines.
12  */
13 #include <config.h>
14 #include <stdio.h>
15 #include <glib.h>
16 #include <errno.h>
17 #include <string.h>
18 #include "image.h"
19 #include "cil-coff.h"
20 #include "rawbuffer.h"
21 #include "endian.h"
22
23 #define INVALID_ADDRESS 0xffffffff
24
25 /*
26  * Keeps track of the various assemblies loaded
27  */
28 static GHashTable *loaded_images_hash;
29
30 guint32
31 cli_rva_image_map (cli_image_info_t *iinfo, guint32 addr)
32 {
33         const int top = iinfo->cli_section_count;
34         section_table_t *tables = iinfo->cli_section_tables;
35         int i;
36         
37         for (i = 0; i < top; i++){
38                 if ((addr >= tables->st_virtual_address) &&
39                     (addr < tables->st_virtual_address + tables->st_raw_data_size)){
40                         return addr - tables->st_virtual_address + tables->st_raw_data_ptr;
41                 }
42                 tables++;
43         }
44         return INVALID_ADDRESS;
45 }
46
47 char *
48 cli_rva_map (cli_image_info_t *iinfo, guint32 addr)
49 {
50         const int top = iinfo->cli_section_count;
51         section_table_t *tables = iinfo->cli_section_tables;
52         int i;
53         
54         for (i = 0; i < top; i++){
55                 if ((addr >= tables->st_virtual_address) &&
56                     (addr < tables->st_virtual_address + tables->st_raw_data_size)){
57                         return iinfo->cli_sections [i] +
58                                 (addr - tables->st_virtual_address);
59                 }
60                 tables++;
61         }
62         return NULL;
63 }
64
65 /**
66  * mono_image_ensure_section_idx:
67  * @image: The image we are operating on
68  * @section: section number that we will load/map into memory
69  *
70  * This routine makes sure that we have an in-memory copy of
71  * an image section (.text, .rsrc, .data).
72  *
73  * Returns: TRUE on success
74  */
75 int
76 mono_image_ensure_section_idx (MonoImage *image, int section)
77 {
78         cli_image_info_t *iinfo = image->image_info;
79         section_table_t *sect;
80         gboolean writable;
81         
82         g_return_val_if_fail (section < iinfo->cli_section_count, FALSE);
83
84         if (iinfo->cli_sections [section] != NULL)
85                 return TRUE;
86
87         sect = &iinfo->cli_section_tables [section];
88         
89         writable = sect->st_flags & SECT_FLAGS_MEM_WRITE;
90
91         iinfo->cli_sections [section] = raw_buffer_load (
92                 fileno (image->f), writable,
93                 sect->st_raw_data_ptr, sect->st_raw_data_size);
94
95         if (iinfo->cli_sections [section] == NULL)
96                 return FALSE;
97
98         return TRUE;
99 }
100
101 /**
102  * mono_image_ensure_section:
103  * @image: The image we are operating on
104  * @section: section name that we will load/map into memory
105  *
106  * This routine makes sure that we have an in-memory copy of
107  * an image section (.text, .rsrc, .data).
108  *
109  * Returns: TRUE on success
110  */
111 int
112 mono_image_ensure_section (MonoImage *image, const char *section)
113 {
114         cli_image_info_t *ii = image->image_info;
115         int i;
116         
117         for (i = 0; i < ii->cli_section_count; i++){
118                 if (strncmp (ii->cli_section_tables [i].st_name, section, 8) != 0)
119                         continue;
120                 
121                 return mono_image_ensure_section_idx (image, i);
122         }
123         return FALSE;
124 }
125
126 static int
127 load_section_tables (MonoImage *image, cli_image_info_t *iinfo)
128 {
129         const int top = iinfo->cli_header.coff.coff_sections;
130         int i;
131
132         iinfo->cli_section_count = top;
133         iinfo->cli_section_tables = g_new0 (section_table_t, top);
134         iinfo->cli_sections = g_new0 (void *, top);
135         
136         for (i = 0; i < top; i++){
137                 section_table_t *t = &iinfo->cli_section_tables [i];
138                 
139                 if (fread (t, sizeof (section_table_t), 1, image->f) != 1)
140                         return FALSE;
141
142                 t->st_virtual_size = le32_to_cpu (t->st_virtual_size);
143                 t->st_virtual_address = le32_to_cpu (t->st_virtual_address);
144                 t->st_raw_data_size = le32_to_cpu (t->st_raw_data_size);
145                 t->st_raw_data_ptr = le32_to_cpu (t->st_raw_data_ptr);
146                 t->st_reloc_ptr = le32_to_cpu (t->st_reloc_ptr);
147                 t->st_lineno_ptr = le32_to_cpu (t->st_lineno_ptr);
148                 t->st_reloc_count = le16_to_cpu (t->st_reloc_count);
149                 t->st_line_count = le16_to_cpu (t->st_line_count);
150         }
151
152         for (i = 0; i < top; i++)
153                 if (!mono_image_ensure_section_idx (image, i))
154                         return FALSE;
155         
156         return TRUE;
157 }
158
159 static gboolean
160 load_cli_header (MonoImage *image, cli_image_info_t *iinfo)
161 {
162         guint32 offset;
163         int n;
164         
165         offset = cli_rva_image_map (iinfo, iinfo->cli_header.datadir.pe_cli_header.rva);
166         if (offset == INVALID_ADDRESS)
167                 return FALSE;
168
169         if (fseek (image->f, offset, 0) != 0)
170                 return FALSE;
171         
172         if ((n = fread (&iinfo->cli_cli_header, sizeof (cli_header_t), 1, image->f)) != 1)
173                 return FALSE;
174
175         /* Catch new uses of the fields that are supposed to be zero */
176
177         if ((iinfo->cli_cli_header.ch_eeinfo_table.rva != 0) ||
178             (iinfo->cli_cli_header.ch_helper_table.rva != 0) ||
179             (iinfo->cli_cli_header.ch_dynamic_info.rva != 0) ||
180             (iinfo->cli_cli_header.ch_delay_load_info.rva != 0) ||
181             (iinfo->cli_cli_header.ch_module_image.rva != 0) ||
182             (iinfo->cli_cli_header.ch_external_fixups.rva != 0) ||
183             (iinfo->cli_cli_header.ch_ridmap.rva != 0) ||
184             (iinfo->cli_cli_header.ch_debug_map.rva != 0) ||
185             (iinfo->cli_cli_header.ch_ip_map.rva != 0)){
186                 g_message ("Some fields in the CLI header which should have been zero are not zero");
187         }
188             
189         return TRUE;
190 }
191
192 static gboolean
193 load_metadata_ptrs (MonoImage *image, cli_image_info_t *iinfo)
194 {
195         metadata_t *metadata = &iinfo->cli_metadata;
196         guint32 offset, size;
197         guint16 streams;
198         int i;
199         char *ptr;
200         
201         offset = cli_rva_image_map (iinfo, iinfo->cli_cli_header.ch_metadata.rva);
202         size = iinfo->cli_cli_header.ch_metadata.size;
203         
204         metadata->raw_metadata = raw_buffer_load (fileno (image->f), FALSE, offset, size);
205         if (metadata->raw_metadata == NULL)
206                 return FALSE;
207
208         ptr = metadata->raw_metadata;
209
210         if (strncmp (ptr, "BSJB", 4) == 0){
211                 guint32 version_string_len;
212
213                 ptr += 12;
214                 version_string_len = read32 (ptr);
215                 ptr += 4;
216                 ptr += version_string_len;
217                 if (((guint32) ptr) % 4)
218                         ptr += 4 - (((guint32) ptr) %4);
219         } else
220                 return FALSE;
221
222         /* skip over flags */
223         ptr += 2;
224         
225         streams = read16 (ptr);
226         ptr += 2;
227
228         for (i = 0; i < streams; i++){
229                 if (strncmp (ptr + 8, "#~", 3) == 0){
230                         metadata->heap_tables.sh_offset = read32 (ptr);
231                         metadata->heap_tables.sh_size = read32 (ptr + 4);
232                         ptr += 8 + 3;
233                 } else if (strncmp (ptr + 8, "#Strings", 9) == 0){
234                         metadata->heap_strings.sh_offset = read32 (ptr);
235                         metadata->heap_strings.sh_size = read32 (ptr + 4);
236                         ptr += 8 + 9;
237                 } else if (strncmp (ptr + 8, "#US", 4) == 0){
238                         metadata->heap_us.sh_offset = read32 (ptr);
239                         metadata->heap_us.sh_size = read32 (ptr + 4);
240                         ptr += 8 + 4;
241                 } else if (strncmp (ptr + 8, "#Blob", 6) == 0){
242                         metadata->heap_blob.sh_offset = read32 (ptr);
243                         metadata->heap_blob.sh_size = read32 (ptr + 4);
244                         ptr += 8 + 6;
245                 } else if (strncmp (ptr + 8, "#GUID", 6) == 0){
246                         metadata->heap_guid.sh_offset = read32 (ptr);
247                         metadata->heap_guid.sh_size = read32 (ptr + 4);
248                         ptr += 8 + 6;
249                 } else
250                         g_message ("Unknown heap type: %s\n", ptr + 8);
251                 if (((guint32)ptr) % 4){
252                         ptr += 4 - (((guint32)ptr) % 4);
253                 }
254         }
255         return TRUE;
256 }
257
258 /*
259  * Load representation of logical metadata tables, from the "#~" stream
260  */
261 static gboolean
262 load_tables (MonoImage *image, metadata_t *meta)
263 {
264         char *heap_tables = meta->raw_metadata + meta->heap_tables.sh_offset;
265         guint32 *rows;
266         guint64 valid_mask;
267         int valid = 0, table;
268         int heap_sizes;
269         
270         heap_sizes = heap_tables [6];
271         meta->idx_string_wide = ((heap_sizes & 0x01) == 1);
272         meta->idx_guid_wide   = ((heap_sizes & 0x02) == 2);
273         meta->idx_blob_wide   = ((heap_sizes & 0x04) == 4);
274         
275         valid_mask = read64 (heap_tables + 8);
276         rows = (guint32 *) (heap_tables + 24);
277         
278         for (table = 0; table < 64; table++){
279                 if ((valid_mask & ((guint64) 1 << table)) == 0){
280                         meta->tables [table].rows = 0;
281                         continue;
282                 }
283                 if (table > 0x2b) {
284                         g_warning("bits in valid must be zero above 0x2b (II - 23.1.6)");
285                 }
286                 meta->tables [table].rows = read32 (rows);
287                 rows++;
288                 valid++;
289         }
290
291         meta->tables_base = (heap_tables + 24) + (4 * valid);
292
293         /* They must be the same */
294         g_assert ((void *) meta->tables_base == (void *) rows);
295
296         mono_metadata_compute_table_bases (meta);
297         return TRUE;
298 }
299
300 static gboolean
301 load_metadata (MonoImage *image, cli_image_info_t *iinfo)
302 {
303         if (!load_metadata_ptrs (image, iinfo))
304                 return FALSE;
305
306         return load_tables (image, &iinfo->cli_metadata);
307 }
308
309 static MonoImage *
310 do_mono_image_open (const char *fname, enum MonoImageOpenStatus *status)
311 {
312         cli_image_info_t *iinfo;
313         dotnet_header_t *header;
314         msdos_header_t msdos;
315         MonoImage *image;
316         int n;
317
318         image = g_new0 (MonoImage, 1);
319         image->f = fopen (fname, "r");
320         image->name = g_strdup (fname);
321         iinfo = g_new0 (cli_image_info_t, 1);
322         image->image_info = iinfo;
323
324         header = &iinfo->cli_header;
325                 
326         if (image->f == NULL){
327                 if (status)
328                         *status = MONO_IMAGE_ERROR_ERRNO;
329                 mono_image_close (image);
330                 return NULL;
331         }
332
333         if (status)
334                 *status = MONO_IMAGE_IMAGE_INVALID;
335         
336         if (fread (&msdos, sizeof (msdos), 1, image->f) != 1)
337                 goto invalid_image;
338         
339         if (!(msdos.msdos_header [0] == 0x4d && msdos.msdos_header [1] == 0x5a))
340                 goto invalid_image;
341         
342         if ((n = fread (header, sizeof (dotnet_header_t), 1, image->f)) != 1)
343                 goto invalid_image;
344
345         /*
346          * FIXME: byte swap all addresses here for header.
347          */
348         
349         if (!load_section_tables (image, iinfo))
350                 goto invalid_image;
351         
352         /* Load the CLI header */
353         if (!load_cli_header (image, iinfo))
354                 goto invalid_image;
355
356         if (!load_metadata (image, iinfo))
357                 goto invalid_image;
358         
359         if (status)
360                 *status = MONO_IMAGE_OK;
361
362         return image;
363
364 invalid_image:
365         mono_image_close (image);
366                 return NULL;
367 }
368
369 /**
370  * mono_image_open:
371  * @fname: filename that points to the module we want to open
372  * @status: An error condition is returned in this field
373  *
374  * Retuns: An open image of type %MonoImage or NULL on error.
375  * if NULL, then check the value of @status for details on the error
376  */
377 MonoImage *
378 mono_image_open (const char *fname, enum MonoImageOpenStatus *status)
379 {
380         MonoImage *image;
381         
382         g_return_val_if_fail (fname != NULL, NULL);
383
384         if (loaded_images_hash){
385                 image = g_hash_table_lookup (loaded_images_hash, fname);
386                 if (image){
387                         image->ref_count++;
388                         return image;
389                 }
390         }
391
392         image = do_mono_image_open (fname, status);
393         if (image == NULL)
394                 return NULL;
395
396         if (!loaded_images_hash)
397                 loaded_images_hash = g_hash_table_new (g_str_hash, g_str_equal);
398         g_hash_table_insert (loaded_images_hash, image->name, image);
399
400         return image;
401 }
402
403 /**
404  * mono_image_close:
405  * @image: The image file we wish to close
406  *
407  * Closes an image file, deallocates all memory consumed and
408  * unmaps all possible sections of the file
409  */
410 void
411 mono_image_close (MonoImage *image)
412 {
413         g_return_if_fail (image != NULL);
414
415         if (--image->ref_count)
416                 return;
417
418         g_hash_table_remove (loaded_images_hash, image->name);
419         
420         if (image->f)
421                 fclose (image->f);
422
423         g_free (image->name);
424         
425         if (image->image_info){
426                 cli_image_info_t *ii = image->image_info;
427                 int i;
428
429                 if (ii->cli_metadata.raw_metadata != NULL)
430                         raw_buffer_free (ii->cli_metadata.raw_metadata);
431         
432                 for (i = 0; i < ii->cli_section_count; i++){
433                         if (!ii->cli_sections [i])
434                                 continue;
435                         raw_buffer_free (ii->cli_sections [i]);
436                 }
437                 if (ii->cli_section_tables)
438                         g_free (ii->cli_section_tables);
439                 if (ii->cli_sections)
440                         g_free (ii->cli_sections);
441                 g_free (image->image_info);
442         }
443         
444         g_free (image);
445 }
446
447 /** 
448  * mono_image_strerror:
449  * @status: an code indicating the result from a recent operation
450  *
451  * Returns: a string describing the error
452  */
453 const char *
454 mono_image_strerror (enum MonoImageOpenStatus status)
455 {
456         switch (status){
457         case MONO_IMAGE_OK:
458                 return "success";
459         case MONO_IMAGE_ERROR_ERRNO:
460                 return strerror (errno);
461         case MONO_IMAGE_IMAGE_INVALID:
462                 return "File does not contain a valid CIL image";
463         case MONO_IMAGE_MISSING_ASSEMBLYREF:
464                 return "An assembly was referenced, but could not be found";
465         }
466         return "Internal error";
467 }
468