Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild.git

This commit is contained in:
Stephen Rothwell 2025-01-13 09:56:47 +11:00
commit e92f4a48e5
43 changed files with 4352 additions and 170 deletions

View File

@ -0,0 +1,308 @@
=======================
DWARF module versioning
=======================
1. Introduction
===============
When CONFIG_MODVERSIONS is enabled, symbol versions for modules
are typically calculated from preprocessed source code using the
**genksyms** tool. However, this is incompatible with languages such
as Rust, where the source code has insufficient information about
the resulting ABI. With CONFIG_GENDWARFKSYMS (and CONFIG_DEBUG_INFO)
selected, **gendwarfksyms** is used instead to calculate symbol versions
from the DWARF debugging information, which contains the necessary
details about the final module ABI.
1.1. Usage
==========
gendwarfksyms accepts a list of object files on the command line, and a
list of symbol names (one per line) in standard input::
Usage: gendwarfksyms [options] elf-object-file ... < symbol-list
Options:
-d, --debug Print debugging information
--dump-dies Dump DWARF DIE contents
--dump-die-map Print debugging information about die_map changes
--dump-types Dump type strings
--dump-versions Dump expanded type strings used for symbol versions
-s, --stable Support kABI stability features
-T, --symtypes file Write a symtypes file
-h, --help Print this message
2. Type information availability
================================
While symbols are typically exported in the same translation unit (TU)
where they're defined, it's also perfectly fine for a TU to export
external symbols. For example, this is done when calculating symbol
versions for exports in stand-alone assembly code.
To ensure the compiler emits the necessary DWARF type information in the
TU where symbols are actually exported, gendwarfksyms adds a pointer
to exported symbols in the `EXPORT_SYMBOL()` macro using the following
macro::
#define __GENDWARFKSYMS_EXPORT(sym) \
static typeof(sym) *__gendwarfksyms_ptr_##sym __used \
__section(".discard.gendwarfksyms") = &sym;
When a symbol pointer is found in DWARF, gendwarfksyms can use its
type for calculating symbol versions even if the symbol is defined
elsewhere. The name of the symbol pointer is expected to start with
`__gendwarfksyms_ptr_`, followed by the name of the exported symbol.
3. Symtypes output format
=========================
Similarly to genksyms, gendwarfksyms supports writing a symtypes
file for each processed object that contain types for exported
symbols and each referenced type that was used in calculating symbol
versions. These files can be useful when trying to determine what
exactly caused symbol versions to change between builds. To generate
symtypes files during a kernel build, set `KBUILD_SYMTYPES=1`.
Matching the existing format, the first column of each line contains
either a type reference or a symbol name. Type references have a
one-letter prefix followed by "#" and the name of the type. Four
reference types are supported::
e#<type> = enum
s#<type> = struct
t#<type> = typedef
u#<type> = union
Type names with spaces in them are wrapped in single quotes, e.g.::
s#'core::result::Result<u8, core::num::error::ParseIntError>'
The rest of the line contains a type string. Unlike with genksyms that
produces C-style type strings, gendwarfksyms uses the same simple parsed
DWARF format produced by **--dump-dies**, but with type references
instead of fully expanded strings.
4. Maintaining a stable kABI
============================
Distribution maintainers often need the ability to make ABI compatible
changes to kernel data structures due to LTS updates or backports. Using
the traditional `#ifndef __GENKSYMS__` to hide these changes from symbol
versioning won't work when processing object files. To support this
use case, gendwarfksyms provides kABI stability features designed to
hide changes that won't affect the ABI when calculating versions. These
features are all gated behind the **--stable** command line flag and are
not used in the mainline kernel. To use stable features during a kernel
build, set `KBUILD_GENDWARFKSYMS_STABLE=1`.
Examples for using these features are provided in the
**scripts/gendwarfksyms/examples** directory, including helper macros
for source code annotation. Note that as these features are only used to
transform the inputs for symbol versioning, the user is responsible for
ensuring that their changes actually won't break the ABI.
4.1. kABI rules
===============
kABI rules allow distributions to fine-tune certain parts
of gendwarfksyms output and thus control how symbol
versions are calculated. These rules are defined in the
`.discard.gendwarfksyms.kabi_rules` section of the object file and
consist of simple null-terminated strings with the following structure::
version\0type\0target\0value\0
This string sequence is repeated as many times as needed to express all
the rules. The fields are as follows:
- `version`: Ensures backward compatibility for future changes to the
structure. Currently expected to be "1".
- `type`: Indicates the type of rule being applied.
- `target`: Specifies the target of the rule, typically the fully
qualified name of the DWARF Debugging Information Entry (DIE).
- `value`: Provides rule-specific data.
The following helper macro, for example, can be used to specify rules
in the source code::
#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
Currently, only the rules discussed in this section are supported, but
the format is extensible enough to allow further rules to be added as
need arises.
4.1.1. Managing definition visibility
=====================================
A declaration can change into a full definition when additional includes
are pulled into the translation unit. This changes the versions of any
symbol that references the type even if the ABI remains unchanged. As
it may not be possible to drop includes without breaking the build, the
`declonly` rule can be used to specify a type as declaration-only, even
if the debugging information contains the full definition.
The rule fields are expected to be as follows:
- `type`: "declonly"
- `target`: The fully qualified name of the target data structure
(as shown in **--dump-dies** output).
- `value`: This field is ignored.
Using the `__KABI_RULE` macro, this rule can be defined as::
#define KABI_DECLONLY(fqn) __KABI_RULE(declonly, fqn, )
Example usage::
struct s {
/* definition */
};
KABI_DECLONLY(s);
4.1.2. Adding enumerators
=========================
For enums, all enumerators and their values are included in calculating
symbol versions, which becomes a problem if we later need to add more
enumerators without changing symbol versions. The `enumerator_ignore`
rule allows us to hide named enumerators from the input.
The rule fields are expected to be as follows:
- `type`: "enumerator_ignore"
- `target`: The fully qualified name of the target enum
(as shown in **--dump-dies** output) and the name of the
enumerator field separated by a space.
- `value`: This field is ignored.
Using the `__KABI_RULE` macro, this rule can be defined as::
#define KABI_ENUMERATOR_IGNORE(fqn, field) \
__KABI_RULE(enumerator_ignore, fqn field, )
Example usage::
enum e {
A, B, C, D,
};
KABI_ENUMERATOR_IGNORE(e, B);
KABI_ENUMERATOR_IGNORE(e, C);
If the enum additionally includes an end marker and new values must
be added in the middle, we may need to use the old value for the last
enumerator when calculating versions. The `enumerator_value` rule allows
us to override the value of an enumerator for version calculation:
- `type`: "enumerator_value"
- `target`: The fully qualified name of the target enum
(as shown in **--dump-dies** output) and the name of the
enumerator field separated by a space.
- `value`: Integer value used for the field.
Using the `__KABI_RULE` macro, this rule can be defined as::
#define KABI_ENUMERATOR_VALUE(fqn, field, value) \
__KABI_RULE(enumerator_value, fqn field, value)
Example usage::
enum e {
A, B, C, LAST,
};
KABI_ENUMERATOR_IGNORE(e, C);
KABI_ENUMERATOR_VALUE(e, LAST, 2);
4.3. Adding structure members
=============================
Perhaps the most common ABI compatible change is adding a member to a
kernel data structure. When changes to a structure are anticipated,
distribution maintainers can pre-emptively reserve space in the
structure and take it into use later without breaking the ABI. If
changes are needed to data structures without reserved space, existing
alignment holes can potentially be used instead. While kABI rules could
be added for these type of changes, using unions is typically a more
natural method. This section describes gendwarfksyms support for using
reserved space in data structures and hiding members that don't change
the ABI when calculating symbol versions.
4.3.1. Reserving space and replacing members
============================================
Space is typically reserved for later use by appending integer types, or
arrays, to the end of the data structure, but any type can be used. Each
reserved member needs a unique name, but as the actual purpose is usually
not known at the time the space is reserved, for convenience, names that
start with `__kabi_` are left out when calculating symbol versions::
struct s {
long a;
long __kabi_reserved_0; /* reserved for future use */
};
The reserved space can be taken into use by wrapping the member in a
union, which includes the original type and the replacement member::
struct s {
long a;
union {
long __kabi_reserved_0; /* original type */
struct b b; /* replaced field */
};
};
If the `__kabi_` naming scheme was used when reserving space, the name
of the first member of the union must start with `__kabi_reserved`. This
ensures the original type is used when calculating versions, but the name
is again left out. The rest of the union is ignored.
If we're replacing a member that doesn't follow this naming convention,
we also need to preserve the original name to avoid changing versions,
which we can do by changing the first union member's name to start with
`__kabi_renamed` followed by the original name.
The examples include `KABI_(RESERVE|USE|REPLACE)*` macros that help
simplify the process and also ensure the replacement member is correctly
aligned and its size won't exceed the reserved space.
4.3.2. Hiding members
=====================
Predicting which structures will require changes during the support
timeframe isn't always possible, in which case one might have to resort
to placing new members into existing alignment holes::
struct s {
int a;
/* a 4-byte alignment hole */
unsigned long b;
};
While this won't change the size of the data structure, one needs to
be able to hide the added members from symbol versioning. Similarly
to reserved fields, this can be accomplished by wrapping the added
member to a union where one of the fields has a name starting with
`__kabi_ignored`::
struct s {
int a;
union {
char __kabi_ignored_0;
int n;
};
unsigned long b;
};
With **--stable**, both versions produce the same symbol version.

