1: /* Copyright (C) 1985 Free Software Foundation, Inc. 2: 3: This file is part of GNU Emacs. 4: 5: GNU Emacs is distributed in the hope that it will be useful, 6: but WITHOUT ANY WARRANTY. No author or distributor 7: accepts responsibility to anyone for the consequences of using it 8: or for whether it serves any particular purpose or works at all, 9: unless he says so in writing. Refer to the GNU Emacs General Public 10: License for full details. 11: 12: Everyone is granted permission to copy, modify and redistribute 13: GNU Emacs, but only under the conditions described in the 14: GNU Emacs General Public License. A copy of this license is 15: supposed to have been given to you along with GNU Emacs so you 16: can know your rights and responsibilities. It should be in a 17: file named COPYING. Among other things, the copyright notice 18: and this notice must be preserved on all copies. */ 19: 20: 21: /* 22: * unexec.c - Convert a running program into an a.out file. 23: * 24: * Author: Spencer W. Thomas 25: * Computer Science Dept. 26: * University of Utah 27: * Date: Tue Mar 2 1982 28: * Modified heavily since then. 29: * 30: * Synopsis: 31: * unexec (new_name, a_name, data_start, bss_start, entry_address) 32: * char *new_name, *a_name; 33: * unsigned data_start, bss_start, entry_address; 34: * 35: * Takes a snapshot of the program and makes an a.out format file in the 36: * file named by the string argument new_name. 37: * If a_name is non-NULL, the symbol table will be taken from the given file. 38: * 39: * The boundaries within the a.out file may be adjusted with the data_start 40: * and bss_start arguments. Either or both may be given as 0 for defaults. 41: * 42: * Data_start gives the boundary between the text segment and the data 43: * segment of the program. The text segment can contain shared, read-only 44: * program code and literal data, while the data segment is always unshared 45: * and unprotected. Data_start gives the lowest unprotected address. Since 46: * the granularity of write-protection is on 1k page boundaries on the VAX, a 47: * given data_start value which is not on a page boundary is rounded down to 48: * the beginning of the page it is on. The default when 0 is given leaves the 49: * number of protected pages the same as it was before. 50: * 51: * Bss_start indicates how much of the data segment is to be saved in the 52: * a.out file and restored when the program is executed. It gives the lowest 53: * unsaved address, and is rounded up to a page boundary. The default when 0 54: * is given assumes that the entire data segment is to be stored, including 55: * the previous data and bss as well as any additional storage allocated with 56: * break (2). 57: * 58: * The new file is set up to start at entry_address. 59: * 60: * If you make improvements I'd like to get them too. 61: * harpo!utah-cs!thomas, thomas@Utah-20 62: * 63: */ 64: 65: #ifndef emacs 66: #define PERROR(arg) perror (arg); return -1 67: #else 68: #include "config.h" 69: #define PERROR(file) report_error (file, new) 70: #endif 71: 72: #ifndef CANNOT_DUMP /* all rest of file! */ 73: 74: #include <sys/param.h> 75: #ifndef makedev /* Try to detect types.h already loaded */ 76: #include <sys/types.h> 77: #endif 78: #include <stdio.h> 79: #include <sys/stat.h> 80: #include <errno.h> 81: 82: extern char *start_of_text (); /* Start of text */ 83: extern char *start_of_data (); /* Start of initialized data */ 84: 85: #ifdef COFF 86: #include <filehdr.h> 87: #include <aouthdr.h> 88: #include <scnhdr.h> 89: #include <syms.h> 90: static long block_copy_start; /* Old executable start point */ 91: static struct filehdr f_hdr; /* File header */ 92: static struct aouthdr f_ohdr; /* Optional file header (a.out) */ 93: long bias; /* Bias to add for growth */ 94: long lnnoptr; /* Pointer to line-number info within file */ 95: #define SYMS_START block_copy_start 96: 97: static int text_scnptr; 98: 99: #else /* not COFF */ 100: 101: extern char *sbrk (); 102: 103: #include <a.out.h> 104: #define SYMS_START ((long) N_SYMOFF (ohdr)) 105: 106: #ifdef HPUX 107: #ifdef hp9000s200 108: #define MY_ID HP9000S200_ID 109: #else 110: #include <model.h> 111: #define MY_ID MYSYS 112: #endif /* not hp9000s200 */ 113: static MAGIC OLDMAGIC = {MY_ID, SHARE_MAGIC}; 114: static MAGIC NEWMAGIC = {MY_ID, DEMAND_MAGIC}; 115: #define N_TXTOFF(x) TEXT_OFFSET(x) 116: #define N_SYMOFF(x) LESYM_OFFSET(x) 117: static struct exec hdr, ohdr; 118: 119: #else /* not HPUX */ 120: 121: #ifdef USG 122: static struct bhdr hdr, ohdr; 123: #define a_magic fmagic 124: #define a_text tsize 125: #define a_data dsize 126: #define a_bss bsize 127: #define a_syms ssize 128: #define a_trsize rtsize 129: #define a_drsize rdsize 130: #define a_entry entry 131: #define N_BADMAG(x) \ 132: (((x).fmagic)!=OMAGIC && ((x).fmagic)!=NMAGIC &&\ 133: ((x).fmagic)!=FMAGIC && ((x).fmagic)!=IMAGIC) 134: #define NEWMAGIC FMAGIC 135: #else /* not USG */ 136: static struct exec hdr, ohdr; 137: #define NEWMAGIC ZMAGIC 138: #endif /* not USG */ 139: #endif /* not HPUX */ 140: 141: #endif /* not COFF */ 142: 143: static int pagemask; 144: 145: #if defined (BSD4_1) || defined (USG) 146: #ifdef EXEC_PAGESIZE 147: #define getpagesize() EXEC_PAGESIZE 148: #else 149: #ifdef NBPG 150: #define getpagesize() NBPG * CLSIZE 151: #ifndef CLSIZE 152: #define CLSIZE 1 153: #endif /* no CLSIZE */ 154: #else /* no NBPG */ 155: #define getpagesize() NBPC 156: #endif /* no NBPG */ 157: #endif /* no EXEC_PAGESIZE */ 158: #endif /* BSD4_1 or USG */ 159: 160: /* Correct an int which is the bit pattern of a pointer to a byte 161: into an int which is the number of a byte. 162: This is a no-op on ordinary machines, but not on all. */ 163: 164: #ifndef ADDR_CORRECT /* Let m-*.h files override this definition */ 165: #define ADDR_CORRECT(x) ((char *)(x) - (char*)0) 166: #endif 167: 168: #ifdef emacs 169: 170: static 171: report_error (file, fd) 172: char *file; 173: int fd; 174: { 175: if (fd) 176: close (fd); 177: error ("Failure operating on %s", file); 178: } 179: #endif /* emacs */ 180: 181: #define ERROR0(msg) report_error_1 (new, msg, 0, 0); return -1 182: #define ERROR1(msg,x) report_error_1 (new, msg, x, 0); return -1 183: #define ERROR2(msg,x,y) report_error_1 (new, msg, x, y); return -1 184: 185: static 186: report_error_1 (fd, msg, a1, a2) 187: int fd; 188: char *msg; 189: int a1, a2; 190: { 191: close (fd); 192: #ifdef emacs 193: error (msg, a1, a2); 194: #else 195: fprintf (stderr, msg, a1, a2); 196: fprintf (stderr, "\n"); 197: #endif 198: } 199: 200: /* **************************************************************** 201: * unexec 202: * 203: * driving logic. 204: */ 205: unexec (new_name, a_name, data_start, bss_start, entry_address) 206: char *new_name, *a_name; 207: unsigned data_start, bss_start, entry_address; 208: { 209: int new, a_out = -1; 210: 211: if (a_name && (a_out = open (a_name, 0)) < 0) 212: { 213: PERROR (a_name); 214: } 215: if ((new = creat (new_name, 0666)) < 0) 216: { 217: PERROR (new_name); 218: } 219: if (make_hdr (new, a_out, data_start, bss_start, entry_address, a_name, new_name) < 0 220: || copy_text_and_data (new) < 0 221: || copy_sym (new, a_out, a_name, new_name) < 0 222: #ifdef COFF 223: || adjust_lnnoptrs (new, a_out, new_name) < 0 224: #endif 225: ) 226: { 227: close (new); 228: /* unlink (new_name); /* Failed, unlink new a.out */ 229: return -1; 230: } 231: 232: close (new); 233: if (a_out >= 0) 234: close (a_out); 235: mark_x (new_name); 236: return 0; 237: } 238: 239: /* **************************************************************** 240: * make_hdr 241: * 242: * Make the header in the new a.out from the header in core. 243: * Modify the text and data sizes. 244: */ 245: static int 246: make_hdr (new, a_out, data_start, bss_start, entry_address, a_name, new_name) 247: int new, a_out; 248: unsigned data_start, bss_start, entry_address; 249: char *a_name; 250: char *new_name; 251: { 252: int tem; 253: #ifdef COFF 254: auto struct scnhdr f_thdr; /* Text section header */ 255: auto struct scnhdr f_dhdr; /* Data section header */ 256: auto struct scnhdr f_bhdr; /* Bss section header */ 257: auto struct scnhdr scntemp; /* Temporary section header */ 258: register int scns; 259: 260: /* Salvage as much info from the existing file as possible */ 261: if (a_out >= 0) 262: { 263: if (read (a_out, &f_hdr, sizeof (f_hdr)) != sizeof (f_hdr)) 264: { 265: PERROR (a_name); 266: } 267: block_copy_start += sizeof (f_hdr); 268: if (f_hdr.f_opthdr > 0) 269: { 270: if (read (a_out, &f_ohdr, sizeof (f_ohdr)) != sizeof (f_ohdr)) 271: { 272: PERROR (a_name); 273: } 274: block_copy_start += sizeof (f_ohdr); 275: } 276: /* Loop through section headers, copying them in */ 277: for (scns = f_hdr.f_nscns; scns > 0; scns--) { 278: if (read (a_out, &scntemp, sizeof (scntemp)) != sizeof (scntemp)) 279: { 280: PERROR (a_name); 281: } 282: block_copy_start += sizeof (scntemp); 283: if (scntemp.s_scnptr > 0L) 284: { 285: block_copy_start += scntemp.s_size; 286: } 287: if (strcmp (scntemp.s_name, ".text") == 0) 288: { 289: f_thdr = scntemp; 290: } 291: else if (strcmp (scntemp.s_name, ".data") == 0) 292: { 293: f_dhdr = scntemp; 294: } 295: else if (strcmp (scntemp.s_name, ".bss") == 0) 296: { 297: f_bhdr = scntemp; 298: } 299: } 300: } 301: else 302: { 303: ERROR0 ("can't build a COFF file from scratch yet"); 304: } 305: 306: pagemask = getpagesize () - 1; 307: 308: #ifdef NO_REMAP 309: data_start = (int) start_of_data (); 310: #else /* not NO_REMAP */ 311: if (!data_start) 312: data_start = (int) start_of_data (); 313: #endif /* not NO_REMAP */ 314: data_start = ADDR_CORRECT (data_start); 315: data_start = data_start & ~pagemask; /* down to a page boundary */ 316: 317: f_hdr.f_flags |= (F_RELFLG | F_EXEC); 318: #ifdef EXEC_MAGIC 319: f_ohdr.magic = EXEC_MAGIC; 320: #endif 321: f_ohdr.text_start = (long) start_of_text (); 322: f_ohdr.tsize = data_start - f_ohdr.text_start; 323: f_ohdr.data_start = data_start; 324: f_ohdr.dsize = (long) sbrk (0) - f_ohdr.data_start; 325: f_ohdr.bsize = 0; 326: f_thdr.s_size = f_ohdr.tsize; 327: f_thdr.s_scnptr = sizeof (f_hdr) + sizeof (f_ohdr); 328: f_thdr.s_scnptr += (f_hdr.f_nscns) * (sizeof (f_thdr)); 329: lnnoptr = f_thdr.s_lnnoptr; 330: #ifdef UMAX 331: /* Umax is bsd using coff; it has restrictions on alignment 332: of the sections in the file itself. */ 333: f_thdr.s_scnptr = (f_thdr.s_scnptr + pagemask) & ~pagemask; /* round up */ 334: #endif /* UMAX */ 335: text_scnptr = f_thdr.s_scnptr; 336: f_dhdr.s_paddr = f_ohdr.data_start; 337: f_dhdr.s_vaddr = f_ohdr.data_start; 338: f_dhdr.s_size = f_ohdr.dsize; 339: f_dhdr.s_scnptr = f_thdr.s_scnptr + f_thdr.s_size; 340: #ifdef UMAX 341: f_dhdr.s_scnptr &= ~pagemask; /* round down to page boundary */ 342: #endif /* UMAX */ 343: f_bhdr.s_paddr = f_ohdr.data_start + f_ohdr.dsize; 344: f_bhdr.s_vaddr = f_ohdr.data_start + f_ohdr.dsize; 345: f_bhdr.s_size = f_ohdr.bsize; 346: f_bhdr.s_scnptr = 0L; 347: bias = f_dhdr.s_scnptr + f_dhdr.s_size - block_copy_start; 348: 349: if (f_hdr.f_symptr > 0L) 350: { 351: f_hdr.f_symptr += bias; 352: } 353: 354: if (f_thdr.s_lnnoptr > 0L) 355: { 356: f_thdr.s_lnnoptr += bias; 357: } 358: 359: if (write (new, &f_hdr, sizeof (f_hdr)) != sizeof (f_hdr)) 360: { 361: PERROR (new_name); 362: } 363: 364: if (write (new, &f_ohdr, sizeof (f_ohdr)) != sizeof (f_ohdr)) 365: { 366: PERROR (new_name); 367: } 368: 369: if (write (new, &f_thdr, sizeof (f_thdr)) != sizeof (f_thdr)) 370: { 371: PERROR (new_name); 372: } 373: 374: if (write (new, &f_dhdr, sizeof (f_dhdr)) != sizeof (f_dhdr)) 375: { 376: PERROR (new_name); 377: } 378: 379: if (write (new, &f_bhdr, sizeof (f_bhdr)) != sizeof (f_bhdr)) 380: { 381: PERROR (new_name); 382: } 383: return (0); 384: 385: #else /* if not COFF */ 386: 387: /* Get symbol table info from header of a.out file if given one. */ 388: if (a_out >= 0) 389: { 390: if (read (a_out, &ohdr, sizeof hdr) != sizeof hdr) 391: { 392: PERROR (a_name); 393: } 394: 395: if N_BADMAG (ohdr) 396: { 397: ERROR1 ("invalid magic number in %s", a_name); 398: } 399: #ifdef celerity 400: hdr.a_scovrfl = ohdr.a_scovrfl; 401: #endif 402: #ifdef HPUX 403: hdr.a_lesyms = ohdr.a_lesyms; 404: hdr.a_sltsize = ohdr.a_sltsize; 405: hdr.a_dnttsize = ohdr.a_dnttsize; 406: hdr.a_vtsize = ohdr.a_vtsize; 407: #else /* not HPUX */ 408: hdr.a_syms = ohdr.a_syms; 409: #endif /* not HPUX */ 410: } 411: else 412: { 413: #ifdef celerity 414: hdr.a_scovrfl = 0; 415: #endif 416: #ifdef HPUX 417: hdr.a_lesyms = 0; 418: hdr.a_sltsize = 0; 419: hdr.a_dnttsize = 0; 420: hdr.a_vtsize = 0; 421: #else /* not HPUX */ 422: hdr.a_syms = 0; /* No a.out, so no symbol info. */ 423: #endif /* not HPUX */ 424: } 425: 426: /* Construct header from user structure. */ 427: #ifdef HPUX 428: /* (((MAGIC) ohdr.a_magic) == ((MAGIC) OLDMAGIC)) This does not work */ 429: hdr.a_magic = ((ohdr.a_magic.file_type == OLDMAGIC.file_type) ? 430: NEWMAGIC : ohdr.a_magic); 431: #else /* not HPUX */ 432: /* hdr.a_magic = NEWMAGIC; */ 433: hdr.a_magic = ohdr.a_magic; 434: #endif /* not HPUX */ 435: 436: #ifdef sun3 437: hdr.a_machtype = ohdr.a_machtype; 438: #endif /* sun3 */ 439: hdr.a_trsize = 0; 440: hdr.a_drsize = 0; 441: hdr.a_entry = entry_address; 442: 443: pagemask = getpagesize () - 1; 444: 445: /* Adjust data/bss boundary. */ 446: if (bss_start != 0) 447: { 448: bss_start = (ADDR_CORRECT (bss_start) + pagemask) & ~pagemask; /* (Up) to page bdry. */ 449: if (bss_start > ADDR_CORRECT (sbrk (0))) 450: { 451: ERROR1 ("unexec: Specified bss_start (%u) is past end of program", 452: bss_start); 453: } 454: } 455: else 456: { 457: bss_start = ADDR_CORRECT (sbrk (0)); 458: bss_start = (bss_start + pagemask) & ~pagemask; 459: } 460: 461: /* Adjust text/data boundary. */ 462: #ifdef NO_REMAP 463: data_start = (int) start_of_data (); 464: #else /* not NO_REMAP */ 465: if (!data_start) 466: data_start = (int) start_of_data (); 467: #endif /* not NO_REMAP */ 468: data_start = ADDR_CORRECT (data_start); 469: 470: #ifdef sun 471: data_start = data_start & ~(SEGSIZ - 1); /* (Down) to segment boundary. */ 472: #else 473: data_start = data_start & ~pagemask; /* (Down) to page boundary. */ 474: #endif 475: 476: if (data_start > bss_start) /* Can't have negative data size. */ 477: { 478: ERROR2 ("unexec: data_start (%u) can't be greater than bss_start (%u)", 479: data_start, bss_start); 480: } 481: 482: tem = ADDR_CORRECT (sbrk (0)); 483: hdr.a_bss = tem - bss_start; 484: if (tem < bss_start) /* Note a_bss is unsigned on some systems */ 485: hdr.a_bss = 0; 486: hdr.a_data = bss_start - data_start; 487: #if defined(sequent) 488: hdr.a_text = data_start - (long) start_of_text () + sizeof(hdr) + N_ADDRADJ(ohdr); 489: #else 490: hdr.a_text = data_start - (long) start_of_text (); 491: #endif /* not sequent */ 492: 493: if (write (new, &hdr, sizeof hdr) != sizeof hdr) 494: { 495: PERROR (new_name); 496: } 497: return 0; 498: 499: #endif /* not COFF */ 500: } 501: 502: /* **************************************************************** 503: * copy_text_and_data 504: * 505: * Copy the text and data segments from memory to the new a.out 506: */ 507: static int 508: copy_text_and_data (new) 509: int new; 510: { 511: register int nwrite, ret; 512: register char *end; 513: int i; 514: register char *ptr; 515: char buf[80]; 516: extern int errno; 517: 518: #ifdef COFF 519: lseek (new, (long) text_scnptr, 0); 520: ptr = (char *) f_ohdr.text_start; 521: end = ptr + f_ohdr.tsize + f_ohdr.dsize; 522: while (ptr < end) 523: { 524: nwrite = 128; 525: if (nwrite > end - ptr) nwrite = end - ptr; 526: ret = write (new, ptr, nwrite); 527: if (nwrite != ret) 528: { 529: sprintf (buf, 530: "unexec write failure: addr 0x%x, fileno %d, size 0x%x, wrote 0x%x, errno %d", 531: ptr, new, nwrite, ret, errno); 532: PERROR (buf); 533: } 534: ptr += nwrite; 535: } 536: return (0); 537: 538: #else /* if not COFF */ 539: 540: #if defined(sun3) || defined(sequent) 541: lseek (new, (long) (N_TXTOFF (hdr) + sizeof (hdr)), 0); 542: #else 543: lseek (new, (long) N_TXTOFF (hdr), 0); 544: #endif 545: 546: ptr = start_of_text (); 547: end = ptr + hdr.a_text + hdr.a_data; 548: #if defined(sequent) 549: end -= (sizeof(hdr) + N_ADDRADJ(hdr)); 550: #endif 551: for (i = 0; ptr < end;) 552: { 553: nwrite = 128; 554: if (nwrite > end - ptr) nwrite = end - ptr; 555: ret = write (new, ptr, nwrite); 556: if (ret == -1 && errno == EFAULT) 557: { 558: /* BZS - again, see above about N_TXTOFF on a SUN */ 559: #if defined(sun3) || defined(sequent) 560: lseek (new, (long) (N_TXTOFF (hdr) + i + nwrite + sizeof (hdr)), 0); 561: #else 562: lseek (new, (long) (N_TXTOFF (hdr) + i + nwrite), 0); 563: #endif 564: } 565: else if (nwrite != ret) 566: { 567: sprintf (buf, 568: "unexec write failure: addr 0x%x, fileno %d, size 0x%x, wrote 0x%x, errno %d", 569: ptr, new, nwrite, ret, errno); 570: PERROR (buf); 571: } 572: i += nwrite; 573: ptr += nwrite; 574: } 575: 576: return 0; 577: #endif /* not COFF */ 578: } 579: 580: /* **************************************************************** 581: * copy_sym 582: * 583: * Copy the relocation information and symbol table from the a.out to the new 584: */ 585: static int 586: copy_sym (new, a_out, a_name, new_name) 587: int new, a_out; 588: char *a_name, *new_name; 589: { 590: char page[1024]; 591: int n; 592: 593: if (a_out < 0) 594: return 0; 595: 596: #ifdef COFF 597: if (SYMS_START == 0L) 598: return 0; 599: #endif /* COFF */ 600: 601: #ifdef sun3 602: /* BZS - I might be covering a sin with this */ 603: lseek (new, N_SYMOFF (hdr), 0); 604: #else 605: #ifdef COFF 606: if (lnnoptr) /* if there is line number info */ 607: lseek (a_out, lnnoptr, 0); /* start copying from there */ 608: else 609: #endif /* COFF */ 610: lseek (a_out, SYMS_START, 0); /* Position a.out to symtab. */ 611: #endif 612: while ((n = read (a_out, page, sizeof page)) > 0) 613: { 614: if (write (new, page, n) != n) 615: { 616: PERROR (new_name); 617: } 618: } 619: if (n < 0) 620: { 621: PERROR (a_name); 622: } 623: return 0; 624: } 625: 626: /* **************************************************************** 627: * mark_x 628: * 629: * After succesfully building the new a.out, mark it executable 630: */ 631: static 632: mark_x (name) 633: char *name; 634: { 635: struct stat sbuf; 636: int um; 637: int new = 0; /* for PERROR */ 638: 639: um = umask (777); 640: umask (um); 641: if (stat (name, &sbuf) == -1) 642: { 643: PERROR (name); 644: } 645: sbuf.st_mode |= 0111 & ~um; 646: if (chmod (name, sbuf.st_mode) == -1) 647: PERROR (name); 648: } 649: 650: /* 651: * If the COFF file contains a symbol table and a line number section, 652: * then any auxiliary entries that have values for x_lnnoptr must 653: * be adjusted by the amount that the line number section has moved 654: * in the file (bias computed in make_hdr). The #@$%&* designers of 655: * the auxiliary entry structures used the absolute file offsets for 656: * the line number entry rather than an offset from the start of the 657: * line number section! 658: * 659: * When I figure out how to scan through the symbol table and pick out 660: * the auxiliary entries that need adjustment, this routine will 661: * be fixed. As it is now, all such entries are wrong and sdb 662: * will complain. Fred Fish, UniSoft Systems Inc. 663: */ 664: 665: #ifdef COFF 666: 667: /* This function is probably very slow. Instead of reopening the new 668: file for input and output it should copy from the old to the new 669: using the two descriptors already open (WRITEDESC and READDESC). 670: Instead of reading one small structure at a time it should use 671: a reasonable size buffer. But I don't have time to work on such 672: things, so I am installing it as submitted to me. -- RMS. */ 673: 674: adjust_lnnoptrs (writedesc, readdesc, new_name) 675: int writedesc; 676: int readdesc; 677: char *new_name; 678: { 679: register int nsyms; 680: register int new; 681: struct syment symentry; 682: struct auxent auxentry; 683: 684: if (!lnnoptr || !f_hdr.f_symptr) 685: return 0; 686: 687: if ((new = open (new_name, 2)) < 0) 688: { 689: PERROR (new_name); 690: return -1; 691: } 692: 693: lseek (new, f_hdr.f_symptr, 0); 694: for (nsyms = 0; nsyms < f_hdr.f_nsyms; nsyms++) 695: { 696: read (new, &symentry, SYMESZ); 697: if (symentry.n_numaux) 698: { 699: read (new, &auxentry, AUXESZ); 700: nsyms++; 701: if (ISFCN (symentry.n_type)) { 702: auxentry.x_sym.x_fcnary.x_fcn.x_lnnoptr += bias; 703: lseek (new, -AUXESZ, 1); 704: write (new, &auxentry, AUXESZ); 705: } 706: } 707: } 708: close (new); 709: } 710: 711: #endif /* COFF */ 712: 713: #endif /* not CANNOT_DUMP */