/* Copyright(C) 2016, Red Hat, Inc., Stanislav Kozina This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * This file contains the code which generates the kabi information for the * given build of the Linux kernel. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "utils.h" #include "generate.h" #include "ksymtab.h" #include "stack.h" #include "hash.h" #include "objects.h" #include "list.h" #define EMPTY_NAME "(NULL)" #define PROCESSED_SIZE 1024 /* DB size is number of hash buckets, do not have to be exact, but since now we have ~20K records, make it this */ #define DB_SIZE (20 * 1024) #define INITIAL_RECORD_SIZE 512 /* * Dwarf5 spec, 7.5.4 Attribute Encodings * libdw doesn't support it yet */ #ifndef DW_AT_alignment #define DW_AT_alignment 0x88 #endif struct set; struct record_db; typedef struct { char *kernel_dir; /* Path to the kernel modules to process */ char *kabi_dir; /* Where to put the output */ struct ksymtab *symbols; /* List of symbols to generate */ size_t symbol_cnt; struct record_db *db; bool rhel_tree; bool verbose; bool gen_extra; } generate_config_t; struct cu_ctx { generate_config_t *conf; Dwarf_Die *cu_die; stack_t *stack; /* Current stack of symbol we're parsing */ struct set *processed; /* Set of processed types for this CU */ }; struct file_ctx { generate_config_t *conf; struct ksymtab *ksymtab; /* ksymtab of the current kernel module */ }; struct dwarf_type { unsigned int dwarf_tag; char *prefix; } known_dwarf_types[] = { { DW_TAG_subprogram, FUNC_FILE }, { DW_TAG_typedef, TYPEDEF_FILE }, { DW_TAG_variable, VAR_FILE }, { DW_TAG_enumeration_type, ENUM_FILE }, { DW_TAG_structure_type, STRUCT_FILE }, { DW_TAG_union_type, UNION_FILE }, { 0, NULL } }; /* * Structure of the database record: * * key: record key, usually includes path the file, where the type is * defined (may include pseudo path, like ); * * version: type's version, used when we need to add another type of the same * name. It may happend, for example, when because of defines the same * structure has changed for different compilation units. * * It is not for the case, when the same structure defined in * different files -- it will have different keys, since it includes * the path; * * ref_count: reference counter, needed since the ownership is shared with the * internal database; * * base_file: base part of the key (without version), used to generate the * unique key for the new version; * * cu: compilation unit, where the type for the record defined; * * origin: "File :" string, describing the source, where the type * for the record defined; * * stack: stack of types to reach this one. * Ex.: on the toplevel * struct A { * struct B fieldA; * } * in another file: * struct B { * basetype fieldB; * } * the "struct B" description will contain key of the "struct A" * description record in the stack; * * obj: pointer to the abstract type object, representing the toplevel type of * the record. * * link: name of weak link alisas for the weak aliases. * * free: type specific function to free the record * (there are normal, weak and assembly records). * * dump: type specific function for record output. * * dependents: objects that reference this record. */ struct record { char *key; int version; int ref_count; char *base_file; char *cu; char *origin; stack_t *stack; obj_t *obj; char *link; void (*free)(struct record *); void (*dump)(struct record *, FILE *); struct list dependents; }; void record_update_dependents(struct record *record) { struct list_node *iter; LIST_FOR_EACH(&record->dependents, iter) { obj_t *obj = list_node_data(iter); free(obj->base_type); obj->base_type = safe_strdup(record->key); } } static const bool is_builtin(Dwarf_Die *die) { char *fname; const char *path = dwarf_decl_file(die); if (path == NULL) return true; fname = basename(path); assert (fname != NULL); if (strcmp(fname, "") == 0) return true; return false; } static const char *get_die_name(Dwarf_Die *die) { if (dwarf_hasattr(die, DW_AT_name)) return dwarf_diename(die); else return EMPTY_NAME; } /* * Check if given DIE has DW_AT_declaration attribute. * That indicates that the symbol is just a declaration, not full definition. */ static bool is_declaration(Dwarf_Die *die) { Dwarf_Attribute attr; if (!dwarf_hasattr(die, DW_AT_declaration)) return false; (void) dwarf_attr(die, DW_AT_declaration, &attr); if (!dwarf_hasform(&attr, DW_FORM_flag_present)) return false; return true; } static char *get_file_replace_path; static char *_get_file(Dwarf_Die *cu_die, Dwarf_Die *die) { const char *filename; char *ret; filename = dwarf_decl_file(die); if (get_file_replace_path) { int len = strlen(get_file_replace_path); if (strncmp(filename, get_file_replace_path, len) == 0) { filename = filename + len; while (*filename == '/') filename++; } } ret = safe_strdup(filename); path_normalize(ret); return ret; } static char *get_file(Dwarf_Die *cu_die, Dwarf_Die *die) { Dwarf_Attribute attr; Dwarf_Die spec_die; /* * Handle types built-in in C compiler. These are for example the * variable argument list which is defined as * struct __va_list_tag. */ if (is_builtin(die)) return safe_strdup(BUILTIN_PATH); if (dwarf_hasattr(die, DW_AT_decl_file)) return _get_file(cu_die, die); if ((dwarf_attr(die, DW_AT_specification, &attr) == NULL) || (dwarf_formref_die(&attr, &spec_die) == NULL)) { fail("DIE missing file information: %s\n", dwarf_diename(die)); } return _get_file(cu_die, &spec_die); } static long get_line(Dwarf_Die *cu_die, Dwarf_Die *die) { Dwarf_Attribute attr; Dwarf_Word line; Dwarf_Die spec_die; if (is_builtin(die)) return 0; if (dwarf_attr(die, DW_AT_decl_line, &attr) != NULL) { dwarf_formudata(&attr, &line); return line; } if ((dwarf_attr(die, DW_AT_specification, &attr) == NULL) || (dwarf_formref_die(&attr, &spec_die) == NULL)) { fail("DIE missing line information: %s\n", dwarf_diename(die)); } return get_line(cu_die, &spec_die); } static obj_t *print_die(struct cu_ctx *, struct record *, Dwarf_Die *); static const char *dwarf_tag_string(unsigned int tag) { switch (tag) { #define DWARF_ONE_KNOWN_DW_TAG(NAME, CODE) case CODE: return #NAME; DWARF_ALL_KNOWN_DW_TAG #undef DWARF_ONE_KNOWN_DW_TAG default: return NULL; } } static char *get_file_prefix(unsigned int dwarf_tag) { struct dwarf_type *current; for (current = known_dwarf_types; current->prefix != NULL; current++) { if (dwarf_tag == current->dwarf_tag) break; } return current->prefix; } static char *get_symbol_file(Dwarf_Die *die, Dwarf_Die *cu_die) { const char *name = dwarf_diename(die); unsigned int tag = dwarf_tag(die); char *file_prefix; char *file_name = NULL; file_prefix = get_file_prefix(tag); if (file_prefix == NULL) { /* No need to redirect output for this type */ return NULL; } /* * DW_AT_declaration don't have DW_AT_decl_file. * Pretend like it's in other, non existent file. */ if (is_declaration(die)) { safe_asprintf(&file_name, DECLARATION_PATH "/%s%s.txt", file_prefix, name); return file_name; } /* * Following types can be anonymous, eg. used directly as variable type * in the declaration. We don't create new file for them if that's * the case, embed them directly in the current file. * Note that anonymous unions can also be embedded directly in the * structure! */ switch (tag) { case DW_TAG_enumeration_type: case DW_TAG_structure_type: case DW_TAG_union_type: if (name == NULL) return NULL; break; } /* We don't expect our name to be empty now */ assert(name != NULL); safe_asprintf(&file_name, "%s%s.txt", file_prefix, name); return file_name; } /* * Check if given DIE is external. * It can has DW_AT_external attribute itself, * or in case of reference to the specification, * the specification DIE can be exported. */ static int is_external(Dwarf_Die *die) { Dwarf_Attribute attr; Dwarf_Die spec_die; if (dwarf_hasattr(die, DW_AT_external)) { dwarf_attr(die, DW_AT_external, &attr); if (!dwarf_hasform(&attr, DW_FORM_flag_present)) return false; return true; } if (dwarf_attr(die, DW_AT_specification, &attr) == NULL) return false; if (dwarf_formref_die(&attr, &spec_die) == NULL) fail("dwarf_formref_die() failed for %s\n", dwarf_diename(die)); return is_external(&spec_die); } static obj_t *die_read_alignment(Dwarf_Die *die, obj_t *obj) { Dwarf_Attribute attr; Dwarf_Word value; if (dwarf_attr(die, DW_AT_alignment, &attr) == NULL) goto out; dwarf_formudata(&attr, &value); obj->alignment = value; out: return obj; } struct set *set_init(size_t size) { struct hash *h; h = hash_new(size, free); if (h == NULL) fail("Cannot create hash"); return (struct set *)h; } static void set_add(struct set *set, const char *key) { char *storage; struct hash *h = (struct hash *)set; storage = safe_strdup(key); hash_add(h, storage, storage); } static bool set_contains(struct set *set, const char *key) { void *v; struct hash *h = (struct hash *)set; v = hash_find(h, key); return v != NULL; } static void set_free(struct set *set) { struct hash *h = (struct hash *)set; hash_free(h); } static struct record *record_alloc(void) { struct record *rec; rec = safe_zmalloc(sizeof(*rec)); return rec; } static void record_free_regular(struct record *rec) { void *data; struct list_node *iter; free(rec->base_file); free(rec->origin); if (rec->cu) free(rec->cu); while ((data = stack_pop(rec->stack)) != NULL) free(data); stack_destroy(rec->stack); LIST_FOR_EACH(&rec->dependents, iter) { obj_t *o = list_node_data(iter); o->depend_rec_node = NULL; } list_clear(&rec->dependents); obj_free(rec->obj); } static void record_free_weak(struct record *rec) { free(rec->link); } static void record_free(struct record *rec) { free(rec->key); if (rec->free) rec->free(rec); free(rec); } static void record_put(struct record *rec) { assert(rec->ref_count > 0); if (--rec->ref_count == 0) record_free(rec); } static void record_get(struct record *rec) { rec->ref_count++; } static void record_dump_regular(struct record *rec, FILE *f); static struct record *record_new_regular(const char *key) { struct record *rec; rec = record_alloc(); rec->key = safe_strdup(key); rec->stack = stack_init(); rec->free = record_free_regular; rec->dump = record_dump_regular; list_init(&rec->dependents, NULL); record_get(rec); return rec; } static void record_dump_assembly(struct record *rec, FILE *f); static struct record *record_new_assembly(const char *key) { struct record *rec; rec = record_alloc(); rec->key = safe_strdup(key); /* * The symbol not necessary belongs to an assembly function, * it is actually "no definition found in the debug information", * but the main goal is to find the assembly ones. */ rec->free = NULL; rec->dump = record_dump_assembly; record_get(rec); return rec; } static void record_dump_weak(struct record *rec, FILE *f); static struct record *record_new_weak(const char *key, const char *link) { struct record *rec; rec = record_alloc(); rec->key = safe_strdup(key); rec->link = safe_strdup(link); rec->free = record_free_weak; rec->dump = record_dump_weak; record_get(rec); return rec; } static obj_t *record_obj(struct record *rec) { return rec->obj; } static obj_t *record_obj_exchange(struct record *rec, obj_t *o) { obj_t *old; old = rec->obj; rec->obj = o; return old; } static const char *record_origin(struct record *rec) { return rec->origin; } static void copy_stack_cb(void *data, void *arg) { char *symbol = (char *)data; struct record *fp = (struct record *)arg; char *copy; copy = safe_strdup(symbol); stack_push(fp->stack, copy); } static void record_add_stack(struct record *rec, stack_t *stack) { walk_stack_backward(stack, copy_stack_cb, rec); } static void record_add_cu(struct record *rec, Dwarf_Die *cu_die) { const char *name; if (cu_die == NULL) return; name = dwarf_diename(cu_die); safe_asprintf(&rec->cu, "CU: \"%s\"\n", name); } static void record_add_origin(struct record *rec, Dwarf_Die *cu_die, Dwarf_Die *die) { char *dec_file; long dec_line; dec_file = get_file(cu_die, die); dec_line = get_line(cu_die, die); safe_asprintf(&rec->origin, "File: %s:%lu\n", dec_file, dec_line); free(dec_file); } static struct record *record_start(struct cu_ctx *ctx, Dwarf_Die *die, char *key) { struct record *rec = NULL; generate_config_t *conf = ctx->conf; Dwarf_Die *cu_die = ctx->cu_die; /* * Don't try to reenter a file that we have seen already for * this CU. * Note that this is just a pure optimization, the same file * (type) in the same CU must be identical. * But this is major optimization, without it a single * re-generation of a top file would require a full regeneration * of its full tree, thus the difference in speed is many orders * of magnitude! */ if (set_contains(ctx->processed, key)) goto done; set_add(ctx->processed, key); if (is_declaration(die)) { if (conf->verbose) printf("WARNING: Skipping following file as we " "have only declaration: %s\n", key); goto done; } if (conf->verbose) printf("Generating %s\n", key); rec = record_new_regular(key); if (conf->gen_extra) record_add_cu(rec, cu_die); record_add_origin(rec, cu_die, die); record_add_stack(rec, ctx->stack); done: return rec; } static void record_set_version(struct record *rec, int version) { char *base_file = rec->base_file; char *key = NULL; if (version == 0) return; if (rec->version == 0) { base_file = safe_strdup(rec->key); /* Remove .txt ending */ base_file[strlen(base_file) - 4] = '\0'; rec->base_file = base_file; } rec->version = version; safe_asprintf(&key, "%s-%i.txt", base_file, rec->version); free(rec->key); rec->key = key; } static void record_close(struct record *rec, obj_t *obj) { obj_fill_parent(obj); rec->obj = obj; } static void record_stack_dump_and_clear(struct record *rec, FILE *f) { char *data = stack_pop(rec->stack); if (data == NULL) return; fprintf(f, "Stack:\n"); do { fprintf(f, "-> \"%s\"\n", data); free(data); } while ((data = stack_pop(rec->stack)) != NULL); } static void record_dump_regular(struct record *rec, FILE *f) { int rc; fprintf(f, FILEFMT_VERSION_STRING); if (rec->cu != NULL) { rc = fputs(rec->cu, f); if (rc == EOF) fail("Could not put CU name"); } rc = fputs(rec->origin, f); if (rc == EOF) fail("Could not put origin"); record_stack_dump_and_clear(rec, f); fprintf(f, "Symbol:\n"); if (rec->obj->alignment != 0) fprintf(f, "Alignment %u\n", rec->obj->alignment); obj_dump(rec->obj, f); } static void record_dump_assembly(struct record *rec, FILE *f) { char *name = filenametosymbol(rec->key); int rc; rc = fprintf(f, FILEFMT_VERSION_STRING "Symbol:\nassembly %s\n", name); free(name); if (rc < 0) fail("Could not put assembly\n"); } static void record_dump_weak(struct record *rec, FILE *f) { char *name = filenametosymbol(rec->key); int rc; rc = fprintf(f, FILEFMT_VERSION_STRING "Symbol:\nweak %s -> %s\n", name, rec->link); free(name); if (rc < 0) fail("Could not put weak link\n"); } static void record_dump(struct record *rec, const char *dir) { char path[PATH_MAX]; FILE *f; char *slash; snprintf(path, sizeof(path), "%s/%s", dir, rec->key); slash = strrchr(path, '/'); assert(slash != NULL); *slash = '\0'; rec_mkdir(path); *slash = '/'; f = fopen(path, "w"); if (f == NULL) fail("Cannot create record file '%s': %m", path); rec->dump(rec, f); fclose(f); } static void list_record_free(void *value) { struct record *rec = value; record_put(rec); } /* * merge rec_src to the record rec_dst */ static bool record_merge(struct record *rec_dst, struct record *rec_src, bool merge_decl) { const char *s1; const char *s2; obj_t *o1; obj_t *o2; obj_t *o; s1 = record_origin(rec_dst); s2 = record_origin(rec_src); if (!safe_streq(s1, s2)) return false; o1 = record_obj(rec_dst); o2 = record_obj(rec_src); o = obj_merge(o1, o2, merge_decl); if (o == NULL) return false; obj_fill_parent(o); o = record_obj_exchange(rec_dst, o); obj_free(o); return true; } static char *record_db_add(struct record_db *db, struct record *rec) { /* * Now we need to put the new type record we've just generated * to the db. * * The problem stopping us from merging every record possible is that * multiple records can't be merged if it is not possible to create * groups in which they should be merged. This is not a problem if all * the records are able to be merged together, but when there is one or * more incompatible merging then merging them aggressively could group * them in the wrong way. And because the right way to group them can't * be decided, it is better not to group and subsequently merge them at * all. * * Meaning that at this stage, when we don't know all the records, we * can't decide if all records can be merged into one, and so the only * records we can merge are those that are completely same. * * In case they aren't same we store them as another node of the list. * We also change the name of the new record, so that two reffile obj_t * referencing records that we couldn't merge wouldn't get merged. */ struct hash *hash = (struct hash *)db; struct record *tmp_rec; struct list *list; struct list_node *iter; list = hash_find(hash, rec->key); if (list == NULL) { list = list_new(list_record_free); hash_add(hash, rec->key, list); } LIST_FOR_EACH(list, iter) { tmp_rec = list_node_data(iter); if (record_merge(tmp_rec, rec, NO_MERGE_DECL)) { list_concat(&tmp_rec->dependents, &rec->dependents); return safe_strdup(tmp_rec->key); } } record_get(rec); record_set_version(rec, list->len); list_add(list, rec); return safe_strdup(rec->key); } static void hash_list_free(void *value) { struct list *list = value; list_free(list); } static struct record_db *record_db_init(void) { struct hash *db; db = hash_new(DB_SIZE, hash_list_free); if (db == NULL) fail("Could not create db (hash)\n"); return (struct record_db *)db; } static void record_db_dump(struct record_db *_db, char *dir) { struct hash_iter iter; const void *v; struct hash *db = (struct hash *)_db; hash_iter_init(db, &iter); while (hash_iter_next(&iter, NULL, &v)) { struct list *list = (struct list *)v; struct list_node *iter; LIST_FOR_EACH(list, iter) { struct record *rec = list_node_data(iter); record_dump(rec, dir); } } } static void record_db_free(struct record_db *_db) { struct hash *db = (struct hash *)_db; hash_free(db); } static obj_t *print_die_type(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { Dwarf_Die type_die; Dwarf_Attribute attr; if (!dwarf_hasattr(die, DW_AT_type)) return obj_basetype_new(safe_strdup("void")); (void) dwarf_attr(die, DW_AT_type, &attr); if (dwarf_formref_die(&attr, &type_die) == NULL) fail("dwarf_formref_die() failed for %s\n", dwarf_diename(die)); /* Print the type of the die */ return print_die(ctx, rec, &type_die); } static obj_t *print_die_struct_member(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die, const char *name) { Dwarf_Attribute attr; Dwarf_Word value; obj_t *type; obj_t *obj; if (dwarf_attr(die, DW_AT_data_member_location, &attr) == NULL) fail("Offset of member %s missing!\n", name); (void) dwarf_formudata(&attr, &value); type = print_die_type(ctx, rec, die); obj = obj_struct_member_new_add(safe_strdup(name), type); obj->offset = value; if (dwarf_hasattr(die, DW_AT_bit_offset)) { Dwarf_Word offset, size; if (!dwarf_hasattr(die, DW_AT_bit_size)) fail("Missing expected bit size attribute in %s!\n", name); if (dwarf_attr(die, DW_AT_bit_offset, &attr) == NULL) fail("Bit offset of member %s missing!\n", name); (void) dwarf_formudata(&attr, &offset); if (dwarf_attr(die, DW_AT_bit_size, &attr) == NULL) fail("Bit size of member %s missing!\n", name); (void) dwarf_formudata(&attr, &size); obj->is_bitfield = 1; obj->first_bit = offset; obj->last_bit = offset + size - 1; } die_read_alignment(die, obj); return obj; } static obj_t *print_die_structure(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { const char *name = get_die_name(die); unsigned int tag; obj_list_head_t *members = NULL; obj_t *obj; obj_t *member; Dwarf_Die child_die; obj = obj_struct_new(safe_strdup(name)); if (!dwarf_haschildren(die)) goto done; dwarf_child(die, &child_die); do { Dwarf_Die *die = &child_die; name = get_die_name(die); tag = dwarf_tag(die); if (tag != DW_TAG_member) fail("Unexpected tag for structure type children: " "%s\n", dwarf_tag_string(tag)); member = print_die_struct_member(ctx, rec, die, name); if (members == NULL) members = obj_list_head_new(member); else obj_list_add(members, member); } while (dwarf_siblingof(&child_die, &child_die) == 0); obj->member_list = members; done: return obj; } static obj_t *print_die_enumerator(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die, const char *name) { Dwarf_Attribute attr; Dwarf_Word value; obj_t *obj; if (dwarf_attr(die, DW_AT_const_value, &attr) == NULL) fail("Value of enumerator %s missing!\n", name); (void) dwarf_formudata(&attr, &value); obj = obj_constant_new(safe_strdup(name)); obj->constant = value; return obj; } static obj_t *print_die_enumeration(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { const char *name = get_die_name(die); obj_list_head_t *members = NULL; obj_t *member; obj_t *obj; Dwarf_Die child_die; obj = obj_enum_new(safe_strdup(name)); if (!dwarf_haschildren(die)) goto done; dwarf_child(die, &child_die); do { Dwarf_Die *die = &child_die; name = get_die_name(die); member = print_die_enumerator(ctx, rec, die, name); if (members == NULL) members = obj_list_head_new(member); else obj_list_add(members, member); } while (dwarf_siblingof(&child_die, &child_die) == 0); members->object = obj; obj->member_list = members; done: return obj; } static obj_t *print_die_union(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { const char *name = get_die_name(die); unsigned int tag; obj_list_head_t *members = NULL; obj_t *member; obj_t *type; obj_t *obj; Dwarf_Die child_die; obj = obj_union_new(safe_strdup(name)); if (!dwarf_haschildren(die)) goto done; dwarf_child(die, &child_die); do { Dwarf_Die *die = &child_die; name = get_die_name(die); tag = dwarf_tag(die); if (tag != DW_TAG_member) fail("Unexpected tag for union type children: %s\n", dwarf_tag_string(tag)); type = print_die_type(ctx, rec, die); member = obj_var_new_add(safe_strdup(name), type); if (members == NULL) members = obj_list_head_new(member); else obj_list_add(members, member); } while (dwarf_siblingof(&child_die, &child_die) == 0); members->object = obj; obj->member_list = members; done: die_read_alignment(die, obj); return obj; } static obj_list_head_t *print_subprogram_arguments(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { Dwarf_Die child_die; obj_t *arg_type; obj_t *arg; obj_list_head_t *arg_list = NULL; if (!dwarf_haschildren(die)) return NULL; /* Grab the first argument */ dwarf_child(die, &child_die); /* Walk all arguments until we run into the function body */ while ((dwarf_tag(&child_die) == DW_TAG_formal_parameter) || (dwarf_tag(&child_die) == DW_TAG_unspecified_parameters)) { const char *name = get_die_name(&child_die); if (dwarf_tag(&child_die) != DW_TAG_unspecified_parameters) arg_type = print_die_type(ctx, rec, &child_die); else arg_type = obj_basetype_new(safe_strdup("...")); arg = obj_var_new_add(safe_strdup(name), arg_type); if (arg_list == NULL) arg_list = obj_list_head_new(arg); else obj_list_add(arg_list, arg); if (dwarf_siblingof(&child_die, &child_die) != 0) break; } return arg_list; } static obj_t *print_die_subprogram(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { char *name; obj_list_head_t *arg_list; obj_t *ret_type; obj_t *obj; arg_list = print_subprogram_arguments(ctx, rec, die); ret_type = print_die_type(ctx, rec, die); name = safe_strdup(get_die_name(die)); obj = obj_func_new_add(name, ret_type); if (arg_list) arg_list->object = obj; obj->member_list = arg_list; return obj; } static obj_t *_print_die_array_type(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *child, obj_t *base_type) { Dwarf_Die next_child; Dwarf_Word value; Dwarf_Attribute attr; int rc; unsigned int tag; unsigned long arr_idx; obj_t *obj; obj_t *sub; if (child == NULL) return base_type; tag = dwarf_tag(child); if (tag != DW_TAG_subrange_type) fail("Unexpected tag for array type children: %s\n", dwarf_tag_string(tag)); if (dwarf_hasattr(child, DW_AT_upper_bound)) { (void) dwarf_attr(child, DW_AT_upper_bound, &attr); (void) dwarf_formudata(&attr, &value); /* Get the UPPER bound, so add 1 */ arr_idx = value + 1; } else if (dwarf_hasattr(child, DW_AT_count)) { (void) dwarf_attr(child, DW_AT_count, &attr); (void) dwarf_formudata(&attr, &value); arr_idx = value; } else { arr_idx = 0; } rc = dwarf_siblingof(child, &next_child); child = rc == 0 ? &next_child : NULL; sub = _print_die_array_type(ctx, rec, child, base_type); obj = obj_array_new_add(sub); obj->index = arr_idx; return obj; } static obj_t *print_die_array_type(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { Dwarf_Die child; obj_t *base_type; /* There should be one child of DW_TAG_subrange_type */ if (!dwarf_haschildren(die)) fail("Array type missing children!\n"); base_type = print_die_type(ctx, rec, die); /* Grab the child */ dwarf_child(die, &child); return _print_die_array_type(ctx, rec, &child, base_type); } static obj_t *print_die_tag(struct cu_ctx *ctx, struct record *rec, Dwarf_Die *die) { unsigned int tag = dwarf_tag(die); const char *name = dwarf_diename(die); obj_t *obj = NULL; if (tag == DW_TAG_invalid) fail("DW_TAG_invalid: %s\n", name); switch (tag) { case DW_TAG_subprogram: obj = print_die_subprogram(ctx, rec, die); break; case DW_TAG_variable: obj = print_die_type(ctx, rec, die); obj = obj_var_new_add(safe_strdup(name), obj); break; case DW_TAG_base_type: obj = obj_basetype_new(safe_strdup(name)); break; case DW_TAG_pointer_type: obj = print_die_type(ctx, rec, die); obj = obj_ptr_new_add(obj); break; case DW_TAG_structure_type: obj = print_die_structure(ctx, rec, die); break; case DW_TAG_enumeration_type: obj = print_die_enumeration(ctx, rec, die); break; case DW_TAG_union_type: obj = print_die_union(ctx, rec, die); break; case DW_TAG_typedef: obj = print_die_type(ctx, rec, die); obj = obj_typedef_new_add(safe_strdup(name), obj); break; case DW_TAG_subroutine_type: obj = print_die_subprogram(ctx, rec, die); break; case DW_TAG_volatile_type: obj = print_die_type(ctx, rec, die); obj = obj_qualifier_new_add(obj); obj->base_type = safe_strdup("volatile"); break; case DW_TAG_const_type: obj = print_die_type(ctx, rec, die); obj = obj_qualifier_new_add(obj); obj->base_type = safe_strdup("const"); break; case DW_TAG_array_type: obj = print_die_array_type(ctx, rec, die); break; default: { const char *tagname = dwarf_tag_string(tag); if (tagname == NULL) tagname = ""; fail("Unexpected tag for symbol %s: %s\n", name, tagname); break; } } obj = die_read_alignment(die, obj); return obj; } static obj_t *print_die(struct cu_ctx *ctx, struct record *parent_file, Dwarf_Die *die) { char *file; struct record *rec; char *old_file; obj_t *obj; obj_t *ref_obj; generate_config_t *conf = ctx->conf; /* * Sigh. The type of some fields (eg. struct member as a pointer to * another struct) can be defined by a mere declaration without a full * specification of the type. In such cases we just print a remote * pointer to the full type and pray it will be printed in a different * occasion. */ /* Check if we need to redirect output or we have a mere declaration */ file = get_symbol_file(die, ctx->cu_die); if (file == NULL) { /* no need for new record, output to the current one */ assert(parent_file != NULL); obj = print_die_tag(ctx, parent_file, die); return obj; } ref_obj = obj_reffile_new(); /* else handle new record */ rec = record_start(ctx, die, file); if (rec == NULL) /* declaration or already processed */ goto out; if (conf->gen_extra) stack_push(ctx->stack, safe_strdup(file)); obj = print_die_tag(ctx, rec, die); if (conf->gen_extra) free(stack_pop(ctx->stack)); record_close(rec, obj); old_file = file; ref_obj->depend_rec_node = list_add(&rec->dependents, ref_obj); /* if it creates new version, key/file name can change */ file = record_db_add(conf->db, rec); record_put(rec); /* record_db_add() returns allocated string */ free(old_file); out: ref_obj->base_type = file; return ref_obj; } /* * Validate if this is the symbol we should print. * Returns true if should. */ static bool is_symbol_valid(struct file_ctx *fctx, Dwarf_Die *die) { const char *name = dwarf_diename(die); unsigned int tag = dwarf_tag(die); bool result = false; generate_config_t *conf = fctx->conf; struct ksym *ksym1 = NULL; struct ksym *ksym2; /* Shortcut, unnamed die cannot be part of whitelist */ if (name == NULL) goto out; /* If symbol file was provided, is the symbol on the list? */ if (conf->symbols != NULL) { ksym1 = ksymtab_find(conf->symbols, name); if (ksym1 == NULL) goto out; } /* Is this symbol exported in this module with EXPORT_SYMBOL? */ ksym2 = ksymtab_find(fctx->ksymtab, name); if (ksym2 == NULL) goto out; /* We don't care about declarations */ if (is_declaration(die)) goto out; /* * Mark the symbol as not eligible to fake symbol generation. * We can come till this place with an assembly function, * because it may have dwarf info for its C declaration, * but others in dwarf are supposed to be normal C functions. */ ksymtab_ksym_mark(ksym2); /* Anything EXPORT_SYMBOLed should be external */ if (!is_external(die)) goto out; /* We expect only variables or functions on whitelist */ switch (tag) { case (DW_TAG_subprogram): /* * We ignore DW_AT_prototyped. This marks functions with * arguments specified in their declaration which the old * pre-ANSI C didn't require. Unfortunatelly people still omit * arguments instead of using foo(void) so we need to handle * even functions without DW_AT_prototyped. What a pity! */ break; case DW_TAG_variable: break; default: fail("Symbol %s has unexpected tag: %s!\n", name, dwarf_tag_string(tag)); } result = true; /* * Mark the symbol as fully processed, * so it will not be in the subset of not found symbols. * We are talking here about kabi symbols set, * which is passed by -s switch. * * The actual processing starts later in the caller, * but the decision is made here. */ if (conf->symbols != NULL) ksymtab_ksym_mark(ksym1); out: return result; } /* * Walk all DIEs in a CU. * Returns true if the given symbol_name was found, otherwise false. */ static void process_cu_die(Dwarf_Die *cu_die, struct file_ctx *fctx) { Dwarf_Die child_die; bool cu_printed = false; obj_t *ref; generate_config_t *conf = fctx->conf; if (!dwarf_haschildren(cu_die)) return; /* Walk all DIEs in the CU */ dwarf_child(cu_die, &child_die); do { void *data; struct cu_ctx ctx; if (!is_symbol_valid(fctx, &child_die)) continue; if (!cu_printed && conf->verbose) { printf("Processing CU %s\n", dwarf_diename(cu_die)); cu_printed = true; } ctx.conf = conf; ctx.cu_die = cu_die; /* Grab a fresh stack of symbols */ ctx.stack = stack_init(); /* And a set of all processed symbols */ ctx.processed = set_init(PROCESSED_SIZE); /* Print both the CU DIE and symbol DIE */ ref = print_die(&ctx, NULL, &child_die); obj_free(ref); /* And clear the stack again */ while ((data = stack_pop(ctx.stack)) != NULL) free(data); stack_destroy(ctx.stack); set_free(ctx.processed); } while (dwarf_siblingof(&child_die, &child_die) == 0); } static int dwflmod_generate_cb(Dwfl_Module *dwflmod, void **userdata, const char *name, Dwarf_Addr base, void *arg) { Dwarf_Addr dwbias; Dwarf *dbg = dwfl_module_getdwarf(dwflmod, &dwbias); struct file_ctx *fctx = (struct file_ctx *)arg; if (*userdata != NULL) fail("Multiple modules found in %s!\n", name); *userdata = dwflmod; Dwarf_Off off = 0; Dwarf_Off old_off = 0; Dwarf_Off type_offset = 0; Dwarf_Half version; size_t hsize; Dwarf_Off abbrev; uint8_t addresssize; uint8_t offsetsize; while (dwarf_next_unit(dbg, off, &off, &hsize, &version, &abbrev, &addresssize, &offsetsize, NULL, &type_offset) == 0) { if (version < 2 || version > 4) fail("Unsupported dwarf version: %d\n", version); /* CU is followed by a single DIE */ Dwarf_Die cu_die; if (dwarf_offdie(dbg, old_off + hsize, &cu_die) == NULL) { fail("dwarf_offdie failed for cu!\n"); } process_cu_die(&cu_die, fctx); old_off = off; } return DWARF_CB_OK; } static void generate_type_info(char *filepath, struct file_ctx *ctx) { static const Dwfl_Callbacks callbacks = { .section_address = dwfl_offline_section_address, .find_debuginfo = dwfl_standard_find_debuginfo }; Dwfl *dwfl = dwfl_begin(&callbacks); if (dwfl_report_offline(dwfl, filepath, filepath, -1) == NULL) { dwfl_report_end(dwfl, NULL, NULL); fail("dwfl_report_offline failed: %s\n", dwfl_errmsg(-1)); } dwfl_report_end(dwfl, NULL, NULL); dwfl_getmodules(dwfl, &dwflmod_generate_cb, ctx, 0); dwfl_end(dwfl); } static bool is_all_done(generate_config_t *conf) { if (conf->symbols == NULL) return false; return ksymtab_mark_count(conf->symbols) == conf->symbol_cnt; } static void generate_assembly_record(generate_config_t *conf, const char *key) { struct record *rec; char *new_key, *name; if (conf->verbose) printf("Generating assembly record for %s\n", key); safe_asprintf(&name, "asm--%s.txt", key); rec = record_new_assembly(name); new_key = record_db_add(conf->db, rec); record_put(rec); free(name); free(new_key); } static bool try_generate_alias(generate_config_t *conf, struct ksym *ksym) { char *link = ksymtab_ksym_get_link(ksym); const char *key = ksymtab_ksym_get_name(ksym); struct record *rec; char *new_key, *name; if (!link) return false; if (conf->verbose) printf("Generating weak record %s -> %s\n", key, link); safe_asprintf(&name, "weak--%s.txt", key); rec = record_new_weak(name, link); new_key = record_db_add(conf->db, rec); record_put(rec); free(name); free(new_key); return true; } /* * process_not_found: * * Generate fake records for symbols, not found in the debug info, * but existed in the export list (most probably, assembly function). * * If there is a checklist, mark the symbol there (as processed). * If the symbol not in the checklist, ignore it completely, * means, do not mark and do not generate fake record for it either. */ static void process_not_found(struct ksym *exported, void *ctx) { generate_config_t *conf = ctx; struct ksym *ksym; const char *key = ksymtab_ksym_get_name(exported); if (ksymtab_ksym_is_marked(exported)) return; if (conf->symbols) { ksym = ksymtab_find(conf->symbols, key); if (ksym == NULL) return; ksymtab_ksym_mark(ksym); } if (!try_generate_alias(conf, exported)) generate_assembly_record(conf, key); } static void ksymtab_add_alias(struct ksym *ksym, void *ctx) { struct ksymtab *ksymtab = ctx; ksymtab_copy_sym(ksymtab, ksym); } static void ksymtab_add_and_link_alias(struct ksym *ksym, void *ctx) { struct ksymtab *ksymtab = ctx; char *link; const char *name; struct ksym *link_ksym; link = ksymtab_ksym_get_link(ksym); link_ksym = ksymtab_find(ksymtab, link); /* if we linked, there must be the symbol in the symtab */ assert(link_ksym != NULL); name = ksymtab_ksym_get_name(ksym); ksymtab_ksym_set_link(link_ksym, name); ksymtab_copy_sym(ksymtab, ksym); } static void merge_aliases(struct ksymtab *ksymtab, struct ksymtab *symbols, struct ksymtab *aliases) { ksymtab_for_each(aliases, ksymtab_add_and_link_alias, ksymtab); if (symbols != NULL) ksymtab_for_each(aliases, ksymtab_add_alias, symbols); } static walk_rv_t process_symbol_file(char *path, void *arg) { struct file_ctx fctx; generate_config_t *conf = (generate_config_t *)arg; struct ksymtab *ksymtab; struct ksymtab *aliases = NULL; walk_rv_t ret = WALK_CONT; /* We want to process only .ko kernel modules and vmlinux itself */ if (!safe_strendswith(path, ".ko") && !safe_strendswith(path, "/vmlinux")) return ret; /* * Don't look into RHEL build cache directories. */ if (conf->rhel_tree) { if (strstr(path, "redhat/rpm") != NULL) return WALK_SKIP; } ksymtab = ksymtab_read(path, &aliases); if (ksymtab_len(ksymtab) == 0) { if (conf->verbose) printf("Skip %s (no exported symbols)\n", path); goto out; } merge_aliases(ksymtab, conf->symbols, aliases); fctx.conf = conf; fctx.ksymtab = ksymtab; if (conf->verbose) printf("Processing %s\n", path); generate_type_info(path, &fctx); ksymtab_for_each(ksymtab, process_not_found, conf); if (is_all_done(conf)) ret = WALK_STOP; out: ksymtab_free(aliases); ksymtab_free(ksymtab); return ret; } static void print_not_found(struct ksym *ksym, void *ctx) { const char *s = ksymtab_ksym_get_name(ksym); if (ksymtab_ksym_is_marked(ksym)) return; printf("%s not found!\n", s); } struct record *record_copy(struct record *src) { struct record *res = record_new_regular(""); obj_t *o1 = record_obj(src); res->obj = obj_merge(o1, o1, MERGE_DECL); obj_fill_parent(res->obj); res->origin = safe_strdup(src->origin); res->base_file = NULL; return res; } bool record_list_can_merge(struct list *rec_list) { bool result = true; struct record *merger; struct list_node *iter; if (list_len(rec_list) <= 1) { /* only one record -> nothing to merge */ return false; } merger = record_copy(list_node_data(rec_list->first)); LIST_FOR_EACH(rec_list, iter) { struct record *record = list_node_data(iter); if (!record_merge(merger, record, MERGE_DECL)) { result = false; break; } } record_put(merger); return result; } void record_list_merge(struct list *rec_list) { struct record *first = rec_list->first->data; struct list_node *next = rec_list->first->next; struct list_node *curr; struct record *record; while (next != NULL) { curr = next; next = next->next; record = curr->data; list_concat(&first->dependents, &record->dependents); record_merge(first, record, MERGE_DECL); record_put(record); free(curr); } record_update_dependents(first); rec_list->first->next = NULL; rec_list->last = rec_list->first; rec_list->len = 1; } void record_db_merge(struct record_db *db) { struct hash *hash = (struct hash *)db; bool merged; struct hash_iter iter; const char *key; const void *val; do { merged = false; hash_iter_init(hash, &iter); while (hash_iter_next(&iter, &key, &val)) { struct list *list = (struct list *)val; if (list != NULL && record_list_can_merge(list)) { record_list_merge(list); merged = true; } } } while (merged); } /* * Print symbol definition by walking all DIEs in a .debug_info section. * Returns true if the definition was printed, otherwise false. */ static void generate_symbol_defs(generate_config_t *conf) { struct stat st; if (stat(conf->kernel_dir, &st) != 0) fail("Failed to stat %s: %s\n", conf->kernel_dir, strerror(errno)); /* Lets walk the normal modules */ printf("Generating symbol defs from %s...\n", conf->kernel_dir); conf->db = record_db_init(); if (S_ISDIR(st.st_mode)) { walk_dir(conf->kernel_dir, false, process_symbol_file, conf); } else if (S_ISREG(st.st_mode)) { char *path = conf->kernel_dir; conf->kernel_dir = ""; process_symbol_file(path, conf); } else { fail("Not a file or directory: %s\n", conf->kernel_dir); } ksymtab_for_each(conf->symbols, print_not_found, NULL); record_db_merge(conf->db); record_db_dump(conf->db, conf->kabi_dir); record_db_free(conf->db); } #define WHITESPACE " \t\n" /* Remove white characters from given buffer */ static void strip(char *buf) { size_t i = 0, j = 0; while (buf[j] != '\0') { if (strchr(WHITESPACE, buf[j]) == NULL) { if (i != j) buf[i] = buf[j]; i++; } j++; } buf[i] = '\0'; } /* * Check if the string is valid C identifier. * We do this so we can easily provide the standard kabi whitelist file as the * symbol list. */ static bool is_valid_c_identifier(char *s) { int i, len; if (s == NULL) return false; len = strlen(s); if (len == 0) return false; if (s[0] != '_' && !isalpha(s[0])) return false; for (i = 1; i < len; i++) { if (s[i] != '_' && !isalnum(s[i])) return false; } return true; } static bool is_kabi_header(char *s) { const char *suffix = "_whitelist]"; int suffixlen = strlen(suffix); int len; assert(s != NULL); len = strlen(s); if (len <= suffixlen + 1) return false; if (s[0] != '[') return false; if (strcmp(s + (len - suffixlen), suffix) != 0) return false; return true; } /* Get list of symbols to generate. */ static struct ksymtab *read_symbols(char *filename) { FILE *fp = fopen(filename, "r"); char *line = NULL; size_t len = 0; size_t i = 0; struct ksymtab *symbols; symbols = ksymtab_new(DEFAULT_BUFSIZE); if (fp == NULL) fail("Failed to open symbol file: %s\n", strerror(errno)); errno = 0; while ((getline(&line, &len, fp)) != -1) { strip(line); if (!is_valid_c_identifier(line)) { if (!is_kabi_header(line)) { printf("WARNING: Ignoring line \'%s\' from the" " symbols file as it's not a valid C " "identifier.\n", line); } continue; } ksymtab_add_sym(symbols, line, len, i); i++; } if (errno != 0) fail("getline() failed for %s: %s\n", filename, strerror(errno)); if (line != NULL) free(line); fclose(fp); return symbols; } static void generate_usage() { printf("Usage:\n" "\tgenerate [options] kernel_dir\n" "\nOptions:\n" " -h, --help:\t\tshow this message\n" " -v, --verbose:\tdisplay debug information\n" " -o, --output kabi_dir:\n\t\t\t" "where to write kabi files (default: \"output\")\n" " -s, --symbols symbol_file:\n\t\t\ta file containing the" " list of symbols of interest (e.g. whitelisted)\n" " -r, --rhel:\n\t\t\trun on the RHEL build tree\n" " -a, --abs-path abs_path:\n\t\t\t" "replace the absolute path by a relative path\n" " -g, --generate-extra-info:\n\t\t\t" "generate extra information (declaration stack, compilation unit)\n"); exit(1); } static void parse_generate_opts(int argc, char **argv, generate_config_t *conf, char **symbol_file) { *symbol_file = NULL; conf->rhel_tree = false; conf->verbose = false; conf->kabi_dir = DEFAULT_OUTPUT_DIR; int opt, opt_index; struct option loptions[] = { {"help", no_argument, 0, 'h'}, {"verbose", no_argument, 0, 'v'}, {"output", required_argument, 0, 'o'}, {"symbols", required_argument, 0, 's'}, {"rhel", no_argument, 0, 'r'}, {"abs-path", required_argument, 0, 'a'}, {"generate-extra-info", no_argument, 0, 'g'}, {0, 0, 0, 0} }; while ((opt = getopt_long(argc, argv, "hvo:s:ra:m:g", loptions, &opt_index)) != -1) { switch (opt) { case 'h': generate_usage(); case 'v': conf->verbose = true; break; case 'o': conf->kabi_dir = optarg; break; case 's': *symbol_file = optarg; break; case 'r': conf->rhel_tree = true; break; case 'a': get_file_replace_path = optarg; break; case 'g': conf->gen_extra = true; break; default: generate_usage(); } } if (optind != argc - 1) generate_usage(); conf->kernel_dir = argv[optind]; rec_mkdir(conf->kabi_dir); } void generate(int argc, char **argv) { char *symbol_file; generate_config_t *conf = safe_zmalloc(sizeof(*conf)); parse_generate_opts(argc, argv, conf, &symbol_file); if (symbol_file != NULL) { conf->symbols = read_symbols(symbol_file); conf->symbol_cnt = ksymtab_len(conf->symbols); if (conf->verbose) printf("Loaded %ld symbols\n", conf->symbol_cnt); } generate_symbol_defs(conf); if (symbol_file != NULL) ksymtab_free(conf->symbols); free(conf); }