diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
commit | 47e6e7c84f008a53061e661f31ae96629bc694ef (patch) | |
tree | 648a07f3b5b9d67ce19b0fd72e8caa1175c98f1a /src/pmdas/linux_proc/ksym.c | |
download | pcp-debian.tar.gz |
Debian 3.9.10debian/3.9.10debian
Diffstat (limited to 'src/pmdas/linux_proc/ksym.c')
-rw-r--r-- | src/pmdas/linux_proc/ksym.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/src/pmdas/linux_proc/ksym.c b/src/pmdas/linux_proc/ksym.c new file mode 100644 index 0000000..1604c84 --- /dev/null +++ b/src/pmdas/linux_proc/ksym.c @@ -0,0 +1,564 @@ +/* + * Copyright (c) International Business Machines Corp., 2002 + * Copyright (c) 2003,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/* + * This code originally contributed by Mike Mason <mmlnx@us.ibm.com> + * with hints from the procps and ksymoops projects. + */ + +#include <ctype.h> +#include <limits.h> +#include <sys/time.h> +#include <sys/utsname.h> +#include "pmapi.h" +#include "impl.h" +#include "ksym.h" +#include "indom.h" + +static struct ksym *ksym_a; +static size_t ksym_a_sz; + +static int +find_index(__psint_t addr, int lo, int hi) +{ + int mid; + + if (lo > hi) { + return -1; + } + + mid = lo + ((hi - lo) / 2); + if (addr == ksym_a[mid].addr || + (addr > ksym_a[mid].addr && addr < ksym_a[mid+1].addr)) { + return mid; + } + + if (addr > ksym_a[mid].addr) + return find_index(addr, mid+1, hi); + else + return find_index(addr, lo, mid-1); +} + +static char * +find_name_by_addr(__psint_t addr) +{ + int ix = -1; + + if (ksym_a) + ix = find_index(addr, 0, ksym_a_sz - 1); + if (ix < 0) + return NULL; + + return ksym_a[ix].name; +} + +static int +find_dup_name(int maxix, __psint_t addr, char *name) +{ + int i, res; + + for (i = 0; i < maxix; i++) { + if (ksym_a[i].name) { + res = strcmp(ksym_a[i].name, name); + if (res > 0) + break; + if (res == 0) { + if (addr == ksym_a[i].addr) + return KSYM_FOUND; + else + return KSYM_FOUND_MISMATCH; + } + } + } + + return KSYM_NOT_FOUND; +} + +/* Brute force linear search to determine if the kernel version + in System.map matches the running kernel version and returns + a tri-state result as follows: + + 0 no match + 1 _end not found but version matched + 2 _end found and matched + */ +static int +validate_sysmap(FILE *fp, char *version, __psint_t end_addr) +{ + __psint_t addr; + char type; + int ret = 0; + char kname[128]; + + while (fscanf(fp, "%p %c %s", (void **)&addr, &type, kname) != EOF) { + if (end_addr && strcmp(kname, "_end") == 0) { + ret = (end_addr == addr) ? 2 : 0; + break; /* no need to look any further */ + } + if (strcmp(kname, version) == 0) + ret = 1; + } + + return ret; +} + +char * +wchan(__psint_t addr) +{ + static char zero; + char *p = NULL; + + if (addr == 0) /* 0 address means not in kernel space */ + p = &zero; + else if ((p = find_name_by_addr(addr))) { + /* strip off "sys_" or leading "_"s if necessary */ + if (strncmp(p, "sys_", 4) == 0) + p += 4; + while (*p == '_' && *p) + ++p; + } + + return p; +} + +static int +ksym_compare_addr(const void *e1, const void *e2) +{ + struct ksym *ks1 = (struct ksym *) e1; + struct ksym *ks2 = (struct ksym *) e2; + + if (ks1->addr < ks2->addr) + return -1; + if (ks1->addr > ks2->addr) + return 1; + return 0; +} + +static int +ksym_compare_name(const void *e1, const void *e2) +{ + struct ksym *ks1 = (struct ksym *) e1; + struct ksym *ks2 = (struct ksym *) e2; + + return(strcmp(ks1->name, ks2->name)); +} + +static int +read_ksyms(__psint_t *end_addr) +{ + char inbuf[256]; + char *ip; + char *sp; + char *tp; + char *p; + int ix = 0; + int l = 0; + int len; + int err; + FILE *fp; + struct ksym *ksym_tmp; + + *end_addr = 0; + if ((fp = proc_statsfile("/proc/ksyms", inbuf, sizeof(inbuf))) == NULL) + return -oserror(); + + while (fgets(inbuf, sizeof(inbuf), fp) != NULL) { + l++; + + /* + * /proc/ksyms lines look like this on ia32 ... + * + * c8804060 __insmod_rtc_S.text_L4576 [rtc] + * c010a320 disable_irq_nosync + * + * else on ia64 ... + * + * a0000000003e0d28 debug [arsess] + * e002100000891140 disable_irq_nosync + */ + + if (strstr(inbuf, "\n") == NULL) { + fprintf(stderr, "read_ksyms: truncated /proc/ksyms line [%d]: %s\n", l-1, inbuf); + continue; + } + + /* Increase array size, if necessary */ + if (ksym_a_sz < ix+1) { + if (ksym_a_sz > 0) + ksym_a_sz += INCR_KSIZE; + else + ksym_a_sz = INIT_KSIZE; + ksym_tmp = (struct ksym *)realloc(ksym_a, ksym_a_sz * sizeof(struct ksym)); + if (ksym_tmp == NULL) { + err = -oserror(); + free(ksym_a); + fclose(fp); + return err; + } + ksym_a = ksym_tmp; + } + + ip = inbuf; + /* parse over address */ + while (isxdigit((int)*ip)) ip++; + + if (!isspace((int)*ip) || ip-inbuf < 4) { + /* bad format line */ +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "read_ksyms: bad addr? %c[%d] line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf); + } +#endif + continue; + } + + sscanf(inbuf, "%p", (void **)&ksym_a[ix].addr); + + while (isblank((int)*ip)) ip++; + + /* next should be the symbol name */ + sp = ip++; + while (!isblank((int)*ip) &&*ip != '\n') ip++; + + /* strip off GPLONLY_ prefix, if found */ + if (strncmp(sp, "GPLONLY_", 8) == 0) + sp += 8; + + /* + * strip off symbol version suffix, if found ... looking for + * trailing pattern of the form _R.*[0-9a-fA-F]{8,} + * - find rightmost _R, if any + */ + tp = sp; + while ((p = strstr(tp, "_R")) != NULL) tp = p+2; + if (tp > sp) { + /* + * found _R, need the last 8 digits to be hex + */ + if (ip - tp + 1 >= 8) { + for (p = &ip[-8]; p < ip; p++) { + if (!isxdigit((int)*p)) { + tp = sp; + break; + } + } + } + else { + /* not enough characters for [0-9a-fA-f]{8,} at the end */ + tp = sp; + } + } + if (tp > sp) + /* need to strip the trailing _R.*[0-9a-fA-f]{8,} */ + len = tp - sp - 2; + else + len = ip - sp + 1; + + ksym_a[ix].name = strndup(sp, len); + if (ksym_a[ix].name == NULL) { + err = -oserror(); + fclose(fp); + return err; + } + ksym_a[ix].name[len-1] = '\0'; + + if (*end_addr == 0 && strcmp(ksym_a[ix].name, "_end") == 0) + *end_addr = ksym_a[ix].addr; + + if (*ip == '\n') + /* nothing after the symbol name, so no module name */ + goto next; + + while (isblank((int)*ip)) ip++; + + /* next expect module name */ + if (*ip != '[') { +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "read_ksyms: bad start module name %c[%d] != [ line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf); + } +#endif + free(ksym_a[ix].name); + continue; + } + + sp = ++ip; + while (!isblank((int)*ip) && *ip != ']') ip++; + + if (*ip != ']') { +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "read_ksyms: bad end module name %c[%d] != ] line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf); + } +#endif + free(ksym_a[ix].name); + continue; + } + + ksym_a[ix].module = strndup(sp, ip - sp + 1); + if (ksym_a[ix].module == NULL) { + err = -oserror(); + fclose(fp); + free(ksym_a[ix].name); + return err; + } + ksym_a[ix].module[ip - sp] = '\0'; + +next: + ix++; + } + + /* release unused ksym array entries */ + if (ix) { + ksym_tmp = (struct ksym *)realloc(ksym_a, ix * sizeof(struct ksym)); + if (ksym_tmp == NULL) { + free(ksym_a); + fclose(fp); + return -oserror(); + } + ksym_a = ksym_tmp; + } + + ksym_a_sz = ix; + + qsort(ksym_a, ksym_a_sz, sizeof(struct ksym), ksym_compare_name); + + fclose(fp); + +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "symbols from ksyms ...\n"); + for (ix = 0; ix < ksym_a_sz; ix++) { + fprintf(stderr, "ksym[%d] " PRINTF_P_PFX "%p %s", ix, (void *)ksym_a[ix].addr, ksym_a[ix].name); + if (ksym_a[ix].module != NULL) fprintf(stderr, " [%s]", ksym_a[ix].module); + fprintf(stderr, "\n"); + } + } +#endif + + return ksym_a_sz; +} + +static int +read_sysmap(const char *release, __psint_t end_addr) +{ + char inbuf[256], path[MAXPATHLEN], **fmt; + struct ksym *ksym_tmp; + __psint_t addr; + int ix, res, e; + int l = 0; + char *ip; + char *sp; + int major, minor, patch; + FILE *fp; + char *bestpath = NULL; + int ksym_mismatch_count; + char *sysmap_paths[] = { /* Paths to check for System.map file */ + "%s/boot/System.map-%s", + "%s/boot/System.map", + "%s/lib/modules/%s/System.map", + "%s/usr/src/linux/System.map", + "%s/System.map", + NULL + }; + + /* Create version symbol name to look for in System.map */ + if (sscanf(release, "%d.%d.%d", &major, &minor, &patch) < 3 ) + return -1; + sprintf(inbuf, "Version_%u", KERNEL_VERSION(major, minor, patch)); + + /* + * Walk through System.map path list looking for one that matches + * either _end from /proc/ksyms or the uts version. + */ + for (fmt = sysmap_paths; *fmt; fmt++) { + snprintf(path, MAXPATHLEN, *fmt, proc_statspath, release); + if ((fp = fopen(path, "r"))) { + if ((e = validate_sysmap(fp, inbuf, end_addr)) != 0) { + if (e == 2) { + /* matched _end, so this is the right System.map */ + if (bestpath) + free(bestpath); + bestpath = strdup(path); + } + else + if (e == 1 && !bestpath) + bestpath = strdup(path); + } + fclose(fp); + if (e == 2) { + /* _end matched => don't look any further */ + break; + } + } + } + + if (bestpath) + fprintf(stderr, "NOTICE: using \"%s\" for kernel symbols map.\n", bestpath); + else { + /* Didn't find a valid System.map */ + fprintf(stderr, "Warning: Valid System.map file not found!\n"); + fprintf(stderr, "Warning: proc.psinfo.wchan_s symbol names cannot be derived!\n"); + fprintf(stderr, "Warning: Addresses will be returned for proc.psinfo.wchan_s instead!\n"); + /* Free symbol array */ + for (ix = 0; ix < ksym_a_sz; ix++) { + if (ksym_a[ix].name) + free(ksym_a[ix].name); + if (ksym_a[ix].module) + free(ksym_a[ix].module); + } + free(ksym_a); + ksym_a = NULL; + ksym_a_sz = 0; + return -1; + } + + /* scan the System map */ + if ((fp = proc_statsfile(bestpath, path, sizeof(path))) == NULL) + return -oserror(); + + ix = ksym_a_sz; + + /* Read each line in System.map */ + ksym_mismatch_count = 0; + while (fgets(inbuf, sizeof(inbuf), fp) != NULL) { + /* + * System.map lines look like this on ia32 ... + * + * c010a320 T disable_irq_nosync + * + * else on ia64 ... + * + * e002000000014c80 T disable_irq_nosync + */ + + if (strstr(inbuf, "\n") == NULL) { + fprintf(stderr, "read_sysmap: truncated System.map line [%d]: %s\n", l-1, inbuf); + continue; + } + + /* Increase array size, if necessary */ + if (ksym_a_sz < ix+1) { + ksym_a_sz += INCR_KSIZE; + ksym_tmp = (struct ksym *)realloc(ksym_a, ksym_a_sz * sizeof(struct ksym)); + if (ksym_tmp == NULL) { + free(ksym_a); + goto fail; + } + ksym_a = ksym_tmp; + } + + ip = inbuf; + /* parse over address */ + while (isxdigit((int)*ip)) ip++; + + if (!isspace((int)*ip) || ip-inbuf < 4) { + /* bad format line */ +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "read_sysmap: bad addr? %c[%d] line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf); + } +#endif + continue; + } + + sscanf(inbuf, "%p", (void **)&addr); + + while (isblank((int)*ip)) ip++; + + /* Only interested in symbol types that map to code addresses, + * so: t, T, W or A + */ + if (*ip != 't' && *ip != 'T' && *ip != 'W' && *ip != 'A') + continue; + + ip++; + while (isblank((int)*ip)) ip++; + + /* next should be the symbol name */ + sp = ip++; + while (!isblank((int)*ip) && *ip != '\n') ip++; + *ip = '\0'; + + /* Determine if symbol is already in ksym array. + If so, make sure the addresses match. */ + res = find_dup_name(ix - 1, addr, sp); + if (res == KSYM_NOT_FOUND) { /* add it */ + ksym_a[ix].name = strdup(sp); + if (ksym_a[ix].name == NULL) + goto fail; + ksym_a[ix].addr = addr; + ix++; + } + else if (res == KSYM_FOUND_MISMATCH) { + if (ksym_mismatch_count++ < KSYM_MISMATCH_MAX_ALLOWED) { + /* + * ia64 function pointer descriptors make this validation + * next to useless. So only report the first + * KSYM_MISMATCH_MAX_ALLOWED mismatches found. + */ + fprintf(stderr, "Warning: mismatch for \"%s\" between System.map" + " and /proc/ksyms.\n", sp); + } + } + } + + if (ksym_mismatch_count > KSYM_MISMATCH_MAX_ALLOWED) { + fprintf(stderr, "Warning: only reported first %d out of %d mismatches " + "between System.map and /proc/ksyms.\n", + KSYM_MISMATCH_MAX_ALLOWED, ksym_mismatch_count); + } + + /* release unused ksym array entries */ + ksym_tmp = (struct ksym *)realloc(ksym_a, ix * sizeof(struct ksym)); + if (ksym_tmp == NULL) { + free(ksym_a); + goto fail; + } + ksym_a = ksym_tmp; + ksym_a_sz = ix; + + qsort(ksym_a, ksym_a_sz, sizeof(struct ksym), ksym_compare_addr); + +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "symbols from ksyms + sysmap ...\n"); + for (ix = 0; ix < ksym_a_sz; ix++) { + fprintf(stderr, "ksym[%d] " PRINTF_P_PFX "%p %s", ix, (void *)ksym_a[ix].addr, ksym_a[ix].name); + if (ksym_a[ix].module != NULL) fprintf(stderr, " [%s]", ksym_a[ix].module); + fprintf(stderr, "\n"); + } + } +#endif + + fclose(fp); + + return ksym_a_sz; + +fail: + e = -oserror(); + if (fp) + fclose(fp); + return e; +} + +void +read_ksym_sources(const char *release) +{ + __psint_t end_addr; + + if (read_ksyms(&end_addr) > 0) /* read /proc/ksyms first */ + read_sysmap(release, end_addr); /* then System.map */ +} |