mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

In rare situations where distributions must make significant changes to otherwise opaque data structures that have inadvertently been included in the published ABI, keeping symbol versions stable using the existing kABI macros can become tedious. For example, Android decided to switch to a newer io_uring implementation in the 5.10 GKI kernel "to resolve a huge number of potential, and known, problems with the codebase," requiring "horrible hacks" with genksyms: "A number of the io_uring structures get used in other core kernel structures, only as "opaque" pointers, so there is not any real ABI breakage. But, due to the visibility of the structures going away, the CRC values of many scheduler variables and functions were changed." -- https://r.android.com/2425293 While these specific changes probably could have been hidden from gendwarfksyms using the existing kABI macros, this may not always be the case. Add a last resort kABI rule that allows distribution maintainers to fully override a type string for a symbol or a type. Also add a more informative error message in case we find a non-existent type references when calculating versions. Suggested-by: Giuliano Procida <gprocida@google.com> Signed-off-by: Sami Tolvanen <samitolvanen@google.com> Reviewed-by: Petr Pavlu <petr.pavlu@suse.com> Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
374 lines
7.8 KiB
C
374 lines
7.8 KiB
C
// 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(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));
|
|
}
|
|
|
|
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);
|
|
}
|