/* * Copyright (c) 2016 GitHub, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "bcc_perf_map.h" #include "bcc_proc.h" #include "bcc_elf.h" char *bcc_procutils_which(const char *binpath) { char buffer[4096]; const char *PATH; if (strchr(binpath, '/')) return bcc_elf_is_exe(binpath) ? strdup(binpath) : 0; if (!(PATH = getenv("PATH"))) return 0; while (PATH) { const char *next = strchr(PATH, ':') ?: strchr(PATH, '\0'); const size_t path_len = next - PATH; if (path_len) { memcpy(buffer, PATH, path_len); buffer[path_len] = '/'; strcpy(buffer + path_len + 1, binpath); if (bcc_elf_is_exe(buffer)) return strdup(buffer); } PATH = *next ? (next + 1) : 0; } return 0; } int bcc_mapping_is_file_backed(const char *mapname) { return mapname[0] && strncmp(mapname, "//anon", sizeof("//anon") - 1) && strncmp(mapname, "/dev/zero", sizeof("/dev/zero") - 1) && strncmp(mapname, "/anon_hugepage", sizeof("/anon_hugepage") - 1) && strncmp(mapname, "[stack", sizeof("[stack") - 1) && strncmp(mapname, "/SYSV", sizeof("/SYSV") - 1) && strncmp(mapname, "[heap]", sizeof("[heap]") - 1); } int bcc_procutils_each_module(int pid, bcc_procutils_modulecb callback, void *payload) { char procmap_filename[128]; FILE *procmap; int ret; sprintf(procmap_filename, "/proc/%ld/maps", (long)pid); procmap = fopen(procmap_filename, "r"); if (!procmap) return -1; do { char endline[4096]; char perm[8], dev[8]; long long begin, end, size, inode; ret = fscanf(procmap, "%llx-%llx %s %llx %s %lld", &begin, &end, perm, &size, dev, &inode); if (!fgets(endline, sizeof(endline), procmap)) break; if (ret == 6) { char *mapname = endline; char *newline = strchr(endline, '\n'); if (newline) newline[0] = '\0'; while (isspace(mapname[0])) mapname++; if (strchr(perm, 'x') && bcc_mapping_is_file_backed(mapname)) { if (callback(mapname, (uint64_t)begin, (uint64_t)end, true, payload) < 0) break; } } } while (ret && ret != EOF); fclose(procmap); // Address mapping for the entire address space maybe in /tmp/perf-.map // This will be used if symbols aren't resolved in an earlier mapping. char map_path[4096]; // Try perf-.map path with process's mount namespace, chroot and NSPID, // in case it is generated by the process itself. if (bcc_perf_map_path(map_path, sizeof(map_path), pid)) callback(map_path, 0, -1, true, payload); // Try perf-.map path with global root and PID, in case it is generated // by other Process. Avoid checking mount namespace for this. int res = snprintf(map_path, 4096, "/tmp/perf-%d.map", pid); if (res > 0 && res < 4096) callback(map_path, 0, -1, false, payload); return 0; } int bcc_procutils_each_ksym(bcc_procutils_ksymcb callback, void *payload) { char line[2048]; FILE *kallsyms; /* root is needed to list ksym addresses */ if (geteuid() != 0) return -1; kallsyms = fopen("/proc/kallsyms", "r"); if (!kallsyms) return -1; if (!fgets(line, sizeof(line), kallsyms)) { fclose(kallsyms); return -1; } while (fgets(line, sizeof(line), kallsyms)) { char *symname, *endsym; unsigned long long addr; addr = strtoull(line, &symname, 16); endsym = symname = symname + 3; while (*endsym && !isspace(*endsym)) endsym++; *endsym = '\0'; callback(symname, addr, payload); } fclose(kallsyms); return 0; } #define CACHE1_HEADER "ld.so-1.7.0" #define CACHE1_HEADER_LEN (sizeof(CACHE1_HEADER) - 1) #define CACHE2_HEADER "glibc-ld.so.cache" #define CACHE2_HEADER_LEN (sizeof(CACHE2_HEADER) - 1) #define CACHE2_VERSION "1.1" struct ld_cache1_entry { int32_t flags; uint32_t key; uint32_t value; }; struct ld_cache1 { char header[CACHE1_HEADER_LEN]; uint32_t entry_count; struct ld_cache1_entry entries[0]; }; struct ld_cache2_entry { int32_t flags; uint32_t key; uint32_t value; uint32_t pad1_; uint64_t pad2_; }; struct ld_cache2 { char header[CACHE2_HEADER_LEN]; char version[3]; uint32_t entry_count; uint32_t string_table_len; uint32_t pad_[5]; struct ld_cache2_entry entries[0]; }; static int lib_cache_count; static struct ld_lib { char *libname; char *path; int flags; } * lib_cache; static int read_cache1(const char *ld_map) { struct ld_cache1 *ldcache = (struct ld_cache1 *)ld_map; const char *ldstrings = (const char *)(ldcache->entries + ldcache->entry_count); uint32_t i; lib_cache = (struct ld_lib *)malloc(ldcache->entry_count * sizeof(struct ld_lib)); lib_cache_count = (int)ldcache->entry_count; for (i = 0; i < ldcache->entry_count; ++i) { const char *key = ldstrings + ldcache->entries[i].key; const char *val = ldstrings + ldcache->entries[i].value; const int flags = ldcache->entries[i].flags; lib_cache[i].libname = strdup(key); lib_cache[i].path = strdup(val); lib_cache[i].flags = flags; } return 0; } static int read_cache2(const char *ld_map) { struct ld_cache2 *ldcache = (struct ld_cache2 *)ld_map; uint32_t i; if (memcmp(ld_map, CACHE2_HEADER, CACHE2_HEADER_LEN)) return -1; lib_cache = (struct ld_lib *)malloc(ldcache->entry_count * sizeof(struct ld_lib)); lib_cache_count = (int)ldcache->entry_count; for (i = 0; i < ldcache->entry_count; ++i) { const char *key = ld_map + ldcache->entries[i].key; const char *val = ld_map + ldcache->entries[i].value; const int flags = ldcache->entries[i].flags; lib_cache[i].libname = strdup(key); lib_cache[i].path = strdup(val); lib_cache[i].flags = flags; } return 0; } static int load_ld_cache(const char *cache_path) { struct stat st; size_t ld_size; const char *ld_map; int ret, fd = open(cache_path, O_RDONLY); if (fd < 0) return -1; if (fstat(fd, &st) < 0 || st.st_size < sizeof(struct ld_cache1)) { close(fd); return -1; } ld_size = st.st_size; ld_map = (const char *)mmap(NULL, ld_size, PROT_READ, MAP_PRIVATE, fd, 0); if (ld_map == MAP_FAILED) { close(fd); return -1; } if (memcmp(ld_map, CACHE1_HEADER, CACHE1_HEADER_LEN) == 0) { const struct ld_cache1 *cache1 = (struct ld_cache1 *)ld_map; size_t cache1_len = sizeof(struct ld_cache1) + (cache1->entry_count * sizeof(struct ld_cache1_entry)); cache1_len = (cache1_len + 0x7) & ~0x7ULL; if (ld_size > (cache1_len + sizeof(struct ld_cache2))) ret = read_cache2(ld_map + cache1_len); else ret = read_cache1(ld_map); } else { ret = read_cache2(ld_map); } munmap((void *)ld_map, ld_size); close(fd); return ret; } #define LD_SO_CACHE "/etc/ld.so.cache" #define FLAG_TYPE_MASK 0x00ff #define TYPE_ELF_LIBC6 0x0003 #define FLAG_ABI_MASK 0xff00 #define ABI_SPARC_LIB64 0x0100 #define ABI_IA64_LIB64 0x0200 #define ABI_X8664_LIB64 0x0300 #define ABI_S390_LIB64 0x0400 #define ABI_POWERPC_LIB64 0x0500 static bool match_so_flags(int flags) { if ((flags & FLAG_TYPE_MASK) != TYPE_ELF_LIBC6) return false; switch (flags & FLAG_ABI_MASK) { case ABI_SPARC_LIB64: case ABI_IA64_LIB64: case ABI_X8664_LIB64: case ABI_S390_LIB64: case ABI_POWERPC_LIB64: return (sizeof(void *) == 8); } return sizeof(void *) == 4; } static bool which_so_in_process(const char* libname, int pid, char* libpath) { int ret, found = false; char endline[4096], *mapname = NULL, *newline; char mappings_file[128]; const size_t search_len = strlen(libname) + strlen("/lib."); char search1[search_len + 1]; char search2[search_len + 1]; sprintf(mappings_file, "/proc/%ld/maps", (long)pid); FILE *fp = fopen(mappings_file, "r"); if (!fp) return NULL; snprintf(search1, search_len + 1, "/lib%s.", libname); snprintf(search2, search_len + 1, "/lib%s-", libname); do { ret = fscanf(fp, "%*x-%*x %*s %*x %*s %*d"); if (!fgets(endline, sizeof(endline), fp)) break; mapname = endline; newline = strchr(endline, '\n'); if (newline) newline[0] = '\0'; while (isspace(mapname[0])) mapname++; if (strstr(mapname, ".so") && (strstr(mapname, search1) || strstr(mapname, search2))) { found = true; memcpy(libpath, mapname, strlen(mapname) + 1); break; } } while (ret != EOF); fclose(fp); return found; } char *bcc_procutils_which_so(const char *libname, int pid) { const size_t soname_len = strlen(libname) + strlen("lib.so"); char soname[soname_len + 1]; char libpath[4096]; int i; if (strchr(libname, '/')) return strdup(libname); if (pid && which_so_in_process(libname, pid, libpath)) return strdup(libpath); if (lib_cache_count < 0) return NULL; if (!lib_cache_count && load_ld_cache(LD_SO_CACHE) < 0) { lib_cache_count = -1; return NULL; } snprintf(soname, soname_len + 1, "lib%s.so", libname); for (i = 0; i < lib_cache_count; ++i) { if (!strncmp(lib_cache[i].libname, soname, soname_len) && match_so_flags(lib_cache[i].flags)) { return strdup(lib_cache[i].path); } } return NULL; } void bcc_procutils_free(const char *ptr) { free((void *)ptr); } /* Detects the following languages + C. */ const char *languages[] = {"java", "python", "ruby", "php", "node"}; const char *language_c = "c"; const int nb_languages = 5; const char *bcc_procutils_language(int pid) { char procfilename[22], line[4096], pathname[32], *str; FILE *procfile; int i, ret; /* Look for clues in the absolute path to the executable. */ sprintf(procfilename, "/proc/%ld/exe", (long)pid); if (realpath(procfilename, line)) { for (i = 0; i < nb_languages; i++) if (strstr(line, languages[i])) return languages[i]; } sprintf(procfilename, "/proc/%ld/maps", (long)pid); procfile = fopen(procfilename, "r"); if (!procfile) return NULL; /* Look for clues in memory mappings. */ bool libc = false; do { char perm[8], dev[8]; long long begin, end, size, inode; ret = fscanf(procfile, "%llx-%llx %s %llx %s %lld", &begin, &end, perm, &size, dev, &inode); if (!fgets(line, sizeof(line), procfile)) break; if (ret == 6) { char *mapname = line; char *newline = strchr(line, '\n'); if (newline) newline[0] = '\0'; while (isspace(mapname[0])) mapname++; for (i = 0; i < nb_languages; i++) { sprintf(pathname, "/lib%s", languages[i]); if (strstr(mapname, pathname)) return languages[i]; if ((str = strstr(mapname, "libc")) && (str[4] == '-' || str[4] == '.')) libc = true; } } } while (ret && ret != EOF); fclose(procfile); /* Return C as the language if libc was found and nothing else. */ return libc ? language_c : NULL; }