| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2024 Google LLC | |
| */ | |
| #define _GNU_SOURCE | |
| #include <errno.h> | |
| #include <stdio.h> | |
| #include "gendwarfksyms.h" | |
| #define KABI_RULE_SECTION ".discard.gendwarfksyms.kabi_rules" | |
| #define KABI_RULE_VERSION "1" | |
| /* | |
| * The rule section consists of four null-terminated strings per | |
| * entry: | |
| * | |
| * 1. version | |
| * Entry format version. Must match KABI_RULE_VERSION. | |
| * | |
| * 2. type | |
| * Type of the kABI rule. Must be one of the tags defined below. | |
| * | |
| * 3. target | |
| * Rule-dependent target, typically the fully qualified name of | |
| * the target DIE. | |
| * | |
| * 4. value | |
| * Rule-dependent value. | |
| */ | |
| #define KABI_RULE_MIN_ENTRY_SIZE \ | |
| (/* version\0 */ 2 + /* type\0 */ 2 + /* target\0" */ 1 + \ | |
| /* value\0 */ 1) | |
| #define KABI_RULE_EMPTY_VALUE "" | |
| /* | |
| * Rule: declonly | |
| * - For the struct/enum/union in the target field, treat it as a | |
| * declaration only even if a definition is available. | |
| */ | |
| #define KABI_RULE_TAG_DECLONLY "declonly" | |
| /* | |
| * Rule: enumerator_ignore | |
| * - For the enum_field in the target field, ignore the enumerator. | |
| */ | |
| #define KABI_RULE_TAG_ENUMERATOR_IGNORE "enumerator_ignore" | |
| /* | |
| * Rule: enumerator_value | |
| * - For the fqn_field in the target field, set the value to the | |
| * unsigned integer in the value field. | |
| */ | |
| #define KABI_RULE_TAG_ENUMERATOR_VALUE "enumerator_value" | |
| /* | |
| * Rule: byte_size | |
| * - For the fqn_field in the target field, set the byte_size | |
| * attribute to the value in the value field. | |
| */ | |
| #define KABI_RULE_TAG_BYTE_SIZE "byte_size" | |
| /* | |
| * Rule: type_string | |
| * - For the type reference in the fqn field, use the type string | |
| * in the value field. | |
| */ | |
| #define KABI_RULE_TAG_TYPE_STRING "type_string" | |
| enum kabi_rule_type { | |
| KABI_RULE_TYPE_UNKNOWN, | |
| KABI_RULE_TYPE_DECLONLY, | |
| KABI_RULE_TYPE_ENUMERATOR_IGNORE, | |
| KABI_RULE_TYPE_ENUMERATOR_VALUE, | |
| KABI_RULE_TYPE_BYTE_SIZE, | |
| KABI_RULE_TYPE_TYPE_STRING, | |
| }; | |
| #define RULE_HASH_BITS 7 | |
| struct rule { | |
| enum kabi_rule_type type; | |
| const char *target; | |
| const char *value; | |
| struct hlist_node hash; | |
| }; | |
| /* { type, target } -> struct rule */ | |
| static HASHTABLE_DEFINE(rules, 1 << RULE_HASH_BITS); | |
| static inline unsigned int rule_values_hash(enum kabi_rule_type type, | |
| const char *target) | |
| { | |
| return hash_32(type) ^ hash_str(target); | |
| } | |
| static inline unsigned int rule_hash(const struct rule *rule) | |
| { | |
| return rule_values_hash(rule->type, rule->target); | |
| } | |
| static inline const char *get_rule_field(const char **pos, ssize_t *left) | |
| { | |
| const char *start = *pos; | |
| size_t len; | |
| if (*left <= 0) | |
| error("unexpected end of kABI rules"); | |
| len = strnlen(start, *left) + 1; | |
| *pos += len; | |
| *left -= len; | |
| return start; | |
| } | |
| void kabi_read_rules(int fd) | |
| { | |
| GElf_Shdr shdr_mem; | |
| GElf_Shdr *shdr; | |
| Elf_Data *rule_data = NULL; | |
| Elf_Scn *scn; | |
| Elf *elf; | |
| size_t shstrndx; | |
| const char *rule_str; | |
| ssize_t left; | |
| int i; | |
| const struct { | |
| enum kabi_rule_type type; | |
| const char *tag; | |
| } rule_types[] = { | |
| { | |
| .type = KABI_RULE_TYPE_DECLONLY, | |
| .tag = KABI_RULE_TAG_DECLONLY, | |
| }, | |
| { | |
| .type = KABI_RULE_TYPE_ENUMERATOR_IGNORE, | |
| .tag = KABI_RULE_TAG_ENUMERATOR_IGNORE, | |
| }, | |
| { | |
| .type = KABI_RULE_TYPE_ENUMERATOR_VALUE, | |
| .tag = KABI_RULE_TAG_ENUMERATOR_VALUE, | |
| }, | |
| { | |
| .type = KABI_RULE_TYPE_BYTE_SIZE, | |
| .tag = KABI_RULE_TAG_BYTE_SIZE, | |
| }, | |
| { | |
| .type = KABI_RULE_TYPE_TYPE_STRING, | |
| .tag = KABI_RULE_TAG_TYPE_STRING, | |
| }, | |
| }; | |
| if (!stable) | |
| return; | |
| if (elf_version(EV_CURRENT) != EV_CURRENT) | |
| error("elf_version failed: %s", elf_errmsg(-1)); | |
| elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); | |
| if (!elf) | |
| error("elf_begin failed: %s", elf_errmsg(-1)); | |
| if (elf_getshdrstrndx(elf, &shstrndx) < 0) | |
| error("elf_getshdrstrndx failed: %s", elf_errmsg(-1)); | |
| scn = elf_nextscn(elf, NULL); | |
| while (scn) { | |
| const char *sname; | |
| shdr = gelf_getshdr(scn, &shdr_mem); | |
| if (!shdr) | |
| error("gelf_getshdr failed: %s", elf_errmsg(-1)); | |
| sname = elf_strptr(elf, shstrndx, shdr->sh_name); | |
| if (!sname) | |
| error("elf_strptr failed: %s", elf_errmsg(-1)); | |
| if (!strcmp(sname, KABI_RULE_SECTION)) { | |
| rule_data = elf_getdata(scn, NULL); | |
| if (!rule_data) | |
| error("elf_getdata failed: %s", elf_errmsg(-1)); | |
| break; | |
| } | |
| scn = elf_nextscn(elf, scn); | |
| } | |
| if (!rule_data) { | |
| debug("kABI rules not found"); | |
| check(elf_end(elf)); | |
| return; | |
| } | |
| rule_str = rule_data->d_buf; | |
| left = shdr->sh_size; | |
| if (left < KABI_RULE_MIN_ENTRY_SIZE) | |
| error("kABI rule section too small: %zd bytes", left); | |
| if (rule_str[left - 1] != '\0') | |
| error("kABI rules are not null-terminated"); | |
| while (left > KABI_RULE_MIN_ENTRY_SIZE) { | |
| enum kabi_rule_type type = KABI_RULE_TYPE_UNKNOWN; | |
| const char *field; | |
| struct rule *rule; | |
| /* version */ | |
| field = get_rule_field(&rule_str, &left); | |
| if (strcmp(field, KABI_RULE_VERSION)) | |
| error("unsupported kABI rule version: '%s'", field); | |
| /* type */ | |
| field = get_rule_field(&rule_str, &left); | |
| for (i = 0; i < ARRAY_SIZE(rule_types); i++) { | |
| if (!strcmp(field, rule_types[i].tag)) { | |
| type = rule_types[i].type; | |
| break; | |
| } | |
| } | |
| if (type == KABI_RULE_TYPE_UNKNOWN) | |
| error("unsupported kABI rule type: '%s'", field); | |
| rule = xmalloc(sizeof(*rule)); | |
| rule->type = type; | |
| rule->target = xstrdup(get_rule_field(&rule_str, &left)); | |
| rule->value = xstrdup(get_rule_field(&rule_str, &left)); | |
| hash_add(rules, &rule->hash, rule_hash(rule)); | |
| debug("kABI rule: type: '%s', target: '%s', value: '%s'", field, | |
| rule->target, rule->value); | |
| } | |
| if (left > 0) | |
| warn("unexpected data at the end of the kABI rules section"); | |
| check(elf_end(elf)); | |
| } | |
| static char *get_enumerator_target(const char *fqn, const char *field) | |
| { | |
| char *target = NULL; | |
| if (asprintf(&target, "%s %s", fqn, field) < 0) | |
| error("asprintf failed for '%s %s'", fqn, field); | |
| return target; | |
| } | |
| static struct rule *find_rule(enum kabi_rule_type type, const char *target) | |
| { | |
| struct rule *rule; | |
| if (!stable) | |
| return NULL; | |
| if (!target || !*target) | |
| return NULL; | |
| hash_for_each_possible(rules, rule, hash, | |
| rule_values_hash(type, target)) { | |
| if (rule->type == type && !strcmp(target, rule->target)) | |
| return rule; | |
| } | |
| return NULL; | |
| } | |
| static struct rule *find_enumerator_rule(enum kabi_rule_type type, | |
| const char *fqn, const char *field) | |
| { | |
| struct rule *rule; | |
| char *target; | |
| if (!stable) | |
| return NULL; | |
| if (!fqn || !*fqn || !field || !*field) | |
| return NULL; | |
| target = get_enumerator_target(fqn, field); | |
| rule = find_rule(type, target); | |
| free(target); | |
| return rule; | |
| } | |
| bool kabi_is_declonly(const char *fqn) | |
| { | |
| return !!find_rule(KABI_RULE_TYPE_DECLONLY, fqn); | |
| } | |
| static unsigned long get_ulong_value(const char *value) | |
| { | |
| unsigned long result = 0; | |
| char *endptr = NULL; | |
| errno = 0; | |
| result = strtoul(value, &endptr, 10); | |
| if (errno || *endptr) | |
| error("invalid unsigned value '%s'", value); | |
| return result; | |
| } | |
| bool kabi_is_enumerator_ignored(const char *fqn, const char *field) | |
| { | |
| return !!find_enumerator_rule(KABI_RULE_TYPE_ENUMERATOR_IGNORE, fqn, | |
| field); | |
| } | |
| bool kabi_get_enumerator_value(const char *fqn, const char *field, | |
| unsigned long *value) | |
| { | |
| struct rule *rule; | |
| rule = find_enumerator_rule(KABI_RULE_TYPE_ENUMERATOR_VALUE, fqn, | |
| field); | |
| if (rule) { | |
| *value = get_ulong_value(rule->value); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool kabi_get_byte_size(const char *fqn, unsigned long *value) | |
| { | |
| struct rule *rule; | |
| rule = find_rule(KABI_RULE_TYPE_BYTE_SIZE, fqn); | |
| if (rule) { | |
| *value = get_ulong_value(rule->value); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool kabi_get_type_string(const char *type, const char **str) | |
| { | |
| struct rule *rule; | |
| rule = find_rule(KABI_RULE_TYPE_TYPE_STRING, type); | |
| if (rule) { | |
| *str = rule->value; | |
| return true; | |
| } | |
| return false; | |
| } | |
| void kabi_free(void) | |
| { | |
| struct hlist_node *tmp; | |
| struct rule *rule; | |
| hash_for_each_safe(rules, rule, tmp, hash) { | |
| free((void *)rule->target); | |
| free((void *)rule->value); | |
| free(rule); | |
| } | |
| hash_init(rules); | |
| } |