diff --git a/compiler-rt/lib/profile/InstrProfiling.h b/compiler-rt/lib/profile/InstrProfiling.h --- a/compiler-rt/lib/profile/InstrProfiling.h +++ b/compiler-rt/lib/profile/InstrProfiling.h @@ -147,6 +147,18 @@ */ int __llvm_profile_write_file(void); +/* + * Source Based Code Coverage (SBCC) can be run automatically through + * atexit() callbacks, or it can be triggered manually by calling + * __llvm_profile_write_file(), but the later doesn't give complete profile + * results because each shared object has its own copy of that function, + * which writes profile data only for that shared object - to get a complete + * profile we need to call *all* of those functions (one from each shared + * object and the binary), so we keep a linked list of them all, providing + * __llvm_profile_write_file_all() to call them all. + */ +void __llvm_profile_write_file_all(void); + int __llvm_orderfile_write_file(void); /*! * \brief this is a wrapper interface to \c __llvm_profile_write_file. diff --git a/compiler-rt/lib/profile/InstrProfilingFile.c b/compiler-rt/lib/profile/InstrProfilingFile.c --- a/compiler-rt/lib/profile/InstrProfilingFile.c +++ b/compiler-rt/lib/profile/InstrProfilingFile.c @@ -31,6 +31,8 @@ #endif #endif +static unsigned int llvm_profile_write_file_all_running = 0; + #include "InstrProfiling.h" #include "InstrProfilingInternal.h" #include "InstrProfilingPort.h" @@ -77,6 +79,7 @@ char Hostname[COMPILER_RT_MAX_HOSTLEN]; unsigned NumPids; unsigned NumHosts; + unsigned NumSoNames; /* When in-process merging is enabled, this parameter specifies * the total number of profile data files shared by all the processes * spawned from the same binary. By default the value is 1. If merging @@ -88,8 +91,8 @@ ProfileNameSpecifier PNS; } lprofFilename; -static lprofFilename lprofCurFilename = {0, 0, 0, {0}, NULL, - {0}, 0, 0, 0, PNS_unknown}; +static lprofFilename lprofCurFilename = {0, 0, 0, {0}, NULL, {0}, + 0, 0, 0, 0, PNS_unknown}; static int ProfileMergeRequested = 0; static int getProfileFileSizeForMerging(FILE *ProfileFile, @@ -464,6 +467,8 @@ int RetVal; FILE *OutputFile; + createProfileDir(OutputName); + int MergeDone = 0; VPMergeHook = &lprofMergeValueProfData; if (doMerging()) @@ -541,6 +546,11 @@ /* Create the profile dir (even if online merging is enabled), so that * the profile file can be set up if continuous mode is enabled. */ + + if (!lprofCurFilename.MergePoolSize || + __llvm_profile_is_continuous_mode_enabled()) + createProfileDir(Filename); + createProfileDir(Filename); /* By pass file truncation to allow online raw profile merging. */ @@ -694,6 +704,7 @@ static int parseFilenamePattern(const char *FilenamePat, unsigned CopyFilenamePat) { int NumPids = 0, NumHosts = 0, I; + int NumSoNames = 0; char *PidChars = &lprofCurFilename.PidChars[0]; char *Hostname = &lprofCurFilename.Hostname[0]; int MergingEnabled = 0; @@ -746,6 +757,8 @@ FilenamePat); return -1; } + } else if (FilenamePat[I] == 'n') { + NumSoNames++; } else if (FilenamePat[I] == 'c') { if (__llvm_profile_is_continuous_mode_enabled()) { PROF_WARN("%%c specifier can only be specified once in %s.\n", @@ -777,6 +790,7 @@ lprofCurFilename.NumPids = NumPids; lprofCurFilename.NumHosts = NumHosts; + lprofCurFilename.NumSoNames = NumSoNames; return 0; } @@ -830,11 +844,13 @@ return 0; if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts || - lprofCurFilename.TmpDir || lprofCurFilename.MergePoolSize)) + lprofCurFilename.NumSoNames || lprofCurFilename.TmpDir || + lprofCurFilename.MergePoolSize)) return strlen(lprofCurFilename.FilenamePat); Len = strlen(lprofCurFilename.FilenamePat) + lprofCurFilename.NumPids * (strlen(lprofCurFilename.PidChars) - 2) + + lprofCurFilename.NumSoNames * (COMPILER_RT_MAX_SOLEN - 2) + lprofCurFilename.NumHosts * (strlen(lprofCurFilename.Hostname) - 2) + (lprofCurFilename.TmpDir ? (strlen(lprofCurFilename.TmpDir) - 1) : 0); if (lprofCurFilename.MergePoolSize) @@ -855,7 +871,8 @@ return 0; if (!(lprofCurFilename.NumPids || lprofCurFilename.NumHosts || - lprofCurFilename.TmpDir || lprofCurFilename.MergePoolSize || + lprofCurFilename.NumSoNames || lprofCurFilename.TmpDir || + lprofCurFilename.MergePoolSize || __llvm_profile_is_continuous_mode_enabled())) { if (!ForceUseBuf) return lprofCurFilename.FilenamePat; @@ -882,6 +899,28 @@ memcpy(FilenameBuf + J, lprofCurFilename.TmpDir, TmpDirLength); FilenameBuf[J + TmpDirLength] = DIR_SEPARATOR; J += TmpDirLength + 1; + } else if (FilenamePat[I] == 'n') { + char SoName[COMPILER_RT_MAX_SOLEN] = {0}; + if (lprofGetSoName(SoName, COMPILER_RT_MAX_SOLEN, + (void *)&__llvm_profile_write_file)) { + PROF_WARN("Unable to find shared object name for code address %p for " + "filename pattern %s. Using the default name\n.", + (void *)&__llvm_profile_write_file, FilenamePat); + } else { + int SoNameLength = strlen(SoName); + assert(SoNameLength < COMPILER_RT_MAX_SOLEN); + char *SoNameStart = &SoName[0]; + /* If the filename thus far ends with '/' then strip any leading '/' + from the SoName so we don't have repeated '/' in our pattern. */ + if (J && FilenameBuf[J - 1] == '/') { + while (*SoNameStart == '/' && SoNameLength) { + SoNameStart++; + SoNameLength--; + } + } + memcpy(FilenameBuf + J, SoNameStart, SoNameLength); + J += SoNameLength; + } } else { if (!getMergePoolSize(FilenamePat, &I)) continue; @@ -1061,6 +1100,13 @@ return rc; } +typedef struct ProfileWriterList { + struct ProfileWriterList *next; + int (*function)(void); +} ProfileWriterList; + +ProfileWriterList *profwrite_list = NULL; + COMPILER_RT_VISIBILITY int __llvm_profile_dump(void) { if (!doMerging()) @@ -1141,9 +1187,57 @@ lprofSetupValueProfiler(); HasBeenRegistered = 1; + + /* + * Source Based Code Coverage (SBCC) can be run automatically through + * atexit() callbacks, or it can be triggered manually by calling + * __llvm_profile_write_file(), but the later doesn't give complete profile + * results because each shared object has its own copy of that function, + * which writes profile data only for that shared object - to get a complete + * profile we need to call *all* of those functions (one from each shared + * object and the binary), so we keep a linked list of them all, providing + * __llvm_profile_write_file_all() to call them all. + */ + + ProfileWriterList *node = + (ProfileWriterList *)malloc(sizeof(ProfileWriterList)); + assert(node); + node->next = profwrite_list; + node->function = __llvm_profile_write_file; + profwrite_list = node; + return atexit(writeFileWithoutReturn); } +COMPILER_RT_VISIBILITY +void __llvm_profile_write_file_all(void) { + ProfileWriterList *node = profwrite_list; + + /* Prevent another run if a previous one is still in progress. */ + + if (!COMPILER_RT_BOOL_CMPXCHG(&llvm_profile_write_file_all_running, 0, 1)) + return; + + /* create a file to show we are dumping. + it's removed at the end of this routine + */ + int sbcc_in_progress_fd; + char sbcc_in_progress_file[256]; + sprintf(sbcc_in_progress_file, "/tmp/gcov-writing.%d", getpid()); + unlink(sbcc_in_progress_file); + sbcc_in_progress_fd = creat(sbcc_in_progress_file, 0777); + if (sbcc_in_progress_fd != -1) + close(sbcc_in_progress_fd); + + while (node) { + (void)(node->function)(); + node = node->next; + } + + unlink(sbcc_in_progress_file); + llvm_profile_write_file_all_running = 0; +} + COMPILER_RT_VISIBILITY int __llvm_profile_set_file_object(FILE *File, int EnableMerge) { if (__llvm_profile_is_continuous_mode_enabled()) { diff --git a/compiler-rt/lib/profile/InstrProfilingPlatformOther.c b/compiler-rt/lib/profile/InstrProfilingPlatformOther.c --- a/compiler-rt/lib/profile/InstrProfilingPlatformOther.c +++ b/compiler-rt/lib/profile/InstrProfilingPlatformOther.c @@ -41,6 +41,11 @@ */ COMPILER_RT_VISIBILITY void __llvm_profile_register_function(void *Data_) { + + /* FIXME: any idea why this erroneous data is being passed here? */ + if (Data_ == (void *)0x0) + return; + /* TODO: Only emit this function if we can't use linker magic. */ const __llvm_profile_data *Data = (__llvm_profile_data *)Data_; if (!DataFirst) { diff --git a/compiler-rt/lib/profile/InstrProfilingPort.h b/compiler-rt/lib/profile/InstrProfilingPort.h --- a/compiler-rt/lib/profile/InstrProfilingPort.h +++ b/compiler-rt/lib/profile/InstrProfilingPort.h @@ -53,6 +53,8 @@ #define COMPILER_RT_SECTION(Sect) __attribute__((section(Sect))) #endif +#define COMPILER_RT_MAX_SOLEN 1024 + #define COMPILER_RT_MAX_HOSTLEN 128 #ifdef __ORBIS__ #define COMPILER_RT_GETHOSTNAME(Name, Len) ((void)(Name), (void)(Len), (-1)) diff --git a/compiler-rt/lib/profile/InstrProfilingUtil.h b/compiler-rt/lib/profile/InstrProfilingUtil.h --- a/compiler-rt/lib/profile/InstrProfilingUtil.h +++ b/compiler-rt/lib/profile/InstrProfilingUtil.h @@ -84,4 +84,7 @@ int lprofReleaseMemoryPagesToOS(uintptr_t Begin, uintptr_t End); +/// Used to get Dynamic lib (i.e. .so) name +int lprofGetSoName(char *Name, int Len, void *LocalAddress); + #endif /* PROFILE_INSTRPROFILINGUTIL_H */ diff --git a/compiler-rt/lib/profile/InstrProfilingUtil.c b/compiler-rt/lib/profile/InstrProfilingUtil.c --- a/compiler-rt/lib/profile/InstrProfilingUtil.c +++ b/compiler-rt/lib/profile/InstrProfilingUtil.c @@ -33,6 +33,16 @@ #include #endif +#if defined(__FreeBSD__) || defined(__linux__) +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#if defined(__linux__) +extern char *__progname_full; +#endif +#endif /* defined(__FreeBSD__) || defined(__linux__) */ + #if defined(__Fuchsia__) #include #include @@ -368,3 +378,85 @@ } return 0; } + +struct SoName { + char *Name; + int Len; + void *LocalAddress; +}; + +static int findSoName(struct dl_phdr_info *info, size_t size, void *data) { + struct SoName *SoName = (struct SoName *)data; + int i; + + for (i = 0; i < info->dlpi_phnum; i++) { + if (info->dlpi_phdr[i].p_type == PT_LOAD) { + uintptr_t start = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr; + uintptr_t end = start + info->dlpi_phdr[i].p_memsz; + + if ((uintptr_t)SoName->LocalAddress >= start && + (uintptr_t)SoName->LocalAddress < end) { + if (!info->dlpi_name[0]) { + /* dl_iterate_phdr give empty main program name */ + /* Read main executable name from /proc/self/exe */ + if (readlink("/proc/self/exe", SoName->Name, SoName->Len) == -1) { +#if defined(__linux__) + /* If we can't get it from proc, pull it from '__progname_full' */ + strncpy(SoName->Name, __progname_full, SoName->Len); +#else + PROF_WARN("LLVM profile warning: Couldn't get main executable name " + "from readlink(/proc/self/exe)\n"); + return 0; /* Indicate not found */ +#endif + } + } else { + strncpy(SoName->Name, info->dlpi_name, SoName->Len); + } + SoName->Name[SoName->Len - 1] = '\0'; + return 1; + } + } + } + return 0; // or else dl_iterate_phdr will stop iterating +} + +#ifdef __linux__ +static void lprofGetSoNameFromMap(char *Name, int Len, void *LocalAddress) { + char buf[1024]; + FILE *fp = fopen("/proc/self/maps", "rt"); + if (!fp) { + return; + } + unsigned long long start, end; + char exe; + char map_entry_name[1024]; + + while (fgets(buf, sizeof(buf), fp)) { + int ntokens = sscanf(buf, "%llx-%llx %*c%*c%c%*c %*x %*x:%*x %*u %1023s", + &start, &end, &exe, (char *)&map_entry_name); + + if (ntokens == 4 && exe == 'x' && LocalAddress >= (void *)start && + LocalAddress < (void *)end) { + strncpy(Name, map_entry_name, Len); + Name[Len - 1] = '\0'; + break; + } + } + fclose(fp); +} +#endif /* __linux__ */ +COMPILER_RT_VISIBILITY int lprofGetSoName(char *Name, int Len, + void *LocalAddress) { + struct SoName SoName = {Name, Len, LocalAddress}; +#if defined(__FreeBSD__) || defined(__linux__) + (void)dl_iterate_phdr(findSoName, &SoName); +#ifdef __linux__ + // On linux dl_iterate_phdr() does not show a header for the main executable + // itself + if (!Name[0]) { + lprofGetSoNameFromMap(Name, Len, LocalAddress); + } +#endif /* __linux__ */ +#endif /* __FreeBSD__ || __linux__ */ + return Name[0] ? 0 : 1; +} diff --git a/compiler-rt/test/profile/Linux/instrprof-shared-nProfraws.test b/compiler-rt/test/profile/Linux/instrprof-shared-nProfraws.test new file mode 100644 --- /dev/null +++ b/compiler-rt/test/profile/Linux/instrprof-shared-nProfraws.test @@ -0,0 +1,28 @@ +""" +This test produces two shared libraries: + +1. libhi.so is instrumented +2. libhello.so is instrumented + +After the libraries are built, the main program is then built with instrumentation and linked +against above libraries. + +The test is to verify that when using '%n' it generates three different profraw files. + +From current build path: +$ cd projects/compiler-rt/test/profile +$ find -name "*instr-main.profraw"; find -name "*hi*.profraw"; find -name "*hello*.profraw" +./Profile-x86_64/Linux/feedbackdir//projects/compiler-rt/test/profile/Profile-x86_64/Linux/Output/instrprof-shared-nProfraws.test.tmp-instr-main.profraw +./Profile-x86_64/Linux/feedbackdir//projects/compiler-rt/test/profile/Profile-x86_64/Linux/Output/instrprof-shared-nProfraws.test.tmp.d/libhi.so.profraw +./Profile-x86_64/Linux/feedbackdir/projects/compiler-rt/test/profile/Profile-x86_64/Linux/Output/instrprof-shared-nProfraws.test.tmp.d/libhello.so.profraw +""" + +RUN: mkdir -p %t.d +RUN: %clang_profgen -o %t.d/libhi.so -fPIC -shared %S/../Inputs/instrprof-dynamic-a.cpp +RUN: %clang_profgen -o %t.d/libhello.so -fPIC -shared %S/../Inputs/instrprof-dynamic-b.cpp + +RUN: %clang_profgen -o %t-instr-main -L%t.d -rpath %t.d -lhi -lhello %S/../Inputs/instrprof-dynamic-main.cpp + +RUN: env LLVM_PROFILE_FILE=./feedbackdir/%n.profraw %run %t-instr-main + +RUN: find -name "*instr-main.profraw"; find -name "*hi*.profraw"; find -name "*hello*.profraw"