7 #include <mono/metadata/image.h>
8 #include <mono/metadata/metadata.h>
9 #include <mono/metadata/assembly.h>
10 #include <mono/metadata/marshal.h>
11 #include <mono/metadata/class-internals.h>
12 #include <mono/metadata/metadata-internals.h>
15 #define DEBUG_PARSER(stmt) do { stmt; } while (0)
17 #define DEBUG_PARSER(stmt)
21 #define DEBUG_SCANNER(stmt) do { stmt; } while (0)
22 #define SCANNER_DEBUG 1
24 #define DEBUG_SCANNER(stmt)
32 identifier ::= ([a-z] | [A-Z]) ([a-z] | [A-Z] | [0-9] | [_-.])*
33 hexa_digit = [0-9] | [a-f] | [A-F]
34 number ::= hexadecimal | decimal
35 hexadecimal ::= (+-)?('0' [xX])? hexa_digit+
44 identifier '{' assembly_directive test_entry* '}'
50 validity patch (',' patch)*
62 ('set-byte' | 'set-ushort' | 'set-uint' | 'set-bit' | 'or-byte' | 'or-ushort' | 'or-uint' | 'truncate' ) expression
68 number | variable | function_call
71 fun_name '(' arg_list ')'
84 expression ',' arg_list
96 TODO For the sake of a simple implementation, tokens are space delimited.
112 INVALID_VALIDITY_TEST,
118 INVALID_VARIABLE_NAME,
119 INVALID_FUNCTION_NAME,
153 typedef struct _expression expression_t;
157 int start, end; /*stream range text is in [start, end[*/
187 expression_t *expression;
193 expression_t *expression;
197 patch_selector_t *selector;
198 patch_effect_t *effect;
214 GSList *patches; /*of test_patch_t*/
217 test_set_t *test_set;
222 /*******************************************************************************************************/
223 static guint32 expression_eval (expression_t *exp, test_entry_t *entry);
224 static expression_t* parse_expression (scanner_t *scanner);
227 test_validity_name (int validity)
230 case TEST_TYPE_VALID:
232 case TEST_TYPE_INVALID:
235 printf ("Invalid test type %d\n", validity);
236 exit (INVALID_VALIDITY_TEST);
241 read_whole_file_and_close (const char *name, int *file_size)
243 FILE *file = fopen (name, "ro");
248 printf ("Could not open file %s\n", name);
249 exit (INVALID_FILE_NAME);
252 fseek (file, 0, SEEK_END);
253 fsize = ftell (file);
254 fseek (file, 0, SEEK_SET);
256 res = g_malloc (fsize + 1);
258 fread (res, fsize, 1, file);
265 init_test_set (test_set_t *test_set)
267 MonoImageOpenStatus status;
270 test_set->assembly_data = read_whole_file_and_close (test_set->assembly, &test_set->assembly_size);
271 test_set->image = mono_image_open_from_data (test_set->assembly_data, test_set->assembly_size, FALSE, &status);
272 if (!test_set->image || status != MONO_IMAGE_OK) {
273 printf ("Could not parse image %s\n", test_set->assembly);
274 exit (INVALID_BAD_FILE);
281 make_test_name (test_entry_t *entry, test_set_t *test_set)
283 return g_strdup_printf ("%s-%s-%d.exe", test_validity_name (entry->validity), test_set->name, test_set->count++);
286 #define READ_VAR(KIND, PTR) GUINT32_FROM_LE((guint32)*((KIND*)(PTR)))
287 #define SET_VAR(KIND, PTR, VAL) do { *((KIND*)(PTR)) = GUINT32_TO_LE ((KIND)VAL); } while (0)
289 #define READ_BIT(PTR,OFF) ((((guint8*)(PTR))[(OFF / 8)] & (1 << ((OFF) % 8))) != 0)
290 #define SET_BIT(PTR,OFF) do { ((guint8*)(PTR))[(OFF / 8)] |= (1 << ((OFF) % 8)); } while (0)
293 get_pe_header (test_entry_t *entry)
295 return READ_VAR (guint32, entry->data + 0x3c) + 4;
299 translate_rva (test_entry_t *entry, guint32 rva)
301 guint32 pe_header = get_pe_header (entry);
302 guint32 sectionCount = READ_VAR (guint16, entry->data + pe_header + 2);
303 guint32 idx = pe_header + 244;
305 while (sectionCount-- > 0) {
306 guint32 size = READ_VAR (guint32, entry->data + idx + 8);
307 guint32 base = READ_VAR (guint32, entry->data + idx + 12);
308 guint32 offset = READ_VAR (guint32, entry->data + idx + 20);
310 if (rva >= base && rva <= base + size)
311 return (rva - base) + offset;
314 printf ("Could not translate RVA %x\n", rva);
319 get_cli_header (test_entry_t *entry)
321 guint32 offset = get_pe_header (entry) + 20; /*pe-optional-header*/
322 offset += 208; /*cli header entry offset in the pe-optional-header*/
323 return translate_rva (entry, READ_VAR (guint32, entry->data + offset));
327 get_cli_metadata_root (test_entry_t *entry)
329 guint32 offset = get_cli_header (entry);
330 offset += 8; /*metadata rva offset*/
331 return translate_rva (entry, READ_VAR (guint32, entry->data + offset));
335 pad4 (guint32 offset)
338 offset += 4 - (offset % 4);
343 get_metadata_stream_header (test_entry_t *entry, guint32 idx)
347 offset = get_cli_metadata_root (entry);
348 offset = pad4 (offset + 16 + READ_VAR (guint32, entry->data + offset + 12));
356 for (i = 0; i < 32; ++i) {
357 if (!READ_VAR (guint8, entry->data + offset++))
360 offset = pad4 (offset);
366 lookup_var (test_entry_t *entry, const char *name)
368 if (!strcmp ("file-size", name))
369 return entry->data_size;
370 if (!strcmp ("pe-signature", name))
371 return get_pe_header (entry) - 4;
372 if (!strcmp ("pe-header", name))
373 return get_pe_header (entry);
374 if (!strcmp ("pe-optional-header", name))
375 return get_pe_header (entry) + 20;
376 if (!strcmp ("section-table", name))
377 return get_pe_header (entry) + 244;
378 if (!strcmp ("cli-header", name))
379 return get_cli_header (entry);
380 if (!strcmp ("cli-metadata", name))
381 return get_cli_metadata_root (entry);
382 if (!strcmp ("tables-header", name)) {
383 guint32 metadata_root = get_cli_metadata_root (entry);
384 guint32 tilde_stream = get_metadata_stream_header (entry, 0);
385 guint32 offset = READ_VAR (guint32, entry->data + tilde_stream);
386 return metadata_root + offset;
389 printf ("Unknown variable in expression %s\n", name);
390 exit (INVALID_VARIABLE_NAME);
394 call_func (test_entry_t *entry, const char *name, GSList *args)
396 if (!strcmp ("read.ushort", name)) {
398 if (g_slist_length (args) != 1) {
399 printf ("Invalid number of args to read.ushort %d\n", g_slist_length (args));
400 exit (INVALID_ARG_COUNT);
402 offset = expression_eval (args->data, entry);
403 return READ_VAR (guint16, entry->data + offset);
405 if (!strcmp ("read.uint", name)) {
407 if (g_slist_length (args) != 1) {
408 printf ("Invalid number of args to read.uint %d\n", g_slist_length (args));
409 exit (INVALID_ARG_COUNT);
411 offset = expression_eval (args->data, entry);
412 return READ_VAR (guint32, entry->data + offset);
414 if (!strcmp ("translate.rva", name)) {
416 if (g_slist_length (args) != 1) {
417 printf ("Invalid number of args to translate.rva %d\n", g_slist_length (args));
418 exit (INVALID_ARG_COUNT);
420 rva = expression_eval (args->data, entry);
421 return translate_rva (entry, rva);
423 if (!strcmp ("translate.rva.ind", name)) {
425 if (g_slist_length (args) != 1) {
426 printf ("Invalid number of args to translate.rva.ind %d\n", g_slist_length (args));
427 exit (INVALID_ARG_COUNT);
429 rva = expression_eval (args->data, entry);
430 rva = READ_VAR (guint32, entry->data + rva);
431 return translate_rva (entry, rva);
433 if (!strcmp ("stream-header", name)) {
435 if (g_slist_length (args) != 1) {
436 printf ("Invalid number of args to stream-header %d\n", g_slist_length (args));
437 exit (INVALID_ARG_COUNT);
439 idx = expression_eval (args->data, entry);
440 return get_metadata_stream_header (entry, idx);
442 if (!strcmp ("table-row", name)) {
445 const MonoTableInfo *info;
446 if (g_slist_length (args) != 2) {
447 printf ("Invalid number of args to table-row %d\n", g_slist_length (args));
448 exit (INVALID_ARG_COUNT);
450 table = expression_eval (args->data, entry);
451 row = expression_eval (args->next->data, entry);
452 info = mono_image_get_table_info (entry->test_set->image, table);
453 data = info->base + row * info->row_size;
454 return data - entry->test_set->assembly_data;
456 if (!strcmp ("blob.i", name)) {
457 guint32 offset, base;
458 MonoStreamHeader blob = entry->test_set->image->heap_blob;
459 if (g_slist_length (args) != 1) {
460 printf ("Invalid number of args to blob %d\n", g_slist_length (args));
461 exit (INVALID_ARG_COUNT);
463 base = blob.data - entry->test_set->image->raw_data;
464 offset = expression_eval (args->data, entry);
465 offset = READ_VAR (guint16, entry->data + offset);
466 return base + offset;
469 printf ("Unknown function %s\n", name);
470 exit (INVALID_FUNCTION_NAME);
475 expression_eval (expression_t *exp, test_entry_t *entry)
478 case EXPRESSION_CONSTANT:
479 return exp->data.constant;
480 case EXPRESSION_VARIABLE:
481 return lookup_var (entry, exp->data.name);
483 return expression_eval (exp->data.bin.left, entry) + expression_eval (exp->data.bin.right, entry);
485 return expression_eval (exp->data.bin.left, entry) - expression_eval (exp->data.bin.right, entry);
486 case EXPRESSION_FUNC:
487 return call_func (entry, exp->data.func.name, exp->data.func.args);
489 printf ("Invalid expression type %d\n", exp->type);
490 exit (INVALID_EXPRESSION);
495 apply_selector (patch_selector_t *selector, test_entry_t *entry)
498 if (selector->expression)
499 value = expression_eval (selector->expression, entry);
500 switch (selector->type) {
501 case SELECTOR_ABS_OFFSET:
502 DEBUG_PARSER (printf("\tabsolute offset selector [%04x]\n", value));
505 printf ("Invalid selector type %d\n", selector->type);
506 exit (INVALID_SELECTOR);
511 apply_effect (patch_effect_t *effect, test_entry_t *entry, guint32 offset)
514 char *ptr = entry->data + offset;
515 if (effect->expression)
516 value = expression_eval (effect->expression, entry);
518 switch (effect->type) {
519 case EFFECT_SET_BYTE:
520 DEBUG_PARSER (printf("\tset-byte effect old value [%x] new value [%x]\n", READ_VAR (guint8, ptr), value));
521 SET_VAR (guint8, ptr, value);
523 case EFFECT_SET_USHORT:
524 DEBUG_PARSER (printf("\tset-ushort effect old value [%x] new value [%x]\n", READ_VAR (guint16, ptr), value));
525 SET_VAR (guint16, ptr, value);
527 case EFFECT_SET_UINT:
528 DEBUG_PARSER (printf("\tset-uint effect old value [%x] new value [%x]\n", READ_VAR (guint32, ptr), value));
529 SET_VAR (guint32, ptr, value);
531 case EFFECT_SET_TRUNC:
532 DEBUG_PARSER (printf("\ttrunc effect [%d]\n", offset));
533 entry->data_size = offset;
536 DEBUG_PARSER (printf("\tset-bit effect bit %d old value [%x]\n", value, READ_BIT (ptr, value)));
537 SET_BIT (ptr, value);
540 DEBUG_PARSER (printf("\tor-byte effect old value [%x] new value [%x]\n", READ_VAR (guint8, ptr), value));
541 SET_VAR (guint8, ptr, READ_VAR (guint8, ptr) | value);
543 case EFFECT_OR_USHORT:
544 DEBUG_PARSER (printf("\tor-ushort effect old value [%x] new value [%x]\n", READ_VAR (guint16, ptr), value));
545 SET_VAR (guint16, ptr, READ_VAR (guint16, ptr) | value);
548 DEBUG_PARSER (printf("\tor-uint effect old value [%x] new value [%x]\n", READ_VAR (guint32, ptr), value));
549 SET_VAR (guint32, ptr, READ_VAR (guint32, ptr) | value);
552 printf ("Invalid effect type %d\n", effect->type);
553 exit (INVALID_EFFECT);
558 apply_patch (test_entry_t *entry, test_patch_t *patch)
560 guint32 offset = apply_selector (patch->selector, entry);
561 apply_effect (patch->effect, entry, offset);
565 process_test_entry (test_set_t *test_set, test_entry_t *entry)
571 init_test_set (test_set);
572 entry->data = g_memdup (test_set->assembly_data, test_set->assembly_size);
573 entry->data_size = test_set->assembly_size;
574 entry->test_set = test_set;
576 DEBUG_PARSER (printf("(%d)%s\n", test_set->count, entry->validity == TEST_TYPE_VALID? "valid" : "invalid"));
577 for (tmp = entry->patches; tmp; tmp = tmp->next)
578 apply_patch (entry, tmp->data);
580 file_name = make_test_name (entry, test_set);
582 f = fopen (file_name, "wo");
583 fwrite (entry->data, entry->data_size, 1, f);
589 /*******************************************************************************************************/
592 patch_free (test_patch_t *patch)
594 free (patch->selector);
595 free (patch->effect);
600 test_set_free (test_set_t *set)
603 free (set->assembly);
604 free (set->assembly_data);
606 mono_image_close (set->image);
610 test_entry_free (test_entry_t *entry)
615 for (tmp = entry->patches; tmp; tmp = tmp->next)
616 patch_free (tmp->data);
617 g_slist_free (entry->patches);
621 /*******************************************************************************************************/
623 token_type_name (int type)
633 return "punctuation";
635 return "end of file";
637 return "unknown token type";
640 #define CUR_CHAR (scanner->input [scanner->idx])
643 is_eof (scanner_t *scanner)
645 return scanner->idx >= scanner->size;
651 return c == '{' || c == '}' || c == ',';
655 skip_spaces (scanner_t *scanner)
658 while (!is_eof (scanner) && isspace (CUR_CHAR)) {
659 if (CUR_CHAR == '\n')
663 if (CUR_CHAR == '#') {
664 while (!is_eof (scanner) && CUR_CHAR != '\n') {
672 token_text_dup (scanner_t *scanner, token_t *token)
674 int len = token->end - token->start;
676 char *str = g_memdup (scanner->input + token->start, len + 1);
683 dump_token (scanner_t *scanner, token_t *token)
685 char *str = token_text_dup (scanner, token);
687 printf ("token '%s' of type '%s' at line %d\n", str, token_type_name (token->type), token->line);
694 is_special_char (char c)
709 next_token (scanner_t *scanner)
711 int start, end, type;
713 skip_spaces (scanner);
714 start = scanner->idx;
715 while (!is_eof (scanner) && !isspace (CUR_CHAR)) {
716 if (scanner->idx == start) {
717 if (is_special_char (CUR_CHAR)) {
721 } else if (is_special_char (CUR_CHAR))
727 c = scanner->input [start];
728 if (start >= scanner->size)
730 else if (isdigit (c) || c == '\'')
732 else if (ispunct_char (c))
736 scanner->current.start = start;
737 scanner->current.end = end;
738 scanner->current.type = type;
739 scanner->current.line = scanner->line;
741 DEBUG_SCANNER (dump_token (scanner, &scanner->current));
745 scanner_new (const char *file_name)
749 res = g_new0 (scanner_t, 1);
750 res->input = read_whole_file_and_close (file_name, &res->size);
759 scanner_free (scanner_t *scanner)
761 free (scanner->input);
766 scanner_get_current_token (scanner_t *scanner)
768 return &scanner->current;
772 scanner_get_type (scanner_t *scanner)
774 return scanner_get_current_token (scanner)->type;
778 scanner_get_line (scanner_t *scanner)
780 return scanner_get_current_token (scanner)->line;
784 scanner_text_dup (scanner_t *scanner)
786 return token_text_dup (scanner, scanner_get_current_token (scanner));
790 scanner_text_parse_number (scanner_t *scanner, long *res)
792 char *text = scanner_text_dup (scanner);
795 if (text [0] == '\'') {
796 ok = strlen (text) != 3 || text [2] != '\'';
800 *res = strtol (text, &end, 0);
809 match_current_type (scanner_t *scanner, int type)
811 return scanner_get_type (scanner) == type;
815 match_current_text (scanner_t *scanner, const char *text)
817 token_t *t = scanner_get_current_token (scanner);
818 return !strncmp (scanner->input + t->start, text, t->end - t->start);
822 match_current_type_and_text (scanner_t *scanner, int type, const char *text)
824 return match_current_type (scanner, type) && match_current_text (scanner, text);
827 /*******************************************************************************************************/
828 #define FAIL(MSG, REASON) do { \
829 printf ("%s at line %d for rule %s\n", MSG, scanner_get_line (scanner), __FUNCTION__); \
833 #define EXPECT_TOKEN(TYPE) do { \
834 if (scanner_get_type (scanner) != TYPE) { \
835 printf ("Expected %s but got %s '%s' at line %d for rule %s\n", token_type_name (TYPE), token_type_name (scanner_get_type (scanner)), scanner_text_dup (scanner), scanner_get_line (scanner), __FUNCTION__); \
836 exit (INVALID_TOKEN_TYPE); \
840 #define CONSUME_SPECIFIC_PUNCT(TEXT) do { \
841 EXPECT_TOKEN (TOKEN_PUNC); \
842 if (!match_current_text (scanner, TEXT)) { \
843 char *__tmp = scanner_text_dup (scanner); \
844 printf ("Expected '%s' but got '%s' at line %d for rule %s\n", TEXT, __tmp, scanner_get_line (scanner), __FUNCTION__); \
846 exit (INVALID_PUNC_TEXT); \
848 next_token (scanner); \
851 #define CONSUME_IDENTIFIER(DEST) do { \
852 EXPECT_TOKEN (TOKEN_ID); \
853 DEST = scanner_text_dup (scanner); \
854 next_token (scanner); \
857 #define CONSUME_SPECIFIC_IDENTIFIER(TEXT) do { \
858 EXPECT_TOKEN (TOKEN_ID); \
859 if (!match_current_text (scanner, TEXT)) { \
860 char *__tmp = scanner_text_dup (scanner); \
861 printf ("Expected '%s' but got '%s' at line %d for rule %s\n", TEXT, __tmp, scanner_get_line (scanner), __FUNCTION__); \
863 exit (INVALID_ID_TEXT); \
865 next_token (scanner); \
868 #define CONSUME_NUMBER(DEST) do { \
870 EXPECT_TOKEN (TOKEN_NUM); \
871 if (scanner_text_parse_number (scanner, &__tmp_num)) { \
872 char *__tmp = scanner_text_dup (scanner); \
873 printf ("Expected a number but got '%s' at line %d for rule %s\n", __tmp, scanner_get_line (scanner), __FUNCTION__); \
875 exit (INVALID_NUMBER); \
878 next_token (scanner); \
881 #define LA_ID(TEXT) (scanner_get_type (scanner) == TOKEN_ID && match_current_text (scanner, TEXT))
882 #define LA_PUNCT(TEXT) (scanner_get_type (scanner) == TOKEN_PUNC && match_current_text (scanner, TEXT))
884 /*******************************************************************************************************/
887 parse_atom (scanner_t *scanner)
889 expression_t *atom = g_new0 (expression_t, 1);
890 if (scanner_get_type (scanner) == TOKEN_NUM) {
891 atom->type = EXPRESSION_CONSTANT;
892 CONSUME_NUMBER (atom->data.constant);
895 CONSUME_IDENTIFIER (name);
897 atom->data.func.name = name;
898 atom->type = EXPRESSION_FUNC;
899 CONSUME_SPECIFIC_IDENTIFIER ("(");
901 while (!LA_ID (")") && !match_current_type (scanner, TOKEN_EOF))
902 atom->data.func.args = g_slist_append (atom->data.func.args, parse_expression (scanner));
904 CONSUME_SPECIFIC_IDENTIFIER (")");
906 atom->data.name = name;
907 atom->type = EXPRESSION_VARIABLE;
915 parse_expression (scanner_t *scanner)
917 expression_t *exp = parse_atom (scanner);
919 while (LA_ID ("-") || LA_ID ("+")) {
921 CONSUME_IDENTIFIER (text);
922 expression_t *left = exp;
923 exp = g_new0 (expression_t, 1);
924 exp->type = !strcmp ("+", text) ? EXPRESSION_ADD: EXPRESSION_SUB;
925 exp->data.bin.left = left;
926 exp->data.bin.right = parse_atom (scanner);
932 static patch_selector_t*
933 parse_selector (scanner_t *scanner)
935 patch_selector_t *selector;
937 CONSUME_SPECIFIC_IDENTIFIER ("offset");
939 selector = g_new0 (patch_selector_t, 1);
940 selector->type = SELECTOR_ABS_OFFSET;
941 selector->expression = parse_expression (scanner);
945 static patch_effect_t*
946 parse_effect (scanner_t *scanner)
948 patch_effect_t *effect;
952 CONSUME_IDENTIFIER(name);
954 if (!strcmp ("set-byte", name))
955 type = EFFECT_SET_BYTE;
956 else if (!strcmp ("set-ushort", name))
957 type = EFFECT_SET_USHORT;
958 else if (!strcmp ("set-uint", name))
959 type = EFFECT_SET_UINT;
960 else if (!strcmp ("set-bit", name))
961 type = EFFECT_SET_BIT;
962 else if (!strcmp ("truncate", name))
963 type = EFFECT_SET_TRUNC;
964 else if (!strcmp ("or-byte", name))
965 type = EFFECT_OR_BYTE;
966 else if (!strcmp ("or-ushort", name))
967 type = EFFECT_OR_USHORT;
968 else if (!strcmp ("or-uint", name))
969 type = EFFECT_OR_UINT;
971 FAIL(g_strdup_printf ("Invalid effect kind, expected one of: (set-byte set-ushort set-uint set-bit or-byte or-ushort or-uint truncate) but got %s",name), INVALID_ID_TEXT);
973 effect = g_new0 (patch_effect_t, 1);
975 if (type != EFFECT_SET_TRUNC)
976 effect->expression = parse_expression (scanner);
981 parse_patch (scanner_t *scanner)
985 patch = g_new0 (test_patch_t, 1);
986 patch->selector = parse_selector (scanner);
987 patch->effect = parse_effect (scanner);
992 parse_validity (scanner_t *scanner)
996 CONSUME_IDENTIFIER (name);
998 if (!strcmp (name, "valid"))
999 validity = TEST_TYPE_VALID;
1000 else if (!strcmp (name, "invalid"))
1001 validity = TEST_TYPE_INVALID;
1003 printf ("Expected either 'valid' or 'invalid' but got '%s' at the begining of a test entry at line %d\n", name, scanner_get_line (scanner));
1004 exit (INVALID_VALIDITY_TEST);
1012 parse_test_entry (scanner_t *scanner, test_set_t *test_set)
1014 test_entry_t entry = { 0 };
1016 entry.validity = parse_validity (scanner);
1020 CONSUME_SPECIFIC_PUNCT (",");
1021 entry.patches = g_slist_append (entry.patches, parse_patch (scanner));
1022 } while (match_current_type_and_text (scanner, TOKEN_PUNC, ","));
1024 process_test_entry (test_set, &entry);
1026 test_entry_free (&entry);
1030 parse_test (scanner_t *scanner)
1032 test_set_t set = { 0 };
1034 CONSUME_IDENTIFIER (set.name);
1035 CONSUME_SPECIFIC_PUNCT ("{");
1036 CONSUME_SPECIFIC_IDENTIFIER ("assembly");
1037 CONSUME_IDENTIFIER (set.assembly);
1039 DEBUG_PARSER (printf ("RULE %s using assembly %s\n", set.name, set.assembly));
1041 while (!match_current_type (scanner, TOKEN_EOF) && !match_current_type_and_text (scanner, TOKEN_PUNC, "}"))
1042 parse_test_entry (scanner, &set);
1044 CONSUME_SPECIFIC_PUNCT ("}");
1046 test_set_free (&set);
1051 parse_program (scanner_t *scanner)
1053 while (!match_current_type (scanner, TOKEN_EOF))
1054 parse_test (scanner);
1059 digest_file (const char *file)
1061 scanner_t *scanner = scanner_new (file);
1062 parse_program (scanner);
1063 scanner_free (scanner);
1067 main (int argc, char **argv)
1070 printf ("usage: gen-md.test file_to_process\n");
1074 mono_init_version ("gen-md-test", "v2.0.50727");
1075 mono_marshal_init ();
1077 digest_file (argv [1]);