Implement support for boot-time DTrace.
This allows one to enable DTrace probes relatively early during boot, during SI_SUB_DTRACE_ANON, before dtrace(1) can invoked. The desired enabling is created using dtrace -A, which writes a /boot/dtrace.dof file and uses nextboot(8) to ensure that DTrace kernel modules are loaded and that the DOF file describing the enabling is loaded by loader(8) during the subsequent boot. The trace output can then be fetched with dtrace -a. With this commit, boot-time DTrace is only functional on i386 and amd64: on other architectures, the high-resolution timer frequency is initialized during SI_SUB_CLOCKS and is thus not available when the anonymous tracing state is initialized. On x86, the TSC is used and is thus available earlier. MFC after: 1 month Relnotes: yes
This commit is contained in:
parent
8553156023
commit
b529028676
@ -50,6 +50,9 @@
|
||||
#ifdef illumos
|
||||
#include <libproc.h>
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
#include <spawn.h>
|
||||
#endif
|
||||
|
||||
typedef struct dtrace_cmd {
|
||||
void (*dc_func)(struct dtrace_cmd *); /* function to compile arg */
|
||||
@ -397,7 +400,41 @@ dof_prune(const char *fname)
|
||||
free(buf);
|
||||
}
|
||||
|
||||
#ifdef illumos
|
||||
#ifdef __FreeBSD__
|
||||
/*
|
||||
* Use nextboot(8) to tell the loader to load DTrace kernel modules during
|
||||
* the next boot of the system. The nextboot(8) configuration is removed during
|
||||
* boot, so it will not persist indefinitely.
|
||||
*/
|
||||
static void
|
||||
bootdof_add(void)
|
||||
{
|
||||
char * const nbargv[] = {
|
||||
"nextboot", "-a",
|
||||
"-e", "dtraceall_load=\"YES\"",
|
||||
"-e", "dtrace_dof_load=\"YES\"",
|
||||
"-e", "dtrace_dof_name=\"/boot/dtrace.dof\"",
|
||||
"-e", "dtrace_dof_type=\"dtrace_dof\"",
|
||||
NULL,
|
||||
};
|
||||
pid_t child;
|
||||
int err, status;
|
||||
|
||||
err = posix_spawnp(&child, "nextboot", NULL, NULL, nbargv,
|
||||
NULL);
|
||||
if (err != 0) {
|
||||
error("failed to execute nextboot: %s", strerror(err));
|
||||
exit(E_ERROR);
|
||||
}
|
||||
|
||||
if (waitpid(child, &status, 0) != child)
|
||||
fatal("waiting for nextboot");
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
error("nextboot returned with status %d", status);
|
||||
exit(E_ERROR);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void
|
||||
etcsystem_prune(void)
|
||||
{
|
||||
@ -508,7 +545,7 @@ etcsystem_add(void)
|
||||
|
||||
error("added forceload directives to %s\n", g_ofile);
|
||||
}
|
||||
#endif /* illumos */
|
||||
#endif /* !__FreeBSD__ */
|
||||
|
||||
static void
|
||||
print_probe_info(const dtrace_probeinfo_t *p)
|
||||
@ -643,24 +680,24 @@ anon_prog(const dtrace_cmd_t *dcp, dof_hdr_t *dof, int n)
|
||||
p = (uchar_t *)dof;
|
||||
q = p + dof->dofh_loadsz;
|
||||
|
||||
#ifdef illumos
|
||||
#ifdef __FreeBSD__
|
||||
/*
|
||||
* On FreeBSD, the DOF file is read directly during boot - just write
|
||||
* two hex characters per byte.
|
||||
*/
|
||||
oprintf("dof-data-%d=", n);
|
||||
|
||||
while (p < q)
|
||||
oprintf("%02x", *p++);
|
||||
|
||||
oprintf("\n");
|
||||
#else
|
||||
oprintf("dof-data-%d=0x%x", n, *p++);
|
||||
|
||||
while (p < q)
|
||||
oprintf(",0x%x", *p++);
|
||||
|
||||
oprintf(";\n");
|
||||
#else
|
||||
/*
|
||||
* On FreeBSD, the DOF data is handled as a kernel environment (kenv)
|
||||
* string. We use two hex characters per DOF byte.
|
||||
*/
|
||||
oprintf("dof-data-%d=%02x", n, *p++);
|
||||
|
||||
while (p < q)
|
||||
oprintf("%02x", *p++);
|
||||
|
||||
oprintf("\n");
|
||||
#endif
|
||||
|
||||
dtrace_dof_destroy(g_dtp, dof);
|
||||
@ -1725,8 +1762,7 @@ main(int argc, char *argv[])
|
||||
#else
|
||||
/*
|
||||
* On FreeBSD, anonymous DOF data is written to
|
||||
* the DTrace DOF file that the boot loader will
|
||||
* read if booting with the DTrace option.
|
||||
* the DTrace DOF file.
|
||||
*/
|
||||
g_ofile = "/boot/dtrace.dof";
|
||||
#endif
|
||||
@ -1765,7 +1801,10 @@ main(int argc, char *argv[])
|
||||
* that itself contains a #pragma D option quiet.
|
||||
*/
|
||||
error("saved anonymous enabling in %s\n", g_ofile);
|
||||
#ifdef illumos
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
bootdof_add();
|
||||
#else
|
||||
etcsystem_add();
|
||||
error("run update_drv(1M) or reboot to enable changes\n");
|
||||
#endif
|
||||
|
@ -117,6 +117,7 @@
|
||||
#include <sys/ctype.h>
|
||||
#include <sys/eventhandler.h>
|
||||
#include <sys/limits.h>
|
||||
#include <sys/linker.h>
|
||||
#include <sys/kdb.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/malloc.h>
|
||||
@ -11916,6 +11917,21 @@ dtrace_buffer_activate(dtrace_state_t *state)
|
||||
dtrace_interrupt_enable(cookie);
|
||||
}
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
/*
|
||||
* Activate the specified per-CPU buffer. This is used instead of
|
||||
* dtrace_buffer_activate() when APs have not yet started, i.e. when
|
||||
* activating anonymous state.
|
||||
*/
|
||||
static void
|
||||
dtrace_buffer_activate_cpu(dtrace_state_t *state, int cpu)
|
||||
{
|
||||
|
||||
if (state->dts_buffer[cpu].dtb_tomax != NULL)
|
||||
state->dts_buffer[cpu].dtb_flags &= ~DTRACEBUF_INACTIVE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
dtrace_buffer_alloc(dtrace_buffer_t *bufs, size_t size, int flags,
|
||||
processorid_t cpu, int *factor)
|
||||
@ -12532,9 +12548,15 @@ dtrace_enabling_dump(dtrace_enabling_t *enab)
|
||||
for (i = 0; i < enab->dten_ndesc; i++) {
|
||||
dtrace_probedesc_t *desc = &enab->dten_desc[i]->dted_probe;
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
printf("dtrace: enabling probe %d (%s:%s:%s:%s)\n", i,
|
||||
desc->dtpd_provider, desc->dtpd_mod,
|
||||
desc->dtpd_func, desc->dtpd_name);
|
||||
#else
|
||||
cmn_err(CE_NOTE, "enabling probe %d (%s:%s:%s:%s)", i,
|
||||
desc->dtpd_provider, desc->dtpd_mod,
|
||||
desc->dtpd_func, desc->dtpd_name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -13185,19 +13207,91 @@ dtrace_dof_char(char c)
|
||||
return (c - 'a' + 10);
|
||||
}
|
||||
/* Should not reach here. */
|
||||
return (0);
|
||||
return (UCHAR_MAX);
|
||||
}
|
||||
#endif /* __FreeBSD__ */
|
||||
|
||||
static dof_hdr_t *
|
||||
dtrace_dof_property(const char *name)
|
||||
{
|
||||
#ifdef __FreeBSD__
|
||||
uint8_t *dofbuf;
|
||||
u_char *data, *eol;
|
||||
caddr_t doffile;
|
||||
size_t bytes, len, i;
|
||||
dof_hdr_t *dof;
|
||||
u_char c1, c2;
|
||||
|
||||
dof = NULL;
|
||||
|
||||
doffile = preload_search_by_type("dtrace_dof");
|
||||
if (doffile == NULL)
|
||||
return (NULL);
|
||||
|
||||
data = preload_fetch_addr(doffile);
|
||||
len = preload_fetch_size(doffile);
|
||||
for (;;) {
|
||||
/* Look for the end of the line. All lines end in a newline. */
|
||||
eol = memchr(data, '\n', len);
|
||||
if (eol == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (strncmp(name, data, strlen(name)) == 0)
|
||||
break;
|
||||
|
||||
eol++; /* skip past the newline */
|
||||
len -= eol - data;
|
||||
data = eol;
|
||||
}
|
||||
|
||||
/* We've found the data corresponding to the specified key. */
|
||||
|
||||
data += strlen(name) + 1; /* skip past the '=' */
|
||||
len = eol - data;
|
||||
bytes = len / 2;
|
||||
|
||||
if (bytes < sizeof(dof_hdr_t)) {
|
||||
dtrace_dof_error(NULL, "truncated header");
|
||||
goto doferr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Each byte is represented by the two ASCII characters in its hex
|
||||
* representation.
|
||||
*/
|
||||
dofbuf = malloc(bytes, M_SOLARIS, M_WAITOK);
|
||||
for (i = 0; i < bytes; i++) {
|
||||
c1 = dtrace_dof_char(data[i * 2]);
|
||||
c2 = dtrace_dof_char(data[i * 2 + 1]);
|
||||
if (c1 == UCHAR_MAX || c2 == UCHAR_MAX) {
|
||||
dtrace_dof_error(NULL, "invalid hex char in DOF");
|
||||
goto doferr;
|
||||
}
|
||||
dofbuf[i] = c1 * 16 + c2;
|
||||
}
|
||||
|
||||
dof = (dof_hdr_t *)dofbuf;
|
||||
if (bytes < dof->dofh_loadsz) {
|
||||
dtrace_dof_error(NULL, "truncated DOF");
|
||||
goto doferr;
|
||||
}
|
||||
|
||||
if (dof->dofh_loadsz >= dtrace_dof_maxsize) {
|
||||
dtrace_dof_error(NULL, "oversized DOF");
|
||||
goto doferr;
|
||||
}
|
||||
|
||||
return (dof);
|
||||
|
||||
doferr:
|
||||
free(dof, M_SOLARIS);
|
||||
return (NULL);
|
||||
#else /* __FreeBSD__ */
|
||||
uchar_t *buf;
|
||||
uint64_t loadsz;
|
||||
unsigned int len, i;
|
||||
dof_hdr_t *dof;
|
||||
|
||||
#ifdef illumos
|
||||
/*
|
||||
* Unfortunately, array of values in .conf files are always (and
|
||||
* only) interpreted to be integer arrays. We must read our DOF
|
||||
@ -13231,49 +13325,9 @@ dtrace_dof_property(const char *name)
|
||||
dof = kmem_alloc(loadsz, KM_SLEEP);
|
||||
bcopy(buf, dof, loadsz);
|
||||
ddi_prop_free(buf);
|
||||
#else
|
||||
char *p;
|
||||
char *p_env;
|
||||
|
||||
if ((p_env = kern_getenv(name)) == NULL)
|
||||
return (NULL);
|
||||
|
||||
len = strlen(p_env) / 2;
|
||||
|
||||
buf = kmem_alloc(len, KM_SLEEP);
|
||||
|
||||
dof = (dof_hdr_t *) buf;
|
||||
|
||||
p = p_env;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
buf[i] = (dtrace_dof_char(p[0]) << 4) |
|
||||
dtrace_dof_char(p[1]);
|
||||
p += 2;
|
||||
}
|
||||
|
||||
freeenv(p_env);
|
||||
|
||||
if (len < sizeof (dof_hdr_t)) {
|
||||
kmem_free(buf, 0);
|
||||
dtrace_dof_error(NULL, "truncated header");
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (len < (loadsz = dof->dofh_loadsz)) {
|
||||
kmem_free(buf, 0);
|
||||
dtrace_dof_error(NULL, "truncated DOF");
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (loadsz >= dtrace_dof_maxsize) {
|
||||
kmem_free(buf, 0);
|
||||
dtrace_dof_error(NULL, "oversized DOF");
|
||||
return (NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
return (dof);
|
||||
#endif /* !__FreeBSD__ */
|
||||
}
|
||||
|
||||
static void
|
||||
@ -14332,7 +14386,7 @@ static dtrace_state_t *
|
||||
#ifdef illumos
|
||||
dtrace_state_create(dev_t *devp, cred_t *cr)
|
||||
#else
|
||||
dtrace_state_create(struct cdev *dev)
|
||||
dtrace_state_create(struct cdev *dev, struct ucred *cred __unused)
|
||||
#endif
|
||||
{
|
||||
#ifdef illumos
|
||||
@ -14945,6 +14999,18 @@ dtrace_state_go(dtrace_state_t *state, processorid_t *cpu)
|
||||
if (state->dts_activity == DTRACE_ACTIVITY_WARMUP)
|
||||
state->dts_activity = DTRACE_ACTIVITY_ACTIVE;
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
/*
|
||||
* We enable anonymous tracing before APs are started, so we must
|
||||
* activate buffers using the current CPU.
|
||||
*/
|
||||
if (state == dtrace_anon.dta_state)
|
||||
for (int i = 0; i < NCPU; i++)
|
||||
dtrace_buffer_activate_cpu(state, i);
|
||||
else
|
||||
dtrace_xcall(DTRACE_CPUALL,
|
||||
(dtrace_xcall_t)dtrace_buffer_activate, state);
|
||||
#else
|
||||
/*
|
||||
* Regardless of whether or not now we're in ACTIVE or DRAINING, we
|
||||
* want each CPU to transition its principal buffer out of the
|
||||
@ -14955,6 +15021,7 @@ dtrace_state_go(dtrace_state_t *state, processorid_t *cpu)
|
||||
*/
|
||||
dtrace_xcall(DTRACE_CPUALL,
|
||||
(dtrace_xcall_t)dtrace_buffer_activate, state);
|
||||
#endif
|
||||
goto out;
|
||||
|
||||
err:
|
||||
@ -15316,11 +15383,7 @@ dtrace_anon_property(void)
|
||||
* If we haven't allocated an anonymous state, we'll do so now.
|
||||
*/
|
||||
if ((state = dtrace_anon.dta_state) == NULL) {
|
||||
#ifdef illumos
|
||||
state = dtrace_state_create(NULL, NULL);
|
||||
#else
|
||||
state = dtrace_state_create(NULL);
|
||||
#endif
|
||||
dtrace_anon.dta_state = state;
|
||||
|
||||
if (state == NULL) {
|
||||
@ -17001,7 +17064,7 @@ dtrace_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
|
||||
|
||||
state = dtrace_state_create(devp, cred_p);
|
||||
#else
|
||||
state = dtrace_state_create(dev);
|
||||
state = dtrace_state_create(dev, NULL);
|
||||
devfs_set_cdevpriv(state, dtrace_dtr);
|
||||
#endif
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user