View File

@ -21,6 +21,7 @@ Kernel Build System
reproducible-builds
gcc-plugins
llvm
gendwarfksyms
.. only:: subproject and html

View File

@ -423,6 +423,26 @@ Symbols From the Kernel (vmlinux + modules)
1) It lists all exported symbols from vmlinux and all modules.
2) It lists the CRC if CONFIG_MODVERSIONS is enabled.
Version Information Formats
---------------------------
Exported symbols have information stored in __ksymtab or __ksymtab_gpl
sections. Symbol names and namespaces are stored in __ksymtab_strings,
using a format similar to the string table used for ELF. If
CONFIG_MODVERSIONS is enabled, the CRCs corresponding to exported
symbols will be added to the __kcrctab or __kcrctab_gpl.
If CONFIG_BASIC_MODVERSIONS is enabled (default with
CONFIG_MODVERSIONS), imported symbols will have their symbol name and
CRC stored in the __versions section of the importing module. This
mode only supports symbols of length up to 64 bytes.
If CONFIG_EXTENDED_MODVERSIONS is enabled (required to enable both
CONFIG_MODVERSIONS and CONFIG_RUST at the same time), imported symbols
will have their symbol name recorded in the __version_ext_names
section as a series of concatenated, null-terminated strings. CRCs for
these symbols will be recorded in the __version_ext_crcs section.
Symbols and External Modules
----------------------------

View File

@ -59,7 +59,6 @@ iptables 1.4.2 iptables -V
openssl & libcrypto 1.0.0 openssl version
bc 1.06.95 bc --version
Sphinx\ [#f1]_ 2.4.4 sphinx-build --version
cpio any cpio --version
GNU tar 1.28 tar --version
gtags (optional) 6.6.5 gtags --version
mkimage (optional) 2017.01 mkimage --version
@ -536,11 +535,6 @@ mcelog
- <https://www.mcelog.org/>
cpio
----
- <https://www.gnu.org/software/cpio/>
Networking
**********

View File

@ -9548,6 +9548,13 @@ W: https://linuxtv.org
T: git git://linuxtv.org/media.git
F: drivers/media/radio/radio-gemtek*
GENDWARFKSYMS
M: Sami Tolvanen <samitolvanen@google.com>
L: linux-modules@vger.kernel.org
L: linux-kbuild@vger.kernel.org
S: Maintained
F: scripts/gendwarfksyms/
GENERIC ARCHITECTURE TOPOLOGY
M: Sudeep Holla <sudeep.holla@arm.com>
L: linux-kernel@vger.kernel.org

View File

@ -369,6 +369,24 @@ static void dedotify_versions(struct modversion_info *vers,
}
}
/* Same as normal versions, remove a leading dot if present. */
static void dedotify_ext_version_names(char *str_seq, unsigned long size)
{
unsigned long out = 0;
unsigned long in;
char last = '\0';
for (in = 0; in < size; in++) {
/* Skip one leading dot */
if (last == '\0' && str_seq[in] == '.')
in++;
last = str_seq[in];
str_seq[out++] = last;
}
/* Zero the trailing portion of the names table for robustness */
memset(&str_seq[out], 0, size - out);
}
/*
* Undefined symbols which refer to .funcname, hack to funcname. Make .TOC.
* seem to be defined (value set later).
@ -438,10 +456,12 @@ int module_frob_arch_sections(Elf64_Ehdr *hdr,
me->arch.toc_section = i;
if (sechdrs[i].sh_addralign < 8)
sechdrs[i].sh_addralign = 8;
}
else if (strcmp(secstrings+sechdrs[i].sh_name,"__versions")==0)
} else if (strcmp(secstrings + sechdrs[i].sh_name, "__versions") == 0)
dedotify_versions((void *)hdr + sechdrs[i].sh_offset,
sechdrs[i].sh_size);
else if (strcmp(secstrings + sechdrs[i].sh_name, "__version_ext_names") == 0)
dedotify_ext_version_names((void *)hdr + sechdrs[i].sh_offset,
sechdrs[i].sh_size);
if (sechdrs[i].sh_type == SHT_SYMTAB)
dedotify((void *)hdr + sechdrs[i].sh_offset,

View File

@ -52,9 +52,24 @@
#else
#ifdef CONFIG_GENDWARFKSYMS
/*
* With CONFIG_GENDWARFKSYMS, ensure the compiler emits debugging
* information for all exported symbols, including those defined in
* different TUs, by adding a __gendwarfksyms_ptr_<symbol> pointer
* that's discarded during the final link.
*/
#define __GENDWARFKSYMS_EXPORT(sym) \
static typeof(sym) *__gendwarfksyms_ptr_##sym __used \
__section(".discard.gendwarfksyms") = &sym;
#else
#define __GENDWARFKSYMS_EXPORT(sym)
#endif
#define __EXPORT_SYMBOL(sym, license, ns) \
extern typeof(sym) sym; \
__ADDRESSABLE(sym) \
__GENDWARFKSYMS_EXPORT(sym) \
asm(__stringify(___EXPORT_SYMBOL(sym, license, ns)))
#endif

View File

