Skip to content

Commit

Permalink
libcommon: move uprobes creation into the common library
Browse files Browse the repository at this point in the history
This commit adds functions in the libcommon library to create uprobes
given a pid and address, create uprobes given more detailed info (a
(dev, ino, addr) triplet, a spec for the uprobe_events file, and a
DTrace probe name), and compute a spec. We also provide
encoding/decoding functions to allow the uprobe to contain the DTrace
probe name as part of its args even though DTrace probes can contain
characters that are not allowed in uprobe names or arg names, and even
though probe arg names must be unique while DTrace probe name components
need not be.

The DTrace provider is rejigged to use this code to generate the
BEGIN/END/ERROR probes, to make sure this code works.

Signed-off-by: Nick Alcock <nick.alcock@oracle.com>
Reviewed-by: Kris Van Hees <kris.van.hees@oracle.com>
  • Loading branch information
nickalcock authored and kvanhees committed Oct 27, 2022
1 parent ec5254b commit 5b43ff1
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 39 deletions.
14 changes: 14 additions & 0 deletions include/tracefs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Oracle Linux DTrace; simple uprobe helper functions
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/

#ifndef _TRACEFS_H
#define _TRACEFS_H

#define TRACEFS "/sys/kernel/debug/tracing/"
#define EVENTSFS TRACEFS "events/"

#endif /* _TRACEFS_H */
2 changes: 1 addition & 1 deletion libcommon/Build
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ LIBS += libcommon
libcommon_TARGET = libcommon
libcommon_DIR := $(current-dir)
libcommon_CPPFLAGS := -Ilibcommon -Ilibproc
libcommon_SOURCES = dt_list.c
libcommon_SOURCES = uprobes.c dt_list.c
libcommon_LIBSOURCES = libcommon
310 changes: 310 additions & 0 deletions libcommon/uprobes.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
/*
* Oracle Linux DTrace.
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/

#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libproc.h>
#include <assert.h>
#include <tracefs.h>

/*
* Return a uprobe spec for a given address in a given PID (or
* process handle, to use an already-grabbed process).
*/
char *
uprobe_spec_by_addr(pid_t pid, ps_prochandle *P, uint64_t addr,
prmap_t *mapp_)
{
int free_p = 0;
int perr = 0;
char *spec = NULL;
const prmap_t *mapp, *first_mapp;

if (!P) {
P = Pgrab(pid, 2, 0, NULL, &perr);
if (P == NULL)
return NULL;
free_p = 1;
}

mapp = Paddr_to_map(P, addr);
if (mapp == NULL)
goto out;

first_mapp = mapp->pr_file->first_segment;

/*
* No need for error-checking here: we do the same on error
* and success.
*/
asprintf(&spec, "%s:0x%lx", mapp->pr_file->prf_mapname,
addr - first_mapp->pr_vaddr);

if (mapp_)
memcpy(mapp_, mapp, sizeof(prmap_t));

out:
if (free_p) {
/*
* Some things in the prmap aren't valid once the prochandle is
* freed.
*/
if (mapp_) {
mapp_->pr_mapaddrname = NULL;
mapp_->pr_file = NULL;
}

Prelease(P, PS_RELEASE_NORMAL);
Pfree(P);
}

return spec;
}

static const char hexdigits[] = "0123456789abcdef";

/*
* Encode a NAME suitably for representation in a uprobe. All non-alphanumeric,
* non-_ characters are replaced with __XX where XX is the hex encoding of the
* ASCII code of the byte. __ itself is replaced with ___.
*/
char *
uprobe_encode_name(const char *name)
{
const char *p = name;
char *out_p;
char *encoded;
size_t sz = strlen(name);

/*
* Compute size changes needed.
*/

while ((p = strstr(p, "__")) != NULL) {
sz++;
p += 2;
}

for (p = name; *p != '\0'; p++) {
if (!isalpha(*p) && !isdigit(*p) && *p != '_')
sz += 3;
}

encoded = malloc(sz + 1);
if (!encoded)
return NULL;
out_p = encoded;

/* Apply translations. */

for (p = name; *p != '\0'; p++) {
int hexencode = 0, underencode = 0;

if (!isalpha(*p) && !isdigit(*p) && *p != '_')
hexencode = 1;
if (p[0] == '_' && p[1] == '_' && p[2] != '\0')
underencode = 1;

if (underencode) {
*out_p++ = '_';
*out_p++ = '_';
*out_p++ = '_';
p++;
continue;
}

if (hexencode) {
*out_p++ = '_';
*out_p++ = '_';
*out_p++ = hexdigits[*p >> 4];
*out_p++ = hexdigits[*p & 0xf];
}
else
*out_p++ = *p;
}
*out_p = '\0';

return encoded;
}

