Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
/gendwarfksyms
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
hostprogs-always-y += gendwarfksyms
gendwarfksyms-objs += gendwarfksyms.o
gendwarfksyms-objs += cache.o
gendwarfksyms-objs += die.o
gendwarfksyms-objs += dwarf.o
gendwarfksyms-objs += kabi.o
gendwarfksyms-objs += symbols.o
gendwarfksyms-objs += types.o
HOSTLDLIBS_gendwarfksyms := -ldw -lelf -lz
+51
View File
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#include "gendwarfksyms.h"
struct cache_item {
unsigned long key;
int value;
struct hlist_node hash;
};
void cache_set(struct cache *cache, unsigned long key, int value)
{
struct cache_item *ci;
ci = xmalloc(sizeof(*ci));
ci->key = key;
ci->value = value;
hash_add(cache->cache, &ci->hash, hash_32(key));
}
int cache_get(struct cache *cache, unsigned long key)
{
struct cache_item *ci;
hash_for_each_possible(cache->cache, ci, hash, hash_32(key)) {
if (ci->key == key)
return ci->value;
}
return -1;
}
void cache_init(struct cache *cache)
{
hash_init(cache->cache);
}
void cache_free(struct cache *cache)
{
struct hlist_node *tmp;
struct cache_item *ci;
hash_for_each_safe(cache->cache, ci, tmp, hash) {
free(ci);
}
hash_init(cache->cache);
}
+166
View File
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#include <string.h>
#include "gendwarfksyms.h"
#define DIE_HASH_BITS 16
/* {die->addr, state} -> struct die * */
static HASHTABLE_DEFINE(die_map, 1 << DIE_HASH_BITS);
static unsigned int map_hits;
static unsigned int map_misses;
static inline unsigned int die_hash(uintptr_t addr, enum die_state state)
{
return hash_32(addr_hash(addr) ^ (unsigned int)state);
}
static void init_die(struct die *cd)
{
cd->state = DIE_INCOMPLETE;
cd->mapped = false;
cd->fqn = NULL;
cd->tag = -1;
cd->addr = 0;
INIT_LIST_HEAD(&cd->fragments);
}
static struct die *create_die(Dwarf_Die *die, enum die_state state)
{
struct die *cd;
cd = xmalloc(sizeof(*cd));
init_die(cd);
cd->addr = (uintptr_t)die->addr;
hash_add(die_map, &cd->hash, die_hash(cd->addr, state));
return cd;
}
int __die_map_get(uintptr_t addr, enum die_state state, struct die **res)
{
struct die *cd;
hash_for_each_possible(die_map, cd, hash, die_hash(addr, state)) {
if (cd->addr == addr && cd->state == state) {
*res = cd;
return 0;
}
}
return -1;
}
struct die *die_map_get(Dwarf_Die *die, enum die_state state)
{
struct die *cd;
if (__die_map_get((uintptr_t)die->addr, state, &cd) == 0) {
map_hits++;
return cd;
}
map_misses++;
return create_die(die, state);
}
static void reset_die(struct die *cd)
{
struct die_fragment *tmp;
struct die_fragment *df;
list_for_each_entry_safe(df, tmp, &cd->fragments, list) {
if (df->type == FRAGMENT_STRING)
free(df->data.str);
free(df);
}
if (cd->fqn && *cd->fqn)
free(cd->fqn);
init_die(cd);
}
void die_map_for_each(die_map_callback_t func, void *arg)
{
struct hlist_node *tmp;
struct die *cd;
hash_for_each_safe(die_map, cd, tmp, hash) {
func(cd, arg);
}
}
void die_map_free(void)
{
struct hlist_node *tmp;
unsigned int stats[DIE_LAST + 1];
struct die *cd;
int i;
memset(stats, 0, sizeof(stats));
hash_for_each_safe(die_map, cd, tmp, hash) {
stats[cd->state]++;
reset_die(cd);
free(cd);
}
hash_init(die_map);
if (map_hits + map_misses > 0)
debug("hits %u, misses %u (hit rate %.02f%%)", map_hits,
map_misses,
(100.0f * map_hits) / (map_hits + map_misses));
for (i = 0; i <= DIE_LAST; i++)
debug("%s: %u entries", die_state_name(i), stats[i]);
}
static struct die_fragment *append_item(struct die *cd)
{
struct die_fragment *df;
df = xmalloc(sizeof(*df));
df->type = FRAGMENT_EMPTY;
list_add_tail(&df->list, &cd->fragments);
return df;
}
void die_map_add_string(struct die *cd, const char *str)
{
struct die_fragment *df;
if (!cd)
return;
df = append_item(cd);
df->data.str = xstrdup(str);
df->type = FRAGMENT_STRING;
}
void die_map_add_linebreak(struct die *cd, int linebreak)
{
struct die_fragment *df;
if (!cd)
return;
df = append_item(cd);
df->data.linebreak = linebreak;
df->type = FRAGMENT_LINEBREAK;
}
void die_map_add_die(struct die *cd, struct die *child)
{
struct die_fragment *df;
if (!cd)
return;
df = append_item(cd);
df->data.addr = child->addr;
df->type = FRAGMENT_DIE;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,174 @@
/* 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
#define __KABI_RULE(hint, target, value) \
___KABI_RULE(hint, #target, #value)
#define __KABI_NORMAL_SIZE_ALIGN(_orig, _new) \
union { \
_Static_assert( \
sizeof(struct { _new; }) <= sizeof(struct { _orig; }), \
__FILE__ ":" __stringify(__LINE__) ": " __stringify( \
_new) " is larger than " __stringify(_orig)); \
_Static_assert( \
__alignof__(struct { _new; }) <= \
__alignof__(struct { _orig; }), \
__FILE__ ":" __stringify(__LINE__) ": " __stringify( \
_orig) " is not aligned the same as " __stringify(_new)); \
}
#define __KABI_REPLACE(_orig, _new) \
union { \
_new; \
struct { \
_orig; \
}; \
__KABI_NORMAL_SIZE_ALIGN(_orig, _new); \
}
/*
* 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)
/*
* KABI_BYTE_SIZE(fqn, value)
* Set the byte_size attribute for the struct/union/enum fqn to
* value bytes.
*/
#define KABI_BYTE_SIZE(fqn, value) __KABI_RULE(byte_size, fqn, value)
/*
* KABI_TYPE_STRING(type, str)
* For the given type, override the type string used in symtypes
* output and version calculation with str.
*/
#define KABI_TYPE_STRING(type, str) ___KABI_RULE(type_string, type, str)
/*
* KABI_RESERVE
* Reserve some "padding" in a structure for use by LTS backports.
* This is normally placed at the end of a structure.
* number: the "number" of the padding variable in the structure. Start with
* 1 and go up.
*/
#define KABI_RESERVE(n) unsigned long __kabi_reserved##n
/*
* KABI_RESERVE_ARRAY
* Same as _BACKPORT_RESERVE but allocates an array with the specified
* size in bytes.
*/
#define KABI_RESERVE_ARRAY(n, s) \
unsigned char __aligned(8) __kabi_reserved##n[s]
/*
* KABI_IGNORE
* Add a new field that's ignored in versioning.
*/
#define KABI_IGNORE(n, _new) \
union { \
_new; \
unsigned char __kabi_ignored##n; \
}
/*
* KABI_REPLACE
* Replace a field with a compatible new field.
*/
#define KABI_REPLACE(_oldtype, _oldname, _new) \
__KABI_REPLACE(_oldtype __kabi_renamed##_oldname, struct { _new; })
/*
* KABI_USE(number, _new)
* Use a previous padding entry that was defined with KABI_RESERVE
* number: the previous "number" of the padding variable
* _new: the variable to use now instead of the padding variable
*/
#define KABI_USE(number, _new) __KABI_REPLACE(KABI_RESERVE(number), _new)
/*
* KABI_USE2(number, _new1, _new2)
* Use a previous padding entry that was defined with KABI_RESERVE for
* two new variables that fit into 64 bits. This is good for when you do not
* want to "burn" a 64bit padding variable for a smaller variable size if not
* needed.
*/
#define KABI_USE2(number, _new1, _new2) \
__KABI_REPLACE( \
KABI_RESERVE(number), struct { \
_new1; \
_new2; \
})
/*
* KABI_USE_ARRAY(number, bytes, _new)
* Use a previous padding entry that was defined with KABI_RESERVE_ARRAY
* number: the previous "number" of the padding variable
* bytes: the size in bytes reserved for the array
* _new: the variable to use now instead of the padding variable
*/
#define KABI_USE_ARRAY(number, bytes, _new) \
__KABI_REPLACE(KABI_RESERVE_ARRAY(number, bytes), _new)
#endif /* __KABI_H__ */
@@ -0,0 +1,37 @@
// 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;
struct ex0a ex0a;
struct ex0b ex0b;
struct ex0c ex0c;
struct ex1a ex1a;
struct ex1b ex1b;
struct ex1c ex1c;
struct ex2a ex2a;
struct ex2b ex2b;
struct ex2c ex2c;
struct ex3a ex3a;
struct ex3b ex3b;
struct ex3c ex3c;
struct ex4a ex4a;
struct ex5a ex5a;
struct ex5b ex5b;
int ex6a;
@@ -0,0 +1,360 @@
/* 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
* $ nm examples/kabi_ex.o | awk '{ print $NF }' | \
* ./gendwarfksyms --stable --dump-versions \
* examples/kabi_ex.o 2>&1 >/dev/null | \
* sort | \
* FileCheck examples/kabi_ex.h --check-prefix=VERSIONS
*/
#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)
*/
/*
* Example: Reserved fields
*/
struct ex0a {
int a;
KABI_RESERVE(0);
KABI_RESERVE(1);
};
/*
* STABLE: variable structure_type ex0a {
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
* STABLE-NEXT: member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) data_member_location(8) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16)
* STABLE-NEXT: } byte_size(24)
*/
struct ex0b {
int a;
KABI_RESERVE(0);
KABI_USE2(1, int b, int c);
};
/*
* STABLE: variable structure_type ex0b {
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(8) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16)
* STABLE-NEXT: } byte_size(24)
*/
struct ex0c {
int a;
KABI_USE(0, void *p);
KABI_USE2(1, int b, int c);
};
/*
* STABLE: variable structure_type ex0c {
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(8) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16)
* STABLE-NEXT: } byte_size(24)
*/
/*
* Example: A reserved array
*/
struct ex1a {
unsigned int a;
KABI_RESERVE_ARRAY(0, 64);
};
/*
* STABLE: variable structure_type ex1a {
* STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) ,
* STABLE-NEXT: member array_type[64] {
* STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8)
* STABLE-NEXT: } data_member_location(8)
* STABLE-NEXT: } byte_size(72)
*/
struct ex1b {
unsigned int a;
KABI_USE_ARRAY(
0, 64, struct {
void *p;
KABI_RESERVE_ARRAY(1, 56);
});
};
/*
* STABLE: variable structure_type ex1b {
* STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) ,
* STABLE-NEXT: member array_type[64] {
* STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8)
* STABLE-NEXT: } data_member_location(8)
* STABLE-NEXT: } byte_size(72)
*/
struct ex1c {
unsigned int a;
KABI_USE_ARRAY(0, 64, void *p[8]);
};
/*
* STABLE: variable structure_type ex1c {
* STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) ,
* STABLE-NEXT: member array_type[64] {
* STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8)
* STABLE-NEXT: } data_member_location(8)
* STABLE-NEXT: } byte_size(72)
*/
/*
* Example: An ignored field added to an alignment hole
*/
struct ex2a {
int a;
unsigned long b;
int c;
unsigned long d;
};
/*
* STABLE: variable structure_type ex2a {
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8)
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24)
* STABLE-NEXT: } byte_size(32)
*/
struct ex2b {
int a;
KABI_IGNORE(0, unsigned int n);
unsigned long b;
int c;
unsigned long d;
};
_Static_assert(sizeof(struct ex2a) == sizeof(struct ex2b), "ex2a size doesn't match ex2b");
/*
* STABLE: variable structure_type ex2b {
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8)
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24)
* STABLE-NEXT: } byte_size(32)
*/
struct ex2c {
int a;
KABI_IGNORE(0, unsigned int n);
unsigned long b;
int c;
KABI_IGNORE(1, unsigned int m);
unsigned long d;
};
_Static_assert(sizeof(struct ex2a) == sizeof(struct ex2c), "ex2a size doesn't match ex2c");
/*
* STABLE: variable structure_type ex2c {
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8)
* STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) ,
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24)
* STABLE-NEXT: } byte_size(32)
*/
/*
* Example: A replaced field
*/
struct ex3a {
unsigned long a;
unsigned long unused;
};
/*
* STABLE: variable structure_type ex3a {
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0)
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8)
* STABLE-NEXT: } byte_size(16)
*/
struct ex3b {
unsigned long a;
KABI_REPLACE(unsigned long, unused, unsigned long renamed);
};
_Static_assert(sizeof(struct ex3a) == sizeof(struct ex3b), "ex3a size doesn't match ex3b");
/*
* STABLE: variable structure_type ex3b {
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0)
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8)
* STABLE-NEXT: } byte_size(16)
*/
struct ex3c {
unsigned long a;
KABI_REPLACE(unsigned long, unused, long replaced);
};
_Static_assert(sizeof(struct ex3a) == sizeof(struct ex3c), "ex3a size doesn't match ex3c");
/*
* STABLE: variable structure_type ex3c {
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0)
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8)
* STABLE-NEXT: } byte_size(16)
*/
/*
* Example: An ignored field added to an end of a partially opaque struct,
* while keeping the byte_size attribute unchanged.
*/
struct ex4a {
unsigned long a;
KABI_IGNORE(0, unsigned long b);
};
/*
* This may be safe if the structure allocation is managed by the core kernel
* and the layout remains unchanged except for appended new members.
*/
KABI_BYTE_SIZE(ex4a, 8);
/*
* STABLE: variable structure_type ex4a {
* STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0)
* STABLE-NEXT: } byte_size(8)
*/
/*
* Example: A type string override.
*/
struct ex5a {
unsigned long a;
};
/*
* This may be safe if the structure is fully opaque to modules, even though
* its definition has inadvertently become part of the ABI.
*/
KABI_TYPE_STRING(
"s#ex5a",
"structure_type ex5a { member pointer_type { s#ex4a } byte_size(8) p data_member_location(0) } byte_size(8)");
/*
* Make sure the fully expanded type string includes ex4a.
*
* VERSIONS: ex5a variable structure_type ex5a {
* VERSIONS-SAME: member pointer_type {
* VERSIONS-SAME: structure_type ex4a {
* VERSIONS-SAME: member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) a data_member_location(0)
* VERSIONS-SAME: } byte_size(8)
* VERSIONS-SAME: } byte_size(8) p data_member_location(0)
* VERSIONS-SAME: } byte_size(8)
*/
/*
* Example: A type string definition for a non-existent type.
*/
struct ex5b {
unsigned long a;
};
/* Replace the type string for struct ex5b */
KABI_TYPE_STRING(
"s#ex5b",
"structure_type ex5b { member pointer_type { s#ex5c } byte_size(8) p data_member_location(0) } byte_size(8)");
/* Define a type string for a non-existent struct ex5c */
KABI_TYPE_STRING(
"s#ex5c",
"structure_type ex5c { member base_type int byte_size(4) encoding(5) n data_member_location(0) } byte_size(8)");
/*
* Make sure the fully expanded type string includes the definition for ex5c.
*
* VERSIONS: ex5b variable structure_type ex5b {
* VERSIONS-SAME: member pointer_type {
* VERSIONS-SAME: structure_type ex5c {
* VERSIONS-SAME: member base_type int byte_size(4) encoding(5) n data_member_location(0)
* VERSIONS-SAME: } byte_size(8)
* VERSIONS-SAME: } byte_size(8) p data_member_location(0)
* VERSIONS-SAME: } byte_size(8)
*/
/*
* Example: A type string override for a symbol.
*/
KABI_TYPE_STRING("ex6a", "variable s#ex5c");
/*
* VERSIONS: ex6a variable structure_type ex5c {
* VERSIONS-SAME: member base_type int byte_size(4) encoding(5) n data_member_location(0)
* VERSIONS-SAME: } byte_size(8)
*/
#endif /* __KABI_EX_H__ */
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*
* Example for symbol pointers. When compiled with Clang, gendwarfkyms
* uses a symbol pointer for `f`.
*
* $ clang -g -c examples/symbolptr.c -o examples/symbolptr.o
* $ echo -e "f\ng\np" | ./gendwarfksyms -d examples/symbolptr.o
*/
/* Kernel macros for userspace testing. */
#ifndef __used
#define __used __attribute__((__used__))
#endif
#ifndef __section
#define __section(section) __attribute__((__section__(section)))
#endif
#define __GENDWARFKSYMS_EXPORT(sym) \
static typeof(sym) *__gendwarfksyms_ptr_##sym __used \
__section(".discard.gendwarfksyms") = &sym;
extern void f(unsigned int arg);
void g(int *arg);
void g(int *arg) {}
struct s;
extern struct s *p;
__GENDWARFKSYMS_EXPORT(f);
__GENDWARFKSYMS_EXPORT(g);
__GENDWARFKSYMS_EXPORT(p);
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#include <fcntl.h>
#include <getopt.h>
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include "gendwarfksyms.h"
/*
* Options
*/
/* Print debugging information to stderr */
int debug;
/* Dump DIE contents */
int dump_dies;
/* Print debugging information about die_map changes */
int dump_die_map;
/* Print out type strings (i.e. type_map) */
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;
static void usage(void)
{
fputs("Usage: gendwarfksyms [options] elf-object-file ... < symbol-list\n\n"
"Options:\n"
" -d, --debug Print debugging information\n"
" --dump-dies Dump DWARF DIE contents\n"
" --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",
stderr);
}
static int process_module(Dwfl_Module *mod, void **userdata, const char *name,
Dwarf_Addr base, void *arg)
{
Dwarf_Addr dwbias;
Dwarf_Die cudie;
Dwarf_CU *cu = NULL;
Dwarf *dbg;
FILE *symfile = arg;
int res;
debug("%s", name);
dbg = dwfl_module_getdwarf(mod, &dwbias);
/*
* Look for exported symbols in each CU, follow the DIE tree, and add
* the entries to die_map.
*/
do {
res = dwarf_get_units(dbg, cu, &cu, NULL, NULL, &cudie, NULL);
if (res < 0)
error("dwarf_get_units failed: no debugging information?");
if (res == 1)
break; /* No more units */
process_cu(&cudie);
} while (cu);
/*
* Use die_map to expand type strings, write them to `symfile`, and
* calculate symbol versions.
*/
generate_symtypes_and_versions(symfile);
die_map_free();
return DWARF_CB_OK;
}
static const Dwfl_Callbacks callbacks = {
.section_address = dwfl_offline_section_address,
.find_debuginfo = dwfl_standard_find_debuginfo,
};
int main(int argc, char **argv)
{
FILE *symfile = NULL;
unsigned int n;
int opt;
static const struct option opts[] = {
{ "debug", 0, NULL, 'd' },
{ "dump-dies", 0, &dump_dies, 1 },
{ "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, "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;
break;
case 'h':
usage();
return 0;
default:
usage();
return 1;
}
}
if (dump_die_map)
dump_dies = 1;
if (optind >= argc) {
usage();
error("no input files?");
}
if (!symbol_read_exports(stdin))
return 0;
if (symtypes_file) {
symfile = fopen(symtypes_file, "w");
if (!symfile)
error("fopen failed for '%s': %s", symtypes_file,
strerror(errno));
}
for (n = optind; n < argc; n++) {
Dwfl *dwfl;
int fd;
fd = open(argv[n], O_RDONLY);
if (fd == -1)
error("open failed for '%s': %s", argv[n],
strerror(errno));
symbol_read_symtab(fd);
kabi_read_rules(fd);
dwfl = dwfl_begin(&callbacks);
if (!dwfl)
error("dwfl_begin failed for '%s': %s", argv[n],
dwarf_errmsg(-1));
if (!dwfl_report_offline(dwfl, argv[n], argv[n], fd))
error("dwfl_report_offline failed for '%s': %s",
argv[n], dwarf_errmsg(-1));
dwfl_report_end(dwfl, NULL, NULL);
if (dwfl_getmodules(dwfl, &process_module, symfile, 0))
error("dwfl_getmodules failed for '%s'", argv[n]);
dwfl_end(dwfl);
kabi_free();
}
if (symfile)
check(fclose(symfile));
symbol_print_versions();
symbol_free();
return 0;
}
@@ -0,0 +1,290 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2024 Google LLC
*/
#include <dwarf.h>
#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <stdlib.h>
#include <stdio.h>
#include <hash.h>
#include <hashtable.h>
#include <xalloc.h>
#ifndef __GENDWARFKSYMS_H
#define __GENDWARFKSYMS_H
/*
* Options -- in gendwarfksyms.c
*/
extern int debug;
extern int dump_dies;
extern int dump_die_map;
extern int dump_types;
extern int dump_versions;
extern int stable;
extern int symtypes;
/*
* Output helpers
*/
#define __PREFIX "gendwarfksyms: "
#define __println(prefix, format, ...) \
fprintf(stderr, prefix __PREFIX "%s: " format "\n", __func__, \
##__VA_ARGS__)
#define debug(format, ...) \
do { \
if (debug) \
__println("", format, ##__VA_ARGS__); \
} while (0)
#define warn(format, ...) __println("warning: ", format, ##__VA_ARGS__)
#define error(format, ...) \
do { \
__println("error: ", format, ##__VA_ARGS__); \
exit(1); \
} while (0)
#define __die_debug(color, format, ...) \
do { \
if (dump_dies && dump_die_map) \
fprintf(stderr, \
"\033[" #color "m<" format ">\033[39m", \
__VA_ARGS__); \
} while (0)
#define die_debug_r(format, ...) __die_debug(91, format, __VA_ARGS__)
#define die_debug_g(format, ...) __die_debug(92, format, __VA_ARGS__)
#define die_debug_b(format, ...) __die_debug(94, format, __VA_ARGS__)
/*
* Error handling helpers
*/
#define __check(expr, test) \
({ \
int __res = expr; \
if (test) \
error("`%s` failed: %d", #expr, __res); \
__res; \
})
/* Error == non-zero values */
#define check(expr) __check(expr, __res)
/* Error == negative values */
#define checkp(expr) __check(expr, __res < 0)
/* Consistent aliases (DW_TAG_<type>_type) for DWARF tags */
#define DW_TAG_enumerator_type DW_TAG_enumerator
#define DW_TAG_formal_parameter_type DW_TAG_formal_parameter
#define DW_TAG_member_type DW_TAG_member
#define DW_TAG_template_type_parameter_type DW_TAG_template_type_parameter
#define DW_TAG_typedef_type DW_TAG_typedef
#define DW_TAG_variant_part_type DW_TAG_variant_part
#define DW_TAG_variant_type DW_TAG_variant
/*
* symbols.c
*/
/* See symbols.c:is_symbol_ptr */
#define SYMBOL_PTR_PREFIX "__gendwarfksyms_ptr_"
#define SYMBOL_PTR_PREFIX_LEN (sizeof(SYMBOL_PTR_PREFIX) - 1)
static inline unsigned int addr_hash(uintptr_t addr)
{
return hash_ptr((const void *)addr);
}
enum symbol_state {
SYMBOL_UNPROCESSED,
SYMBOL_MAPPED,
SYMBOL_PROCESSED
};
struct symbol_addr {
uint32_t section;
Elf64_Addr address;
};
struct symbol {
const char *name;
struct symbol_addr addr;
struct hlist_node addr_hash;
struct hlist_node name_hash;
enum symbol_state state;
uintptr_t die_addr;
uintptr_t ptr_die_addr;
unsigned long crc;
};
typedef void (*symbol_callback_t)(struct symbol *, void *arg);
bool is_symbol_ptr(const char *name);
int symbol_read_exports(FILE *file);
void symbol_read_symtab(int fd);
struct symbol *symbol_get(const char *name);
void symbol_set_ptr(struct symbol *sym, Dwarf_Die *ptr);
void symbol_set_die(struct symbol *sym, Dwarf_Die *die);
void symbol_set_crc(struct symbol *sym, unsigned long crc);
void symbol_for_each(symbol_callback_t func, void *arg);
void symbol_print_versions(void);
void symbol_free(void);
/*
* die.c
*/
enum die_state {
DIE_INCOMPLETE,
DIE_FQN,
DIE_UNEXPANDED,
DIE_COMPLETE,
DIE_SYMBOL,
DIE_LAST = DIE_SYMBOL
};
enum die_fragment_type {
FRAGMENT_EMPTY,
FRAGMENT_STRING,
FRAGMENT_LINEBREAK,
FRAGMENT_DIE
};
struct die_fragment {
enum die_fragment_type type;
union {
char *str;
int linebreak;
uintptr_t addr;
} data;
struct list_head list;
};
#define CASE_CONST_TO_STR(name) \
case name: \
return #name;
static inline const char *die_state_name(enum die_state state)
{
switch (state) {
CASE_CONST_TO_STR(DIE_INCOMPLETE)
CASE_CONST_TO_STR(DIE_FQN)
CASE_CONST_TO_STR(DIE_UNEXPANDED)
CASE_CONST_TO_STR(DIE_COMPLETE)
CASE_CONST_TO_STR(DIE_SYMBOL)
}
error("unexpected die_state: %d", state);
}
struct die {
enum die_state state;
bool mapped;
char *fqn;
int tag;
uintptr_t addr;
struct list_head fragments;
struct hlist_node hash;
};
typedef void (*die_map_callback_t)(struct die *, void *arg);
int __die_map_get(uintptr_t addr, enum die_state state, struct die **res);
struct die *die_map_get(Dwarf_Die *die, enum die_state state);
void die_map_add_string(struct die *pd, const char *str);
void die_map_add_linebreak(struct die *pd, int linebreak);
void die_map_for_each(die_map_callback_t func, void *arg);
void die_map_add_die(struct die *pd, struct die *child);
void die_map_free(void);
/*
* cache.c
*/
#define CACHE_HASH_BITS 10
/* A cache for addresses we've already seen. */
struct cache {
HASHTABLE_DECLARE(cache, 1 << CACHE_HASH_BITS);
};
void cache_set(struct cache *cache, unsigned long key, int value);
int cache_get(struct cache *cache, unsigned long key);
void cache_init(struct cache *cache);
void cache_free(struct cache *cache);
static inline void cache_mark_expanded(struct cache *cache, void *addr)
{
cache_set(cache, (unsigned long)addr, 1);
}
static inline bool cache_was_expanded(struct cache *cache, void *addr)
{
return cache_get(cache, (unsigned long)addr) == 1;
}
/*
* dwarf.c
*/
struct expansion_state {
bool expand;
const char *current_fqn;
};
struct kabi_state {
int members;
Dwarf_Die placeholder;
const char *orig_name;
};
struct state {
struct symbol *sym;
Dwarf_Die die;
/* List expansion */
bool first_list_item;
/* Structure expansion */
struct expansion_state expand;
struct cache expansion_cache;
/* Reserved or ignored members */
struct kabi_state kabi;
};
typedef int (*die_callback_t)(struct state *state, struct die *cache,
Dwarf_Die *die);
typedef bool (*die_match_callback_t)(Dwarf_Die *die);
bool match_all(Dwarf_Die *die);
int process_die_container(struct state *state, struct die *cache,
Dwarf_Die *die, die_callback_t func,
die_match_callback_t match);
void process_cu(Dwarf_Die *cudie);
/*
* types.c
*/
void generate_symtypes_and_versions(FILE *file);
/*
* kabi.c
*/
bool kabi_get_byte_size(const char *fqn, unsigned long *value);
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);
bool kabi_get_type_string(const char *type, const char **str);
void kabi_read_rules(int fd);
void kabi_free(void);
#endif /* __GENDWARFKSYMS_H */
+373
View File
@@ -0,0 +1,373 @@
// 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);
}
+344
View File
@@ -0,0 +1,344 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#include <inttypes.h>
#include "gendwarfksyms.h"
#define SYMBOL_HASH_BITS 12
/* struct symbol_addr -> struct symbol */
static HASHTABLE_DEFINE(symbol_addrs, 1 << SYMBOL_HASH_BITS);
/* name -> struct symbol */
static HASHTABLE_DEFINE(symbol_names, 1 << SYMBOL_HASH_BITS);
static inline unsigned int symbol_addr_hash(const struct symbol_addr *addr)
{
return hash_32(addr->section ^ addr_hash(addr->address));
}
static unsigned int __for_each_addr(struct symbol *sym, symbol_callback_t func,
void *data)
{
struct hlist_node *tmp;
struct symbol *match = NULL;
unsigned int processed = 0;
hash_for_each_possible_safe(symbol_addrs, match, tmp, addr_hash,
symbol_addr_hash(&sym->addr)) {
if (match == sym)
continue; /* Already processed */
if (match->addr.section == sym->addr.section &&
match->addr.address == sym->addr.address) {
func(match, data);
++processed;
}
}
return processed;
}
/*
* For symbols without debugging information (e.g. symbols defined in other
* TUs), we also match __gendwarfksyms_ptr_<symbol_name> symbols, which the
* kernel uses to ensure type information is present in the TU that exports
* the symbol. A __gendwarfksyms_ptr pointer must have the same type as the
* exported symbol, e.g.:
*
* typeof(symname) *__gendwarf_ptr_symname = &symname;
*/
bool is_symbol_ptr(const char *name)
{
return name && !strncmp(name, SYMBOL_PTR_PREFIX, SYMBOL_PTR_PREFIX_LEN);
}
static unsigned int for_each(const char *name, symbol_callback_t func,
void *data)
{
struct hlist_node *tmp;
struct symbol *match;
if (!name || !*name)
return 0;
if (is_symbol_ptr(name))
name += SYMBOL_PTR_PREFIX_LEN;
hash_for_each_possible_safe(symbol_names, match, tmp, name_hash,
hash_str(name)) {
if (strcmp(match->name, name))
continue;
/* Call func for the match, and all address matches */
if (func)
func(match, data);
if (match->addr.section != SHN_UNDEF)
return __for_each_addr(match, func, data) + 1;
return 1;
}
return 0;
}
static void set_crc(struct symbol *sym, void *data)
{
unsigned long *crc = data;
if (sym->state == SYMBOL_PROCESSED && sym->crc != *crc)
warn("overriding version for symbol %s (crc %lx vs. %lx)",
sym->name, sym->crc, *crc);
sym->state = SYMBOL_PROCESSED;
sym->crc = *crc;
}
void symbol_set_crc(struct symbol *sym, unsigned long crc)
{
if (for_each(sym->name, set_crc, &crc) == 0)
error("no matching symbols: '%s'", sym->name);
}
static void set_ptr(struct symbol *sym, void *data)
{
sym->ptr_die_addr = (uintptr_t)((Dwarf_Die *)data)->addr;
}
void symbol_set_ptr(struct symbol *sym, Dwarf_Die *ptr)
{
if (for_each(sym->name, set_ptr, ptr) == 0)
error("no matching symbols: '%s'", sym->name);
}
static void set_die(struct symbol *sym, void *data)
{
sym->die_addr = (uintptr_t)((Dwarf_Die *)data)->addr;
sym->state = SYMBOL_MAPPED;
}
void symbol_set_die(struct symbol *sym, Dwarf_Die *die)
{
if (for_each(sym->name, set_die, die) == 0)
error("no matching symbols: '%s'", sym->name);
}
static bool is_exported(const char *name)
{
return for_each(name, NULL, NULL) > 0;
}
int symbol_read_exports(FILE *file)
{
struct symbol *sym;
char *line = NULL;
char *name = NULL;
size_t size = 0;
int nsym = 0;
while (getline(&line, &size, file) > 0) {
if (sscanf(line, "%ms\n", &name) != 1)
error("malformed input line: %s", line);
if (is_exported(name)) {
/* Ignore duplicates */
free(name);
continue;
}
sym = xcalloc(1, sizeof(*sym));
sym->name = name;
sym->addr.section = SHN_UNDEF;
sym->state = SYMBOL_UNPROCESSED;
hash_add(symbol_names, &sym->name_hash, hash_str(sym->name));
++nsym;
debug("%s", sym->name);
}
free(line);
debug("%d exported symbols", nsym);
return nsym;
}
static void get_symbol(struct symbol *sym, void *arg)
{
struct symbol **res = arg;
if (sym->state == SYMBOL_UNPROCESSED)
*res = sym;
}
struct symbol *symbol_get(const char *name)
{
struct symbol *sym = NULL;
for_each(name, get_symbol, &sym);
return sym;
}
void symbol_for_each(symbol_callback_t func, void *arg)
{
struct hlist_node *tmp;
struct symbol *sym;
hash_for_each_safe(symbol_names, sym, tmp, name_hash) {
func(sym, arg);
}
}
typedef void (*elf_symbol_callback_t)(const char *name, GElf_Sym *sym,
Elf32_Word xndx, void *arg);
static void elf_for_each_global(int fd, elf_symbol_callback_t func, void *arg)
{
size_t sym_size;
GElf_Shdr shdr_mem;
GElf_Shdr *shdr;
Elf_Data *xndx_data = NULL;
Elf_Scn *scn;
Elf *elf;
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));
scn = elf_nextscn(elf, NULL);
while (scn) {
shdr = gelf_getshdr(scn, &shdr_mem);
if (!shdr)
error("gelf_getshdr failed: %s", elf_errmsg(-1));
if (shdr->sh_type == SHT_SYMTAB_SHNDX) {
xndx_data = elf_getdata(scn, NULL);
if (!xndx_data)
error("elf_getdata failed: %s", elf_errmsg(-1));
break;
}
scn = elf_nextscn(elf, scn);
}
sym_size = gelf_fsize(elf, ELF_T_SYM, 1, EV_CURRENT);
scn = elf_nextscn(elf, NULL);
while (scn) {
shdr = gelf_getshdr(scn, &shdr_mem);
if (!shdr)
error("gelf_getshdr failed: %s", elf_errmsg(-1));
if (shdr->sh_type == SHT_SYMTAB) {
unsigned int nsyms;
unsigned int n;
Elf_Data *data = elf_getdata(scn, NULL);
if (!data)
error("elf_getdata failed: %s", elf_errmsg(-1));
if (shdr->sh_entsize != sym_size)
error("expected sh_entsize (%" PRIu64 ") to be %zu",
shdr->sh_entsize, sym_size);
nsyms = shdr->sh_size / shdr->sh_entsize;
for (n = 1; n < nsyms; ++n) {
const char *name = NULL;
Elf32_Word xndx = 0;
GElf_Sym sym_mem;
GElf_Sym *sym;
sym = gelf_getsymshndx(data, xndx_data, n,
&sym_mem, &xndx);
if (!sym)
error("gelf_getsymshndx failed: %s",
elf_errmsg(-1));
if (GELF_ST_BIND(sym->st_info) == STB_LOCAL)
continue;
if (sym->st_shndx != SHN_XINDEX)
xndx = sym->st_shndx;
name = elf_strptr(elf, shdr->sh_link,
sym->st_name);
if (!name)
error("elf_strptr failed: %s",
elf_errmsg(-1));
/* Skip empty symbol names */
if (*name)
func(name, sym, xndx, arg);
}
}
scn = elf_nextscn(elf, scn);
}
check(elf_end(elf));
}
static void set_symbol_addr(struct symbol *sym, void *arg)
{
struct symbol_addr *addr = arg;
if (sym->addr.section == SHN_UNDEF) {
sym->addr = *addr;
hash_add(symbol_addrs, &sym->addr_hash,
symbol_addr_hash(&sym->addr));
debug("%s -> { %u, %" PRIx64 " }", sym->name, sym->addr.section,
sym->addr.address);
} else if (sym->addr.section != addr->section ||
sym->addr.address != addr->address) {
warn("multiple addresses for symbol %s?", sym->name);
}
}
static void elf_set_symbol_addr(const char *name, GElf_Sym *sym,
Elf32_Word xndx, void *arg)
{
struct symbol_addr addr = { .section = xndx, .address = sym->st_value };
/* Set addresses for exported symbols */
if (addr.section != SHN_UNDEF)
for_each(name, set_symbol_addr, &addr);
}
void symbol_read_symtab(int fd)
{
elf_for_each_global(fd, elf_set_symbol_addr, NULL);
}
void symbol_print_versions(void)
{
struct hlist_node *tmp;
struct symbol *sym;
hash_for_each_safe(symbol_names, sym, tmp, name_hash) {
if (sym->state != SYMBOL_PROCESSED)
warn("no information for symbol %s", sym->name);
printf("#SYMVER %s 0x%08lx\n", sym->name, sym->crc);
}
}
void symbol_free(void)
{
struct hlist_node *tmp;
struct symbol *sym;
hash_for_each_safe(symbol_names, sym, tmp, name_hash) {
free((void *)sym->name);
free(sym);
}
hash_init(symbol_addrs);
hash_init(symbol_names);
}
+589
View File
@@ -0,0 +1,589 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#define _GNU_SOURCE
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#include "gendwarfksyms.h"
static struct cache expansion_cache;
/*
* A simple linked list of shared or owned strings to avoid copying strings
* around when not necessary.
*/
struct type_list_entry {
const char *str;
void *owned;
struct list_head list;
};
static void type_list_free(struct list_head *list)
{
struct type_list_entry *entry;
struct type_list_entry *tmp;
list_for_each_entry_safe(entry, tmp, list, list) {
if (entry->owned)
free(entry->owned);
free(entry);
}
INIT_LIST_HEAD(list);
}
static int type_list_append(struct list_head *list, const char *s, void *owned)
{
struct type_list_entry *entry;
if (!s)
return 0;
entry = xmalloc(sizeof(*entry));
entry->str = s;
entry->owned = owned;
list_add_tail(&entry->list, list);
return strlen(entry->str);
}
static void type_list_write(struct list_head *list, FILE *file)
{
struct type_list_entry *entry;
list_for_each_entry(entry, list, list) {
if (entry->str)
checkp(fputs(entry->str, file));
}
}
/*
* An expanded type string in symtypes format.
*/
struct type_expansion {
char *name;
size_t len;
struct list_head expanded;
struct hlist_node hash;
};
static void type_expansion_init(struct type_expansion *type)
{
type->name = NULL;
type->len = 0;
INIT_LIST_HEAD(&type->expanded);
}
static inline void type_expansion_free(struct type_expansion *type)
{
free(type->name);
type->name = NULL;
type->len = 0;
type_list_free(&type->expanded);
}
static void type_expansion_append(struct type_expansion *type, const char *s,
void *owned)
{
type->len += type_list_append(&type->expanded, s, owned);
}
/*
* type_map -- the longest expansions for each type.
*
* const char *name -> struct type_expansion *
*/
#define TYPE_HASH_BITS 12
static HASHTABLE_DEFINE(type_map, 1 << TYPE_HASH_BITS);
static int __type_map_get(const char *name, struct type_expansion **res)
{
struct type_expansion *e;
hash_for_each_possible(type_map, e, hash, hash_str(name)) {
if (!strcmp(name, e->name)) {
*res = e;
return 0;
}
}
return -1;
}
static struct type_expansion *type_map_add(const char *name,
struct type_expansion *type)
{
struct type_expansion *e;
if (__type_map_get(name, &e)) {
e = xmalloc(sizeof(*e));
type_expansion_init(e);
e->name = xstrdup(name);
hash_add(type_map, &e->hash, hash_str(e->name));
if (dump_types)
debug("adding %s", e->name);
} else {
/* Use the longest available expansion */
if (type->len <= e->len)
return e;
type_list_free(&e->expanded);
if (dump_types)
debug("replacing %s", e->name);
}
/* Take ownership of type->expanded */
list_replace_init(&type->expanded, &e->expanded);
e->len = type->len;
if (dump_types) {
checkp(fputs(e->name, stderr));
checkp(fputs(" ", stderr));
type_list_write(&e->expanded, stderr);
checkp(fputs("\n", stderr));
}
return e;
}
static void type_parse(const char *name, const char *str,
struct type_expansion *type);
static int type_map_get(const char *name, struct type_expansion **res)
{
struct type_expansion type;
const char *override;
if (!__type_map_get(name, res))
return 0;
/*
* If die_map didn't contain a type, we might still have
* a type_string kABI rule that defines it.
*/
if (stable && kabi_get_type_string(name, &override)) {
type_expansion_init(&type);
type_parse(name, override, &type);
*res = type_map_add(name, &type);
type_expansion_free(&type);
return 0;
}
return -1;
}
static int cmp_expansion_name(const void *p1, const void *p2)
{
struct type_expansion *const *e1 = p1;
struct type_expansion *const *e2 = p2;
return strcmp((*e1)->name, (*e2)->name);
}
static void type_map_write(FILE *file)
{
struct type_expansion *e;
struct hlist_node *tmp;
struct type_expansion **es;
size_t count = 0;
size_t i = 0;
if (!file)
return;
hash_for_each_safe(type_map, e, tmp, hash)
++count;
es = xmalloc(count * sizeof(*es));
hash_for_each_safe(type_map, e, tmp, hash)
es[i++] = e;
qsort(es, count, sizeof(*es), cmp_expansion_name);
for (i = 0; i < count; ++i) {
checkp(fputs(es[i]->name, file));
checkp(fputs(" ", file));
type_list_write(&es[i]->expanded, file);
checkp(fputs("\n", file));
}
free(es);
}
static void type_map_free(void)
{
struct type_expansion *e;
struct hlist_node *tmp;
hash_for_each_safe(type_map, e, tmp, hash) {
type_expansion_free(e);
free(e);
}
hash_init(type_map);
}
/*
* CRC for a type, with an optional fully expanded type string for
* debugging.
*/
struct version {
struct type_expansion type;
unsigned long crc;
};
static void version_init(struct version *version)
{
version->crc = crc32(0, NULL, 0);
type_expansion_init(&version->type);
}
static void version_free(struct version *version)
{
type_expansion_free(&version->type);
}
static void version_add(struct version *version, const char *s)
{
version->crc = crc32(version->crc, (void *)s, strlen(s));
if (dump_versions)
type_expansion_append(&version->type, s, NULL);
}
/*
* Type reference format: <prefix>#<name>, where prefix:
* s -> structure
* u -> union
* e -> enum
* t -> typedef
*
* Names with spaces are additionally wrapped in single quotes.
*/
static inline bool is_type_prefix(const char *s)
{
return (s[0] == 's' || s[0] == 'u' || s[0] == 'e' || s[0] == 't') &&
s[1] == '#';
}
static char get_type_prefix(int tag)
{
switch (tag) {
case DW_TAG_class_type:
case DW_TAG_structure_type:
return 's';
case DW_TAG_union_type:
return 'u';
case DW_TAG_enumeration_type:
return 'e';
case DW_TAG_typedef_type:
return 't';
default:
return 0;
}
}
static char *get_type_name(struct die *cache)
{
const char *quote;
char prefix;
char *name;
if (cache->state == DIE_INCOMPLETE) {
warn("found incomplete cache entry: %p", cache);
return NULL;
}
if (cache->state == DIE_SYMBOL || cache->state == DIE_FQN)
return NULL;
if (!cache->fqn || !*cache->fqn)
return NULL;
prefix = get_type_prefix(cache->tag);
if (!prefix)
return NULL;
/* Wrap names with spaces in single quotes */
quote = strstr(cache->fqn, " ") ? "'" : "";
/* <prefix>#<type_name>\0 */
if (asprintf(&name, "%c#%s%s%s", prefix, quote, cache->fqn, quote) < 0)
error("asprintf failed for '%s'", cache->fqn);
return name;
}
static void __calculate_version(struct version *version,
struct type_expansion *type)
{
struct type_list_entry *entry;
struct type_expansion *e;
/* Calculate a CRC over an expanded type string */
list_for_each_entry(entry, &type->expanded, list) {
if (is_type_prefix(entry->str)) {
if (type_map_get(entry->str, &e))
error("unknown type reference to '%s' when expanding '%s'",
entry->str, type->name);
/*
* It's sufficient to expand each type reference just
* once to detect changes.
*/
if (cache_was_expanded(&expansion_cache, e)) {
version_add(version, entry->str);
} else {
cache_mark_expanded(&expansion_cache, e);
__calculate_version(version, e);
}
} else {
version_add(version, entry->str);
}
}
}
static void calculate_version(struct version *version,
struct type_expansion *type)
{
version_init(version);
__calculate_version(version, type);
cache_free(&expansion_cache);
}
static void __type_expand(struct die *cache, struct type_expansion *type)
{
struct die_fragment *df;
struct die *child;
char *name;
list_for_each_entry(df, &cache->fragments, list) {
switch (df->type) {
case FRAGMENT_STRING:
type_expansion_append(type, df->data.str, NULL);
break;
case FRAGMENT_DIE:
/* Use a complete die_map expansion if available */
if (__die_map_get(df->data.addr, DIE_COMPLETE,
&child) &&
__die_map_get(df->data.addr, DIE_UNEXPANDED,
&child))
error("unknown child: %" PRIxPTR,
df->data.addr);
name = get_type_name(child);
if (name)
type_expansion_append(type, name, name);
else
__type_expand(child, type);
break;
case FRAGMENT_LINEBREAK:
/*
* Keep whitespace in the symtypes format, but avoid
* repeated spaces.
*/
if (list_is_last(&df->list, &cache->fragments) ||
list_next_entry(df, list)->type !=
FRAGMENT_LINEBREAK)
type_expansion_append(type, " ", NULL);
break;
default:
error("empty die_fragment in %p", cache);
}
}
}
static void type_expand(const char *name, struct die *cache,
struct type_expansion *type)
{
const char *override;
type_expansion_init(type);
if (stable && kabi_get_type_string(name, &override))
type_parse(name, override, type);
else
__type_expand(cache, type);
}
static void type_parse(const char *name, const char *str,
struct type_expansion *type)
{
char *fragment;
size_t start = 0;
size_t end;
size_t pos;
if (!*str)
error("empty type string override for '%s'", name);
for (pos = 0; str[pos]; ++pos) {
bool empty;
char marker = ' ';
if (!is_type_prefix(&str[pos]))
continue;
end = pos + 2;
/*
* Find the end of the type reference. If the type name contains
* spaces, it must be in single quotes.
*/
if (str[end] == '\'') {
marker = '\'';
++end;
}
while (str[end] && str[end] != marker)
++end;
/* Check that we have a non-empty type name */
if (marker == '\'') {
if (str[end] != marker)
error("incomplete %c# type reference for '%s' (string : '%s')",
str[pos], name, str);
empty = end == pos + 3;
++end;
} else {
empty = end == pos + 2;
}
if (empty)
error("empty %c# type name for '%s' (string: '%s')",
str[pos], name, str);
/* Append the part of the string before the type reference */
if (pos > start) {
fragment = xstrndup(&str[start], pos - start);
type_expansion_append(type, fragment, fragment);
}
/*
* Append the type reference -- note that if the reference
* is invalid, i.e. points to a non-existent type, we will
* print out an error when calculating versions.
*/
fragment = xstrndup(&str[pos], end - pos);
type_expansion_append(type, fragment, fragment);
start = end;
pos = end - 1;
}
/* Append the rest of the type string, if there's any left */
if (str[start])
type_expansion_append(type, &str[start], NULL);
}
static void expand_type(struct die *cache, void *arg)
{
struct type_expansion type;
char *name;
if (cache->mapped)
return;
cache->mapped = true;
/*
* Skip unexpanded die_map entries if there's a complete
* expansion available for this DIE.
*/
if (cache->state == DIE_UNEXPANDED &&
!__die_map_get(cache->addr, DIE_COMPLETE, &cache)) {
if (cache->mapped)
return;
cache->mapped = true;
}
name = get_type_name(cache);
if (!name)
return;
debug("%s", name);
type_expand(name, cache, &type);
type_map_add(name, &type);
type_expansion_free(&type);
free(name);
}
static void expand_symbol(struct symbol *sym, void *arg)
{
struct type_expansion type;
struct version version;
struct die *cache;
/*
* No need to expand again unless we want a symtypes file entry
* for the symbol. Note that this means `sym` has the same address
* as another symbol that was already processed.
*/
if (!symtypes && sym->state == SYMBOL_PROCESSED)
return;
if (__die_map_get(sym->die_addr, DIE_SYMBOL, &cache))
return; /* We'll warn about missing CRCs later. */
type_expand(sym->name, cache, &type);
/* If the symbol already has a version, don't calculate it again. */
if (sym->state != SYMBOL_PROCESSED) {
calculate_version(&version, &type);
symbol_set_crc(sym, version.crc);
debug("%s = %lx", sym->name, version.crc);
if (dump_versions) {
checkp(fputs(sym->name, stderr));
checkp(fputs(" ", stderr));
type_list_write(&version.type.expanded, stderr);
checkp(fputs("\n", stderr));
}
version_free(&version);
}
/* These aren't needed in type_map unless we want a symtypes file. */
if (symtypes)
type_map_add(sym->name, &type);
type_expansion_free(&type);
}
void generate_symtypes_and_versions(FILE *file)
{
cache_init(&expansion_cache);
/*
* die_map processing:
*
* 1. die_map contains all types referenced in exported symbol
* signatures, but can contain duplicates just like the original
* DWARF, and some references may not be fully expanded depending
* on how far we processed the DIE tree for that specific symbol.
*
* For each die_map entry, find the longest available expansion,
* and add it to type_map.
*/
die_map_for_each(expand_type, NULL);
/*
* 2. For each exported symbol, expand the die_map type, and use
* type_map expansions to calculate a symbol version from the
* fully expanded type string.
*/
symbol_for_each(expand_symbol, NULL);
/*
* 3. If a symtypes file is requested, write type_map contents to
* the file.
*/
type_map_write(file);
type_map_free();
}