@ -430,7 +430,7 @@ struct module {
/* Exported symbols */
const struct kernel_symbol *syms;
const s32 *crcs;
const u32 *crcs;
unsigned int num_syms;
#ifdef CONFIG_ARCH_USES_CFI_TRAPS
@ -448,7 +448,7 @@ struct module {
/* GPL-only exported symbols. */
unsigned int num_gpl_syms;
const struct kernel_symbol *gpl_syms;
const s32 *gpl_crcs;
const u32 *gpl_crcs;
bool using_gplonly_symbols;
#ifdef CONFIG_MODULE_SIG

View File

@ -1959,7 +1959,8 @@ config RUST
bool "Rust support"
depends on HAVE_RUST
depends on RUST_IS_AVAILABLE
depends on !MODVERSIONS
select EXTENDED_MODVERSIONS if MODVERSIONS
depends on !MODVERSIONS || GENDWARFKSYMS
depends on !GCC_PLUGIN_RANDSTRUCT
depends on !RANDSTRUCT
depends on !DEBUG_INFO_BTF || PAHOLE_HAS_LANG_EXCLUDE

View File

@ -7,20 +7,13 @@ set -e
sfile="$(readlink -f "$0")"
outdir="$(pwd)"
tarfile=$1
cpio_dir=$outdir/${tarfile%/*}/.tmp_cpio_dir
tmpdir=$outdir/${tarfile%/*}/.tmp_dir
dir_list="
include/
arch/$SRCARCH/include/
"
if ! command -v cpio >/dev/null; then
echo >&2 "***"
echo >&2 "*** 'cpio' could not be found."
echo >&2 "***"
exit 1
fi
# Support incremental builds by skipping archive generation
# if timestamps of files being archived are not changed.
@ -48,9 +41,9 @@ all_dirs="$all_dirs $dir_list"
# check include/generated/autoconf.h explicitly.
#
# Ignore them for md5 calculation to avoid pointless regeneration.
headers_md5="$(find $all_dirs -name "*.h" |
grep -v "include/generated/utsversion.h" |
grep -v "include/generated/autoconf.h" |
headers_md5="$(find $all_dirs -name "*.h" -a \
! -path include/generated/utsversion.h -a \
! -path include/generated/autoconf.h |
xargs ls -l | md5sum | cut -d ' ' -f1)"
# Any changes to this script will also cause a rebuild of the archive.
@ -65,36 +58,43 @@ fi
echo " GEN $tarfile"
rm -rf $cpio_dir
mkdir $cpio_dir
rm -rf "${tmpdir}"
mkdir "${tmpdir}"
if [ "$building_out_of_srctree" ]; then
(
cd $srctree
for f in $dir_list
do find "$f" -name "*.h";
done | cpio --quiet -pd $cpio_dir
done | tar -c -f - -T - | tar -xf - -C "${tmpdir}"
)
fi
# The second CPIO can complain if files already exist which can happen with out
# of tree builds having stale headers in srctree. Just silence CPIO for now.
for f in $dir_list;
do find "$f" -name "*.h";
done | cpio --quiet -pdu $cpio_dir >/dev/null 2>&1
done | tar -c -f - -T - | tar -xf - -C "${tmpdir}"
# Always exclude include/generated/utsversion.h
# Otherwise, the contents of the tarball may vary depending on the build steps.
rm -f "${tmpdir}/include/generated/utsversion.h"
# Remove comments except SDPX lines
find $cpio_dir -type f -print0 |
xargs -0 -P8 -n1 perl -pi -e 'BEGIN {undef $/;}; s/\/\*((?!SPDX).)*?\*\///smg;'
# Use a temporary file to store directory contents to prevent find/xargs from
# seeing temporary files created by perl.
find "${tmpdir}" -type f -print0 > "${tmpdir}.contents.txt"
xargs -0 -P8 -n1 \
perl -pi -e 'BEGIN {undef $/;}; s/\/\*((?!SPDX).)*?\*\///smg;' \
< "${tmpdir}.contents.txt"
rm -f "${tmpdir}.contents.txt"
# Create archive and try to normalize metadata for reproducibility.
tar "${KBUILD_BUILD_TIMESTAMP:+--mtime=$KBUILD_BUILD_TIMESTAMP}" \
--exclude=".__afs*" --exclude=".nfs*" \
--owner=0 --group=0 --sort=name --numeric-owner --mode=u=rw,go=r,a+X \
-I $XZ -cf $tarfile -C $cpio_dir/ . > /dev/null
-I $XZ -cf $tarfile -C "${tmpdir}/" . > /dev/null
echo $headers_md5 > kernel/kheaders.md5
echo "$this_file_md5" >> kernel/kheaders.md5
echo "$(md5sum $tarfile | cut -d ' ' -f1)" >> kernel/kheaders.md5
rm -rf $cpio_dir
rm -rf "${tmpdir}"

View File

@ -169,6 +169,36 @@ config MODVERSIONS
make them incompatible with the kernel you are running. If
unsure, say N.
choice
prompt "Module versioning implementation"
depends on MODVERSIONS
help
Select the tool used to calculate symbol versions for modules.
If unsure, select GENKSYMS.
config GENKSYMS
bool "genksyms (from source code)"
help
Calculate symbol versions from pre-processed source code using
genksyms.
If unsure, say Y.
config GENDWARFKSYMS
bool "gendwarfksyms (from debugging information)"
depends on DEBUG_INFO
# Requires full debugging information, split DWARF not supported.
depends on !DEBUG_INFO_REDUCED && !DEBUG_INFO_SPLIT
# Requires ELF object files.
depends on !LTO
help
Calculate symbol versions from DWARF debugging information using
gendwarfksyms. Requires DEBUG_INFO to be enabled.
If unsure, say N.
endchoice
config ASM_MODVERSIONS
bool
default HAVE_ASM_MODVERSIONS && MODVERSIONS
@ -177,6 +207,31 @@ config ASM_MODVERSIONS
assembly. This can be enabled only when the target architecture
supports it.
config EXTENDED_MODVERSIONS
bool "Extended Module Versioning Support"
depends on MODVERSIONS
help
This enables extended MODVERSIONs support, allowing long symbol
names to be versioned.
The most likely reason you would enable this is to enable Rust
support. If unsure, say N.
config BASIC_MODVERSIONS
bool "Basic Module Versioning Support"
depends on MODVERSIONS
default y
help
This enables basic MODVERSIONS support, allowing older tools or
kernels to potentially load modules.
Disabling this may cause older `modprobe` or `kmod` to be unable
to read MODVERSIONS information from built modules. With this
disabled, older kernels may treat this module as unversioned.
This is enabled by default when MODVERSIONS are enabled.
If unsure, say Y.
config MODULE_SRCVERSION_ALL
bool "Source checksum for all modules"
help

View File

@ -55,8 +55,8 @@ extern const struct kernel_symbol __start___ksymtab[];
extern const struct kernel_symbol __stop___ksymtab[];
extern const struct kernel_symbol __start___ksymtab_gpl[];
extern const struct kernel_symbol __stop___ksymtab_gpl[];
extern const s32 __start___kcrctab[];
extern const s32 __start___kcrctab_gpl[];
extern const u32 __start___kcrctab[];
extern const u32 __start___kcrctab_gpl[];
struct load_info {
const char *name;
@ -86,6 +86,8 @@ struct load_info {
unsigned int vers;
unsigned int info;
unsigned int pcpu;
unsigned int vers_ext_crc;
unsigned int vers_ext_name;
} index;
};
@ -102,7 +104,7 @@ struct find_symbol_arg {
/* Output */
struct module *owner;
const s32 *crc;
const u32 *crc;
const struct kernel_symbol *sym;
enum mod_license license;
};
@ -384,16 +386,25 @@ static inline void init_param_lock(struct module *mod) { }
#ifdef CONFIG_MODVERSIONS
int check_version(const struct load_info *info,
const char *symname, struct module *mod, const s32 *crc);
const char *symname, struct module *mod, const u32 *crc);
void module_layout(struct module *mod, struct modversion_info *ver, struct kernel_param *kp,
struct kernel_symbol *ks, struct tracepoint * const *tp);
int check_modstruct_version(const struct load_info *info, struct module *mod);
int same_magic(const char *amagic, const char *bmagic, bool has_crcs);
struct modversion_info_ext {
size_t remaining;
const u32 *crc;
const char *name;
};
void modversion_ext_start(const struct load_info *info, struct modversion_info_ext *ver);
void modversion_ext_advance(struct modversion_info_ext *ver);
#define for_each_modversion_info_ext(ver, info) \
for (modversion_ext_start(info, &ver); ver.remaining > 0; modversion_ext_advance(&ver))
#else /* !CONFIG_MODVERSIONS */
static inline int check_version(const struct load_info *info,
const char *symname,
struct module *mod,
const s32 *crc)
const u32 *crc)
{
return 1;
}

View File

