gendwarfksyms-objs += cache.o
 gendwarfksyms-objs += die.o
 gendwarfksyms-objs += dwarf.o
+gendwarfksyms-objs += kabi.o
 gendwarfksyms-objs += symbols.o
 gendwarfksyms-objs += types.o
 
 
        return !!res;
 }
 
-static bool is_kabi_definition(Dwarf_Die *die)
+static bool is_kabi_definition(struct die *cache, Dwarf_Die *die)
 {
        bool value;
 
        if (get_flag_attr(die, DW_AT_declaration, &value) && value)
                return false;
 
+       if (kabi_is_declonly(cache->fqn))
+               return false;
+
        return !is_definition_private(die);
 }
 
        process(cache, " {");
        process_linebreak(cache, 1);
 
-       expand = state->expand.expand && is_kabi_definition(die);
+       expand = state->expand.expand && is_kabi_definition(cache, die);
 
        if (expand) {
+               state->expand.current_fqn = cache->fqn;
                check(process_die_container(state, cache, die, process_func,
                                            match_func));
        }
 static void process_enumerator_type(struct state *state, struct die *cache,
                                    Dwarf_Die *die)
 {
+       bool overridden = false;
        Dwarf_Word value;
 
+       if (stable) {
+               /* Get the fqn before we process anything */
+               update_fqn(cache, die);
+
+               if (kabi_is_enumerator_ignored(state->expand.current_fqn,
+                                              cache->fqn))
+                       return;
+
+               overridden = kabi_get_enumerator_value(
+                       state->expand.current_fqn, cache->fqn, &value);
+       }
+
        process_list_comma(state, cache);
        process(cache, "enumerator");
        process_fqn(cache, die);
 
-       if (get_udata_attr(die, DW_AT_const_value, &value)) {
+       if (overridden || get_udata_attr(die, DW_AT_const_value, &value)) {
                process(cache, " = ");
                process_fmt(cache, "%" PRIu64, value);
        }
 static void state_init(struct state *state)
 {
        state->expand.expand = true;
+       state->expand.current_fqn = NULL;
        cache_init(&state->expansion_cache);
 }
 
                                    struct expansion_state *saved)
 {
        state->expand = saved->expand;
+       state->current_fqn = saved->current_fqn;
 }
 
 static void expansion_state_save(struct expansion_state *state,
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2024 Google LLC
+ *
+ * Example macros for maintaining kABI stability.
+ *
+ * This file is based on android_kabi.h, which has the following notice:
+ *
+ * Heavily influenced by rh_kabi.h which came from the RHEL/CENTOS kernel
+ * and was:
+ *     Copyright (c) 2014 Don Zickus
+ *     Copyright (c) 2015-2018 Jiri Benc
+ *     Copyright (c) 2015 Sabrina Dubroca, Hannes Frederic Sowa
+ *     Copyright (c) 2016-2018 Prarit Bhargava
+ *     Copyright (c) 2017 Paolo Abeni, Larry Woodman
+ */
+
+#ifndef __KABI_H__
+#define __KABI_H__
+
+/* Kernel macros for userspace testing. */
+#ifndef __aligned
+#define __aligned(x) __attribute__((__aligned__(x)))
+#endif
+#ifndef __used
+#define __used __attribute__((__used__))
+#endif
+#ifndef __section
+#define __section(section) __attribute__((__section__(section)))
+#endif
+#ifndef __PASTE
+#define ___PASTE(a, b) a##b
+#define __PASTE(a, b) ___PASTE(a, b)
+#endif
+#ifndef __stringify
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+#endif
+
+#define __KABI_RULE(hint, target, value)                             \
+       static const char __PASTE(__gendwarfksyms_rule_,             \
+                                 __COUNTER__)[] __used __aligned(1) \
+               __section(".discard.gendwarfksyms.kabi_rules") =     \
+                       "1\0" #hint "\0" #target "\0" #value
+
+/*
+ * KABI_DECLONLY(fqn)
+ *   Treat the struct/union/enum fqn as a declaration, i.e. even if
+ *   a definition is available, don't expand the contents.
+ */
+#define KABI_DECLONLY(fqn) __KABI_RULE(declonly, fqn, )
+
+/*
+ * KABI_ENUMERATOR_IGNORE(fqn, field)
+ *   When expanding enum fqn, skip the provided field. This makes it
+ *   possible to hide added enum fields from versioning.
+ */
+#define KABI_ENUMERATOR_IGNORE(fqn, field) \
+       __KABI_RULE(enumerator_ignore, fqn field, )
+
+/*
+ * KABI_ENUMERATOR_VALUE(fqn, field, value)
+ *   When expanding enum fqn, use the provided value for the
+ *   specified field. This makes it possible to override enumerator
+ *   values when calculating versions.
+ */
+#define KABI_ENUMERATOR_VALUE(fqn, field, value) \
+       __KABI_RULE(enumerator_value, fqn field, value)
+
+#endif /* __KABI_H__ */
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kabi_ex.c
+ *
+ * Copyright (C) 2024 Google LLC
+ *
+ * Examples for kABI stability features with --stable. See kabi_ex.h
+ * for details.
+ */
+
+#include "kabi_ex.h"
+
+struct s e0;
+enum e e1;
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * kabi_ex.h
+ *
+ * Copyright (C) 2024 Google LLC
+ *
+ * Examples for kABI stability features with --stable.
+ */
+
+/*
+ * The comments below each example contain the expected gendwarfksyms
+ * output, which can be verified using LLVM's FileCheck tool:
+ *
+ * https://llvm.org/docs/CommandGuide/FileCheck.html
+ *
+ * Usage:
+ *
+ * $ gcc -g -c examples/kabi_ex.c -o examples/kabi_ex.o
+ *
+ * $ nm examples/kabi_ex.o | awk '{ print $NF }' | \
+ *     ./gendwarfksyms --stable --dump-dies \
+ *             examples/kabi_ex.o 2>&1 >/dev/null | \
+ *     FileCheck examples/kabi_ex.h --check-prefix=STABLE
+ */
+
+#ifndef __KABI_EX_H__
+#define __KABI_EX_H__
+
+#include "kabi.h"
+
+/*
+ * Example: kABI rules
+ */
+
+struct s {
+       int a;
+};
+
+KABI_DECLONLY(s);
+
+/*
+ * STABLE:      variable structure_type s {
+ * STABLE-NEXT: }
+ */
+
+enum e {
+       A,
+       B,
+       C,
+       D,
+};
+
+KABI_ENUMERATOR_IGNORE(e, B);
+KABI_ENUMERATOR_IGNORE(e, C);
+KABI_ENUMERATOR_VALUE(e, D, 123456789);
+
+/*
+ * STABLE:      variable enumeration_type e {
+ * STABLE-NEXT:   enumerator A = 0 ,
+ * STABLE-NEXT:   enumerator D = 123456789
+ * STABLE-NEXT: } byte_size(4)
+ */
+
+#endif /* __KABI_EX_H__ */
 
 int dump_types;
 /* Print out expanded type strings used for symbol versions */
 int dump_versions;
+/* Support kABI stability features */
+int stable;
 /* Write a symtypes file */
 int symtypes;
 static const char *symtypes_file;
              "      --dump-die-map   Print debugging information about die_map changes\n"
              "      --dump-types     Dump type strings\n"
              "      --dump-versions  Dump expanded type strings used for symbol versions\n"
+             "  -s, --stable         Support kABI stability features\n"
              "  -T, --symtypes file  Write a symtypes file\n"
              "  -h, --help           Print this message\n"
              "\n",
                { "dump-die-map", 0, &dump_die_map, 1 },
                { "dump-types", 0, &dump_types, 1 },
                { "dump-versions", 0, &dump_versions, 1 },
+               { "stable", 0, NULL, 's' },
                { "symtypes", 1, NULL, 'T' },
                { "help", 0, NULL, 'h' },
                { 0, 0, NULL, 0 }
        };
 
-       while ((opt = getopt_long(argc, argv, "dT:h", opts, NULL)) != EOF) {
+       while ((opt = getopt_long(argc, argv, "dsT:h", opts, NULL)) != EOF) {
                switch (opt) {
                case 0:
                        break;
                case 'd':
                        debug = 1;
                        break;
+               case 's':
+                       stable = 1;
+                       break;
                case 'T':
                        symtypes = 1;
                        symtypes_file = optarg;
                              strerror(errno));
 
                symbol_read_symtab(fd);
+               kabi_read_rules(fd);
 
                dwfl = dwfl_begin(&callbacks);
                if (!dwfl)
                        error("dwfl_getmodules failed for '%s'", argv[n]);
 
                dwfl_end(dwfl);
+               kabi_free();
        }
 
        if (symfile)
 
 extern int dump_die_map;
 extern int dump_types;
 extern int dump_versions;
+extern int stable;
 extern int symtypes;
 
 /*
 
 struct expansion_state {
        bool expand;
+       const char *current_fqn;
 };
 
 struct state {
 
 void generate_symtypes_and_versions(FILE *file);
 
+/*
+ * kabi.c
+ */
+
+bool kabi_is_enumerator_ignored(const char *fqn, const char *field);
+bool kabi_get_enumerator_value(const char *fqn, const char *field,
+                              unsigned long *value);
+bool kabi_is_declonly(const char *fqn);
+
+void kabi_read_rules(int fd);
+void kabi_free(void);
+
 #endif /* __GENDWARFKSYMS_H */
 
--- /dev/null
+// 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);
+}