mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-14 17:53:39 +00:00
Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild.git
This commit is contained in:
commit
e92f4a48e5
308
Documentation/kbuild/gendwarfksyms.rst
Normal file
308
Documentation/kbuild/gendwarfksyms.rst
Normal 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.
|
@ -21,6 +21,7 @@ Kernel Build System
|
||||
reproducible-builds
|
||||
gcc-plugins
|
||||
llvm
|
||||
gendwarfksyms
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
|
@ -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
|
||||
----------------------------
|
||||
|
||||
|
@ -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
|
||||
**********
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 $@ $<
|
||||
|
@ -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
|
||||
|
@ -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
2
scripts/gendwarfksyms/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
/gendwarfksyms
|
12
scripts/gendwarfksyms/Makefile
Normal file
12
scripts/gendwarfksyms/Makefile
Normal 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
|
51
scripts/gendwarfksyms/cache.c
Normal file
51
scripts/gendwarfksyms/cache.c
Normal 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
166
scripts/gendwarfksyms/die.c
Normal 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;
|
||||
}
|
1159
scripts/gendwarfksyms/dwarf.c
Normal file
1159
scripts/gendwarfksyms/dwarf.c
Normal file
File diff suppressed because it is too large
Load Diff
157
scripts/gendwarfksyms/examples/kabi.h
Normal file
157
scripts/gendwarfksyms/examples/kabi.h
Normal 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__ */
|
30
scripts/gendwarfksyms/examples/kabi_ex.c
Normal file
30
scripts/gendwarfksyms/examples/kabi_ex.c
Normal 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;
|
263
scripts/gendwarfksyms/examples/kabi_ex.h
Normal file
263
scripts/gendwarfksyms/examples/kabi_ex.h
Normal 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__ */
|
33
scripts/gendwarfksyms/examples/symbolptr.c
Normal file
33
scripts/gendwarfksyms/examples/symbolptr.c
Normal 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);
|
187
scripts/gendwarfksyms/gendwarfksyms.c
Normal file
187
scripts/gendwarfksyms/gendwarfksyms.c
Normal 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;
|
||||
}
|
296
scripts/gendwarfksyms/gendwarfksyms.h
Normal file
296
scripts/gendwarfksyms/gendwarfksyms.h
Normal 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 */
|
336
scripts/gendwarfksyms/kabi.c
Normal file
336
scripts/gendwarfksyms/kabi.c
Normal 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);
|
||||
}
|
341
scripts/gendwarfksyms/symbols.c
Normal file
341
scripts/gendwarfksyms/symbols.c
Normal 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);
|
||||
}
|
481
scripts/gendwarfksyms/types.c
Normal file
481
scripts/gendwarfksyms/types.c
Normal 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();
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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())) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ license=(GPL-2.0-only)
|
||||
makedepends=(
|
||||
bc
|
||||
bison
|
||||
cpio
|
||||
flex
|
||||
gettext
|
||||
kmod
|
||||
|
@ -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}"
|
||||
|
@ -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
|
||||
|
@ -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>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user