@ -86,7 +86,7 @@ struct mod_tree_root mod_tree __cacheline_aligned = {
struct symsearch {
const struct kernel_symbol *start, *stop;
const s32 *crcs;
const u32 *crcs;
enum mod_license license;
};
@ -2073,6 +2073,82 @@ static int elf_validity_cache_index_str(struct load_info *info)
return 0;
}
/**
* elf_validity_cache_index_versions() - Validate and cache version indices
* @info: Load info to cache version indices in.
* Must have &load_info->sechdrs and &load_info->secstrings populated.
* @flags: Load flags, relevant to suppress version loading, see
* uapi/linux/module.h
*
* If we're ignoring modversions based on @flags, zero all version indices
* and return validity. Othewrise check:
*
* * If "__version_ext_crcs" is present, "__version_ext_names" is present
* * There is a name present for every crc
*
* Then populate:
*
* * &load_info->index.vers
* * &load_info->index.vers_ext_crc
* * &load_info->index.vers_ext_names
*
* if present.
*
* Return: %0 if valid, %-ENOEXEC on failure.
*/
static int elf_validity_cache_index_versions(struct load_info *info, int flags)
{
unsigned int vers_ext_crc;
unsigned int vers_ext_name;
size_t crc_count;
size_t remaining_len;
size_t name_size;
char *name;
/* If modversions were suppressed, pretend we didn't find any */
if (flags & MODULE_INIT_IGNORE_MODVERSIONS) {
info->index.vers = 0;
info->index.vers_ext_crc = 0;
info->index.vers_ext_name = 0;
return 0;
}
vers_ext_crc = find_sec(info, "__version_ext_crcs");
vers_ext_name = find_sec(info, "__version_ext_names");
/* If we have one field, we must have the other */
if (!!vers_ext_crc != !!vers_ext_name) {
pr_err("extended version crc+name presence does not match");
return -ENOEXEC;
}
/*
* If we have extended version information, we should have the same
* number of entries in every section.
*/
if (vers_ext_crc) {
crc_count = info->sechdrs[vers_ext_crc].sh_size / sizeof(u32);
name = (void *)info->hdr +
info->sechdrs[vers_ext_name].sh_offset;
remaining_len = info->sechdrs[vers_ext_name].sh_size;
while (crc_count--) {
name_size = strnlen(name, remaining_len) + 1;
if (name_size > remaining_len) {
pr_err("more extended version crcs than names");
return -ENOEXEC;
}
remaining_len -= name_size;
name += name_size;
}
}
info->index.vers = find_sec(info, "__versions");
info->index.vers_ext_crc = vers_ext_crc;
info->index.vers_ext_name = vers_ext_name;
return 0;
}
/**
* elf_validity_cache_index() - Resolve, validate, cache section indices
* @info: Load info to read from and update.
@ -2087,9 +2163,7 @@ static int elf_validity_cache_index_str(struct load_info *info)
* * elf_validity_cache_index_mod()
* * elf_validity_cache_index_sym()
* * elf_validity_cache_index_str()
*
* If versioning is not suppressed via flags, load the version index from
* a section called "__versions" with no validation.
* * elf_validity_cache_index_versions()
*
* If CONFIG_SMP is enabled, load the percpu section by name with no
* validation.
@ -2112,11 +2186,9 @@ static int elf_validity_cache_index(struct load_info *info, int flags)
err = elf_validity_cache_index_str(info);
if (err < 0)
return err;
if (flags & MODULE_INIT_IGNORE_MODVERSIONS)
info->index.vers = 0; /* Pretend no __versions section! */
else
info->index.vers = find_sec(info, "__versions");
err = elf_validity_cache_index_versions(info, flags);
if (err < 0)
return err;
info->index.pcpu = find_pcpusec(info);
@ -2327,6 +2399,10 @@ static int rewrite_section_headers(struct load_info *info, int flags)
/* Track but don't keep modinfo and version sections. */
info->sechdrs[info->index.vers].sh_flags &= ~(unsigned long)SHF_ALLOC;
info->sechdrs[info->index.vers_ext_crc].sh_flags &=
~(unsigned long)SHF_ALLOC;
info->sechdrs[info->index.vers_ext_name].sh_flags &=
~(unsigned long)SHF_ALLOC;
info->sechdrs[info->index.info].sh_flags &= ~(unsigned long)SHF_ALLOC;
return 0;

View File

