summaryrefslogtreecommitdiff
path: root/scripts/gendwarfksyms/kabi.c
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/gendwarfksyms/kabi.c')
-rw-r--r--scripts/gendwarfksyms/kabi.c336
1 files changed, 336 insertions, 0 deletions
diff --git a/scripts/gendwarfksyms/kabi.c b/scripts/gendwarfksyms/kabi.c
new file mode 100644
index 000000000000..66f01fcd1607
--- /dev/null
+++ b/scripts/gendwarfksyms/kabi.c
@@ -0,0 +1,336 @@
+// 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);
+}