| // 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" |
| |
| enum kabi_rule_type { |
| KABI_RULE_TYPE_UNKNOWN, |
| KABI_RULE_TYPE_DECLONLY, |
| KABI_RULE_TYPE_ENUMERATOR_IGNORE, |
| KABI_RULE_TYPE_ENUMERATOR_VALUE, |
| }; |
| |
| #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, |
| }, |
| }; |
| |
| 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(struct 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)); |
| } |
| |
| bool kabi_is_declonly(const char *fqn) |
| { |
| struct rule *rule; |
| |
| if (!stable) |
| return false; |
| if (!fqn || !*fqn) |
| return false; |
| |
| hash_for_each_possible(rules, rule, hash, |
| rule_values_hash(KABI_RULE_TYPE_DECLONLY, fqn)) { |
| if (rule->type == KABI_RULE_TYPE_DECLONLY && |
| !strcmp(fqn, rule->target)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| 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 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) |
| { |
| bool match = false; |
| struct rule *rule; |
| char *target; |
| |
| if (!stable) |
| return false; |
| if (!fqn || !*fqn || !field || !*field) |
| return false; |
| |
| target = get_enumerator_target(fqn, field); |
| |
| hash_for_each_possible( |
| rules, rule, hash, |
| rule_values_hash(KABI_RULE_TYPE_ENUMERATOR_IGNORE, target)) { |
| if (rule->type == KABI_RULE_TYPE_ENUMERATOR_IGNORE && |
| !strcmp(target, rule->target)) { |
| match = true; |
| break; |
| } |
| } |
| |
| free(target); |
| return match; |
| } |
| |
| bool kabi_get_enumerator_value(const char *fqn, const char *field, |
| unsigned long *value) |
| { |
| bool match = false; |
| struct rule *rule; |
| char *target; |
| |
| if (!stable) |
| return false; |
| if (!fqn || !*fqn || !field || !*field) |
| return false; |
| |
| target = get_enumerator_target(fqn, field); |
| |
| hash_for_each_possible(rules, rule, hash, |
| rule_values_hash(KABI_RULE_TYPE_ENUMERATOR_VALUE, |
| target)) { |
| if (rule->type == KABI_RULE_TYPE_ENUMERATOR_VALUE && |
| !strcmp(target, rule->target)) { |
| *value = get_ulong_value(rule->value); |
| match = true; |
| break; |
| } |
| } |
| |
| free(target); |
| return match; |
| } |
| |
| 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); |
| } |