@ -13,17 +13,34 @@
int check_version(const struct load_info *info,
const char *symname,
struct module *mod,
const s32 *crc)
const u32 *crc)
{
Elf_Shdr *sechdrs = info->sechdrs;
unsigned int versindex = info->index.vers;
unsigned int i, num_versions;
struct modversion_info *versions;
struct modversion_info_ext version_ext;
/* Exporting module didn't supply crcs? OK, we're already tainted. */
if (!crc)
return 1;
/* If we have extended version info, rely on it */
if (info->index.vers_ext_crc) {
for_each_modversion_info_ext(version_ext, info) {
if (strcmp(version_ext.name, symname) != 0)
continue;
if (*version_ext.crc == *crc)
return 1;
pr_debug("Found checksum %X vs module %X\n",
*crc, *version_ext.crc);
goto bad_version;
}
pr_warn_once("%s: no extended symbol version for %s\n",
info->name, symname);
return 1;
}
/* No versions at all? modprobe --force does this. */
if (versindex == 0)
return try_to_force_load(mod, symname) == 0;
@ -87,6 +104,34 @@ int same_magic(const char *amagic, const char *bmagic,
return strcmp(amagic, bmagic) == 0;
}
void modversion_ext_start(const struct load_info *info,
struct modversion_info_ext *start)
{
unsigned int crc_idx = info->index.vers_ext_crc;
unsigned int name_idx = info->index.vers_ext_name;
Elf_Shdr *sechdrs = info->sechdrs;
/*
* Both of these fields are needed for this to be useful
* Any future fields should be initialized to NULL if absent.
*/
if (crc_idx == 0 || name_idx == 0) {
start->remaining = 0;
return;
}
start->crc = (const u32 *)sechdrs[crc_idx].sh_addr;
start->name = (const char *)sechdrs[name_idx].sh_addr;
start->remaining = sechdrs[crc_idx].sh_size / sizeof(*start->crc);
}
void modversion_ext_advance(struct modversion_info_ext *vers)
{
vers->remaining--;
vers->crc++;
vers->name += strlen(vers->name) + 1;
}
/*
* Generate the signature for all relevant module structures here.
* If these change, we don't want to try to parse the module.

View File

@ -329,10 +329,11 @@ $(obj)/bindings/bindings_helpers_generated.rs: private bindgen_target_extra = ;
$(obj)/bindings/bindings_helpers_generated.rs: $(src)/helpers/helpers.c FORCE
$(call if_changed_dep,bindgen)
rust_exports = $(NM) -p --defined-only $(1) | awk '$$2~/(T|R|D|B)/ && $$3!~/__cfi/ { printf $(2),$(3) }'
quiet_cmd_exports = EXPORTS $@
cmd_exports = \
$(NM) -p --defined-only $< \
| awk '$$2~/(T|R|D|B)/ && $$3!~/__cfi/ {printf "EXPORT_SYMBOL_RUST_GPL(%s);\n",$$3}' > $@
$(call rust_exports,$<,"EXPORT_SYMBOL_RUST_GPL(%s);\n",$$3) > $@
$(obj)/exports_core_generated.h: $(obj)/core.o FORCE
$(call if_changed,exports)
@ -401,11 +402,36 @@ ifneq ($(or $(CONFIG_ARM64),$(and $(CONFIG_RISCV),$(CONFIG_64BIT))),)
__ashlti3 __lshrti3
endif
ifdef CONFIG_MODVERSIONS
cmd_gendwarfksyms = $(if $(skip_gendwarfksyms),, \
$(call rust_exports,$@,"%s\n",$$3) | \
scripts/gendwarfksyms/gendwarfksyms \
$(if $(KBUILD_GENDWARFKSYMS_STABLE), --stable) \
$(if $(KBUILD_SYMTYPES), --symtypes $(@:.o=.symtypes),) \
$@ >> $(dot-target).cmd)
endif
define rule_rustc_library
$(call cmd_and_fixdep,rustc_library)
$(call cmd,gen_objtooldep)
$(call cmd,gendwarfksyms)
endef
define rule_rust_cc_library
$(call if_changed_rule,cc_o_c)
$(call cmd,force_checksrc)
$(call cmd,gendwarfksyms)
endef
# helpers.o uses the same export mechanism as Rust libraries, so ensure symbol
# versions are calculated for the helpers too.
$(obj)/helpers/helpers.o: $(src)/helpers/helpers.c $(recordmcount_source) FORCE
+$(call if_changed_rule,rust_cc_library)
# Disable symbol versioning for exports.o to avoid conflicts with the actual
# symbol versions generated from Rust objects.
$(obj)/exports.o: private skip_gendwarfksyms = 1
$(obj)/core.o: private skip_clippy = 1
$(obj)/core.o: private skip_flags = -Wunreachable_pub
$(obj)/core.o: private rustc_objcopy = $(foreach sym,$(redirect-intrinsics),--redefine-sym $(sym)=__rust$(sym))
@ -417,13 +443,16 @@ ifneq ($(or $(CONFIG_X86_64),$(CONFIG_X86_32)),)
$(obj)/core.o: scripts/target.json
endif
$(obj)/compiler_builtins.o: private skip_gendwarfksyms = 1
$(obj)/compiler_builtins.o: private rustc_objcopy = -w -W '__*'
$(obj)/compiler_builtins.o: $(src)/compiler_builtins.rs $(obj)/core.o FORCE
+$(call if_changed_rule,rustc_library)
$(obj)/build_error.o: private skip_gendwarfksyms = 1
$(obj)/build_error.o: $(src)/build_error.rs $(obj)/compiler_builtins.o FORCE
+$(call if_changed_rule,rustc_library)
$(obj)/ffi.o: private skip_gendwarfksyms = 1
$(obj)/ffi.o: $(src)/ffi.rs $(obj)/compiler_builtins.o FORCE
+$(call if_changed_rule,rustc_library)
@ -435,6 +464,7 @@ $(obj)/bindings.o: $(src)/bindings/lib.rs \
+$(call if_changed_rule,rustc_library)
$(obj)/uapi.o: private rustc_target_flags = --extern ffi
$(obj)/uapi.o: private skip_gendwarfksyms = 1
$(obj)/uapi.o: $(src)/uapi/lib.rs \
$(obj)/ffi.o \
$(obj)/uapi/uapi_generated.rs FORCE

View File

@ -53,7 +53,8 @@ hostprogs += unifdef
targets += module.lds
subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins
subdir-$(CONFIG_MODVERSIONS) += genksyms
subdir-$(CONFIG_GENKSYMS) += genksyms
subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_IPE) += ipe

View File

@ -107,13 +107,24 @@ cmd_cpp_i_c = $(CPP) $(c_flags) -o $@ $<
$(obj)/%.i: $(obj)/%.c FORCE
$(call if_changed_dep,cpp_i_c)
getexportsymbols = $(NM) $@ | sed -n 's/.* __export_symbol_\(.*\)/$(1)/p'
gendwarfksyms = $(objtree)/scripts/gendwarfksyms/gendwarfksyms \
$(if $(KBUILD_SYMTYPES), --symtypes $(@:.o=.symtypes)) \
$(if $(KBUILD_GENDWARFKSYMS_STABLE), --stable)
genksyms = $(objtree)/scripts/genksyms/genksyms \
$(if $(KBUILD_SYMTYPES), -T $(@:.o=.symtypes)) \
$(if $(KBUILD_PRESERVE), -p) \
$(addprefix -r , $(wildcard $(@:.o=.symref)))
# These mirror gensymtypes_S and co below, keep them in synch.
ifdef CONFIG_GENDWARFKSYMS
cmd_gensymtypes_c = $(if $(skip_gendwarfksyms),, \
$(call getexportsymbols,\1) | $(gendwarfksyms) $@)
else
cmd_gensymtypes_c = $(CPP) -D__GENKSYMS__ $(c_flags) $< | $(genksyms)
endif # CONFIG_GENDWARFKSYMS
# LLVM assembly
# Generate .ll files from .c
@ -286,14 +297,26 @@ $(obj)/%.rs: $(obj)/%.rs.S FORCE
# This is convoluted. The .S file must first be preprocessed to run guards and
# expand names, then the resulting exports must be constructed into plain
# EXPORT_SYMBOL(symbol); to build our dummy C file, and that gets preprocessed
# to make the genksyms input.
# to make the genksyms input or compiled into an object for gendwarfksyms.
#
# These mirror gensymtypes_c and co above, keep them in synch.
cmd_gensymtypes_S = \
{ echo "\#include <linux/kernel.h>" ; \
echo "\#include <asm/asm-prototypes.h>" ; \
$(NM) $@ | sed -n 's/.* __export_symbol_\(.*\)/EXPORT_SYMBOL(\1);/p' ; } | \
$(CPP) -D__GENKSYMS__ $(c_flags) -xc - | $(genksyms)
getasmexports = \
{ echo "\#include <linux/kernel.h>" ; \
echo "\#include <linux/string.h>" ; \
echo "\#include <asm/asm-prototypes.h>" ; \
$(call getexportsymbols,EXPORT_SYMBOL(\1);) ; }
ifdef CONFIG_GENDWARFKSYMS
cmd_gensymtypes_S = \
$(getasmexports) | \
$(CC) $(c_flags) -c -o $(@:.o=.gendwarfksyms.o) -xc -; \
$(call getexportsymbols,\1) | \
$(gendwarfksyms) $(@:.o=.gendwarfksyms.o)
else
cmd_gensymtypes_S = \
$(getasmexports) | \
$(CPP) -D__GENKSYMS__ $(c_flags) -xc - | $(genksyms)
endif # CONFIG_GENDWARFKSYMS
quiet_cmd_cpp_s_S = CPP $(quiet_modtag) $@
cmd_cpp_s_S = $(CPP) $(a_flags) -o $@ $<

View File

@ -1,6 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
# Configuration heplers
cmd_merge_fragments = \
$(srctree)/scripts/kconfig/merge_config.sh \
$4 -m -O $(objtree) $(srctree)/arch/$(SRCARCH)/configs/$2 \
$(foreach config,$3,$(srctree)/arch/$(SRCARCH)/configs/$(config).config)
# Creates 'merged defconfigs'
# ---------------------------------------------------------------------------
# Usage:
@ -8,9 +13,7 @@
#
# Input config fragments without '.config' suffix
define merge_into_defconfig
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/kconfig/merge_config.sh \
-m -O $(objtree) $(srctree)/arch/$(SRCARCH)/configs/$(1) \
$(foreach config,$(2),$(srctree)/arch/$(SRCARCH)/configs/$(config).config)
$(call cmd,merge_fragments,$1,$2)
+$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig
endef
@ -22,8 +25,6 @@ endef
#
# Input config fragments without '.config' suffix
define merge_into_defconfig_override
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/kconfig/merge_config.sh \
-Q -m -O $(objtree) $(srctree)/arch/$(SRCARCH)/configs/$(1) \
$(foreach config,$(2),$(srctree)/arch/$(SRCARCH)/configs/$(config).config)
$(call cmd,merge_fragments,$1,$2,-Q)
+$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig
endef

View File

@ -43,6 +43,8 @@ MODPOST = $(objtree)/scripts/mod/modpost
modpost-args = \
$(if $(CONFIG_MODULES),-M) \
$(if $(CONFIG_MODVERSIONS),-m) \
$(if $(CONFIG_BASIC_MODVERSIONS),-b) \
$(if $(CONFIG_EXTENDED_MODVERSIONS),-x) \
$(if $(CONFIG_MODULE_SRCVERSION_ALL),-a) \
$(if $(CONFIG_SECTION_MISMATCH_WARN_ONLY),,-E) \
$(if $(KBUILD_MODPOST_WARN),-w) \

2
scripts/gendwarfksyms/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
/gendwarfksyms

View File

@ -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

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(struct cache_item));
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
scripts/gendwarfksyms/die.c Normal file
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 15
/* {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(struct die));
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(struct die_fragment));
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

View File

@ -0,0 +1,157 @@
/* 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_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_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__ */

View File

@ -0,0 +1,30 @@
// 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;

View File

@ -0,0 +1,263 @@
/* 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)
*/
/*
* 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:long unsigned int|unsigned long]] 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:long unsigned int|unsigned long]] 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)
*/
#endif /* __KABI_EX_H__ */

View File

@ -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);

