1 /*===- InstrProfilingFile.c - Write instrumentation to a file -------------===*\
2 |*
3 |*                     The LLVM Compiler Infrastructure
4 |*
5 |* This file is distributed under the University of Illinois Open Source
6 |* License. See LICENSE.TXT for details.
7 |*
8 \*===----------------------------------------------------------------------===*/
9 
10 #include <errno.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #ifdef _MSC_VER
15 /* For _alloca. */
16 #include <malloc.h>
17 #endif
18 #if defined(_WIN32)
19 #include "WindowsMMap.h"
20 /* For _chsize_s */
21 #include <io.h>
22 #include <process.h>
23 #else
24 #include <sys/file.h>
25 #include <sys/mman.h>
26 #include <unistd.h>
27 #if defined(__linux__)
28 #include <sys/types.h>
29 #endif
30 #endif
31 
32 #include "InstrProfiling.h"
33 #include "InstrProfilingInternal.h"
34 #include "InstrProfilingUtil.h"
35 
36 /* From where is profile name specified.
37  * The order the enumerators define their
38  * precedence. Re-order them may lead to
39  * runtime behavior change. */
40 typedef enum ProfileNameSpecifier {
41   PNS_unknown = 0,
42   PNS_default,
43   PNS_command_line,
44   PNS_environment,
45   PNS_runtime_api
46 } ProfileNameSpecifier;
47 
48 static const char *getPNSStr(ProfileNameSpecifier PNS) {
49   switch (PNS) {
50   case PNS_default:
51     return "default setting";
52   case PNS_command_line:
53     return "command line";
54   case PNS_environment:
55     return "environment variable";
56   case PNS_runtime_api:
57     return "runtime API";
58   default:
59     return "Unknown";
60   }
61 }
62 
63 #define MAX_PID_SIZE 16
64 /* Data structure holding the result of parsed filename pattern. */
65 typedef struct lprofFilename {
66   /* File name string possibly with %p or %h specifiers. */
67   const char *FilenamePat;
68   /* A flag indicating if FilenamePat's memory is allocated
69    * by runtime. */
70   unsigned OwnsFilenamePat;
71   const char *ProfilePathPrefix;
72   char PidChars[MAX_PID_SIZE];
73   char Hostname[COMPILER_RT_MAX_HOSTLEN];
74   unsigned NumPids;
75   unsigned NumHosts;
76   /* When in-process merging is enabled, this parameter specifies
77    * the total number of profile data files shared by all the processes
78    * spawned from the same binary. By default the value is 1. If merging
79    * is not enabled, its value should be 0. This parameter is specified
80    * by the %[0-9]m specifier. For instance %2m enables merging using
81    * 2 profile data files. %1m is equivalent to %m. Also %m specifier
82    * can only appear once at the end of the name pattern. */
83   unsigned MergePoolSize;
84   ProfileNameSpecifier PNS;
85 } lprofFilename;
86 
87 COMPILER_RT_WEAK lprofFilename lprofCurFilename = {0, 0, 0, {0}, {0},
88                                                    0, 0, 0, PNS_unknown};
89 
90 static int getCurFilenameLength();
91 static const char *getCurFilename(char *FilenameBuf);
92 static unsigned doMerging() { return lprofCurFilename.MergePoolSize; }
93 
94 /* Return 1 if there is an error, otherwise return  0.  */
95 static uint32_t fileWriter(ProfDataWriter *This, ProfDataIOVec *IOVecs,
96                            uint32_t NumIOVecs) {
97   uint32_t I;
98   FILE *File = (FILE *)This->WriterCtx;
99   for (I = 0; I < NumIOVecs; I++) {
100     if (IOVecs[I].Data) {
101       if (fwrite(IOVecs[I].Data, IOVecs[I].ElmSize, IOVecs[I].NumElm, File) !=
102           IOVecs[I].NumElm)
103         return 1;
104     } else {
105       if (fseek(File, IOVecs[I].ElmSize * IOVecs[I].NumElm, SEEK_CUR) == -1)
106         return 1;
107     }
108   }
109   return 0;
110 }
111 
112 static void initFileWriter(ProfDataWriter *This, FILE *File) {
113   This->Write = fileWriter;
114   This->WriterCtx = File;
115 }
116 
117 COMPILER_RT_VISIBILITY ProfBufferIO *
118 lprofCreateBufferIOInternal(void *File, uint32_t BufferSz) {
119   FreeHook = &free;
120   DynamicBufferIOBuffer = (uint8_t *)calloc(BufferSz, 1);
121   VPBufferSize = BufferSz;
122   ProfDataWriter *fileWriter =
123       (ProfDataWriter *)calloc(sizeof(ProfDataWriter), 1);
124   initFileWriter(fileWriter, File);
125   ProfBufferIO *IO = lprofCreateBufferIO(fileWriter);
126   IO->OwnFileWriter = 1;
127   return IO;
128 }
129 
130 static void setupIOBuffer() {
131   const char *BufferSzStr = 0;
132   BufferSzStr = getenv("LLVM_VP_BUFFER_SIZE");
133   if (BufferSzStr && BufferSzStr[0]) {
134     VPBufferSize = atoi(BufferSzStr);
135     DynamicBufferIOBuffer = (uint8_t *)calloc(VPBufferSize, 1);
136   }
137 }
138 
139 /* Read profile data in \c ProfileFile and merge with in-memory
140    profile counters. Returns -1 if there is fatal error, otheriwse
141    0 is returned. Returning 0 does not mean merge is actually
142    performed. If merge is actually done, *MergeDone is set to 1.
143 */
144 static int doProfileMerging(FILE *ProfileFile, int *MergeDone) {
145   uint64_t ProfileFileSize;
146   char *ProfileBuffer;
147 
148   if (fseek(ProfileFile, 0L, SEEK_END) == -1) {
149     PROF_ERR("Unable to merge profile data, unable to get size: %s\n",
150              strerror(errno));
151     return -1;
152   }
153   ProfileFileSize = ftell(ProfileFile);
154 
155   /* Restore file offset.  */
156   if (fseek(ProfileFile, 0L, SEEK_SET) == -1) {
157     PROF_ERR("Unable to merge profile data, unable to rewind: %s\n",
158              strerror(errno));
159     return -1;
160   }
161 
162   /* Nothing to merge.  */
163   if (ProfileFileSize < sizeof(__llvm_profile_header)) {
164     if (ProfileFileSize)
165       PROF_WARN("Unable to merge profile data: %s\n",
166                 "source profile file is too small.");
167     return 0;
168   }
169 
170   ProfileBuffer = mmap(NULL, ProfileFileSize, PROT_READ, MAP_SHARED | MAP_FILE,
171                        fileno(ProfileFile), 0);
172   if (ProfileBuffer == MAP_FAILED) {
173     PROF_ERR("Unable to merge profile data, mmap failed: %s\n",
174              strerror(errno));
175     return -1;
176   }
177 
178   if (__llvm_profile_check_compatibility(ProfileBuffer, ProfileFileSize)) {
179     (void)munmap(ProfileBuffer, ProfileFileSize);
180     PROF_WARN("Unable to merge profile data: %s\n",
181               "source profile file is not compatible.");
182     return 0;
183   }
184 
185   /* Now start merging */
186   __llvm_profile_merge_from_buffer(ProfileBuffer, ProfileFileSize);
187 
188   // Truncate the file in case merging of value profile did not happend to
189   // prevent from leaving garbage data at the end of the profile file.
190   COMPILER_RT_FTRUNCATE(ProfileFile, __llvm_profile_get_size_for_buffer());
191 
192   (void)munmap(ProfileBuffer, ProfileFileSize);
193   *MergeDone = 1;
194 
195   return 0;
196 }
197 
198 /* Create the directory holding the file, if needed. */
199 static void createProfileDir(const char *Filename) {
200   size_t Length = strlen(Filename);
201   if (lprofFindFirstDirSeparator(Filename)) {
202     char *Copy = (char *)COMPILER_RT_ALLOCA(Length + 1);
203     strncpy(Copy, Filename, Length + 1);
204     __llvm_profile_recursive_mkdir(Copy);
205   }
206 }
207 
208 /* Open the profile data for merging. It opens the file in r+b mode with
209  * file locking.  If the file has content which is compatible with the
210  * current process, it also reads in the profile data in the file and merge
211  * it with in-memory counters. After the profile data is merged in memory,
212  * the original profile data is truncated and gets ready for the profile
213  * dumper. With profile merging enabled, each executable as well as any of
214  * its instrumented shared libraries dump profile data into their own data file.
215 */
216 static FILE *openFileForMerging(const char *ProfileFileName, int *MergeDone) {
217   FILE *ProfileFile;
218   int rc;
219 
220   createProfileDir(ProfileFileName);
221   ProfileFile = lprofOpenFileEx(ProfileFileName);
222   if (!ProfileFile)
223     return NULL;
224 
225   rc = doProfileMerging(ProfileFile, MergeDone);
226   if (rc || (!*MergeDone && COMPILER_RT_FTRUNCATE(ProfileFile, 0L)) ||
227       fseek(ProfileFile, 0L, SEEK_SET) == -1) {
228     PROF_ERR("Profile Merging of file %s failed: %s\n", ProfileFileName,
229              strerror(errno));
230     fclose(ProfileFile);
231     return NULL;
232   }
233   return ProfileFile;
234 }
235 
236 /* Write profile data to file \c OutputName.  */
237 static int writeFile(const char *OutputName) {
238   int RetVal;
239   FILE *OutputFile;
240 
241   int MergeDone = 0;
242   VPMergeHook = &lprofMergeValueProfData;
243   if (!doMerging())
244     OutputFile = fopen(OutputName, "ab");
245   else
246     OutputFile = openFileForMerging(OutputName, &MergeDone);
247 
248   if (!OutputFile)
249     return -1;
250 
251   FreeHook = &free;
252   setupIOBuffer();
253   ProfDataWriter fileWriter;
254   initFileWriter(&fileWriter, OutputFile);
255   RetVal = lprofWriteData(&fileWriter, lprofGetVPDataReader(), MergeDone);
256 
257   fclose(OutputFile);
258   return RetVal;
259 }
260 
261 static void truncateCurrentFile(void) {
262   const char *Filename;
263   char *FilenameBuf;
264   FILE *File;
265   int Length;
266 
267   Length = getCurFilenameLength();
268   FilenameBuf = (char *)COMPILER_RT_ALLOCA(Length + 1);
269   Filename = getCurFilename(FilenameBuf);
270   if (!Filename)
271     return;
272 
273   /* By pass file truncation to allow online raw profile
274    * merging. */
275   if (lprofCurFilename.MergePoolSize)
276     return;
277 
278   createProfileDir(Filename);
279 
280   /* Truncate the file.  Later we'll reopen and append. */
281   File = fopen(Filename, "w");
282   if (!File)
283     return;
284   fclose(File);
285 }
286 
287 static const char *DefaultProfileName = "default.profraw";
288 static void resetFilenameToDefault(void) {
289   if (lprofCurFilename.FilenamePat && lprofCurFilename.OwnsFilenamePat) {
290     free((void *)lprofCurFilename.FilenamePat);
291   }
292   memset(&lprofCurFilename, 0, sizeof(lprofCurFilename));
293   lprofCurFilename.FilenamePat = DefaultProfileName;
294   lprofCurFilename.PNS = PNS_default;
295 }
296 
297 static int containsMergeSpecifier(const char *FilenamePat, int I) {
298   return (FilenamePat[I] == 'm' ||
299           (FilenamePat[I] >= '1' && FilenamePat[I] <= '9' &&
300            /* If FilenamePat[I] is not '\0', the next byte is guaranteed
301             * to be in-bound as the string is null terminated. */
302            FilenamePat[I + 1] == 'm'));
303 }
304 
305 /* Parses the pattern string \p FilenamePat and stores the result to
306  * lprofcurFilename structure. */
307 static int parseFilenamePattern(const char *FilenamePat,
308                                 unsigned CopyFilenamePat) {
309   int NumPids = 0, NumHosts = 0, I;
310   char *PidChars = &lprofCurFilename.PidChars[0];
311   char *Hostname = &lprofCurFilename.Hostname[0];
312   int MergingEnabled = 0;
313 
314   /* Clean up cached prefix.  */
315   if (lprofCurFilename.ProfilePathPrefix)
316     free((void *)lprofCurFilename.ProfilePathPrefix);
317   memset(&lprofCurFilename, 0, sizeof(lprofCurFilename));
318 
319   if (lprofCurFilename.FilenamePat && lprofCurFilename.OwnsFilenamePat) {
320     free((void *)lprofCurFilename.FilenamePat);
321   }
322 
323   if (!CopyFilenamePat)
324     lprofCurFilename.FilenamePat = FilenamePat;
325   else {
326     lprofCurFilename.FilenamePat = strdup(FilenamePat);
327     lprofCurFilename.OwnsFilenamePat = 1;
328   }
329   /* Check the filename for "%p", which indicates a pid-substitution. */
330   for (I = 0; FilenamePat[I]; ++I)
331     if (FilenamePat[I] == '%') {
332       if (FilenamePat[++I] == 'p') {
333         if (!NumPids++) {
334           if (snprintf(PidChars, MAX_PID_SIZE, "%ld", (long)getpid()) <= 0) {
335             PROF_WARN("Unable to get pid for filename pattern %s. Using the "
336                       "default name.",
337                       FilenamePat);
338             return -1;
339           }
340         }
341       } else if (FilenamePat[I] == 'h') {
342         if (!NumHosts++)
343           if (COMPILER_RT_GETHOSTNAME(Hostname, COMPILER_RT_MAX_HOSTLEN)) {
344             PROF_WARN("Unable to get hostname for filename pattern %s. Using "
345                       "the default name.",
346                       FilenamePat);
347             return -1;
348           }
349       } else if (containsMergeSpecifier(FilenamePat, I)) {
350         if (MergingEnabled) {
351           PROF_WARN("%%m specifier can only be specified once in %s.\n",
352                     FilenamePat);
353           return -1;
354         }
355         MergingEnabled = 1;
356         if (FilenamePat[I] == 'm')
357           lprofCurFilename.MergePoolSize = 1;
358         else {
359           lprofCurFilename.MergePoolSize = FilenamePat[I] - '0';
360           I++; /* advance to 'm' */
361         }
362       }
363     }
364 
365   lprofCurFilename.NumPids = NumPids;
366   lprofCurFilename.NumHosts = NumHosts;
367   return 0;
368 }
369 
370 static void parseAndSetFilename(const char *FilenamePat,
371                                 ProfileNameSpecifier PNS,
372                                 unsigned CopyFilenamePat) {
373 
374   const char *OldFilenamePat = lprofCurFilename.FilenamePat;
375   ProfileNameSpecifier OldPNS = lprofCurFilename.PNS;
376 
377   if (PNS < OldPNS)
378     return;
379 
380   if (!FilenamePat)
381     FilenamePat = DefaultProfileName;
382 
383   if (OldFilenamePat && !strcmp(OldFilenamePat, FilenamePat)) {
384     lprofCurFilename.PNS = PNS;
385     return;
386   }
387 
388   /* When PNS >= OldPNS, the last one wins. */
389   if (!FilenamePat || parseFilenamePattern(FilenamePat, CopyFilenamePat))
390     resetFilenameToDefault();
391   lprofCurFilename.PNS = PNS;
392 
393   if (!OldFilenamePat) {
394     if (getenv("LLVM_PROFILE_VERBOSE"))
395       PROF_NOTE("Set profile file path to \"%s\" via %s.\n",
396                 lprofCurFilename.FilenamePat, getPNSStr(PNS));
397   } else {
398     if (getenv("LLVM_PROFILE_VERBOSE"))
399       PROF_NOTE("Override old profile path \"%s\" via %s to \"%s\" via %s.\n",
400                 OldFilenamePat, getPNSStr(OldPNS), lprofCurFilename.FilenamePat,
401                 getPNSStr(PNS));
402   }
403 
404   truncateCurrentFile();
405 }
406 
407 /* Return buffer length that is required to store the current profile
408  * filename with PID and hostname substitutions. */
409 /* The length to hold uint64_t followed by 2 digit pool id including '_' */
410 #define SIGLEN 24
411 static int getCurFilenameLength() {
412   int Len;
413   if (!lprofCurFilename.FilenamePat || !lprofCurFilename.FilenamePat[0])
414     return 0;
415 
416   if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts ||
417         lprofCurFilename.MergePoolSize))
418     return strlen(lprofCurFilename.FilenamePat);
419 
420   Len = strlen(lprofCurFilename.FilenamePat) +
421         lprofCurFilename.NumPids * (strlen(lprofCurFilename.PidChars) - 2) +
422         lprofCurFilename.NumHosts * (strlen(lprofCurFilename.Hostname) - 2);
423   if (lprofCurFilename.MergePoolSize)
424     Len += SIGLEN;
425   return Len;
426 }
427 
428 /* Return the pointer to the current profile file name (after substituting
429  * PIDs and Hostnames in filename pattern. \p FilenameBuf is the buffer
430  * to store the resulting filename. If no substitution is needed, the
431  * current filename pattern string is directly returned. */
432 static const char *getCurFilename(char *FilenameBuf) {
433   int I, J, PidLength, HostNameLength;
434   const char *FilenamePat = lprofCurFilename.FilenamePat;
435 
436   if (!lprofCurFilename.FilenamePat || !lprofCurFilename.FilenamePat[0])
437     return 0;
438 
439   if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts ||
440         lprofCurFilename.MergePoolSize))
441     return lprofCurFilename.FilenamePat;
442 
443   PidLength = strlen(lprofCurFilename.PidChars);
444   HostNameLength = strlen(lprofCurFilename.Hostname);
445   /* Construct the new filename. */
446   for (I = 0, J = 0; FilenamePat[I]; ++I)
447     if (FilenamePat[I] == '%') {
448       if (FilenamePat[++I] == 'p') {
449         memcpy(FilenameBuf + J, lprofCurFilename.PidChars, PidLength);
450         J += PidLength;
451       } else if (FilenamePat[I] == 'h') {
452         memcpy(FilenameBuf + J, lprofCurFilename.Hostname, HostNameLength);
453         J += HostNameLength;
454       } else if (containsMergeSpecifier(FilenamePat, I)) {
455         char LoadModuleSignature[SIGLEN];
456         int S;
457         int ProfilePoolId = getpid() % lprofCurFilename.MergePoolSize;
458         S = snprintf(LoadModuleSignature, SIGLEN, "%" PRIu64 "_%d",
459                      lprofGetLoadModuleSignature(), ProfilePoolId);
460         if (S == -1 || S > SIGLEN)
461           S = SIGLEN;
462         memcpy(FilenameBuf + J, LoadModuleSignature, S);
463         J += S;
464         if (FilenamePat[I] != 'm')
465           I++;
466       }
467       /* Drop any unknown substitutions. */
468     } else
469       FilenameBuf[J++] = FilenamePat[I];
470   FilenameBuf[J] = 0;
471 
472   return FilenameBuf;
473 }
474 
475 /* Returns the pointer to the environment variable
476  * string. Returns null if the env var is not set. */
477 static const char *getFilenamePatFromEnv(void) {
478   const char *Filename = getenv("LLVM_PROFILE_FILE");
479   if (!Filename || !Filename[0])
480     return 0;
481   return Filename;
482 }
483 
484 COMPILER_RT_VISIBILITY
485 const char *__llvm_profile_get_path_prefix(void) {
486   int Length;
487   char *FilenameBuf, *Prefix;
488   const char *Filename, *PrefixEnd;
489 
490   if (lprofCurFilename.ProfilePathPrefix)
491     return lprofCurFilename.ProfilePathPrefix;
492 
493   Length = getCurFilenameLength();
494   FilenameBuf = (char *)COMPILER_RT_ALLOCA(Length + 1);
495   Filename = getCurFilename(FilenameBuf);
496   if (!Filename)
497     return "\0";
498 
499   PrefixEnd = lprofFindLastDirSeparator(Filename);
500   if (!PrefixEnd)
501     return "\0";
502 
503   Length = PrefixEnd - Filename + 1;
504   Prefix = (char *)malloc(Length + 1);
505   if (!Prefix) {
506     PROF_ERR("Failed to %s\n", "allocate memory.");
507     return "\0";
508   }
509   memcpy(Prefix, Filename, Length);
510   Prefix[Length] = '\0';
511   lprofCurFilename.ProfilePathPrefix = Prefix;
512   return Prefix;
513 }
514 
515 /* This method is invoked by the runtime initialization hook
516  * InstrProfilingRuntime.o if it is linked in. Both user specified
517  * profile path via -fprofile-instr-generate= and LLVM_PROFILE_FILE
518  * environment variable can override this default value. */
519 COMPILER_RT_VISIBILITY
520 void __llvm_profile_initialize_file(void) {
521   const char *EnvFilenamePat;
522   const char *SelectedPat = NULL;
523   ProfileNameSpecifier PNS = PNS_unknown;
524   int hasCommandLineOverrider = (INSTR_PROF_PROFILE_NAME_VAR[0] != 0);
525 
526   EnvFilenamePat = getFilenamePatFromEnv();
527   if (EnvFilenamePat) {
528     /* Pass CopyFilenamePat = 1, to ensure that the filename would be valid
529        at the  moment when __llvm_profile_write_file() gets executed. */
530     parseAndSetFilename(EnvFilenamePat, PNS_environment, 1);
531     return;
532   } else if (hasCommandLineOverrider) {
533     SelectedPat = INSTR_PROF_PROFILE_NAME_VAR;
534     PNS = PNS_command_line;
535   } else {
536     SelectedPat = NULL;
537     PNS = PNS_default;
538   }
539 
540   parseAndSetFilename(SelectedPat, PNS, 0);
541 }
542 
543 /* This API is directly called by the user application code. It has the
544  * highest precedence compared with LLVM_PROFILE_FILE environment variable
545  * and command line option -fprofile-instr-generate=<profile_name>.
546  */
547 COMPILER_RT_VISIBILITY
548 void __llvm_profile_set_filename(const char *FilenamePat) {
549   parseAndSetFilename(FilenamePat, PNS_runtime_api, 1);
550 }
551 
552 /* The public API for writing profile data into the file with name
553  * set by previous calls to __llvm_profile_set_filename or
554  * __llvm_profile_override_default_filename or
555  * __llvm_profile_initialize_file. */
556 COMPILER_RT_VISIBILITY
557 int __llvm_profile_write_file(void) {
558   int rc, Length;
559   const char *Filename;
560   char *FilenameBuf;
561   int PDeathSig = 0;
562 
563   if (lprofProfileDumped()) {
564     PROF_NOTE("Profile data not written to file: %s.\n",
565               "already written");
566     return 0;
567   }
568 
569   Length = getCurFilenameLength();
570   FilenameBuf = (char *)COMPILER_RT_ALLOCA(Length + 1);
571   Filename = getCurFilename(FilenameBuf);
572 
573   /* Check the filename. */
574   if (!Filename) {
575     PROF_ERR("Failed to write file : %s\n", "Filename not set");
576     return -1;
577   }
578 
579   /* Check if there is llvm/runtime version mismatch.  */
580   if (GET_VERSION(__llvm_profile_get_version()) != INSTR_PROF_RAW_VERSION) {
581     PROF_ERR("Runtime and instrumentation version mismatch : "
582              "expected %d, but get %d\n",
583              INSTR_PROF_RAW_VERSION,
584              (int)GET_VERSION(__llvm_profile_get_version()));
585     return -1;
586   }
587 
588   // Temporarily suspend getting SIGKILL when the parent exits.
589   PDeathSig = lprofSuspendSigKill();
590 
591   /* Write profile data to the file. */
592   rc = writeFile(Filename);
593   if (rc)
594     PROF_ERR("Failed to write file \"%s\": %s\n", Filename, strerror(errno));
595 
596   // Restore SIGKILL.
597   if (PDeathSig == 1)
598     lprofRestoreSigKill();
599 
600   return rc;
601 }
602 
603 COMPILER_RT_VISIBILITY
604 int __llvm_profile_dump(void) {
605   if (!doMerging())
606     PROF_WARN("Later invocation of __llvm_profile_dump can lead to clobbering "
607               " of previously dumped profile data : %s. Either use %%m "
608               "in profile name or change profile name before dumping.\n",
609               "online profile merging is not on");
610   int rc = __llvm_profile_write_file();
611   lprofSetProfileDumped();
612   return rc;
613 }
614 
615 static void writeFileWithoutReturn(void) { __llvm_profile_write_file(); }
616 
617 COMPILER_RT_VISIBILITY
618 int __llvm_profile_register_write_file_atexit(void) {
619   static int HasBeenRegistered = 0;
620 
621   if (HasBeenRegistered)
622     return 0;
623 
624   lprofSetupValueProfiler();
625 
626   HasBeenRegistered = 1;
627   return atexit(writeFileWithoutReturn);
628 }
629