b7cd0b6fdf
Signed-off-by: Arnaldo Carvalho de Melo <acme@ghostprotocols.net>
796 lines
22 KiB
C
796 lines
22 KiB
C
/*
|
|
Copyright (C) 2006 Mandriva Conectiva S.A.
|
|
Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms of version 2 of the GNU General Public License as
|
|
published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <argp.h>
|
|
#include <limits.h>
|
|
#include <search.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "dwarves.h"
|
|
|
|
/*
|
|
* List of compilation units being looked for functions with
|
|
* pointers to the specified struct.
|
|
*/
|
|
static struct cus *methods_cus;
|
|
|
|
/**
|
|
* Compilation units with the definitions for the kprobes functions and struct
|
|
* definitions for the, can point to methods_cus if those definitions are
|
|
* available there (example: when using 'ctracer vmlinux sk_buff', vmlinux
|
|
* will have the sk_buff "methods" and the kprobes "classes" and "methods".
|
|
*/
|
|
static struct cus *kprobes_cus;
|
|
|
|
/**
|
|
* Mini class, the subset of the traced class that is collected at the probes
|
|
*/
|
|
|
|
static struct class *mini_class;
|
|
|
|
/*
|
|
* Directory where to generate source files
|
|
*/
|
|
static const char *src_dir = ".";
|
|
|
|
/*
|
|
* Where to print the ctracer_methods.c file
|
|
*/
|
|
static FILE *fp_methods;
|
|
|
|
/*
|
|
* List of definitions and forward declarations already emitted for
|
|
* methods_cus and kprobes_cus, to avoid duplication.
|
|
*/
|
|
static LIST_HEAD(cus__definitions);
|
|
static LIST_HEAD(cus__fwd_decls);
|
|
|
|
/*
|
|
* List of jprobes and kretprobes already emitted, this is a hack to cope with
|
|
* name space collisions, a better solution would be to in these cases to use the
|
|
* compilation unit name (net/ipv4/tcp.o, for instance) as a prefix when a
|
|
* static function has the same name in multiple compilation units (aka object
|
|
* files).
|
|
*/
|
|
static void *jprobes_emitted;
|
|
static void *kretprobes_emitted;
|
|
|
|
static int methods__compare(const void *a, const void *b)
|
|
{
|
|
return strcmp(a, b);
|
|
}
|
|
|
|
/*
|
|
* Add a method to jprobes_emitted or kretprobes_emitted, see comment above.
|
|
*/
|
|
static int methods__add(void **table, const char *str)
|
|
{
|
|
char **s = tsearch(str, table, methods__compare);
|
|
|
|
if (s != NULL) {
|
|
if (*s == str) {
|
|
char *dup = strdup(str);
|
|
if (dup != NULL)
|
|
*s = dup;
|
|
else {
|
|
tdelete(str, table, methods__compare);
|
|
return -1;
|
|
}
|
|
} else
|
|
return -1;
|
|
} else
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void method__add(struct cu *cu, struct function *function)
|
|
{
|
|
list_add(&function->tool_node, &cu->tool_list);
|
|
}
|
|
|
|
/*
|
|
* We want just the DW_TAG_subprogram tags that have as one of its parameters
|
|
* a pointer to the specified "class" (a struct, unions can be added later).
|
|
*/
|
|
static struct tag *function__filter(struct tag *tag, struct cu *cu, void *cookie)
|
|
{
|
|
struct function *function;
|
|
|
|
if (tag->tag != DW_TAG_subprogram)
|
|
return NULL;
|
|
|
|
function = tag__function(tag);
|
|
if (function__inlined(function) ||
|
|
function->abstract_origin != 0 ||
|
|
!ftype__has_parm_of_type(&function->proto, cookie, cu))
|
|
return NULL;
|
|
|
|
return tag;
|
|
}
|
|
|
|
/*
|
|
* Add the function to the list of methods since it matches function__filter
|
|
* criteria.
|
|
*/
|
|
static int find_methods_iterator(struct tag *tag, struct cu *cu,
|
|
void *cookie __unused)
|
|
{
|
|
struct function *function = tag__function(tag);
|
|
method__add(cu, function);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Iterate thru all the tags in the compilation unit, looking just for the
|
|
* DW_TAG_subprogram tags that have as one of its parameters a pointer to
|
|
* the specified "class" (struct).
|
|
*/
|
|
static int cu_find_methods_iterator(struct cu *cu, void *cookie)
|
|
{
|
|
struct tag *target = cu__find_struct_by_name(cu, cookie);
|
|
|
|
if (target == NULL)
|
|
return 0;
|
|
|
|
return cu__for_each_tag(cu, find_methods_iterator, target, function__filter);
|
|
}
|
|
|
|
static void class__remove_member(struct class *self, const struct cu *cu,
|
|
struct class_member *member)
|
|
{
|
|
const size_t size = class_member__size(member, cu);
|
|
/*
|
|
* Is this the first member?
|
|
*/
|
|
if (member->tag.node.prev == &self->type.members) {
|
|
self->type.size -= size;
|
|
class__subtract_offsets_from(self, cu, member, size);
|
|
} else {
|
|
struct class_member *from_prev =
|
|
list_entry(member->tag.node.prev,
|
|
struct class_member, tag.node);
|
|
if (member->hole + size >= cu->addr_size) {
|
|
self->type.size -= size + member->hole;
|
|
class__subtract_offsets_from(self, cu, member,
|
|
size + member->hole);
|
|
} else
|
|
from_prev->hole += size + member->hole;
|
|
}
|
|
if (member->hole != 0)
|
|
self->nr_holes--;
|
|
list_del(&member->tag.node);
|
|
class_member__delete(member);
|
|
}
|
|
|
|
static size_t class__find_biggest_member_name(const struct class *self)
|
|
{
|
|
struct class_member *pos;
|
|
size_t biggest_name_len = 0;
|
|
|
|
list_for_each_entry(pos, &self->type.members, tag.node) {
|
|
const size_t len = strlen(pos->name);
|
|
|
|
if (len > biggest_name_len)
|
|
biggest_name_len = len;
|
|
}
|
|
|
|
return biggest_name_len;
|
|
}
|
|
|
|
static void class__emit_class_state_collector(const struct class *self,
|
|
const struct class *clone)
|
|
{
|
|
struct class_member *pos;
|
|
int len = class__find_biggest_member_name(clone);
|
|
|
|
fprintf(fp_methods,
|
|
"void ctracer__class_state(const void *from, void *to)\n"
|
|
"{\n"
|
|
"\tconst struct %s *obj = from;\n"
|
|
"\tstruct %s *mini_obj = to;\n\n",
|
|
class__name(self), class__name(clone));
|
|
list_for_each_entry(pos, &clone->type.members, tag.node) {
|
|
fprintf(fp_methods, "\tmini_obj->%-*s = obj->%s;\n",
|
|
len, pos->name, pos->name);
|
|
}
|
|
fputs("}\n\n", fp_methods);
|
|
}
|
|
|
|
static struct class *class__clone_base_types(const struct tag *tag_self,
|
|
const struct cu *cu,
|
|
const char *new_class_name)
|
|
{
|
|
struct class *self = tag__class(tag_self);
|
|
struct class_member *pos, *next;
|
|
struct class *clone = class__clone(self, new_class_name);
|
|
|
|
if (clone == NULL)
|
|
return NULL;
|
|
|
|
class__find_holes(clone, cu);
|
|
|
|
list_for_each_entry_safe(pos, next, &clone->type.members, tag.node) {
|
|
struct tag *member_type = cu__find_tag_by_id(cu, pos->tag.type);
|
|
|
|
if (member_type->tag != DW_TAG_base_type)
|
|
class__remove_member(clone, cu, pos);
|
|
}
|
|
class__reorganize(clone, cu, 0, NULL);
|
|
return clone;
|
|
}
|
|
|
|
/**
|
|
* Converter to the legacy ostra tables, will be much improved in the future.
|
|
*/
|
|
static void emit_struct_member_table_entry(FILE *fp,
|
|
int field, const char *name,
|
|
int traced, const char *hooks)
|
|
{
|
|
fprintf(fp, "%u:%s:", field, name);
|
|
if (traced)
|
|
fprintf(fp, "yes:%%object->%s:u:%s:none\n", name, hooks);
|
|
else
|
|
fprintf(fp, "no:None:None:%s:dev_null\n", hooks);
|
|
}
|
|
|
|
/**
|
|
* Generates a converter to the ostra lebacy tables format, needef by
|
|
* ostra-cg to preprocess the raw data collected from the debugfs/relay
|
|
* channel.
|
|
*/
|
|
static int class__emit_ostra_converter(const struct tag *tag_self,
|
|
const struct cu *cu)
|
|
{
|
|
const struct class *self = tag__class(tag_self);
|
|
struct class_member *pos;
|
|
struct type *type = &mini_class->type;
|
|
int field = 0, first = 1;
|
|
char filename[128];
|
|
char parm_list[1024];
|
|
char *p = parm_list;
|
|
size_t n;
|
|
size_t plen = sizeof(parm_list);
|
|
FILE *fp_fields, *fp_converter;
|
|
|
|
snprintf(filename, sizeof(filename), "%s/%s.fields",
|
|
src_dir, class__name(self));
|
|
fp_fields = fopen(filename, "w");
|
|
if (fp_fields == NULL) {
|
|
fprintf(stderr, "ctracer: couldn't create %s\n", filename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
snprintf(filename, sizeof(filename), "%s/ctracer2ostra.c", src_dir);
|
|
|
|
fp_converter = fopen(filename, "w");
|
|
if (fp_converter == NULL) {
|
|
fprintf(stderr, "ctracer: couldn't create %s\n", filename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
fputs("#include <stdio.h>\n"
|
|
"#include <string.h>\n"
|
|
"#include \"ctracer_relay.h\"\n\n", fp_converter);
|
|
class__fprintf(mini_class, cu, NULL, NULL, 0, 0, 26, 23, 1,
|
|
fp_converter);
|
|
emit_struct_member_table_entry(fp_fields, field++, "action", 0,
|
|
"entry,exit");
|
|
emit_struct_member_table_entry(fp_fields, field++, "function_id", 0,
|
|
"entry,exit");
|
|
emit_struct_member_table_entry(fp_fields, field++, "object", 1,
|
|
"entry,exit");
|
|
|
|
fputs("\n"
|
|
"int main(void)\n"
|
|
"{\n"
|
|
"\twhile (1) {\n"
|
|
"\t\tstruct trace_entry hdr;\n"
|
|
"\t\tstruct ctracer__mini_sock obj;\n"
|
|
"\n"
|
|
"\t\tif (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))\n"
|
|
"\t\t\tbreak;\n"
|
|
"\n"
|
|
"\t\tfprintf(stdout, \"%u.%06u %c:%llu:%p\",\n"
|
|
"\t\t\thdr.sec, hdr.usec,\n"
|
|
"\t\t\thdr.probe_type ? 'o' : 'i',\n"
|
|
"\t\t\thdr.function_id,\n"
|
|
"\t\t\thdr.object);\n"
|
|
"\n"
|
|
"\t\tif (hdr.probe_type) {\n"
|
|
"\t\t\tfputc('\\n', stdout);\n"
|
|
"\t\t\tcontinue;\n"
|
|
"\t\t}\n"
|
|
"\n"
|
|
"\t\tif (read(0, &obj, sizeof(obj)) != sizeof(obj))\n"
|
|
"\t\t\tbreak;\n"
|
|
"\t\tfprintf(stdout,\n"
|
|
"\t\t\t\":",
|
|
fp_converter);
|
|
|
|
list_for_each_entry(pos, &type->members, tag.node) {
|
|
if (first)
|
|
first = 0;
|
|
else {
|
|
fputc(':', fp_converter);
|
|
n = snprintf(p, plen, ",\n\t\t\t ");
|
|
plen -= n; p += n;
|
|
}
|
|
fprintf(fp_converter, "%%u");
|
|
n = snprintf(p, plen, "obj.%s", pos->name);
|
|
plen -= n; p += n;
|
|
emit_struct_member_table_entry(fp_fields, field++,
|
|
pos->name, 1, "entry");
|
|
}
|
|
fprintf(fp_converter,
|
|
"\\n\",\n\t\t\t %s);\n"
|
|
"\t}\n"
|
|
"\treturn 0;\n"
|
|
"}\n", parm_list);
|
|
fclose(fp_fields);
|
|
fclose(fp_converter);
|
|
return 0;
|
|
}
|
|
|
|
static int class__emit_subset(const struct tag *tag_self, const struct cu *cu)
|
|
{
|
|
struct class *self = tag__class(tag_self);
|
|
int err = -1;
|
|
char mini_class_name[128];
|
|
|
|
snprintf(mini_class_name, sizeof(mini_class_name), "ctracer__mini_%s",
|
|
class__name(self));
|
|
|
|
mini_class = class__clone_base_types(tag_self, cu, mini_class_name);
|
|
if (mini_class == NULL)
|
|
goto out;
|
|
|
|
class__fprintf(mini_class, cu, NULL, NULL, 0, 0, 26, 23, 1,
|
|
fp_methods);
|
|
fputc('\n', fp_methods);
|
|
class__emit_class_state_collector(self, mini_class);
|
|
err = 0;
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Emit the kprobes routine for one of the selected "methods", later we'll
|
|
* put this into the 'kprobes' table, in cu_emit_kprobes_table_iterator.
|
|
*
|
|
* This marks the function entry, function__emit_kretprobes will emit the
|
|
* probe for the function exit.
|
|
*/
|
|
static int function__emit_kprobes(struct function *self, const struct cu *cu,
|
|
const struct tag *target)
|
|
{
|
|
char jprobe_name[256];
|
|
struct parameter *pos;
|
|
const char *name = function__name(self, cu);
|
|
|
|
fputs("static ", fp_methods);
|
|
snprintf(jprobe_name, sizeof(jprobe_name), "jprobe_entry__%s", name);
|
|
ftype__fprintf(&self->proto, cu, jprobe_name, 0, 0, 0, fp_methods);
|
|
fputs("\n{\n", fp_methods);
|
|
|
|
list_for_each_entry(pos, &self->proto.parms, tag.node) {
|
|
struct tag *type = cu__find_tag_by_id(cu, pos->tag.type);
|
|
|
|
if (type->tag != DW_TAG_pointer_type)
|
|
continue;
|
|
|
|
type = cu__find_tag_by_id(cu, type->type);
|
|
if (type == NULL || type->id != target->id)
|
|
continue;
|
|
|
|
fprintf(fp_methods,
|
|
"\tctracer__method_entry(%#llx, %s, %zd);\n",
|
|
(unsigned long long)self->proto.tag.id, pos->name,
|
|
class__size(mini_class));
|
|
break;
|
|
}
|
|
|
|
fprintf(fp_methods, "\tjprobe_return();\n"
|
|
"\t/* NOTREACHED */%s\n}\n\n",
|
|
self->proto.tag.type != 0 ? "\n\treturn 0;" : "");
|
|
|
|
fprintf(fp_methods, "static struct jprobe jprobe__%s = {\n"
|
|
"\t.kp = { .symbol_name = \"%s\", },\n"
|
|
"\t.entry = (kprobe_opcode_t *)jprobe_entry__%s,\n"
|
|
"};\n\n", name, name, name);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Iterate thru the list of methods previously collected by
|
|
* cu_find_methods_iterator, emitting the probes for function entry.
|
|
*/
|
|
static int cu_emit_kprobes_iterator(struct cu *cu, void *cookie)
|
|
{
|
|
struct tag *target = cu__find_struct_by_name(cu, cookie);
|
|
struct function *pos;
|
|
|
|
list_for_each_entry(pos, &cu->tool_list, tool_node) {
|
|
if (methods__add(&jprobes_emitted, function__name(pos, cu)) != 0)
|
|
continue;
|
|
pos->priv = (void *)1; /* Mark as visited, for the table iterator */
|
|
cus__emit_ftype_definitions(methods_cus, cu,
|
|
&pos->proto, fp_methods);
|
|
function__emit_kprobes(pos, cu, target);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Iterate thru the list of methods previously collected by
|
|
* cu_find_methods_iterator, creating the 'kprobes' table, that will
|
|
* be used at the module init routine to register the kprobes for function
|
|
* entry, and at module exit time to unregister the kprobes.
|
|
*/
|
|
static int cu_emit_kprobes_table_iterator(struct cu *cu, void *cookie __unused)
|
|
{
|
|
struct function *pos;
|
|
|
|
list_for_each_entry(pos, &cu->tool_list, tool_node)
|
|
if (pos->priv != NULL) {
|
|
const char *name = function__name(pos, cu);
|
|
fprintf(fp_methods, "\t&jprobe__%s,\n", name);
|
|
fprintf(cookie, "%llu:%s\n",
|
|
(unsigned long long)pos->proto.tag.id, name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Emit the kprobes routine for one of the selected "methods", later we'll
|
|
* put this into the 'kprobes' table, in cu_emit_kprobes_table_iterator.
|
|
*
|
|
* This marks the function exit.
|
|
*
|
|
* We still need to get the pointer to the "class instance", i.e. the pointer
|
|
* to the specified struct, this will be done using the "data pouch" mentioned
|
|
* in the kprobes mailing list, where we at the entry kprobes we store the
|
|
* pointer to be used here, or possibly using plain kprobes at the function
|
|
* entry and using DW_AT_location to discover where in the stack or in a
|
|
* processor register were the parameters for the function.
|
|
*/
|
|
static void function__emit_kretprobes(struct function *self,
|
|
const struct cu *cu)
|
|
{
|
|
const char *name = function__name(self, cu);
|
|
|
|
fprintf(fp_methods,
|
|
"static int kretprobe_handler__%s(struct kretprobe_instance *ri, "
|
|
"struct pt_regs *regs)\n"
|
|
"{\n"
|
|
"\tctracer__method_exit(%#llx);\n"
|
|
"\treturn 0;\n"
|
|
"}\n\n", name, (unsigned long long)self->proto.tag.id);
|
|
fprintf(fp_methods,
|
|
"static struct kretprobe kretprobe__%s = {\n"
|
|
"\t.kp = { .symbol_name = \"%s\", },\n"
|
|
"\t.handler = (kretprobe_handler_t)kretprobe_handler__%s,\n"
|
|
"};\n\n", name, name, name);
|
|
}
|
|
|
|
/*
|
|
* Iterate thru the list of methods previously collected by
|
|
* cu_find_methods_iterator, emitting the probes for function exit.
|
|
*/
|
|
static int cu_emit_kretprobes_iterator(struct cu *cu, void *cookie __unused)
|
|
{
|
|
struct function *pos;
|
|
|
|
list_for_each_entry(pos, &cu->tool_list, tool_node) {
|
|
if (methods__add(&kretprobes_emitted,
|
|
function__name(pos, cu)) != 0)
|
|
continue;
|
|
pos->priv = (void *)1; /* Mark as visited, for the table iterator */
|
|
function__emit_kretprobes(pos, cu);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Iterate thru the list of methods previously collected by
|
|
* cu_find_methods_iterator, creating the 'kretprobes' table, that will
|
|
* be used at the module init routine to register the kprobes for function
|
|
* entry, and at module exit time to unregister the kretprobes.
|
|
*/
|
|
static int cu_emit_kretprobes_table_iterator(struct cu *cu,
|
|
void *cookie __unused)
|
|
{
|
|
struct function *pos;
|
|
|
|
list_for_each_entry(pos, &cu->tool_list, tool_node)
|
|
if (pos->priv != NULL)
|
|
fprintf(fp_methods, "\t&kretprobe__%s,\n",
|
|
function__name(pos, cu));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Emit a definition for the specified function, looking for it in the
|
|
* tags previously collected, cus__emit_ftype_definitions will look at the
|
|
* function return type and recursively emit all the definitions needed,
|
|
* ditto for all the function parameters, emitting just a forward declaration
|
|
* if the parameter is just a pointer, or all of the enums, struct, unions,
|
|
* etc that are required for the resulting C source code to be built.
|
|
*/
|
|
static void emit_function_defs(const char *fn)
|
|
{
|
|
struct cu *cu;
|
|
struct tag *f = cus__find_function_by_name(kprobes_cus, &cu, fn);
|
|
|
|
if (f != NULL) {
|
|
cus__emit_ftype_definitions(kprobes_cus, cu,
|
|
&tag__function(f)->proto,
|
|
fp_methods);
|
|
tag__fprintf(f, cu, NULL, NULL, 0, fp_methods);
|
|
fputs(";\n", fp_methods);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Emit a struct definition, looking at all the function members and recursively
|
|
* emitting its type definitions (enums, structs, unions, etc).
|
|
*/
|
|
static void emit_struct_defs(const char *name)
|
|
{
|
|
struct cu *cu;
|
|
struct tag *c = cus__find_struct_by_name(kprobes_cus, &cu, name);
|
|
if (c != NULL) {
|
|
cus__emit_type_definitions(kprobes_cus, cu, c, fp_methods);
|
|
type__emit(c, cu, NULL, NULL, fp_methods);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Emit a forward declaration ("struct foo;" or "union bar").
|
|
*/
|
|
static void emit_class_fwd_decl(const char *name)
|
|
{
|
|
struct cu *cu;
|
|
struct tag *c = cus__find_struct_by_name(kprobes_cus, &cu, name);
|
|
if (c != NULL)
|
|
cus__emit_fwd_decl(kprobes_cus, tag__type(c), fp_methods);
|
|
}
|
|
|
|
/*
|
|
* Emit the definitions used in the resulting kernel module C source code,
|
|
* we do this to avoid using #includes, that would emit definitions for
|
|
* things we emit, causing redefinitions.
|
|
*/
|
|
static void emit_module_preamble(void)
|
|
{
|
|
fputs("#include \"ctracer_relay.h\"\n", fp_methods);
|
|
|
|
emit_struct_defs("jprobe");
|
|
emit_struct_defs("kretprobe");
|
|
|
|
emit_class_fwd_decl("pt_regs");
|
|
emit_class_fwd_decl("kretprobe_instance");
|
|
|
|
emit_function_defs("printk");
|
|
emit_function_defs("jprobe_return");
|
|
}
|
|
|
|
static const struct argp_option ctracer__options[] = {
|
|
{
|
|
.key = 'd',
|
|
.name = "src_dir",
|
|
.doc = "generate source files in this directory",
|
|
},
|
|
{
|
|
.key = 'D',
|
|
.name = "dir",
|
|
.doc = "load files in this directory",
|
|
},
|
|
{
|
|
.key = 'g',
|
|
.name = "glob",
|
|
.doc = "file mask to load",
|
|
},
|
|
{
|
|
.key = 'k',
|
|
.name = "kprobes",
|
|
.doc = "kprobes object file",
|
|
},
|
|
{
|
|
.key = 'r',
|
|
.name = "recursive",
|
|
.doc = "recursively load files",
|
|
},
|
|
{
|
|
.name = NULL,
|
|
}
|
|
};
|
|
|
|
static const char *dirname, *glob, *kprobes_filename;
|
|
static char *class_name;
|
|
static int recursive;
|
|
|
|
static error_t ctracer__options_parser(int key, char *arg,
|
|
struct argp_state *state __unused)
|
|
{
|
|
switch (key) {
|
|
case 'd': src_dir = arg; break;
|
|
case 'D': dirname = arg; break;
|
|
case 'g': glob = arg; break;
|
|
case 'k': kprobes_filename = arg; break;
|
|
case 'r': recursive = 1; break;
|
|
default: return ARGP_ERR_UNKNOWN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char ctracer__args_doc[] = "[FILE] [CLASS]";
|
|
|
|
static struct argp ctracer__argp = {
|
|
.options = ctracer__options,
|
|
.parser = ctracer__options_parser,
|
|
.args_doc = ctracer__args_doc,
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int remaining, err;
|
|
struct tag *class;
|
|
struct cu *cu;
|
|
const char *filename;
|
|
char functions_filename[PATH_MAX];
|
|
char methods_filename[PATH_MAX];
|
|
FILE *fp;
|
|
|
|
argp_parse(&ctracer__argp, argc, argv, 0, &remaining, NULL);
|
|
|
|
if (remaining < argc) {
|
|
switch (argc - remaining) {
|
|
case 1: if (kprobes_filename == NULL)
|
|
goto failure;
|
|
class_name = argv[remaining++]; break;
|
|
case 2: filename = argv[remaining++];
|
|
class_name = argv[remaining++]; break;
|
|
default: goto failure;
|
|
}
|
|
} else {
|
|
failure:
|
|
argp_help(&ctracer__argp, stderr, ARGP_HELP_SEE, "ctracer");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* Initialize libdwarves, for now just to get the machine L1 cacheline
|
|
* size, in the future may do more stuff.
|
|
*/
|
|
dwarves__init(0);
|
|
|
|
/*
|
|
* Create the methods_cus (Compilation Units) object where we will
|
|
* load the objects where we'll look for functions pointers to the
|
|
* specified class, i.e. to find its "methods", where we'll insert
|
|
* the entry and exit hooks.
|
|
*/
|
|
methods_cus = cus__new(&cus__definitions, &cus__fwd_decls);
|
|
if (methods_cus == NULL) {
|
|
out_enomem:
|
|
fputs("ctracer: insufficient memory\n", stderr);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* If --kprobes was specified load the binary with the definitions
|
|
* for the kprobes structs and functions used in the generated kernel
|
|
* module C source file.
|
|
*/
|
|
if (kprobes_filename != NULL) {
|
|
kprobes_cus = cus__new(&cus__definitions, &cus__fwd_decls);
|
|
if (kprobes_cus == NULL)
|
|
goto out_enomem;
|
|
err = cus__load(kprobes_cus, kprobes_filename);
|
|
if (err != 0) {
|
|
filename = kprobes_filename;
|
|
goto out_dwarf_err;
|
|
}
|
|
} else {
|
|
/*
|
|
* Or use the methods_cus specified for the methods as the
|
|
* source for the kprobes structs and functions definitions.
|
|
*/
|
|
kprobes_cus = methods_cus;
|
|
}
|
|
|
|
/*
|
|
* if --dir/-D was specified, recursively traverse the path looking for
|
|
* object files (compilation units) that match the glob specified (*.ko)
|
|
* for kernel modules, but could be "*.o" in the future when we support
|
|
* uprobes for user space tracing.
|
|
*/
|
|
if (dirname != NULL && cus__load_dir(methods_cus, dirname, glob,
|
|
recursive) != 0) {
|
|
fprintf(stderr, "ctracer: couldn't load DWARF info "
|
|
"from %s dir with glob %s\n",
|
|
dirname, glob);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* If a filename was specified, for instance "vmlinux", load it too.
|
|
*/
|
|
if (filename != NULL) {
|
|
err = cus__load(methods_cus, filename);
|
|
if (err != 0) {
|
|
out_dwarf_err:
|
|
cus__print_error_msg("ctracer", filename, err);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* See if the specified struct exists:
|
|
*/
|
|
class = cus__find_struct_by_name(methods_cus, &cu, class_name);
|
|
if (class == NULL) {
|
|
fprintf(stderr, "ctracer: struct %s not found!\n", class_name);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
snprintf(functions_filename, sizeof(functions_filename),
|
|
"%s/%s.functions", src_dir, class__name(tag__class(class)));
|
|
fp = fopen(functions_filename, "w");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "ctracer: couldn't create %s\n",
|
|
functions_filename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
snprintf(methods_filename, sizeof(methods_filename),
|
|
"%s/ctracer_methods.c", src_dir);
|
|
fp_methods = fopen(methods_filename, "w");
|
|
if (fp_methods == NULL) {
|
|
fprintf(stderr, "ctracer: couldn't create %s\n",
|
|
methods_filename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
emit_module_preamble();
|
|
|
|
cus__emit_type_definitions(methods_cus, cu, class, fp_methods);
|
|
type__emit(class, cu, NULL, NULL, fp_methods);
|
|
class__emit_subset(class, cu);
|
|
class__emit_ostra_converter(class, cu);
|
|
cus__for_each_cu(methods_cus, cu_find_methods_iterator,
|
|
class_name, NULL);
|
|
cus__for_each_cu(methods_cus, cu_emit_kprobes_iterator,
|
|
class_name, NULL);
|
|
cus__for_each_cu(methods_cus, cu_emit_kretprobes_iterator,
|
|
NULL, NULL);
|
|
|
|
fputs("struct jprobe *ctracer__jprobes[] = {", fp_methods);
|
|
cus__for_each_cu(methods_cus, cu_emit_kprobes_table_iterator,
|
|
fp, NULL);
|
|
/* Emit the sentinel */
|
|
fputs("\t(void *)0,\n};\n", fp_methods);
|
|
fclose(fp);
|
|
fputs("struct kretprobe *ctracer__kretprobes[] = {", fp_methods);
|
|
cus__for_each_cu(methods_cus, cu_emit_kretprobes_table_iterator,
|
|
NULL, NULL);
|
|
/* Emit the sentinel */
|
|
fputs("\t(void *)0,\n};\n", fp_methods);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|