View File

@ -0,0 +1,187 @@
// 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?");
}
symbol_read_exports(stdin);
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;
}

View File

@ -0,0 +1,296 @@
/* 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);
void 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_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_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, uintptr_t addr)
{
cache_set(cache, addr, 1);
}
static inline bool __cache_was_expanded(struct cache *cache, uintptr_t addr)
{
return cache_get(cache, addr) == 1;
}
static inline void cache_mark_expanded(struct cache *cache, void *addr)
{
__cache_mark_expanded(cache, (uintptr_t)addr);
}
static inline bool cache_was_expanded(struct cache *cache, void *addr)
{
return __cache_was_expanded(cache, (uintptr_t)addr);
}
/*
* 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_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 */

View File

@ -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);
}

View File

@ -0,0 +1,341 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#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;
}
void 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(struct symbol));
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);
}
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 (%lu) 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, %lx }", 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);
}

View File

@ -0,0 +1,481 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#define _GNU_SOURCE
#include <inttypes.h>
#include <stdio.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(struct type_list_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 void type_map_add(const char *name, struct type_expansion *type)
{
struct type_expansion *e;
if (type_map_get(name, &e)) {
e = xmalloc(sizeof(struct type_expansion));
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;
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));
}
}
static void type_map_write(FILE *file)
{
struct type_expansion *e;
struct hlist_node *tmp;
if (!file)
return;
hash_for_each_safe(type_map, e, tmp, hash) {
checkp(fputs(e->name, file));
checkp(fputs(" ", file));
type_list_write(&e->expanded, file);
checkp(fputs("\n", file));
}
}
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)
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 list_head *list)
{
struct type_list_entry *entry;
struct type_expansion *e;
/* Calculate a CRC over an expanded type string */
list_for_each_entry(entry, list, list) {
if (is_type_prefix(entry->str)) {
check(type_map_get(entry->str, &e));
/*
* 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->expanded);
}
} else {
version_add(version, entry->str);
}
}
}
static void calculate_version(struct version *version, struct list_head *list)
{
version_init(version);
__calculate_version(version, list);
cache_free(&expansion_cache);
}
static void __type_expand(struct die *cache, struct type_expansion *type,
bool recursive);
static void type_expand_child(struct die *cache, struct type_expansion *type,
bool recursive)
{
struct type_expansion child;
char *name;
name = get_type_name(cache);
if (!name) {
__type_expand(cache, type, recursive);
return;
}
if (recursive && !__cache_was_expanded(&expansion_cache, cache->addr)) {
__cache_mark_expanded(&expansion_cache, cache->addr);
type_expansion_init(&child);
__type_expand(cache, &child, true);
type_map_add(name, &child);
type_expansion_free(&child);
}
type_expansion_append(type, name, name);
}
static void __type_expand(struct die *cache, struct type_expansion *type,
bool recursive)
{
struct die_fragment *df;
struct die *child;
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);
type_expand_child(child, type, recursive);
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(struct die *cache, struct type_expansion *type,
bool recursive)
{
type_expansion_init(type);
__type_expand(cache, type, recursive);
cache_free(&expansion_cache);
}
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(cache, &type, true);
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(cache, &type, false);
/* If the symbol already has a version, don't calculate it again. */
if (sym->state != SYMBOL_PROCESSED) {
calculate_version(&version, &type.expanded);
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();
}

View File

@ -12,18 +12,19 @@
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <stdarg.h>
#include <getopt.h>
#include <hashtable.h>
#include "genksyms.h"
/*----------------------------------------------------------------------*/
#define HASH_BUCKETS 4096
static struct symbol *symtab[HASH_BUCKETS];
static HASHTABLE_DEFINE(symbol_hashtable, 1U << 12);
static FILE *debugfile;
int cur_line = 1;
@ -60,7 +61,7 @@ static void print_type_name(enum symbol_type type, const char *name);
/*----------------------------------------------------------------------*/
static const unsigned int crctab32[] = {
static const uint32_t crctab32[] = {
0x00000000U, 0x77073096U, 0xee0e612cU, 0x990951baU, 0x076dc419U,
0x706af48fU, 0xe963a535U, 0x9e6495a3U, 0x0edb8832U, 0x79dcb8a4U,
0xe0d5e91eU, 0x97d2d988U, 0x09b64c2bU, 0x7eb17cbdU, 0xe7b82d07U,
@ -115,19 +116,19 @@ static const unsigned int crctab32[] = {
0x2d02ef8dU
};
static unsigned long partial_crc32_one(unsigned char c, unsigned long crc)
static uint32_t partial_crc32_one(uint8_t c, uint32_t crc)
{
return crctab32[(crc ^ c) & 0xff] ^ (crc >> 8);
}
static unsigned long partial_crc32(const char *s, unsigned long crc)
static uint32_t partial_crc32(const char *s, uint32_t crc)
{
while (*s)
crc = partial_crc32_one(*s++, crc);
return crc;
}
static unsigned long crc32(const char *s)
static uint32_t crc32(const char *s)
{
return partial_crc32(s, 0xffffffff) ^ 0xffffffff;
}
@ -151,14 +152,14 @@ static enum symbol_type map_to_ns(enum symbol_type t)
struct symbol *find_symbol(const char *name, enum symbol_type ns, int exact)
{
unsigned long h = crc32(name) % HASH_BUCKETS;
struct symbol *sym;
for (sym = symtab[h]; sym; sym = sym->hash_next)
hash_for_each_possible(symbol_hashtable, sym, hnode, crc32(name)) {
if (map_to_ns(sym->type) == map_to_ns(ns) &&
strcmp(name, sym->name) == 0 &&
sym->is_declared)
break;
}
if (exact && sym && sym->type != ns)
return NULL;
@ -224,64 +225,56 @@ static struct symbol *__add_symbol(const char *name, enum symbol_type type,
return NULL;
}
h = crc32(name) % HASH_BUCKETS;
for (sym = symtab[h]; sym; sym = sym->hash_next) {
if (map_to_ns(sym->type) == map_to_ns(type) &&
strcmp(name, sym->name) == 0) {
if (is_reference)
/* fall through */ ;
else if (sym->type == type &&
equal_list(sym->defn, defn)) {
if (!sym->is_declared && sym->is_override) {
print_location();
print_type_name(type, name);
fprintf(stderr, " modversion is "
"unchanged\n");
}
sym->is_declared = 1;
return sym;
} else if (!sym->is_declared) {
if (sym->is_override && flag_preserve) {
print_location();
fprintf(stderr, "ignoring ");
print_type_name(type, name);
fprintf(stderr, " modversion change\n");
sym->is_declared = 1;
return sym;
} else {
status = is_unknown_symbol(sym) ?
STATUS_DEFINED : STATUS_MODIFIED;
}
} else {
error_with_pos("redefinition of %s", name);
return sym;
h = crc32(name);
hash_for_each_possible(symbol_hashtable, sym, hnode, h) {
if (map_to_ns(sym->type) != map_to_ns(type) ||
strcmp(name, sym->name))
continue;
if (is_reference) {
break;
} else if (sym->type == type && equal_list(sym->defn, defn)) {
if (!sym->is_declared && sym->is_override) {
print_location();
print_type_name(type, name);
fprintf(stderr, " modversion is unchanged\n");
}
sym->is_declared = 1;
} else if (sym->is_declared) {
error_with_pos("redefinition of %s", name);
} else if (sym->is_override && flag_preserve) {
print_location();
fprintf(stderr, "ignoring ");
print_type_name(type, name);
fprintf(stderr, " modversion change\n");
sym->is_declared = 1;
} else {
status = is_unknown_symbol(sym) ?
STATUS_DEFINED : STATUS_MODIFIED;
break;
}
free_list(defn, NULL);
return sym;
}
if (sym) {
struct symbol **psym;
hash_del(&sym->hnode);
for (psym = &symtab[h]; *psym; psym = &(*psym)->hash_next) {
if (*psym == sym) {
*psym = sym->hash_next;
break;
}
}
free_list(sym->defn, NULL);
free(sym->name);
free(sym);
--nsyms;
}
sym = xmalloc(sizeof(*sym));
sym->name = name;
sym->name = xstrdup(name);
sym->type = type;
sym->defn = defn;
sym->expansion_trail = NULL;
sym->visited = NULL;
sym->is_extern = is_extern;
sym->hash_next = symtab[h];
symtab[h] = sym;
hash_add(symbol_hashtable, &sym->hnode, h);
sym->is_declared = !is_reference;
sym->status = status;
@ -480,7 +473,7 @@ static void read_reference(FILE *f)
defn = def;
def = read_node(f);
}
subsym = add_reference_symbol(xstrdup(sym->string), sym->tag,
subsym = add_reference_symbol(sym->string, sym->tag,
defn, is_extern);
subsym->is_override = is_override;
free_node(sym);
@ -525,7 +518,7 @@ static void print_list(FILE * f, struct string_list *list)
}
}
static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc)
static uint32_t expand_and_crc_sym(struct symbol *sym, uint32_t crc)
{
struct string_list *list = sym->defn;
struct string_list **e, **b;
@ -632,7 +625,7 @@ static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc)
void export_symbol(const char *name)
{
struct symbol *sym;
unsigned long crc;
uint32_t crc;
int has_changed = 0;
sym = find_symbol(name, SYM_NORMAL, 0);
@ -680,7 +673,7 @@ void export_symbol(const char *name)
if (flag_dump_defs)
fputs(">\n", debugfile);
printf("#SYMVER %s 0x%08lx\n", name, crc);
printf("#SYMVER %s 0x%08lx\n", name, (unsigned long)crc);
}
/*----------------------------------------------------------------------*/
@ -832,9 +825,9 @@ int main(int argc, char **argv)
}
if (flag_debug) {
fprintf(debugfile, "Hash table occupancy %d/%d = %g\n",
nsyms, HASH_BUCKETS,
(double)nsyms / (double)HASH_BUCKETS);
fprintf(debugfile, "Hash table occupancy %d/%zd = %g\n",
nsyms, HASH_SIZE(symbol_hashtable),
(double)nsyms / HASH_SIZE(symbol_hashtable));
}
if (dumpfile)

View File

@ -14,6 +14,8 @@
#include <stdio.h>
#include <list_types.h>
enum symbol_type {
SYM_NORMAL, SYM_TYPEDEF, SYM_ENUM, SYM_STRUCT, SYM_UNION,
SYM_ENUM_CONST
@ -31,8 +33,8 @@ struct string_list {
};
struct symbol {
struct symbol *hash_next;
const char *name;
struct hlist_node hnode;
char *name;
enum symbol_type type;
struct string_list *defn;
struct symbol *expansion_trail;

View File

@ -152,14 +152,19 @@ simple_declaration:
;
init_declarator_list_opt:
/* empty */ { $$ = NULL; }
| init_declarator_list
/* empty */ { $$ = NULL; }
| init_declarator_list { free_list(decl_spec, NULL); $$ = $1; }
;
init_declarator_list:
init_declarator
{ struct string_list *decl = *$1;
*$1 = NULL;
/* avoid sharing among multiple init_declarators */
if (decl_spec)
decl_spec = copy_list_range(decl_spec, NULL);
add_symbol(current_name,
is_typedef ? SYM_TYPEDEF : SYM_NORMAL, decl, is_extern);
current_name = NULL;
@ -170,6 +175,11 @@ init_declarator_list:
*$3 = NULL;
free_list(*$2, NULL);
*$2 = decl_spec;
/* avoid sharing among multiple init_declarators */
if (decl_spec)
decl_spec = copy_list_range(decl_spec, NULL);
add_symbol(current_name,
is_typedef ? SYM_TYPEDEF : SYM_NORMAL, decl, is_extern);
current_name = NULL;
@ -472,12 +482,12 @@ enumerator_list:
enumerator:
IDENT
{
const char *name = strdup((*$1)->string);
const char *name = (*$1)->string;
add_symbol(name, SYM_ENUM_CONST, NULL, 0);
}
| IDENT '=' EXPRESSION_PHRASE
{
const char *name = strdup((*$1)->string);
const char *name = (*$1)->string;
struct string_list *expr = copy_list_range(*$3, *$2);
add_symbol(name, SYM_ENUM_CONST, expr, 0);
}

View File

@ -105,9 +105,11 @@ configfiles = $(wildcard $(srctree)/kernel/configs/$(1) $(srctree)/arch/$(SRCARC
all-config-fragments = $(call configfiles,*.config)
config-fragments = $(call configfiles,$@)
cmd_merge_fragments = $(srctree)/scripts/kconfig/merge_config.sh -m $(KCONFIG_CONFIG) $(config-fragments)
%.config: $(obj)/conf
$(if $(config-fragments),, $(error $@ fragment does not exists on this architecture))
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/kconfig/merge_config.sh -m $(KCONFIG_CONFIG) $(config-fragments)
$(call cmd,merge_fragments)
$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig
PHONY += tinyconfig

View File

@ -1464,8 +1464,8 @@ void ConfigMainWindow::loadConfig(void)
{
QString str;
str = QFileDialog::getOpenFileName(this, "", configname);
if (str.isNull())
str = QFileDialog::getOpenFileName(this, QString(), configname);
if (str.isEmpty())
return;
if (conf_read(str.toLocal8Bit().constData()))
@ -1491,8 +1491,8 @@ void ConfigMainWindow::saveConfigAs(void)
{
QString str;
str = QFileDialog::getSaveFileName(this, "", configname);
if (str.isNull())
str = QFileDialog::getSaveFileName(this, QString(), configname);
if (str.isEmpty())
return;
if (conf_write(str.toLocal8Bit().constData())) {

View File

@ -33,6 +33,10 @@ static bool module_enabled;
static bool modversions;
/* Is CONFIG_MODULE_SRCVERSION_ALL set? */
static bool all_versions;
/* Is CONFIG_BASIC_MODVERSIONS set? */
static bool basic_modversions;
/* Is CONFIG_EXTENDED_MODVERSIONS set? */
static bool extended_modversions;
/* If we are modposting external module set to 1 */
static bool external_module;
/* Only warn about unresolved symbols */
@ -1805,6 +1809,49 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
}
}
/**
* Record CRCs for unresolved symbols, supporting long names
*/
static void add_extended_versions(struct buffer *b, struct module *mod)
{
struct symbol *s;
if (!extended_modversions)
return;
buf_printf(b, "\n");
buf_printf(b, "static const u32 ____version_ext_crcs[]\n");
buf_printf(b, "__used __section(\"__version_ext_crcs\") = {\n");
list_for_each_entry(s, &mod->unresolved_symbols, list) {
if (!s->module)
continue;
if (!s->crc_valid) {
warn("\"%s\" [%s.ko] has no CRC!\n",
s->name, mod->name);
continue;
}
buf_printf(b, "\t0x%08x,\n", s->crc);
}
buf_printf(b, "};\n");
buf_printf(b, "static const char ____version_ext_names[]\n");
buf_printf(b, "__used __section(\"__version_ext_names\") =\n");
list_for_each_entry(s, &mod->unresolved_symbols, list) {
if (!s->module)
continue;
if (!s->crc_valid)
/*
* We already warned on this when producing the crc
* table.
* We need to skip its name too, as the indexes in
* both tables need to align.
*/
continue;
buf_printf(b, "\t\"%s\\0\"\n", s->name);
}
buf_printf(b, ";\n");
}
/**
* Record CRCs for unresolved symbols
**/
@ -1812,7 +1859,7 @@ static void add_versions(struct buffer *b, struct module *mod)
{
struct symbol *s;
if (!modversions)
if (!basic_modversions)
return;
buf_printf(b, "\n");
@ -1828,11 +1875,16 @@ static void add_versions(struct buffer *b, struct module *mod)
continue;
}
if (strlen(s->name) >= MODULE_NAME_LEN) {
error("too long symbol \"%s\" [%s.ko]\n",
s->name, mod->name);
break;
if (extended_modversions) {
/* this symbol will only be in the extended info */
continue;
} else {
error("too long symbol \"%s\" [%s.ko]\n",
s->name, mod->name);
break;
}
}
buf_printf(b, "\t{ %#8x, \"%s\" },\n",
buf_printf(b, "\t{ 0x%08x, \"%s\" },\n",
s->crc, s->name);
}
@ -1961,6 +2013,7 @@ static void write_mod_c_file(struct module *mod)
add_header(&buf, mod);
add_exported_symbols(&buf, mod);
add_versions(&buf, mod);
add_extended_versions(&buf, mod);
add_depends(&buf, mod);
buf_printf(&buf, "\n");
@ -2126,7 +2179,7 @@ int main(int argc, char **argv)
LIST_HEAD(dump_lists);
struct dump_list *dl, *dl2;
while ((opt = getopt(argc, argv, "ei:MmnT:to:au:WwENd:")) != -1) {
while ((opt = getopt(argc, argv, "ei:MmnT:to:au:WwENd:xb")) != -1) {
switch (opt) {
case 'e':
external_module = true;
@ -2175,6 +2228,12 @@ int main(int argc, char **argv)
case 'd':
missing_namespace_deps = optarg;
break;
case 'b':
basic_modversions = true;
break;
case 'x':
extended_modversions = true;
break;
default:
exit(1);
}

View File

@ -22,7 +22,6 @@ license=(GPL-2.0-only)
makedepends=(
bc
bison
cpio
flex
gettext
kmod

View File

@ -5,10 +5,12 @@
#
# Simple script to generate a deb package for a Linux kernel. All the
# complexity of what to do with a kernel after it is installed or removed
# is left to other scripts and packages: they can install scripts in the
# /etc/kernel/{pre,post}{inst,rm}.d/ directories (or an alternative location
# specified in KDEB_HOOKDIR) that will be called on package install and
# removal.
# is left to other scripts and packages. Scripts can be placed into the
# preinst, postinst, prerm and postrm directories in /etc/kernel or
# /usr/share/kernel. A different list of search directories can be given
# via KDEB_HOOKDIR. Scripts in directories earlier in the list will
# override scripts of the same name in later directories. The script will
# be called on package installation and removal.
set -eu
@ -74,10 +76,8 @@ install_maint_scripts () {
# kernel packages, as well as kernel packages built using make-kpkg.
# make-kpkg sets $INITRD to indicate whether an initramfs is wanted, and
# so do we; recent versions of dracut and initramfs-tools will obey this.
debhookdir=${KDEB_HOOKDIR:-/etc/kernel}
debhookdir=${KDEB_HOOKDIR:-/etc/kernel /usr/share/kernel}
for script in postinst postrm preinst prerm; do
mkdir -p "${pdir}${debhookdir}/${script}.d"
mkdir -p "${pdir}/DEBIAN"
cat <<-EOF > "${pdir}/DEBIAN/${script}"
#!/bin/sh
@ -90,7 +90,15 @@ install_maint_scripts () {
# Tell initramfs builder whether it's wanted
export INITRD=$(if_enabled_echo CONFIG_BLK_DEV_INITRD Yes No)
test -d ${debhookdir}/${script}.d && run-parts --arg="${KERNELRELEASE}" --arg="/${installed_image_path}" ${debhookdir}/${script}.d
# run-parts will error out if one of its directory arguments does not
# exist, so filter the list of hook directories accordingly.
hookdirs=
for dir in ${debhookdir}; do
test -d "\$dir/${script}.d" || continue
hookdirs="\$hookdirs \$dir/${script}.d"
done
hookdirs="\${hookdirs# }"
test -n "\$hookdirs" && run-parts --arg="${KERNELRELEASE}" --arg="/${installed_image_path}" \$hookdirs
exit 0
EOF
chmod 755 "${pdir}/DEBIAN/${script}"

View File

@ -49,17 +49,10 @@ mkdir -p "${destdir}"
# This caters to host programs that participate in Kbuild. objtool and
# resolve_btfids are out of scope.
if [ "${CC}" != "${HOSTCC}" ]; then
echo "Rebuilding host programs with ${CC}..."
# This leverages external module building.
# - Clear sub_make_done to allow the top-level Makefile to redo sub-make.
# - Filter out --no-print-directory to print "Entering directory" logs
# when Make changes the working directory.
unset sub_make_done
MAKEFLAGS=$(echo "${MAKEFLAGS}" | sed s/--no-print-directory//)
cat <<-'EOF' > "${destdir}/Kbuild"
subdir-y := scripts
cat "${destdir}/scripts/Makefile" - <<-'EOF' > "${destdir}/scripts/Kbuild"
subdir-y += basic
hostprogs-always-y += mod/modpost
mod/modpost-objs := $(addprefix mod/, modpost.o file2alias.o sumversion.o symsearch.o)
EOF
# HOSTCXX is not overridden. The C++ compiler is used to build:
@ -67,20 +60,12 @@ if [ "${CC}" != "${HOSTCC}" ]; then
# - GCC plugins, which will not work on the installed system even after
# being rebuilt.
#
# Use the single-target build to avoid the modpost invocation, which
# would overwrite Module.symvers.
"${MAKE}" HOSTCC="${CC}" KBUILD_OUTPUT=. KBUILD_EXTMOD="${destdir}" scripts/
# Clear VPATH and srcroot because the source files reside in the output
# directory.
# shellcheck disable=SC2016 # $(MAKE), $(CC), and $(build) will be expanded by Make
"${MAKE}" run-command KBUILD_RUN_COMMAND='+$(MAKE) HOSTCC=$(CC) VPATH= srcroot=. $(build)='"${destdir}"/scripts
cat <<-'EOF' > "${destdir}/scripts/Kbuild"
subdir-y := basic
hostprogs-always-y := mod/modpost
mod/modpost-objs := $(addprefix mod/, modpost.o file2alias.o sumversion.o symsearch.o)
EOF
# Run once again to rebuild scripts/basic/ and scripts/mod/modpost.
"${MAKE}" HOSTCC="${CC}" KBUILD_OUTPUT=. KBUILD_EXTMOD="${destdir}" scripts/
rm -f "${destdir}/Kbuild" "${destdir}/scripts/Kbuild"
rm -f "${destdir}/scripts/Kbuild"
fi
find "${destdir}" \( -name '.*.cmd' -o -name '*.o' \) -delete

View File

@ -205,7 +205,7 @@ Priority: optional
Maintainer: $maintainer
Rules-Requires-Root: no
Build-Depends: debhelper-compat (= 12)
Build-Depends-Arch: bc, bison, cpio, flex,
Build-Depends-Arch: bc, bison, flex,
gcc-${host_gnu} <!pkg.${sourcename}.nokernelheaders>,
kmod, libelf-dev:native,
libssl-dev:native, libssl-dev <!pkg.${sourcename}.nokernelheaders>,