/* * 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 "bcc_proc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcc_elf.h" #include "bcc_perf_map.h" #include "bcc_zip.h" #ifdef __x86_64__ // https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt const unsigned long long kernelAddrSpace = 0x00ffffffffffffff; #else const unsigned long long kernelAddrSpace = 0x0; #endif char *bcc_procutils_which(const char *binpath) { char buffer[PATH_MAX]; 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) { int ret = snprintf(buffer, sizeof(buffer), "%.*s/%s", (int)path_len, PATH, binpath); if (ret < 0 || ret >= sizeof(buffer)) return 0; if (bcc_elf_is_exe(buffer)) return strdup(buffer); } PATH = *next ? (next + 1) : 0; } return 0; } #define STARTS_WITH(mapname, prefix) (!strncmp(mapname, prefix, sizeof(prefix)-1)) int bcc_mapping_is_file_backed(const char *mapname) { return mapname[0] && !( STARTS_WITH(mapname, "//anon") || STARTS_WITH(mapname, "/dev/zero") || STARTS_WITH(mapname, "/anon_hugepage") || STARTS_WITH(mapname, "[stack") || STARTS_WITH(mapname, "/SYSV") || STARTS_WITH(mapname, "[heap]") || STARTS_WITH(mapname, "[vsyscall]")); } /* Finds a file descriptor for a given inode if it's a memory-backed fd. */ static char *_procutils_memfd_path(const int pid, const uint64_t inum) { char path_buffer[PATH_MAX + 1]; char *path = NULL; char *dirstr; DIR *dirstream; struct stat sb; struct dirent *dent; snprintf(path_buffer, (PATH_MAX + 1), "/proc/%d/fd", pid); dirstr = malloc(strlen(path_buffer) + 1); strcpy(dirstr, path_buffer); dirstream = opendir(dirstr); if (dirstream == NULL) { free(dirstr); return NULL; } while (path == NULL && (dent = readdir(dirstream)) != NULL) { snprintf(path_buffer, (PATH_MAX + 1), "%s/%s", dirstr, dent->d_name); if (stat(path_buffer, &sb) == -1) continue; if (sb.st_ino == inum) { char *pid_fd_path = malloc(strlen(path_buffer) + 1); strcpy(pid_fd_path, path_buffer); path = pid_fd_path; } } closedir(dirstream); free(dirstr); return path; } static int _procfs_might_be_zip_path(const char *path) { return strstr(path, ".zip") || strstr(path, ".apk"); } static char *_procfs_find_zip_entry(const char *path, int pid, uint32_t *offset) { char ns_relative_path[PATH_MAX]; int rc = snprintf(ns_relative_path, sizeof(ns_relative_path), "/proc/%d/root%s", pid, path); if (rc < 0 || rc >= sizeof(ns_relative_path)) { return NULL; } struct bcc_zip_archive *archive = bcc_zip_archive_open(ns_relative_path); if (archive == NULL) { return NULL; } struct bcc_zip_entry entry; if (bcc_zip_archive_find_entry_at_offset(archive, *offset, &entry) || entry.compression) { bcc_zip_archive_close(archive); return NULL; } char *result = malloc(strlen(path) + entry.name_length + 3); if (result == NULL) { bcc_zip_archive_close(archive); return NULL; } sprintf(result, "%s!/%.*s", path, entry.name_length, entry.name); *offset -= entry.data_offset; bcc_zip_archive_close(archive); return result; } // return: 0 -> callback returned < 0, stopped iterating // -1 -> callback never indicated to stop int _procfs_maps_each_module(FILE *procmap, int pid, bcc_procutils_modulecb callback, void *payload) { char buf[PATH_MAX + 1], perm[5]; char *name, *resolved_name; mod_info mod; uint8_t enter_ns; while (true) { enter_ns = 1; buf[0] = '\0'; // From fs/proc/task_mmu.c:show_map_vma if (fscanf(procmap, "%" PRIx64 "-%" PRIx64 " %4s %llx %" PRIx64 ":%" PRIx64 " %" PRIu64 "%[^\n]", &mod.start_addr, &mod.end_addr, perm, &mod.file_offset, &mod.dev_major, &mod.dev_minor, &mod.inode, buf) != 8) break; if (perm[2] != 'x') continue; name = buf; while (isspace(*name)) name++; mod.name = name; if (!bcc_mapping_is_file_backed(name)) continue; resolved_name = NULL; if (strstr(name, "/memfd:")) { resolved_name = _procutils_memfd_path(pid, mod.inode); if (resolved_name != NULL) { enter_ns = 0; } } else if (_procfs_might_be_zip_path(mod.name)) { uint32_t zip_entry_offset = mod.file_offset; resolved_name = _procfs_find_zip_entry(mod.name, pid, &zip_entry_offset); if (resolved_name != NULL) { mod.file_offset = zip_entry_offset; } } if (resolved_name != NULL) { strncpy(buf, resolved_name, PATH_MAX); buf[PATH_MAX] = 0; free(resolved_name); mod.name = buf; } if (callback(&mod, enter_ns, payload) < 0) return 0; } return -1; } int bcc_procutils_each_module(int pid, bcc_procutils_modulecb callback, void *payload) { char procmap_filename[128]; FILE *procmap; snprintf(procmap_filename, sizeof(procmap_filename), "/proc/%ld/maps", (long)pid); procmap = fopen(procmap_filename, "r"); if (!procmap) return -1; _procfs_maps_each_module(procmap, pid, callback, payload); // 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. mod_info mod; memset(&mod, 0, sizeof(mod_info)); if (bcc_perf_map_path(map_path, sizeof(map_path), pid)) { mod.name = map_path; mod.end_addr = -1; if (callback(&mod, 1, payload) < 0) goto done; } // Try perf-.map path with global root and PID, in case it is generated // by other Process. Avoid checking mount namespace for this. memset(&mod, 0, sizeof(mod_info)); int res = snprintf(map_path, 4096, "/tmp/perf-%d.map", pid); if (res > 0 && res < 4096) { mod.name = map_path; mod.end_addr = -1; if (callback(&mod, 0, payload) < 0) goto done; } done: fclose(procmap); return 0; } int bcc_procutils_each_ksym(bcc_procutils_ksymcb callback, void *payload) { char line[2048]; char *symname, *endsym, *modname, *endmod = NULL; FILE *kallsyms; unsigned long long addr; kallsyms = fopen("/proc/kallsyms", "r"); if (!kallsyms) return -1; while (fgets(line, sizeof(line), kallsyms)) { addr = strtoull(line, &symname, 16); if (addr == 0 || addr == ULLONG_MAX) continue; if (addr < kernelAddrSpace) continue; symname++; // Ignore data symbols if (*symname == 'b' || *symname == 'B' || *symname == 'd' || *symname == 'D' || *symname == 'r' || *symname =='R') continue; endsym = (symname = symname + 2); while (*endsym && !isspace(*endsym)) endsym++; *endsym = '\0'; // Parse module name if it's available modname = endsym + 1; while (*modname && isspace(*endsym)) modname++; if (*modname && *modname == '[') { endmod = ++modname; while (*endmod && *endmod != ']') endmod++; if (*endmod) *(endmod) = '\0'; else endmod = NULL; } if (!endmod) modname = "kernel"; callback(symname, modname, 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 #define ABI_AARCH64_LIB64 0x0a00 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: case ABI_AARCH64_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]; snprintf(mappings_file, sizeof(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))) { const size_t mapnamelen = strlen(mapname); if (mapnamelen >= PATH_MAX) { fprintf(stderr, "Found mapped library path is too long\n"); break; } found = true; memcpy(libpath, mapname, mapnamelen + 1); break; } } while (ret != EOF); fclose(fp); return found; } static bool which_so_in_ldconfig_cache(const char* libname, char* libpath) { const size_t soname_len = strlen(libname) + strlen("lib.so"); char soname[soname_len + 1]; int i; if (lib_cache_count < 0) return false; if (!lib_cache_count && load_ld_cache(LD_SO_CACHE) < 0) { lib_cache_count = -1; return false; } 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)) { const char* path = lib_cache[i].path; const size_t pathlen = strlen(path); if (pathlen >= PATH_MAX) { fprintf(stderr, "Found library path is too long\n"); return false; } memcpy(libpath, path, pathlen + 1); return true; } } return false; } char *bcc_procutils_which_so(const char *libname, int pid) { char libpath[PATH_MAX]; if (strchr(libname, '/')) return strdup(libname); if (pid && which_so_in_process(libname, pid, libpath)) return strdup(libpath); if (which_so_in_ldconfig_cache(libname, libpath)) return strdup(libpath); return NULL; } char *bcc_procutils_which_so_in_process(const char *libname, int pid) { char libpath[PATH_MAX]; if (pid && which_so_in_process(libname, pid, libpath)) return strdup(libpath); return NULL; } void bcc_procutils_free(const char *ptr) { free((void *)ptr); } /* Detects the following languages + C. */ const char *languages[] = {"java", "node", "perl", "php", "python", "ruby"}; const char *language_c = "c"; const int nb_languages = 6; const char *bcc_procutils_language(int pid) { char procfilename[24], line[4096], pathname[32], *str; FILE *procfile; int i, ret; /* Look for clues in the absolute path to the executable. */ snprintf(procfilename, sizeof(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]; } snprintf(procfilename, sizeof(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++) { snprintf(pathname, sizeof(pathname), "/lib%s", languages[i]); if (strstr(mapname, pathname)) { fclose(procfile); 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; }