| /* | 
 |   SPDX-License-Identifier: GPL-2.0-only | 
 |  | 
 |   Copyright (C) 2006 Mandriva Conectiva S.A. | 
 |   Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> | 
 | */ | 
 |  | 
 | #include <argp.h> | 
 | #include <elf.h> | 
 | #include <errno.h> | 
 | #include <fcntl.h> | 
 | #include <gelf.h> | 
 | #include <limits.h> | 
 | #include <search.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <sys/types.h> | 
 | #include <sys/stat.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include "dwarves_reorganize.h" | 
 | #include "dwarves_emit.h" | 
 | #include "dwarves.h" | 
 | #include "dutil.h" | 
 | #include "elf_symtab.h" | 
 |  | 
 | /* | 
 |  * target class name | 
 |  */ | 
 | static char *class_name; | 
 |  | 
 | /* | 
 |  * List of compilation units being looked for functions with | 
 |  * pointers to the specified struct. | 
 |  */ | 
 | static struct cus *methods_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.stp file | 
 |  */ | 
 | static FILE *fp_methods; | 
 |  | 
 | /* | 
 |  * Where to print the ctracer_collector.c file | 
 |  */ | 
 | static FILE *fp_collector; | 
 |  | 
 | /* | 
 |  * Where to print the ctracer_classes.h file | 
 |  */ | 
 | static FILE *fp_classes; | 
 |  | 
 | /* | 
 |  * blacklist __init marked functions, i.e. functions that are | 
 |  * in the ".init.text" ELF section and are thus discarded after | 
 |  * boot. | 
 |  */ | 
 | static struct strlist *init_blacklist; | 
 |  | 
 | /* | 
 |  * List of definitions and forward declarations already emitted for | 
 |  * methods_cus, to avoid duplication. | 
 |  */ | 
 | static struct type_emissions emissions; | 
 |  | 
 | /* | 
 |  * CU blacklist: if a "blacklist.cu" file is present, don't consider the | 
 |  * CUs listed. Use a default of blacklist.cu. | 
 |  */ | 
 | static const char *cu_blacklist_filename = "blacklist.cu"; | 
 |  | 
 | static struct strlist *cu_blacklist; | 
 |  | 
 | static struct cu *cu_filter(struct cu *cu) | 
 | { | 
 | 	if (strlist__has_entry(cu_blacklist, cu->name)) | 
 | 		return NULL; | 
 | 	return cu; | 
 | } | 
 |  | 
 | /* | 
 |  * List of probes 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 *probes_emitted; | 
 |  | 
 | struct structure { | 
 | 	struct list_head  node; | 
 | 	struct tag	  *class; | 
 | 	struct cu	  *cu; | 
 | }; | 
 |  | 
 | static struct structure *structure__new(struct tag *class, struct cu *cu) | 
 | { | 
 | 	struct structure *st = malloc(sizeof(*st)); | 
 |  | 
 | 	if (st != NULL) { | 
 | 		st->class = class; | 
 | 		st->cu    = cu; | 
 | 	} | 
 |  | 
 | 	return st; | 
 | } | 
 |  | 
 | /* | 
 |  * structs that can be casted to the target class, e.g. i.e. that has the target | 
 |  * class at its first member. | 
 |  */ | 
 | static LIST_HEAD(aliases); | 
 |  | 
 | /* | 
 |  * structs have pointers to the target class. | 
 |  */ | 
 | static LIST_HEAD(pointers); | 
 |  | 
 | static const char *structure__name(const struct structure *st) | 
 | { | 
 | 	return class__name(tag__class(st->class)); | 
 | } | 
 |  | 
 | static struct structure *structures__find(struct list_head *list, const char *name) | 
 | { | 
 | 	struct structure *pos; | 
 |  | 
 | 	if (name == NULL) | 
 | 		return NULL; | 
 |  | 
 | 	list_for_each_entry(pos, list, node) | 
 | 		if (strcmp(structure__name(pos), name) == 0) | 
 | 			return pos; | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static void structures__add(struct list_head *list, struct tag *class, struct cu *cu) | 
 | { | 
 | 	struct structure *str = structure__new(class, cu); | 
 |  | 
 | 	if (str != NULL) | 
 | 		list_add(&str->node, list); | 
 | } | 
 |  | 
 | static int methods__compare(const void *a, const void *b) | 
 | { | 
 | 	return strcmp(a, b); | 
 | } | 
 |  | 
 | 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, uint32_t id) | 
 | { | 
 | 	list_add(&function->tool_node, &cu->tool_list); | 
 | 	function->priv = (void *)(long)id; | 
 | } | 
 |  | 
 | /* | 
 |  * We want just the function tags that have as one of its parameters | 
 |  * a pointer to the specified "class" (a struct, unions can be added later). | 
 |  */ | 
 | static struct function *function__filter(struct function *function, | 
 | 					 struct cu *cu, type_id_t target_type_id) | 
 | { | 
 | 	if (function__inlined(function) || | 
 | 	    function->abstract_origin != 0 || | 
 | 	    !list_empty(&function->tool_node) || | 
 | 	    !ftype__has_parm_of_type(&function->proto, target_type_id, cu) || | 
 | 	    strlist__has_entry(init_blacklist, function__name(function))) { | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	return function; | 
 | } | 
 |  | 
 | /* | 
 |  * Iterate thru all the tags in the compilation unit, looking just for the | 
 |  * function 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) | 
 | { | 
 | 	type_id_t target_type_id; | 
 | 	uint32_t function_id; | 
 | 	struct function *function; | 
 | 	struct tag *target = cu__find_struct_by_name(cu, cookie, 0, | 
 | 						     &target_type_id); | 
 |  | 
 | 	INIT_LIST_HEAD(&cu->tool_list); | 
 |  | 
 | 	if (target == NULL) | 
 | 		return 0; | 
 |  | 
 | 	cu__for_each_function(cu, function_id, function) | 
 | 		if (function__filter(function, cu, target_type_id)) | 
 | 			method__add(cu, function, function_id); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct class_member *class_member__bitfield_tail(struct class_member *head, | 
 | 							struct class *class) | 
 | { | 
 |         struct class_member *tail = head, | 
 | 			    *member = list_prepare_entry(head, | 
 | 							 class__tags(class), | 
 | 							 tag.node); | 
 |         list_for_each_entry_continue(member, class__tags(class), tag.node) | 
 | 		if (member->byte_offset == head->byte_offset) | 
 | 			tail = member; | 
 | 		else | 
 | 			break; | 
 |  | 
 | 	return tail; | 
 | } | 
 |  | 
 | /* | 
 |  * Bitfields are removed as one for simplification right now. | 
 |  */ | 
 | static struct class_member *class__remove_member(struct class *class, const struct cu *cu, | 
 | 						 struct class_member *member) | 
 | { | 
 | 	size_t size = member->byte_size; | 
 | 	struct class_member *bitfield_tail = NULL; | 
 | 	struct list_head *next; | 
 | 	uint16_t member_hole = member->hole; | 
 |  | 
 | 	if (member->bitfield_size != 0) { | 
 | 		bitfield_tail = class_member__bitfield_tail(member, class); | 
 | 		member_hole = bitfield_tail->hole; | 
 | 	} | 
 | 	/* | 
 | 	 * Is this the first member? | 
 | 	 */ | 
 | 	if (member->tag.node.prev == class__tags(class)) { | 
 | 		class->type.size -= size + member_hole; | 
 | 		class__subtract_offsets_from(class, bitfield_tail ?: member, | 
 | 					     size + member_hole); | 
 | 	/* | 
 | 	 * Is this the last member? | 
 | 	 */ | 
 | 	} else if (member->tag.node.next == class__tags(class)) { | 
 | 		if (size + class->padding >= cu->addr_size) { | 
 | 			class->type.size -= size + class->padding; | 
 | 			class->padding = 0; | 
 | 		} else | 
 | 			class->padding += size; | 
 | 	} else { | 
 | 		if (size + member_hole >= cu->addr_size) { | 
 | 			class->type.size -= size + member_hole; | 
 | 			class__subtract_offsets_from(class, | 
 | 						     bitfield_tail ?: member, | 
 | 						     size + member_hole); | 
 | 		} else { | 
 | 			struct class_member *from_prev = | 
 | 					list_entry(member->tag.node.prev, | 
 | 						   struct class_member, | 
 | 						   tag.node); | 
 | 			if (from_prev->hole == 0) | 
 | 				class->nr_holes++; | 
 | 			from_prev->hole += size + member_hole; | 
 | 		} | 
 | 	} | 
 | 	if (member_hole != 0) | 
 | 		class->nr_holes--; | 
 |  | 
 | 	if (bitfield_tail != NULL) { | 
 | 		next = bitfield_tail->tag.node.next; | 
 | 		list_del_range(&member->tag.node, &bitfield_tail->tag.node); | 
 | 		if (bitfield_tail->bit_hole != 0) | 
 | 			class->nr_bit_holes--; | 
 | 	} else { | 
 | 		next = member->tag.node.next; | 
 | 		list_del(&member->tag.node); | 
 | 	} | 
 |  | 
 | 	return list_entry(next, struct class_member, tag.node); | 
 | } | 
 |  | 
 | static size_t class__find_biggest_member_name(const struct class *class) | 
 | { | 
 | 	struct class_member *pos; | 
 | 	size_t biggest_name_len = 0; | 
 |  | 
 | 	type__for_each_data_member(&class->type, pos) { | 
 | 		const size_t len = pos->name ? | 
 | 					strlen(class_member__name(pos)) : 0; | 
 |  | 
 | 		if (len > biggest_name_len) | 
 | 			biggest_name_len = len; | 
 | 	} | 
 |  | 
 | 	return biggest_name_len; | 
 | } | 
 |  | 
 | static void class__emit_class_state_collector(struct class *class, struct class *clone) | 
 | { | 
 | 	struct class_member *pos; | 
 | 	int len = class__find_biggest_member_name(clone); | 
 |  | 
 | 	fprintf(fp_collector, | 
 | 		"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(class), class__name(clone)); | 
 | 	type__for_each_data_member(&clone->type, pos) | 
 | 		fprintf(fp_collector, "\tmini_obj->%-*s = obj->%s;\n", len, | 
 | 			class_member__name(pos), | 
 | 			class_member__name(pos)); | 
 | 	fputs("}\n\n", fp_collector); | 
 | } | 
 |  | 
 | static struct class *class__clone_base_types(const struct tag *tag, | 
 | 					     struct cu *cu, | 
 | 					     const char *new_class_name) | 
 | { | 
 | 	struct class *class = tag__class(tag); | 
 | 	struct class_member *pos, *next; | 
 | 	struct class *clone = class__clone(class, new_class_name); | 
 |  | 
 | 	if (clone == NULL) | 
 | 		return NULL; | 
 |  | 
 | 	type__for_each_data_member_safe(&clone->type, pos, next) { | 
 | 		struct tag *member_type = cu__type(cu, pos->tag.type); | 
 |  | 
 | 		tag__assert_search_result(member_type, pos->tag.tag, class_member__name(pos)); | 
 | 		if (!tag__is_base_type(member_type, cu)) { | 
 | 			next = class__remove_member(clone, cu, pos); | 
 | 			class_member__delete(pos); | 
 | 		} | 
 | 	} | 
 | 	class__fixup_alignment(clone, cu); | 
 | 	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(struct tag *tag) | 
 | { | 
 | 	struct class *class = tag__class(tag); | 
 | 	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; | 
 | 	const char *name = class__name(class); | 
 |  | 
 | 	snprintf(filename, sizeof(filename), "%s/%s.fields", src_dir, name); | 
 | 	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 \"ctracer_classes.h\"\n" | 
 | 	      "#include <stdio.h>\n" | 
 | 	      "#include <string.h>\n" | 
 | 	      "#include \"ctracer_relay.h\"\n\n", 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"); | 
 |  | 
 | 	fprintf(fp_converter, "\n" | 
 | 	      "int main(void)\n" | 
 | 	      "{\n" | 
 | 	      "\twhile (1) {\n" | 
 | 	      "\t\tstruct trace_entry hdr;\n" | 
 | 	      "\t\tstruct ctracer__mini_%s obj;\n" | 
 | 	      "\n" | 
 | 	      "\t\tif (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))\n" | 
 | 	      "\t\t\tbreak;\n" | 
 | 	      "\n" | 
 | 	      "\t\tfprintf(stdout, \"%%llu %%c:%%llu:%%p\",\n" | 
 | 	      "\t\t\thdr.nsec,\n" | 
 | 	      "\t\t\thdr.probe_type ? 'o' : 'i',\n" | 
 | 	      "\t\t\thdr.function_id,\n" | 
 | 	      "\t\t\thdr.object);\n" | 
 | 	      "\n" | 
 | 	      "\t\tif (read(0, &obj, sizeof(obj)) != sizeof(obj))\n" | 
 | 	      "\t\t\tbreak;\n" | 
 | 	      "\t\tfprintf(stdout,\n" | 
 | 	      "\t\t\t\":", name); | 
 |  | 
 | 	type__for_each_data_member(type, pos) { | 
 | 		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", class_member__name(pos)); | 
 | 		plen -= n; p += n; | 
 | 		emit_struct_member_table_entry(fp_fields, field++, | 
 | 					       class_member__name(pos), | 
 | 					       1, "entry,exit"); | 
 | 	} | 
 | 	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; | 
 | } | 
 |  | 
 | /* | 
 |  * We want just the DW_TAG_structure_type tags that have a member that is a pointer | 
 |  * to the target class. | 
 |  */ | 
 | static struct tag *pointer_filter(struct tag *tag, struct cu *cu, | 
 | 				  type_id_t target_type_id) | 
 | { | 
 | 	struct type *type; | 
 | 	struct class_member *pos; | 
 | 	const char *class_name; | 
 |  | 
 | 	if (!tag__is_struct(tag)) | 
 | 		return NULL; | 
 |  | 
 | 	type = tag__type(tag); | 
 | 	if (type->nr_members == 0) | 
 | 		return NULL; | 
 |  | 
 | 	class_name = class__name(tag__class(tag)); | 
 | 	if (class_name == NULL || structures__find(&pointers, class_name)) | 
 | 		return NULL; | 
 |  | 
 | 	type__for_each_member(type, pos) { | 
 | 		struct tag *ctype = cu__type(cu, pos->tag.type); | 
 |  | 
 | 		tag__assert_search_result(ctype, pos->tag.tag, class_member__name(pos)); | 
 | 		if (tag__is_pointer_to(ctype, target_type_id)) | 
 | 			return tag; | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | /* | 
 |  * Iterate thru all the tags in the compilation unit, looking for classes | 
 |  * that have as one member that is a pointer to the target type. | 
 |  */ | 
 | static int cu_find_pointers_iterator(struct cu *cu, void *class_name) | 
 | { | 
 | 	type_id_t target_type_id, id; | 
 | 	struct tag *target = cu__find_struct_by_name(cu, class_name, 0, | 
 | 						     &target_type_id), *pos; | 
 |  | 
 | 	if (target == NULL) | 
 | 		return 0; | 
 |  | 
 | 	cu__for_each_type(cu, id, pos) | 
 | 		if (pointer_filter(pos, cu, target_type_id)) | 
 | 			structures__add(&pointers, pos, cu); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void class__find_pointers(const char *class_name) | 
 | { | 
 | 	cus__for_each_cu(methods_cus, cu_find_pointers_iterator, (void *)class_name, cu_filter); | 
 | } | 
 |  | 
 | /* | 
 |  * We want just the DW_TAG_structure_type tags that have as its first member | 
 |  * a struct of type target. | 
 |  */ | 
 | static struct tag *alias_filter(struct tag *tag, type_id_t target_type_id) | 
 | { | 
 | 	struct type *type; | 
 | 	struct class_member *first_member; | 
 |  | 
 | 	if (!tag__is_struct(tag)) | 
 | 		return NULL; | 
 |  | 
 | 	type = tag__type(tag); | 
 | 	if (type->nr_members == 0) | 
 | 		return NULL; | 
 |  | 
 | 	first_member = list_first_entry(&type->namespace.tags, | 
 | 					struct class_member, tag.node); | 
 | 	if (first_member->tag.type != target_type_id) | 
 | 		return NULL; | 
 |  | 
 | 	if (structures__find(&aliases, class__name(tag__class(tag)))) | 
 | 		return NULL; | 
 |  | 
 | 	return tag; | 
 | } | 
 |  | 
 | static void class__find_aliases(const char *class_name); | 
 |  | 
 | /* | 
 |  * Iterate thru all the tags in the compilation unit, looking for classes | 
 |  * that have as its first member the specified "class" (struct). | 
 |  */ | 
 | static int cu_find_aliases_iterator(struct cu *cu, void *class_name) | 
 | { | 
 | 	type_id_t target_type_id, id; | 
 | 	struct tag *target = cu__find_struct_by_name(cu, class_name, 0, | 
 | 						     &target_type_id), *pos; | 
 | 	if (target == NULL) | 
 | 		return 0; | 
 |  | 
 | 	cu__for_each_type(cu, id, pos) { | 
 | 		if (alias_filter(pos, target_type_id)) { | 
 | 			const char *alias_name = class__name(tag__class(pos)); | 
 |  | 
 | 			structures__add(&aliases, pos, cu); | 
 |  | 
 | 			/* | 
 | 			 * Now find aliases to this alias, e.g.: | 
 | 			 * | 
 | 			 * struct tcp_sock { | 
 | 			 * 	struct inet_connection_sock { | 
 | 			 * 		struct inet_sock { | 
 | 			 * 			struct sock { | 
 | 			 * 			} | 
 | 			 * 		} | 
 | 			 * 	} | 
 | 			 * } | 
 | 			 */ | 
 | 			class__find_aliases(alias_name); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void class__find_aliases(const char *class_name) | 
 | { | 
 | 	cus__for_each_cu(methods_cus, cu_find_aliases_iterator, (void *)class_name, cu_filter); | 
 | } | 
 |  | 
 | static void emit_list_of_types(struct list_head *list) | 
 | { | 
 | 	struct structure *pos; | 
 |  | 
 | 	list_for_each_entry(pos, list, node) { | 
 | 		struct type *type = tag__type(pos->class); | 
 | 		/* | 
 | 		 * Lets look at the other CUs, perhaps we have already | 
 | 		 * emmited this one | 
 | 		 */ | 
 | 		if (type_emissions__find_definition(&emissions, type__tag(type)->tag, structure__name(pos))) { | 
 | 			type->definition_emitted = 1; | 
 | 			continue; | 
 | 		} | 
 | 		type__emit_definitions(pos->class, pos->cu, &emissions, | 
 | 				       fp_classes); | 
 | 		type->definition_emitted = 1; | 
 | 		type__emit(pos->class, pos->cu, NULL, NULL, fp_classes); | 
 | 		tag__type(pos->class)->definition_emitted = 1; | 
 | 		fputc('\n', fp_classes); | 
 | 	} | 
 | } | 
 |  | 
 | static int class__emit_classes(struct tag *tag, struct cu *cu) | 
 | { | 
 | 	struct class *class = tag__class(tag); | 
 | 	int err = -1; | 
 | 	char mini_class_name[128]; | 
 |  | 
 | 	snprintf(mini_class_name, sizeof(mini_class_name), "ctracer__mini_%s", | 
 | 		 class__name(class)); | 
 |  | 
 | 	mini_class = class__clone_base_types(tag, cu, mini_class_name); | 
 | 	if (mini_class == NULL) | 
 | 		goto out; | 
 |  | 
 | 	type__emit_definitions(tag, cu, &emissions, fp_classes); | 
 |  | 
 | 	type__emit(tag, cu, NULL, NULL, fp_classes); | 
 | 	fputs("\n/* class aliases */\n\n", fp_classes); | 
 |  | 
 | 	emit_list_of_types(&aliases); | 
 |  | 
 | 	fputs("\n/* class with pointers */\n\n", fp_classes); | 
 |  | 
 | 	emit_list_of_types(&pointers); | 
 |  | 
 | 	class__fprintf(mini_class, cu, fp_classes); | 
 | 	fputs(";\n\n", fp_classes); | 
 | 	class__emit_class_state_collector(class, 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_probes(struct function *func, uint32_t function_id, | 
 | 				 const struct cu *cu, | 
 | 				 const type_id_t target_type_id, int probe_type, | 
 | 				 const char *member) | 
 | { | 
 | 	struct parameter *pos; | 
 | 	const char *name = function__name(func); | 
 |  | 
 | 	fprintf(fp_methods, "probe %s%s = kernel.function(\"%s@%s\")%s\n" | 
 | 			    "{\n" | 
 | 			    "}\n\n" | 
 | 			    "probe %s%s\n" | 
 | 			    "{\n", name, | 
 | 			    probe_type == 0 ? "" : "__return", | 
 | 			    name, | 
 | 			    cu->name, | 
 | 			    probe_type == 0 ? "" : ".return", | 
 | 			    name, | 
 | 			    probe_type == 0 ? "" : "__return"); | 
 |  | 
 | 	list_for_each_entry(pos, &func->proto.parms, tag.node) { | 
 | 		struct tag *type = cu__type(cu, pos->tag.type); | 
 |  | 
 | 		tag__assert_search_result(type, pos->tag.tag, parameter__name(pos)); | 
 | 		if (!tag__is_pointer_to(type, target_type_id)) | 
 | 			continue; | 
 |  | 
 | 		if (member != NULL) | 
 | 			fprintf(fp_methods, "\tif ($%s)\n\t", parameter__name(pos)); | 
 |  | 
 | 		fprintf(fp_methods, | 
 | 			"\tctracer__method_hook(%d, %d, $%s%s%s, %d);\n", | 
 | 			probe_type, | 
 | 			function_id, | 
 | 			parameter__name(pos), | 
 | 			member ? "->" : "", member ?: "", | 
 | 			class__size(mini_class)); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	fputs("}\n\n", fp_methods); | 
 | 	fflush(fp_methods); | 
 |  | 
 | 	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_probes_iterator(struct cu *cu, void *cookie) | 
 | { | 
 | 	type_id_t target_type_id; | 
 | 	struct tag *target = cu__find_struct_by_name(cu, cookie, 0, &target_type_id); | 
 | 	struct function *pos; | 
 |  | 
 | 	/* OK, this type is not present in this compile unit */ | 
 | 	if (target == NULL) | 
 | 		return 0; | 
 |  | 
 | 	list_for_each_entry(pos, &cu->tool_list, tool_node) { | 
 | 		uint32_t function_id = (long)pos->priv; | 
 |  | 
 | 		if (methods__add(&probes_emitted, function__name(pos)) != 0) | 
 | 			continue; | 
 | 		function__emit_probes(pos, function_id, cu, target_type_id, 0, NULL); /* entry */ | 
 | 		function__emit_probes(pos, function_id, cu, target_type_id, 1, NULL); /* exit */ | 
 | 	} | 
 |  | 
 | 	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_pointer_probes_iterator(struct cu *cu, void *cookie) | 
 | { | 
 | 	type_id_t target_type_id, pointer_id; | 
 | 	struct tag *target, *pointer; | 
 | 	struct function *pos_tag; | 
 | 	struct class_member *pos_member; | 
 |  | 
 | 	/* This CU doesn't have our classes */ | 
 | 	if (list_empty(&cu->tool_list)) | 
 | 		return 0; | 
 |  | 
 | 	target = cu__find_struct_by_name(cu, class_name, 1, &target_type_id); | 
 | 	pointer = cu__find_struct_by_name(cu, cookie, 0, &pointer_id); | 
 |  | 
 | 	/* OK, this type is not present in this compile unit */ | 
 | 	if (target == NULL || pointer == NULL) | 
 | 		return 0; | 
 |  | 
 | 	/* for now just for the first member that is a pointer */ | 
 | 	type__for_each_member(tag__type(pointer), pos_member) { | 
 | 		struct tag *ctype = cu__type(cu, pos_member->tag.type); | 
 |  | 
 | 		tag__assert_search_result(ctype, pos_member->tag.tag, class_member__name(pos_member)); | 
 | 		if (tag__is_pointer_to(ctype, target_type_id)) | 
 | 			break; | 
 | 	} | 
 |  | 
 | 	list_for_each_entry(pos_tag, &cu->tool_list, tool_node) { | 
 | 		uint32_t function_id = (long)pos_tag->priv; | 
 |  | 
 | 		if (methods__add(&probes_emitted, function__name(pos_tag)) != 0) | 
 | 			continue; | 
 |  | 
 | 		function__emit_probes(pos_tag, function_id, cu, target_type_id, 0, | 
 | 				      class_member__name(pos_member)); /* entry */ | 
 | 		function__emit_probes(pos_tag, function_id, cu, target_type_id, 1, | 
 | 				      class_member__name(pos_member)); /* exit */ | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Iterate thru the list of methods previously collected by | 
 |  * cu_find_methods_iterator, creating the functions table that will | 
 |  * be used by ostra-cg | 
 |  */ | 
 | static int cu_emit_functions_table(struct cu *cu, void *fp) | 
 | { | 
 |        struct function *pos; | 
 |  | 
 |        list_for_each_entry(pos, &cu->tool_list, tool_node) | 
 |                if (pos->priv != NULL) { | 
 | 			uint32_t function_id = (long)pos->priv; | 
 | 			fprintf(fp, "%d:%s\n", function_id, function__name(pos)); | 
 | 			pos->priv = NULL; | 
 | 		} | 
 |  | 
 |        return 0; | 
 | } | 
 |  | 
 | static int elf__open(const char *filename) | 
 | { | 
 | 	int fd = open(filename, O_RDONLY); | 
 |  | 
 | 	if (fd < 0) | 
 | 		return -1; | 
 |  | 
 | 	int err = -1; | 
 |  | 
 | 	if (elf_version(EV_CURRENT) == EV_NONE) { | 
 | 		fprintf(stderr, "%s: cannot set libelf version.\n", __func__); | 
 | 		goto out_close; | 
 | 	} | 
 |  | 
 | 	Elf *elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); | 
 | 	if (elf == NULL) { | 
 | 		fprintf(stderr, "%s: cannot read %s ELF file.\n", | 
 | 			__func__, filename); | 
 | 		goto out_close; | 
 | 	} | 
 |  | 
 | 	GElf_Shdr shdr; | 
 | 	size_t init_index; | 
 | 	Elf_Scn *init = elf_section_by_name(elf, &shdr, ".init.text", &init_index); | 
 | 	if (init == NULL) | 
 | 		goto out_elf_end; | 
 |  | 
 | 	struct elf_symtab *symtab = elf_symtab__new(".symtab", elf); | 
 | 	if (symtab == NULL) | 
 | 		goto out_elf_end; | 
 |  | 
 | 	init_blacklist = strlist__new(true); | 
 | 	if (init_blacklist == NULL) | 
 | 		goto out_elf_symtab_delete; | 
 |  | 
 | 	uint32_t index; | 
 | 	GElf_Sym sym; | 
 | 	elf_symtab__for_each_symbol(symtab, index, sym) { | 
 | 		if (!elf_sym__is_local_function(&sym)) | 
 | 			continue; | 
 | 		if (elf_sym__section(&sym) != init_index) | 
 | 			continue; | 
 | 		err = strlist__add(init_blacklist, elf_sym__name(&sym, symtab)); | 
 | 		if (err == -ENOMEM) { | 
 | 			fprintf(stderr, "failed for %s(%d,%zd)\n", elf_sym__name(&sym, symtab),elf_sym__section(&sym),init_index); | 
 | 			goto out_delete_blacklist; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	err = 0; | 
 | out_elf_symtab_delete: | 
 | 	elf_symtab__delete(symtab); | 
 | out_elf_end: | 
 | 	elf_end(elf); | 
 | out_close: | 
 | 	close(fd); | 
 | 	return err; | 
 | out_delete_blacklist: | 
 | 	strlist__delete(init_blacklist); | 
 | 	goto out_elf_symtab_delete; | 
 | } | 
 |  | 
 | /* Name and version of program.  */ | 
 | ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version; | 
 |  | 
 | static const struct argp_option ctracer__options[] = { | 
 | 	{ | 
 | 		.key  = 'd', | 
 | 		.name = "src_dir", | 
 | 		.arg  = "SRC_DIR", | 
 | 		.doc  = "generate source files in this directory", | 
 | 	}, | 
 | 	{ | 
 | 		.key  = 'C', | 
 | 		.name = "cu_blacklist", | 
 | 		.arg  = "FILE", | 
 | 		.doc  = "Blacklist the CUs in FILE", | 
 | 	}, | 
 | 	{ | 
 | 		.key  = 'D', | 
 | 		.name = "dir", | 
 | 		.arg  = "DIR", | 
 | 		.doc  = "load files in this directory", | 
 | 	}, | 
 | 	{ | 
 | 		.key  = 'g', | 
 | 		.name = "glob", | 
 | 		.arg  = "GLOB", | 
 | 		.doc  = "file mask to load", | 
 | 	}, | 
 | 	{ | 
 | 		.key  = 'r', | 
 | 		.name = "recursive", | 
 | 		.doc  = "recursively load files", | 
 | 	}, | 
 | 	{ | 
 | 		.name = NULL, | 
 | 	} | 
 | }; | 
 |  | 
 | static const char *dirname, *glob; | 
 | static int recursive; | 
 |  | 
 | static error_t ctracer__options_parser(int key, char *arg, | 
 | 				      struct argp_state *state __maybe_unused) | 
 | { | 
 | 	switch (key) { | 
 | 	case 'd': src_dir = arg;		break; | 
 | 	case 'C': cu_blacklist_filename = arg;	break; | 
 | 	case 'D': dirname = arg;		break; | 
 | 	case 'g': glob = 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; | 
 | 	char *filename; | 
 | 	char functions_filename[PATH_MAX]; | 
 | 	char methods_filename[PATH_MAX]; | 
 | 	char collector_filename[PATH_MAX]; | 
 | 	char classes_filename[PATH_MAX]; | 
 | 	struct structure *pos; | 
 | 	FILE *fp_functions; | 
 | 	int rc = EXIT_FAILURE; | 
 |  | 
 | 	if (dwarves__init()) { | 
 | 		fputs("ctracer: insufficient memory\n", stderr); | 
 | 		goto out; | 
 | 	} | 
 | 	dwarves__resolve_cacheline_size(NULL, 0); | 
 |  | 
 | 	if (argp_parse(&ctracer__argp, argc, argv, 0, &remaining, NULL) || | 
 | 	    remaining < argc) { | 
 | 		switch (argc - remaining) { | 
 | 		case 1:	 goto failure; | 
 | 		case 2:	 filename  = argv[remaining++]; | 
 | 			 class_name = argv[remaining++];	break; | 
 | 		default: goto failure; | 
 | 		} | 
 | 	} else { | 
 | failure: | 
 | 		argp_help(&ctracer__argp, stderr, ARGP_HELP_SEE, argv[0]); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	type_emissions__init(&emissions, NULL); | 
 |  | 
 |         /* | 
 |          * 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(); | 
 | 	if (methods_cus == NULL) { | 
 | 		fputs("ctracer: insufficient memory\n", stderr); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* | 
 |          * 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, NULL, dirname, glob, | 
 | 					     recursive) != 0) { | 
 | 		fprintf(stderr, "ctracer: couldn't load DWARF info " | 
 | 				"from %s dir with glob %s\n", | 
 | 			dirname, glob); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 |         /* | 
 |          * If a filename was specified, for instance "vmlinux", load it too. | 
 |          */ | 
 | 	if (filename != NULL) { | 
 | 		if (elf__open(filename)) { | 
 | 			fprintf(stderr, "ctracer: couldn't load ELF symtab " | 
 | 					"info from %s\n", filename); | 
 | 			goto out; | 
 | 		} | 
 | 		err = cus__load_file(methods_cus, NULL, filename); | 
 | 		if (err != 0) { | 
 | 			cus__print_error_msg("ctracer", methods_cus, filename, err); | 
 | 			goto out; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * See if the specified struct exists: | 
 | 	 */ | 
 | 	class = cus__find_struct_by_name(methods_cus, &cu, class_name, 0, NULL); | 
 | 	if (class == NULL) { | 
 | 		fprintf(stderr, "ctracer: struct %s not found!\n", class_name); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	snprintf(functions_filename, sizeof(functions_filename), | 
 | 		 "%s/%s.functions", src_dir, class__name(tag__class(class))); | 
 | 	fp_functions = fopen(functions_filename, "w"); | 
 | 	if (fp_functions == NULL) { | 
 | 		fprintf(stderr, "ctracer: couldn't create %s\n", | 
 | 			functions_filename); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	snprintf(methods_filename, sizeof(methods_filename), | 
 | 		 "%s/ctracer_methods.stp", src_dir); | 
 | 	fp_methods = fopen(methods_filename, "w"); | 
 | 	if (fp_methods == NULL) { | 
 | 		fprintf(stderr, "ctracer: couldn't create %s\n", | 
 | 			methods_filename); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	snprintf(collector_filename, sizeof(collector_filename), | 
 | 		 "%s/ctracer_collector.c", src_dir); | 
 | 	fp_collector = fopen(collector_filename, "w"); | 
 | 	if (fp_collector == NULL) { | 
 | 		fprintf(stderr, "ctracer: couldn't create %s\n", | 
 | 			collector_filename); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	snprintf(classes_filename, sizeof(classes_filename), | 
 | 		 "%s/ctracer_classes.h", src_dir); | 
 | 	fp_classes = fopen(classes_filename, "w"); | 
 | 	if (fp_classes == NULL) { | 
 | 		fprintf(stderr, "ctracer: couldn't create %s\n", | 
 | 			classes_filename); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	fputs("%{\n" | 
 | 	      "#include </home/acme/git/pahole/lib/ctracer_relay.h>\n" | 
 | 	      "%}\n" | 
 | 	      "function ctracer__method_hook(probe_type, func, object, state_len)\n" | 
 | 	      "%{\n" | 
 | 	      "\tctracer__method_hook(_stp_gettimeofday_ns(), " | 
 | 				     "THIS->probe_type, THIS->func, " | 
 | 				     "(void *)(long)THIS->object, " | 
 | 				     "THIS->state_len);\n" | 
 | 	      "%}\n\n", fp_methods); | 
 |  | 
 | 	fputs("\n#include \"ctracer_classes.h\"\n\n", fp_collector); | 
 | 	class__find_aliases(class_name); | 
 | 	class__find_pointers(class_name); | 
 |  | 
 | 	class__emit_classes(class, cu); | 
 | 	fputc('\n', fp_collector); | 
 |  | 
 | 	class__emit_ostra_converter(class); | 
 |  | 
 | 	cu_blacklist = strlist__new(true); | 
 | 	if (cu_blacklist != NULL) | 
 | 		strlist__load(cu_blacklist, cu_blacklist_filename); | 
 |  | 
 | 	cus__for_each_cu(methods_cus, cu_find_methods_iterator, | 
 | 			 class_name, cu_filter); | 
 | 	cus__for_each_cu(methods_cus, cu_emit_probes_iterator, | 
 | 			 class_name, cu_filter); | 
 | 	cus__for_each_cu(methods_cus, cu_emit_functions_table, | 
 | 			 fp_functions, cu_filter); | 
 |  | 
 | 	list_for_each_entry(pos, &aliases, node) { | 
 | 		const char *alias_name = structure__name(pos); | 
 |  | 
 | 		cus__for_each_cu(methods_cus, cu_find_methods_iterator, | 
 | 				 (void *)alias_name, cu_filter); | 
 | 		cus__for_each_cu(methods_cus, cu_emit_probes_iterator, | 
 | 				 (void *)alias_name, cu_filter); | 
 | 		cus__for_each_cu(methods_cus, cu_emit_functions_table, | 
 | 				 fp_functions, cu_filter); | 
 | 	} | 
 |  | 
 | 	list_for_each_entry(pos, &pointers, node) { | 
 | 		const char *pointer_name = structure__name(pos); | 
 | 		cus__for_each_cu(methods_cus, cu_find_methods_iterator, | 
 | 				 (void *)pointer_name, cu_filter); | 
 | 		cus__for_each_cu(methods_cus, cu_emit_pointer_probes_iterator, | 
 | 				 (void *)pointer_name, cu_filter); | 
 | 		cus__for_each_cu(methods_cus, cu_emit_functions_table, fp_functions, | 
 | 				 cu_filter); | 
 | 	} | 
 |  | 
 | 	fclose(fp_methods); | 
 | 	fclose(fp_collector); | 
 | 	fclose(fp_functions); | 
 | 	fclose(fp_classes); | 
 | 	strlist__delete(cu_blacklist); | 
 |  | 
 | 	rc = EXIT_SUCCESS; | 
 | out: | 
 | 	cus__delete(methods_cus); | 
 | 	dwarves__exit(); | 
 | 	return rc; | 
 | } |