/*
* Decode a NAME: the converse of uprobe_encode_name.
*/
char *
uprobe_decode_name(const char *name)
{
const char *p = name;
char *new_p, *out_p;
char *decoded;
size_t sz = strlen(name);

/*
* Compute size changes needed.
*/

while ((p = strstr(p, "__")) != NULL) {
if (p[3] == '_') {
sz--;
p += 3;
}
else if (strspn(&p[2], hexdigits) >= 2) {
sz -= 3;
p += 4;
}
}

decoded = malloc(sz + 1);
if (!decoded)
return NULL;
out_p = decoded;

/* Apply translations. */

p = name;
while ((new_p = strstr(p, "__")) != NULL) {

/*
* Copy unchanged bytes.
*/
memcpy(out_p, p, new_p - p);
out_p += new_p - p;
p = new_p;

if (p[3] == '_') {
*out_p++ = '_';
*out_p++ = '_';
p += 3;
} else if (strspn(&p[2], hexdigits) >= 2) {
if (isdigit(p[2]))
*out_p = (p[2] - '0') << 4;
else
*out_p = (p[2] - 'a' + 10) << 4;
if (isdigit(p[3]))
*out_p += p[3] - '0';
else
*out_p += p[3] - 'a' + 10;
p += 4;
out_p++;
}
else {
*out_p++ = '_';
*out_p++ = '_';
p += 2;
}
}
/*
* Copy the remainder.
*/
strcpy(out_p, p);

return decoded;
}

/*
* Create a uprobe for a given mapping, address, and spec: the uprobe may be a
* uretprobe. Return the probe's name as a new dynamically-allocated string, or
* NULL on error. If prv/mod/fun/prb are all set, they are passed down as the
* name of the corresponding DTrace probe.
*/
char *
uprobe_create_named(dev_t dev, ino_t ino, uint64_t addr, const char *spec, int isret,
const char *prv, const char *mod, const char *fun,
const char *prb)
{
int fd;
int rc;
char *name, *args = "";

if (asprintf(&name, "dt_pid/%c_%llx_%llx_%lx",
isret ? 'r' : 'p',
(unsigned long long) dev,
(unsigned long long) ino,
(unsigned long) addr) < 0)
return NULL;

if (prv && mod && fun && prb) {
char *eprv, *emod, *efun, *eprb;
int failed = 0;

eprv = uprobe_encode_name(prv);
emod = uprobe_encode_name(mod);
efun = uprobe_encode_name(fun);
eprb = uprobe_encode_name(prb);

if (eprv && emod && efun && eprb) {
if (asprintf(&args, "P%s=\\1 M%s=\\2 F%s=\\3 N%s=\\4",
eprv, emod, efun, eprb) < 0)
failed = 1;
}
else
failed = 1;

free(eprv);
free(emod);
free(efun);
free(eprb);

if (failed)
return NULL;
}

/* Add the uprobe. */
fd = open(TRACEFS "uprobe_events", O_WRONLY | O_APPEND);
if (fd != -1) {
rc = dprintf(fd, "%c:%s %s %s\n", isret ? 'r' : 'p',
name, spec, args);

/* TODO: error reporting, probably via a callback */
close(fd);
}

if (fd == -1 || rc == -1) {
return NULL;
}

return name;
}

/*
* Like uprobe_create, but do not specify the name of a corresponding DTrace
* probe. (Used when the caller already knows what probe will be needed, and
* there is no possibility of another DTrace having to pick it up from the
* systemwide uprobe list.)
*/
char *
uprobe_create(dev_t dev, ino_t ino, uint64_t addr, const char *spec, int isret)
{
return uprobe_create_named(dev, ino, addr, spec, isret,
NULL, NULL, NULL, NULL);
}

/*
* Create a uprobe given a particular pid and address. Return the probe's name
* as a new dynamically-allocated string, or NULL on error. If prv/mod/fun/prb
* are set, they are passed down as the name of the corresponding DTrace probe.
*/
char *
uprobe_create_from_addr(pid_t pid, uint64_t addr, const char *prv,
const char *mod, const char *fun, const char *prb)
{
char *spec;
char *name;
prmap_t mapp;

spec = uprobe_spec_by_addr(pid, NULL, addr, &mapp);
if (!spec)
return NULL;

name = uprobe_create_named(mapp.pr_dev, mapp.pr_inum, addr, spec, 0,
prv, mod, fun, prb);
free(spec);
return name;
}
30 changes: 30 additions & 0 deletions libcommon/uprobes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Oracle Linux DTrace; simple uprobe helper functions
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/

#ifndef _UPROBES_H
#define _UPROBES_H

#include <sys/types.h>
#include <inttypes.h>
#include <libproc.h>
#include <unistd.h>

extern char *uprobe_spec_by_addr(pid_t pid, ps_prochandle *P, uint64_t addr,
prmap_t *mapp);
extern char *uprobe_create_named(dev_t dev, ino_t ino, uint64_t addr,
const char *spec, int isret, const char *prv,
const char *mod, const char *fun,
const char *prb);
extern char *uprobe_create(dev_t dev, ino_t ino, uint64_t addr, const char *spec,
int isret);
extern char *uprobe_create_from_addr(pid_t pid, uint64_t addr, const char *prv,
const char *mod, const char *fun,
const char *prb);
extern char *uprobe_encode_name(const char *);
extern char *uprobe_decode_name(const char *);

#endif /* _UPROBES_H */

0 comments on commit 5b43ff1

Please sign in to comment.