322 lines
8.8 KiB
C
322 lines
8.8 KiB
C
|
/*
|
||
|
* Copyright (c) 2012, Chris Andrews. All rights reserved.
|
||
|
*/
|
||
|
|
||
|
#include "usdt_internal.h"
|
||
|
|
||
|
#include <stdarg.h>
|
||
|
#include <string.h>
|
||
|
#include <stdio.h>
|
||
|
|
||
|
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);
|
||
|
}
|