/* * Copyright (c) 2012, Chris Andrews. All rights reserved. */ #include "usdt_internal.h" #include #include #include char *usdt_errors[] = { "failed to allocate memory", "failed to allocate page-aligned memory", "no probes defined", "failed to load DOF: %s", "provider is already enabled", "failed to unload DOF: %s", "probe named %s:%s:%s:%s already exists", "failed to remove probe %s:%s:%s:%s" }; static void free_probedef(usdt_probedef_t *pd) { int i; switch (pd->refcnt) { case 1: free((char *)pd->function); free((char *)pd->name); if (pd->probe) { usdt_free_tracepoints(pd->probe); free(pd->probe); } for (i = 0; i < pd->argc; i++) free(pd->types[i]); free(pd); break; case 2: pd->refcnt = 1; break; default: break; } } usdt_provider_t * usdt_create_provider(const char *name, const char *module) { usdt_provider_t *provider; if ((provider = malloc(sizeof *provider)) == NULL) return NULL; provider->name = strdup(name); provider->module = strdup(module); provider->probedefs = NULL; provider->enabled = 0; return provider; } usdt_probedef_t * usdt_create_probe(const char *func, const char *name, size_t argc, const char **types) { int i; usdt_probedef_t *p; if (argc > USDT_ARG_MAX) argc = USDT_ARG_MAX; if ((p = malloc(sizeof *p)) == NULL) return (NULL); p->refcnt = 2; p->function = strdup(func); p->name = strdup(name); p->argc = argc; p->probe = NULL; for (i = 0; i < argc; i++) p->types[i] = strdup(types[i]); return (p); } void usdt_probe_release(usdt_probedef_t *probedef) { free_probedef(probedef); } int usdt_provider_add_probe(usdt_provider_t *provider, usdt_probedef_t *probedef) { usdt_probedef_t *pd; if (provider->probedefs != NULL) { for (pd = provider->probedefs; (pd != NULL); pd = pd->next) { if ((strcmp(pd->name, probedef->name) == 0) && (strcmp(pd->function, probedef->function) == 0)) { usdt_error(provider, USDT_ERROR_DUP_PROBE, provider->name, provider->module, probedef->function, probedef->name); return (-1); } } } probedef->next = NULL; if (provider->probedefs == NULL) provider->probedefs = probedef; else { for (pd = provider->probedefs; (pd->next != NULL); pd = pd->next) ; pd->next = probedef; } return (0); } int usdt_provider_remove_probe(usdt_provider_t *provider, usdt_probedef_t *probedef) { usdt_probedef_t *pd, *prev_pd = NULL; if (provider->probedefs == NULL) { usdt_error(provider, USDT_ERROR_NOPROBES); return (-1); } for (pd = provider->probedefs; (pd != NULL); prev_pd = pd, pd = pd->next) { if ((strcmp(pd->name, probedef->name) == 0) && (strcmp(pd->function, probedef->function) == 0)) { if (prev_pd == NULL) provider->probedefs = pd->next; else prev_pd->next = pd->next; return (0); } } usdt_error(provider, USDT_ERROR_REMOVE_PROBE, provider->name, provider->module, probedef->function, probedef->name); return (-1); } int usdt_provider_enable(usdt_provider_t *provider) { usdt_strtab_t strtab; usdt_dof_file_t *file; usdt_probedef_t *pd; int i; size_t size; usdt_dof_section_t sects[5]; if (provider->enabled == 1) { usdt_error(provider, USDT_ERROR_ALREADYENABLED); return (0); /* not fatal */ } if (provider->probedefs == NULL) { usdt_error(provider, USDT_ERROR_NOPROBES); return (-1); } for (pd = provider->probedefs; pd != NULL; pd = pd->next) { if ((pd->probe = malloc(sizeof(*pd->probe))) == NULL) { usdt_error(provider, USDT_ERROR_MALLOC); return (-1); } } if ((usdt_strtab_init(&strtab, 0)) < 0) { usdt_error(provider, USDT_ERROR_MALLOC); return (-1); } if ((usdt_strtab_add(&strtab, provider->name)) == 0) { usdt_error(provider, USDT_ERROR_MALLOC); return (-1); } if ((usdt_dof_probes_sect(§s[0], provider, &strtab)) < 0) return (-1); if ((usdt_dof_prargs_sect(§s[1], provider)) < 0) return (-1); size = usdt_provider_dof_size(provider, &strtab); if ((file = usdt_dof_file_init(provider, size)) == NULL) return (-1); if ((usdt_dof_proffs_sect(§s[2], provider, file->dof)) < 0) return (-1); if ((usdt_dof_prenoffs_sect(§s[3], provider, file->dof)) < 0) return (-1); if ((usdt_dof_provider_sect(§s[4], provider)) < 0) return (-1); for (i = 0; i < 5; i++) usdt_dof_file_append_section(file, §s[i]); usdt_dof_file_generate(file, &strtab); usdt_dof_section_free((usdt_dof_section_t *)&strtab); for (i = 0; i < 5; i++) usdt_dof_section_free(§s[i]); if ((usdt_dof_file_load(file, provider->module)) < 0) { usdt_error(provider, USDT_ERROR_LOADDOF, strerror(errno)); return (-1); } provider->enabled = 1; provider->file = file; return (0); } int usdt_provider_disable(usdt_provider_t *provider) { usdt_probedef_t *pd; if (provider->enabled == 0) return (0); if ((usdt_dof_file_unload((usdt_dof_file_t *)provider->file)) < 0) { usdt_error(provider, USDT_ERROR_UNLOADDOF, strerror(errno)); return (-1); } usdt_dof_file_free(provider->file); provider->file = NULL; /* We would like to free the tracepoints here too, but OS X * (and to a lesser extent Illumos) struggle with this: * * If a provider is repeatedly disabled and re-enabled, and is * allowed to reuse the same memory for its tracepoints, *and* * there's a DTrace consumer running with enablings for these * probes, tracepoints are not always cleaned up sufficiently * that the newly-created probes work. * * Here, then, we will leak the memory holding the * tracepoints, which serves to stop us reusing the same * memory address for new tracepoints, avoiding the bug. */ for (pd = provider->probedefs; (pd != NULL); pd = pd->next) { /* may have an as yet never-enabled probe on an otherwise enabled provider */ if (pd->probe) { /* usdt_free_tracepoints(pd->probe); */ free(pd->probe); pd->probe = NULL; } } provider->enabled = 0; return (0); } void usdt_provider_free(usdt_provider_t *provider) { usdt_probedef_t *pd, *next; for (pd = provider->probedefs; pd != NULL; pd = next) { next = pd->next; free_probedef(pd); } free((char *)provider->name); free((char *)provider->module); free(provider); } int usdt_is_enabled(usdt_probe_t *probe) { if (probe != NULL) return (*probe->isenabled_addr)(); else return 0; } void usdt_fire_probe(usdt_probe_t *probe, size_t argc, void **nargv) { if (probe != NULL) usdt_probe_args(probe->probe_addr, argc, nargv); } static void usdt_verror(usdt_provider_t *provider, usdt_error_t error, va_list argp) { vasprintf(&provider->error, usdt_errors[error], argp); } void usdt_error(usdt_provider_t *provider, usdt_error_t error, ...) { va_list argp; va_start(argp, error); usdt_verror(provider, error, argp); va_end(argp); } char * usdt_errstr(usdt_provider_t *provider) { return (provider->error); }