summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/.gitignore1
-rw-r--r--scripts/Makefile5
-rw-r--r--scripts/Makefile.build37
-rw-r--r--scripts/Makefile.clang2
-rw-r--r--scripts/Makefile.defconf13
-rw-r--r--scripts/Makefile.extrawarn18
-rw-r--r--scripts/Makefile.lib18
-rw-r--r--scripts/Makefile.modinst2
-rw-r--r--scripts/Makefile.modpost2
-rw-r--r--scripts/Makefile.ubsan10
-rwxr-xr-xscripts/checkpatch.pl30
-rw-r--r--scripts/coccinelle/misc/secs_to_jiffies.cocci22
-rwxr-xr-xscripts/decode_stacktrace.sh16
-rwxr-xr-xscripts/documentation-file-ref-check2
-rwxr-xr-xscripts/gcc-x86_32-has-stack-protector.sh8
-rwxr-xr-xscripts/gcc-x86_64-has-stack-protector.sh4
-rw-r--r--scripts/gdb/linux/cpus.py4
-rw-r--r--scripts/gen_packed_field_checks.c37
-rw-r--r--scripts/gendwarfksyms/.gitignore2
-rw-r--r--scripts/gendwarfksyms/Makefile12
-rw-r--r--scripts/gendwarfksyms/cache.c51
-rw-r--r--scripts/gendwarfksyms/die.c166
-rw-r--r--scripts/gendwarfksyms/dwarf.c1159
-rw-r--r--scripts/gendwarfksyms/examples/kabi.h157
-rw-r--r--scripts/gendwarfksyms/examples/kabi_ex.c30
-rw-r--r--scripts/gendwarfksyms/examples/kabi_ex.h263
-rw-r--r--scripts/gendwarfksyms/examples/symbolptr.c33
-rw-r--r--scripts/gendwarfksyms/gendwarfksyms.c187
-rw-r--r--scripts/gendwarfksyms/gendwarfksyms.h296
-rw-r--r--scripts/gendwarfksyms/kabi.c336
-rw-r--r--scripts/gendwarfksyms/symbols.c341
-rw-r--r--scripts/gendwarfksyms/types.c481
-rwxr-xr-xscripts/generate_rust_analyzer.py90
-rw-r--r--scripts/generate_rust_target.rs18
-rw-r--r--scripts/genksyms/Makefile18
-rw-r--r--scripts/genksyms/genksyms.c107
-rw-r--r--scripts/genksyms/genksyms.h9
-rw-r--r--scripts/genksyms/lex.l17
-rw-r--r--scripts/genksyms/parse.y166
-rwxr-xr-xscripts/get_abi.pl1103
-rwxr-xr-xscripts/get_abi.py214
-rwxr-xr-xscripts/get_feat.pl4
-rw-r--r--scripts/integer-wrap-ignore.scl3
-rw-r--r--scripts/kallsyms.c72
-rw-r--r--scripts/kconfig/Makefile4
-rw-r--r--scripts/kconfig/confdata.c6
-rw-r--r--scripts/kconfig/qconf.cc8
-rw-r--r--scripts/kconfig/symbol.c1
-rwxr-xr-xscripts/kernel-doc165
-rw-r--r--scripts/lib/abi/abi_parser.py628
-rw-r--r--scripts/lib/abi/abi_regex.py234
-rw-r--r--scripts/lib/abi/helpers.py38
-rw-r--r--scripts/lib/abi/system_symbols.py378
-rwxr-xr-xscripts/link-vmlinux.sh10
-rwxr-xr-xscripts/min-tool-version.sh4
-rwxr-xr-xscripts/mksysmap4
-rw-r--r--scripts/mod/devicetable-offsets.c1
-rw-r--r--scripts/mod/file2alias.c45
-rw-r--r--scripts/mod/modpost.c134
-rw-r--r--scripts/mod/modpost.h20
-rw-r--r--scripts/module.lds.S1
-rw-r--r--scripts/package/PKGBUILD3
-rwxr-xr-xscripts/package/builddeb24
-rwxr-xr-xscripts/package/install-extmod-build33
-rwxr-xr-xscripts/package/mkdebian2
-rwxr-xr-xscripts/rust_is_available.sh6
-rw-r--r--scripts/rustdoc_test_gen.rs4
-rw-r--r--scripts/sorttable.c740
-rw-r--r--scripts/sorttable.h497
-rwxr-xr-xscripts/spdxcheck.py6
-rw-r--r--scripts/spelling.txt37
-rw-r--r--scripts/syscall.tbl1
-rwxr-xr-xscripts/tags.sh11
73 files changed, 6400 insertions, 2211 deletions
diff --git a/scripts/.gitignore b/scripts/.gitignore
index 3dbb8bb2457b..c2ef68848da5 100644
--- a/scripts/.gitignore
+++ b/scripts/.gitignore
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
/asn1_compiler
+/gen_packed_field_checks
/generate_rust_target
/insert-sys-cert
/kallsyms
diff --git a/scripts/Makefile b/scripts/Makefile
index 6bcda4b9d054..46f860529df5 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -47,13 +47,14 @@ HOSTCFLAGS_sorttable.o += -DMCOUNT_SORT_ENABLED
endif
# The following programs are only built on demand
-hostprogs += unifdef
+hostprogs += unifdef gen_packed_field_checks
# The module linker script is preprocessed on demand
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
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index c16e4cf54d77..993708d11874 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -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
@@ -183,7 +194,9 @@ endif # CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT
is-standard-object = $(if $(filter-out y%, $(OBJECT_FILES_NON_STANDARD_$(target-stem).o)$(OBJECT_FILES_NON_STANDARD)n),$(is-kernel-object))
+ifdef CONFIG_OBJTOOL
$(obj)/%.o: private objtool-enabled = $(if $(is-standard-object),$(if $(delay-objtool),$(is-single-obj-m),y))
+endif
ifneq ($(findstring 1, $(KBUILD_EXTRA_WARN)),)
cmd_warn_shared_object = $(if $(word 2, $(modname-multi)),$(warning $(kbuild-file): $*.o is added to multiple modules: $(modname-multi)))
@@ -286,14 +299,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 $@ $<
diff --git a/scripts/Makefile.clang b/scripts/Makefile.clang
index 2435efae67f5..b67636b28c35 100644
--- a/scripts/Makefile.clang
+++ b/scripts/Makefile.clang
@@ -12,6 +12,8 @@ CLANG_TARGET_FLAGS_riscv := riscv64-linux-gnu
CLANG_TARGET_FLAGS_s390 := s390x-linux-gnu
CLANG_TARGET_FLAGS_sparc := sparc64-linux-gnu
CLANG_TARGET_FLAGS_x86 := x86_64-linux-gnu
+# This is only for i386 UM builds, which need the 32-bit target not -m32
+CLANG_TARGET_FLAGS_i386 := i386-linux-gnu
CLANG_TARGET_FLAGS_um := $(CLANG_TARGET_FLAGS_$(SUBARCH))
CLANG_TARGET_FLAGS := $(CLANG_TARGET_FLAGS_$(SRCARCH))
diff --git a/scripts/Makefile.defconf b/scripts/Makefile.defconf
index 226ea3df3b4b..a44307f08e9d 100644
--- a/scripts/Makefile.defconf
+++ b/scripts/Makefile.defconf
@@ -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
diff --git a/scripts/Makefile.extrawarn b/scripts/Makefile.extrawarn
index 1d13cecc7cc7..dc081cf46d21 100644
--- a/scripts/Makefile.extrawarn
+++ b/scripts/Makefile.extrawarn
@@ -31,6 +31,11 @@ KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-bounds
ifdef CONFIG_CC_IS_CLANG
# The kernel builds with '-std=gnu11' so use of GNU extensions is acceptable.
KBUILD_CFLAGS += -Wno-gnu
+
+# Clang checks for overflow/truncation with '%p', while GCC does not:
+# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111219
+KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow-non-kprintf)
+KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation-non-kprintf)
else
# gcc inanely warns about local variables called 'main'
@@ -77,6 +82,9 @@ KBUILD_CFLAGS += $(call cc-option,-Werror=designated-init)
# Warn if there is an enum types mismatch
KBUILD_CFLAGS += $(call cc-option,-Wenum-conversion)
+# Explicitly clear padding bits during variable initialization
+KBUILD_CFLAGS += $(call cc-option,-fzero-init-padding-bits=all)
+
KBUILD_CFLAGS += -Wextra
KBUILD_CFLAGS += -Wunused
@@ -102,11 +110,6 @@ KBUILD_CFLAGS += $(call cc-disable-warning, packed-not-aligned)
KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow)
ifdef CONFIG_CC_IS_GCC
KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation)
-else
-# Clang checks for overflow/truncation with '%p', while GCC does not:
-# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111219
-KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow-non-kprintf)
-KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation-non-kprintf)
endif
KBUILD_CFLAGS += $(call cc-disable-warning, stringop-truncation)
@@ -130,7 +133,6 @@ KBUILD_CFLAGS += $(call cc-disable-warning, pointer-to-enum-cast)
KBUILD_CFLAGS += -Wno-tautological-constant-out-of-range-compare
KBUILD_CFLAGS += $(call cc-disable-warning, unaligned-access)
KBUILD_CFLAGS += -Wno-enum-compare-conditional
-KBUILD_CFLAGS += -Wno-enum-enum-conversion
endif
endif
@@ -154,6 +156,10 @@ KBUILD_CFLAGS += -Wno-missing-field-initializers
KBUILD_CFLAGS += -Wno-type-limits
KBUILD_CFLAGS += -Wno-shift-negative-value
+ifdef CONFIG_CC_IS_CLANG
+KBUILD_CFLAGS += -Wno-enum-enum-conversion
+endif
+
ifdef CONFIG_CC_IS_GCC
KBUILD_CFLAGS += -Wno-maybe-uninitialized
endif
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 7395200538da..57620b439a1f 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -166,8 +166,8 @@ _c_flags += $(if $(patsubst n%,, \
$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SANITIZE)$(is-kernel-object)), \
$(CFLAGS_UBSAN))
_c_flags += $(if $(patsubst n%,, \
- $(UBSAN_SIGNED_WRAP_$(target-stem).o)$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SIGNED_WRAP)$(UBSAN_SANITIZE)$(is-kernel-object)), \
- $(CFLAGS_UBSAN_SIGNED_WRAP))
+ $(UBSAN_INTEGER_WRAP_$(target-stem).o)$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_INTEGER_WRAP)$(UBSAN_SANITIZE)$(is-kernel-object)), \
+ $(CFLAGS_UBSAN_INTEGER_WRAP))
endif
ifeq ($(CONFIG_KCOV),y)
@@ -277,6 +277,7 @@ objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call
objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess
objtool-args-$(CONFIG_GCOV_KERNEL) += --no-unreachable
objtool-args-$(CONFIG_PREFIX_SYMBOLS) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES)
+objtool-args-$(CONFIG_OBJTOOL_WERROR) += --Werror --backtrace
objtool-args = $(objtool-args-y) \
$(if $(delay-objtool), --link) \
@@ -287,6 +288,8 @@ delay-objtool := $(or $(CONFIG_LTO_CLANG),$(CONFIG_X86_KERNEL_IBT))
cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool-args) $@)
cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$@: $$(wildcard $(objtool))' ; } >> $(dot-target).cmd)
+objtool-enabled := y
+
endif # CONFIG_OBJTOOL
# Useful for describing the dependency of composite objects
@@ -302,11 +305,11 @@ endef
# ===========================================================================
# These are shared by some Makefile.* files.
-objtool-enabled := y
-
ifdef CONFIG_LTO_CLANG
-# objtool cannot process LLVM IR. Make $(LD) covert LLVM IR to ELF here.
-cmd_ld_single = $(if $(objtool-enabled), ; $(LD) $(ld_flags) -r -o $(tmp-target) $@; mv $(tmp-target) $@)
+# Run $(LD) here to convert LLVM IR to ELF in the following cases:
+# - when this object needs objtool processing, as objtool cannot process LLVM IR
+# - when this is a single-object module, as modpost cannot process LLVM IR
+cmd_ld_single = $(if $(objtool-enabled)$(is-single-obj-m), ; $(LD) $(ld_flags) -r -o $(tmp-target) $@; mv $(tmp-target) $@)
endif
quiet_cmd_cc_o_c = CC $(quiet_modtag) $@
@@ -374,6 +377,9 @@ quiet_cmd_ar = AR $@
quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
+quiet_cmd_strip_relocs = RSTRIP $@
+cmd_strip_relocs = $(OBJCOPY) --remove-section='.rel*' $@
+
# Gzip
# ---------------------------------------------------------------------------
diff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst
index f97c9926ed31..1628198f3e83 100644
--- a/scripts/Makefile.modinst
+++ b/scripts/Makefile.modinst
@@ -105,7 +105,7 @@ else
sig-key := $(CONFIG_MODULE_SIG_KEY)
endif
quiet_cmd_sign = SIGN $@
- cmd_sign = scripts/sign-file $(CONFIG_MODULE_SIG_HASH) "$(sig-key)" certs/signing_key.x509 $@ \
+ cmd_sign = $(objtree)/scripts/sign-file $(CONFIG_MODULE_SIG_HASH) "$(sig-key)" $(objtree)/certs/signing_key.x509 $@ \
$(if $(KBUILD_EXTMOD),|| true)
ifeq ($(sign-only),)
diff --git a/scripts/Makefile.modpost b/scripts/Makefile.modpost
index ab0e94ea6249..d7d45067d08b 100644
--- a/scripts/Makefile.modpost
+++ b/scripts/Makefile.modpost
@@ -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) \
diff --git a/scripts/Makefile.ubsan b/scripts/Makefile.ubsan
index b2d3b273b802..9e35198edbf0 100644
--- a/scripts/Makefile.ubsan
+++ b/scripts/Makefile.ubsan
@@ -14,5 +14,11 @@ ubsan-cflags-$(CONFIG_UBSAN_TRAP) += $(call cc-option,-fsanitize-trap=undefined
export CFLAGS_UBSAN := $(ubsan-cflags-y)
-ubsan-signed-wrap-cflags-$(CONFIG_UBSAN_SIGNED_WRAP) += -fsanitize=signed-integer-overflow
-export CFLAGS_UBSAN_SIGNED_WRAP := $(ubsan-signed-wrap-cflags-y)
+ubsan-integer-wrap-cflags-$(CONFIG_UBSAN_INTEGER_WRAP) += \
+ -fsanitize-undefined-ignore-overflow-pattern=all \
+ -fsanitize=signed-integer-overflow \
+ -fsanitize=unsigned-integer-overflow \
+ -fsanitize=implicit-signed-integer-truncation \
+ -fsanitize=implicit-unsigned-integer-truncation \
+ -fsanitize-ignorelist=$(srctree)/scripts/integer-wrap-ignore.scl
+export CFLAGS_UBSAN_INTEGER_WRAP := $(ubsan-integer-wrap-cflags-y)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 9eed3683ad76..7b28ad331742 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -834,16 +834,6 @@ foreach my $entry (@mode_permission_funcs) {
$mode_perms_search = "(?:${mode_perms_search})";
our %deprecated_apis = (
- "synchronize_rcu_bh" => "synchronize_rcu",
- "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited",
- "call_rcu_bh" => "call_rcu",
- "rcu_barrier_bh" => "rcu_barrier",
- "synchronize_sched" => "synchronize_rcu",
- "synchronize_sched_expedited" => "synchronize_rcu_expedited",
- "call_rcu_sched" => "call_rcu",
- "rcu_barrier_sched" => "rcu_barrier",
- "get_state_synchronize_sched" => "get_state_synchronize_rcu",
- "cond_synchronize_sched" => "cond_synchronize_rcu",
"kmap" => "kmap_local_page",
"kunmap" => "kunmap_local",
"kmap_atomic" => "kmap_local_page",
@@ -2875,7 +2865,7 @@ sub process {
if ($realfile =~ m@^include/asm/@) {
ERROR("MODIFIED_INCLUDE_ASM",
- "do not modify files in include/asm, change architecture specific files in include/asm-<architecture>\n" . "$here$rawline\n");
+ "do not modify files in include/asm, change architecture specific files in arch/<architecture>/include/asm\n" . "$here$rawline\n");
}
$found_file = 1;
}
@@ -3230,19 +3220,19 @@ sub process {
my $tag_case = not ($tag eq "Fixes:");
my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i);
- my $id_length = not ($orig_commit =~ /^[0-9a-f]{12}$/i);
+ my $id_length = not ($orig_commit =~ /^[0-9a-f]{12,40}$/i);
my $id_case = not ($orig_commit !~ /[A-F]/);
my $id = "0123456789ab";
my ($cid, $ctitle) = git_commit_info($orig_commit, $id,
$title);
- if ($ctitle ne $title || $tag_case || $tag_space ||
- $id_length || $id_case || !$title_has_quotes) {
+ if (defined($cid) && ($ctitle ne $title || $tag_case || $tag_space || $id_length || $id_case || !$title_has_quotes)) {
+ my $fixed = "Fixes: $cid (\"$ctitle\")";
if (WARN("BAD_FIXES_TAG",
- "Please use correct Fixes: style 'Fixes: <12 chars of sha1> (\"<title line>\")' - ie: 'Fixes: $cid (\"$ctitle\")'\n" . $herecurr) &&
+ "Please use correct Fixes: style 'Fixes: <12+ chars of sha1> (\"<title line>\")' - ie: '$fixed'\n" . $herecurr) &&
$fix) {
- $fixed[$fixlinenr] = "Fixes: $cid (\"$ctitle\")";
+ $fixed[$fixlinenr] = $fixed;
}
}
}
@@ -5513,9 +5503,9 @@ sub process {
}
}
-# check for unnecessary parentheses around comparisons in if uses
-# when !drivers/staging or command-line uses --strict
- if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) &&
+# check for unnecessary parentheses around comparisons
+# except in drivers/staging
+ if (($realfile !~ m@^(?:drivers/staging/)@) &&
$perl_version_ok && defined($stat) &&
$stat =~ /(^.\s*if\s*($balanced_parens))/) {
my $if_stat = $1;
@@ -5843,6 +5833,8 @@ sub process {
#CamelCase
if ($var !~ /^$Constant$/ &&
$var =~ /[A-Z][a-z]|[a-z][A-Z]/ &&
+#Ignore C keywords
+ $var !~ /^_Generic$/ &&
#Ignore some autogenerated defines and enum values
$var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ &&
#Ignore Page<foo> variants
diff --git a/scripts/coccinelle/misc/secs_to_jiffies.cocci b/scripts/coccinelle/misc/secs_to_jiffies.cocci
new file mode 100644
index 000000000000..8bbb2884ea5d
--- /dev/null
+++ b/scripts/coccinelle/misc/secs_to_jiffies.cocci
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-only
+///
+/// Find usages of:
+/// - msecs_to_jiffies(value*1000)
+/// - msecs_to_jiffies(value*MSEC_PER_SEC)
+///
+// Confidence: High
+// Copyright: (C) 2024 Easwar Hariharan, Microsoft
+// Keywords: secs, seconds, jiffies
+//
+
+virtual patch
+
+@depends on patch@ constant C; @@
+
+- msecs_to_jiffies(C * 1000)
++ secs_to_jiffies(C)
+
+@depends on patch@ constant C; @@
+
+- msecs_to_jiffies(C * MSEC_PER_SEC)
++ secs_to_jiffies(C)
diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh
index 46fa18b80fc1..17abc4e7a985 100755
--- a/scripts/decode_stacktrace.sh
+++ b/scripts/decode_stacktrace.sh
@@ -286,6 +286,18 @@ handle_line() {
last=$(( $last - 1 ))
fi
+ # Extract info after the symbol if present. E.g.:
+ # func_name+0x54/0x80 (P)
+ # ^^^
+ # The regex assumes only uppercase letters will be used. To be
+ # extended if needed.
+ local info_str=""
+ if [[ ${words[$last]} =~ \([A-Z]*\) ]]; then
+ info_str=${words[$last]}
+ unset words[$last]
+ last=$(( $last - 1 ))
+ fi
+
if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
module=${words[$last]}
# some traces format is "(%pS)", which like "(foo+0x0/0x1 [bar])"
@@ -313,9 +325,9 @@ handle_line() {
# Add up the line number to the symbol
if [[ -z ${module} ]]
then
- echo "${words[@]}" "$symbol"
+ echo "${words[@]}" "$symbol ${info_str}"
else
- echo "${words[@]}" "$symbol $module"
+ echo "${words[@]}" "$symbol $module ${info_str}"
fi
}
diff --git a/scripts/documentation-file-ref-check b/scripts/documentation-file-ref-check
index 68083f2f1122..408b1dbe7884 100755
--- a/scripts/documentation-file-ref-check
+++ b/scripts/documentation-file-ref-check
@@ -92,7 +92,7 @@ while (<IN>) {
next if ($f =~ m,^Next/,);
# Makefiles and scripts contain nasty expressions to parse docs
- next if ($f =~ m/Makefile/ || $f =~ m/\.sh$/);
+ next if ($f =~ m/Makefile/ || $f =~ m/\.(sh|py|pl|~|rej|org|orig)$/);
# It doesn't make sense to parse hidden files
next if ($f =~ m#/\.#);
diff --git a/scripts/gcc-x86_32-has-stack-protector.sh b/scripts/gcc-x86_32-has-stack-protector.sh
deleted file mode 100755
index 9459ca4f0f11..000000000000
--- a/scripts/gcc-x86_32-has-stack-protector.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: GPL-2.0
-
-# This requires GCC 8.1 or better. Specifically, we require
-# -mstack-protector-guard-reg, added by
-# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81708
-
-echo "int foo(void) { char X[200]; return 3; }" | $* -S -x c -m32 -O0 -fstack-protector -mstack-protector-guard-reg=fs -mstack-protector-guard-symbol=__stack_chk_guard - -o - 2> /dev/null | grep -q "%fs"
diff --git a/scripts/gcc-x86_64-has-stack-protector.sh b/scripts/gcc-x86_64-has-stack-protector.sh
deleted file mode 100755
index f680bb01aeeb..000000000000
--- a/scripts/gcc-x86_64-has-stack-protector.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: GPL-2.0
-
-echo "int foo(void) { char X[200]; return 3; }" | $* -S -x c -m64 -O0 -mcmodel=kernel -fno-PIE -fstack-protector - -o - 2> /dev/null | grep -q "%gs"
diff --git a/scripts/gdb/linux/cpus.py b/scripts/gdb/linux/cpus.py
index 2f11c4f9c345..8f7c4fb78c2c 100644
--- a/scripts/gdb/linux/cpus.py
+++ b/scripts/gdb/linux/cpus.py
@@ -164,10 +164,10 @@ def get_current_task(cpu):
var_ptr = gdb.parse_and_eval("(struct task_struct *)cpu_tasks[0].task")
return var_ptr.dereference()
else:
- var_ptr = gdb.parse_and_eval("&pcpu_hot.current_task")
+ var_ptr = gdb.parse_and_eval("&current_task")
return per_cpu(var_ptr, cpu).dereference()
elif utils.is_target_arch("aarch64"):
- current_task_addr = gdb.parse_and_eval("$SP_EL0")
+ current_task_addr = gdb.parse_and_eval("(unsigned long)$SP_EL0")
if (current_task_addr >> 63) != 0:
current_task = current_task_addr.cast(task_ptr_type)
return current_task.dereference()
diff --git a/scripts/gen_packed_field_checks.c b/scripts/gen_packed_field_checks.c
new file mode 100644
index 000000000000..60042b7616ee
--- /dev/null
+++ b/scripts/gen_packed_field_checks.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2024, Intel Corporation
+#include <stdbool.h>
+#include <stdio.h>
+
+#define MAX_PACKED_FIELD_SIZE 50
+
+int main(int argc, char **argv)
+{
+ /* The first macro doesn't need a 'do {} while(0)' loop */
+ printf("#define CHECK_PACKED_FIELDS_1(fields) \\\n");
+ printf("\tCHECK_PACKED_FIELD(fields, 0)\n\n");
+
+ /* Remaining macros require a do/while loop, and are implemented
+ * recursively by calling the previous iteration's macro.
+ */
+ for (int i = 2; i <= MAX_PACKED_FIELD_SIZE; i++) {
+ printf("#define CHECK_PACKED_FIELDS_%d(fields) do { \\\n", i);
+ printf("\tCHECK_PACKED_FIELDS_%d(fields); \\\n", i - 1);
+ printf("\tCHECK_PACKED_FIELD(fields, %d); \\\n", i - 1);
+ printf("} while (0)\n\n");
+ }
+
+ printf("#define CHECK_PACKED_FIELDS(fields) \\\n");
+
+ for (int i = 1; i <= MAX_PACKED_FIELD_SIZE; i++)
+ printf("\t__builtin_choose_expr(ARRAY_SIZE(fields) == %d, ({ CHECK_PACKED_FIELDS_%d(fields); }), \\\n",
+ i, i);
+
+ printf("\t({ BUILD_BUG_ON_MSG(1, \"CHECK_PACKED_FIELDS() must be regenerated to support array sizes larger than %d.\"); }) \\\n",
+ MAX_PACKED_FIELD_SIZE);
+
+ for (int i = 1; i <= MAX_PACKED_FIELD_SIZE; i++)
+ printf(")");
+
+ printf("\n");
+}
diff --git a/scripts/gendwarfksyms/.gitignore b/scripts/gendwarfksyms/.gitignore
new file mode 100644
index 000000000000..0927f8d3cd96
--- /dev/null
+++ b/scripts/gendwarfksyms/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+/gendwarfksyms
diff --git a/scripts/gendwarfksyms/Makefile b/scripts/gendwarfksyms/Makefile
new file mode 100644
index 000000000000..6334c7d3c4d5
--- /dev/null
+++ b/scripts/gendwarfksyms/Makefile
@@ -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
diff --git a/scripts/gendwarfksyms/cache.c b/scripts/gendwarfksyms/cache.c
new file mode 100644
index 000000000000..c9c19b86a686
--- /dev/null
+++ b/scripts/gendwarfksyms/cache.c
@@ -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);
+}
diff --git a/scripts/gendwarfksyms/die.c b/scripts/gendwarfksyms/die.c
new file mode 100644
index 000000000000..66bd4c9bc952
--- /dev/null
+++ b/scripts/gendwarfksyms/die.c
@@ -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;
+}
diff --git a/scripts/gendwarfksyms/dwarf.c b/scripts/gendwarfksyms/dwarf.c
new file mode 100644
index 000000000000..534d9aa7c114
--- /dev/null
+++ b/scripts/gendwarfksyms/dwarf.c
@@ -0,0 +1,1159 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Google LLC
+ */
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include "gendwarfksyms.h"
+
+/* See get_union_kabi_status */
+#define KABI_PREFIX "__kabi_"
+#define KABI_PREFIX_LEN (sizeof(KABI_PREFIX) - 1)
+#define KABI_RESERVED_PREFIX "reserved"
+#define KABI_RESERVED_PREFIX_LEN (sizeof(KABI_RESERVED_PREFIX) - 1)
+#define KABI_RENAMED_PREFIX "renamed"
+#define KABI_RENAMED_PREFIX_LEN (sizeof(KABI_RENAMED_PREFIX) - 1)
+#define KABI_IGNORED_PREFIX "ignored"
+#define KABI_IGNORED_PREFIX_LEN (sizeof(KABI_IGNORED_PREFIX) - 1)
+
+static inline bool is_kabi_prefix(const char *name)
+{
+ return name && !strncmp(name, KABI_PREFIX, KABI_PREFIX_LEN);
+}
+
+enum kabi_status {
+ /* >0 to stop DIE processing */
+ KABI_NORMAL = 1,
+ KABI_RESERVED,
+ KABI_IGNORED,
+};
+
+static bool do_linebreak;
+static int indentation_level;
+
+/* Line breaks and indentation for pretty-printing */
+static void process_linebreak(struct die *cache, int n)
+{
+ indentation_level += n;
+ do_linebreak = true;
+ die_map_add_linebreak(cache, n);
+}
+
+#define DEFINE_GET_ATTR(attr, type) \
+ static bool get_##attr##_attr(Dwarf_Die *die, unsigned int id, \
+ type *value) \
+ { \
+ Dwarf_Attribute da; \
+ return dwarf_attr(die, id, &da) && \
+ !dwarf_form##attr(&da, value); \
+ }
+
+DEFINE_GET_ATTR(flag, bool)
+DEFINE_GET_ATTR(udata, Dwarf_Word)
+
+static bool get_ref_die_attr(Dwarf_Die *die, unsigned int id, Dwarf_Die *value)
+{
+ Dwarf_Attribute da;
+
+ /* dwarf_formref_die returns a pointer instead of an error value. */
+ return dwarf_attr(die, id, &da) && dwarf_formref_die(&da, value);
+}
+
+#define DEFINE_GET_STRING_ATTR(attr) \
+ static const char *get_##attr##_attr(Dwarf_Die *die) \
+ { \
+ Dwarf_Attribute da; \
+ if (dwarf_attr(die, DW_AT_##attr, &da)) \
+ return dwarf_formstring(&da); \
+ return NULL; \
+ }
+
+DEFINE_GET_STRING_ATTR(name)
+DEFINE_GET_STRING_ATTR(linkage_name)
+
+static const char *get_symbol_name(Dwarf_Die *die)
+{
+ const char *name;
+
+ /* rustc uses DW_AT_linkage_name for exported symbols */
+ name = get_linkage_name_attr(die);
+ if (!name)
+ name = get_name_attr(die);
+
+ return name;
+}
+
+static bool match_export_symbol(struct state *state, Dwarf_Die *die)
+{
+ Dwarf_Die *source = die;
+ Dwarf_Die origin;
+
+ /* If the DIE has an abstract origin, use it for type information. */
+ if (get_ref_die_attr(die, DW_AT_abstract_origin, &origin))
+ source = &origin;
+
+ state->sym = symbol_get(get_symbol_name(die));
+
+ /* Look up using the origin name if there are no matches. */
+ if (!state->sym && source != die)
+ state->sym = symbol_get(get_symbol_name(source));
+
+ state->die = *source;
+ return !!state->sym;
+}
+
+/* DW_AT_decl_file -> struct srcfile */
+static struct cache srcfile_cache;
+
+static bool is_definition_private(Dwarf_Die *die)
+{
+ Dwarf_Word filenum;
+ Dwarf_Files *files;
+ Dwarf_Die cudie;
+ const char *s;
+ int res;
+
+ /*
+ * Definitions in .c files cannot change the public ABI,
+ * so consider them private.
+ */
+ if (!get_udata_attr(die, DW_AT_decl_file, &filenum))
+ return false;
+
+ res = cache_get(&srcfile_cache, filenum);
+ if (res >= 0)
+ return !!res;
+
+ if (!dwarf_cu_die(die->cu, &cudie, NULL, NULL, NULL, NULL, NULL, NULL))
+ error("dwarf_cu_die failed: '%s'", dwarf_errmsg(-1));
+
+ if (dwarf_getsrcfiles(&cudie, &files, NULL))
+ error("dwarf_getsrcfiles failed: '%s'", dwarf_errmsg(-1));
+
+ s = dwarf_filesrc(files, filenum, NULL, NULL);
+ if (!s)
+ error("dwarf_filesrc failed: '%s'", dwarf_errmsg(-1));
+
+ s = strrchr(s, '.');
+ res = s && !strcmp(s, ".c");
+ cache_set(&srcfile_cache, filenum, res);
+
+ return !!res;
+}
+
+static bool is_kabi_definition(struct die *cache, Dwarf_Die *die)
+{
+ bool value;
+
+ if (get_flag_attr(die, DW_AT_declaration, &value) && value)
+ return false;
+
+ if (kabi_is_declonly(cache->fqn))
+ return false;
+
+ return !is_definition_private(die);
+}
+
+/*
+ * Type string processing
+ */
+static void process(struct die *cache, const char *s)
+{
+ s = s ?: "<null>";
+
+ if (dump_dies && do_linebreak) {
+ fputs("\n", stderr);
+ for (int i = 0; i < indentation_level; i++)
+ fputs(" ", stderr);
+ do_linebreak = false;
+ }
+ if (dump_dies)
+ fputs(s, stderr);
+
+ if (cache)
+ die_debug_r("cache %p string '%s'", cache, s);
+ die_map_add_string(cache, s);
+}
+
+#define MAX_FMT_BUFFER_SIZE 128
+
+static void process_fmt(struct die *cache, const char *fmt, ...)
+{
+ char buf[MAX_FMT_BUFFER_SIZE];
+ va_list args;
+
+ va_start(args, fmt);
+
+ if (checkp(vsnprintf(buf, sizeof(buf), fmt, args)) >= sizeof(buf))
+ error("vsnprintf overflow: increase MAX_FMT_BUFFER_SIZE");
+
+ process(cache, buf);
+ va_end(args);
+}
+
+#define MAX_FQN_SIZE 64
+
+/* Get a fully qualified name from DWARF scopes */
+static char *get_fqn(Dwarf_Die *die)
+{
+ const char *list[MAX_FQN_SIZE];
+ Dwarf_Die *scopes = NULL;
+ bool has_name = false;
+ char *fqn = NULL;
+ char *p;
+ int count = 0;
+ int len = 0;
+ int res;
+ int i;
+
+ res = checkp(dwarf_getscopes_die(die, &scopes));
+ if (!res) {
+ list[count] = get_name_attr(die);
+
+ if (!list[count])
+ return NULL;
+
+ len += strlen(list[count]);
+ count++;
+
+ goto done;
+ }
+
+ for (i = res - 1; i >= 0 && count < MAX_FQN_SIZE; i--) {
+ if (dwarf_tag(&scopes[i]) == DW_TAG_compile_unit)
+ continue;
+
+ list[count] = get_name_attr(&scopes[i]);
+
+ if (list[count]) {
+ has_name = true;
+ } else {
+ list[count] = "<anonymous>";
+ has_name = false;
+ }
+
+ len += strlen(list[count]);
+ count++;
+
+ if (i > 0) {
+ list[count++] = "::";
+ len += 2;
+ }
+ }
+
+ free(scopes);
+
+ if (count == MAX_FQN_SIZE)
+ warn("increase MAX_FQN_SIZE: reached the maximum");
+
+ /* Consider the DIE unnamed if the last scope doesn't have a name */
+ if (!has_name)
+ return NULL;
+done:
+ fqn = xmalloc(len + 1);
+ *fqn = '\0';
+
+ p = fqn;
+ for (i = 0; i < count; i++)
+ p = stpcpy(p, list[i]);
+
+ return fqn;
+}
+
+static void update_fqn(struct die *cache, Dwarf_Die *die)
+{
+ if (!cache->fqn)
+ cache->fqn = get_fqn(die) ?: "";
+}
+
+static void process_fqn(struct die *cache, Dwarf_Die *die)
+{
+ update_fqn(cache, die);
+ if (*cache->fqn)
+ process(cache, " ");
+ process(cache, cache->fqn);
+}
+
+#define DEFINE_PROCESS_UDATA_ATTRIBUTE(attribute) \
+ static void process_##attribute##_attr(struct die *cache, \
+ Dwarf_Die *die) \
+ { \
+ Dwarf_Word value; \
+ if (get_udata_attr(die, DW_AT_##attribute, &value)) \
+ process_fmt(cache, " " #attribute "(%" PRIu64 ")", \
+ value); \
+ }
+
+DEFINE_PROCESS_UDATA_ATTRIBUTE(accessibility)
+DEFINE_PROCESS_UDATA_ATTRIBUTE(alignment)
+DEFINE_PROCESS_UDATA_ATTRIBUTE(bit_size)
+DEFINE_PROCESS_UDATA_ATTRIBUTE(byte_size)
+DEFINE_PROCESS_UDATA_ATTRIBUTE(encoding)
+DEFINE_PROCESS_UDATA_ATTRIBUTE(data_bit_offset)
+DEFINE_PROCESS_UDATA_ATTRIBUTE(data_member_location)
+DEFINE_PROCESS_UDATA_ATTRIBUTE(discr_value)
+
+/* Match functions -- die_match_callback_t */
+#define DEFINE_MATCH(type) \
+ static bool match_##type##_type(Dwarf_Die *die) \
+ { \
+ return dwarf_tag(die) == DW_TAG_##type##_type; \
+ }
+
+DEFINE_MATCH(enumerator)
+DEFINE_MATCH(formal_parameter)
+DEFINE_MATCH(member)
+DEFINE_MATCH(subrange)
+
+bool match_all(Dwarf_Die *die)
+{
+ return true;
+}
+
+int process_die_container(struct state *state, struct die *cache,
+ Dwarf_Die *die, die_callback_t func,
+ die_match_callback_t match)
+{
+ Dwarf_Die current;
+ int res;
+
+ /* Track the first item in lists. */
+ if (state)
+ state->first_list_item = true;
+
+ res = checkp(dwarf_child(die, &current));
+ while (!res) {
+ if (match(&current)) {
+ /* <0 = error, 0 = continue, >0 = stop */
+ res = checkp(func(state, cache, &current));
+ if (res)
+ goto out;
+ }
+
+ res = checkp(dwarf_siblingof(&current, &current));
+ }
+
+ res = 0;
+out:
+ if (state)
+ state->first_list_item = false;
+
+ return res;
+}
+
+static int process_type(struct state *state, struct die *parent,
+ Dwarf_Die *die);
+
+static void process_type_attr(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ Dwarf_Die type;
+
+ if (get_ref_die_attr(die, DW_AT_type, &type)) {
+ check(process_type(state, cache, &type));
+ return;
+ }
+
+ /* Compilers can omit DW_AT_type -- print out 'void' to clarify */
+ process(cache, "base_type void");
+}
+
+static void process_list_comma(struct state *state, struct die *cache)
+{
+ if (state->first_list_item) {
+ state->first_list_item = false;
+ } else {
+ process(cache, " ,");
+ process_linebreak(cache, 0);
+ }
+}
+
+/* Comma-separated with DW_AT_type */
+static void __process_list_type(struct state *state, struct die *cache,
+ Dwarf_Die *die, const char *type)
+{
+ const char *name = get_name_attr(die);
+
+ if (stable) {
+ if (is_kabi_prefix(name))
+ name = NULL;
+ state->kabi.orig_name = NULL;
+ }
+
+ process_list_comma(state, cache);
+ process(cache, type);
+ process_type_attr(state, cache, die);
+
+ if (stable && state->kabi.orig_name)
+ name = state->kabi.orig_name;
+ if (name) {
+ process(cache, " ");
+ process(cache, name);
+ }
+
+ process_accessibility_attr(cache, die);
+ process_bit_size_attr(cache, die);
+ process_data_bit_offset_attr(cache, die);
+ process_data_member_location_attr(cache, die);
+}
+
+#define DEFINE_PROCESS_LIST_TYPE(type) \
+ static void process_##type##_type(struct state *state, \
+ struct die *cache, Dwarf_Die *die) \
+ { \
+ __process_list_type(state, cache, die, #type " "); \
+ }
+
+DEFINE_PROCESS_LIST_TYPE(formal_parameter)
+DEFINE_PROCESS_LIST_TYPE(member)
+
+/* Container types with DW_AT_type */
+static void __process_type(struct state *state, struct die *cache,
+ Dwarf_Die *die, const char *type)
+{
+ process(cache, type);
+ process_fqn(cache, die);
+ process(cache, " {");
+ process_linebreak(cache, 1);
+ process_type_attr(state, cache, die);
+ process_linebreak(cache, -1);
+ process(cache, "}");
+ process_byte_size_attr(cache, die);
+ process_alignment_attr(cache, die);
+}
+
+#define DEFINE_PROCESS_TYPE(type) \
+ static void process_##type##_type(struct state *state, \
+ struct die *cache, Dwarf_Die *die) \
+ { \
+ __process_type(state, cache, die, #type "_type"); \
+ }
+
+DEFINE_PROCESS_TYPE(atomic)
+DEFINE_PROCESS_TYPE(const)
+DEFINE_PROCESS_TYPE(immutable)
+DEFINE_PROCESS_TYPE(packed)
+DEFINE_PROCESS_TYPE(pointer)
+DEFINE_PROCESS_TYPE(reference)
+DEFINE_PROCESS_TYPE(restrict)
+DEFINE_PROCESS_TYPE(rvalue_reference)
+DEFINE_PROCESS_TYPE(shared)
+DEFINE_PROCESS_TYPE(template_type_parameter)
+DEFINE_PROCESS_TYPE(volatile)
+DEFINE_PROCESS_TYPE(typedef)
+
+static void process_subrange_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ Dwarf_Word count = 0;
+
+ if (get_udata_attr(die, DW_AT_count, &count))
+ process_fmt(cache, "[%" PRIu64 "]", count);
+ else if (get_udata_attr(die, DW_AT_upper_bound, &count))
+ process_fmt(cache, "[%" PRIu64 "]", count + 1);
+ else
+ process(cache, "[]");
+}
+
+static void process_array_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ process(cache, "array_type");
+ /* Array size */
+ check(process_die_container(state, cache, die, process_type,
+ match_subrange_type));
+ process(cache, " {");
+ process_linebreak(cache, 1);
+ process_type_attr(state, cache, die);
+ process_linebreak(cache, -1);
+ process(cache, "}");
+}
+
+static void __process_subroutine_type(struct state *state, struct die *cache,
+ Dwarf_Die *die, const char *type)
+{
+ process(cache, type);
+ process(cache, " (");
+ process_linebreak(cache, 1);
+ /* Parameters */
+ check(process_die_container(state, cache, die, process_type,
+ match_formal_parameter_type));
+ process_linebreak(cache, -1);
+ process(cache, ")");
+ process_linebreak(cache, 0);
+ /* Return type */
+ process(cache, "-> ");
+ process_type_attr(state, cache, die);
+}
+
+static void process_subroutine_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ __process_subroutine_type(state, cache, die, "subroutine_type");
+}
+
+static void process_variant_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ process_list_comma(state, cache);
+ process(cache, "variant {");
+ process_linebreak(cache, 1);
+ check(process_die_container(state, cache, die, process_type,
+ match_member_type));
+ process_linebreak(cache, -1);
+ process(cache, "}");
+ process_discr_value_attr(cache, die);
+}
+
+static void process_variant_part_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ process_list_comma(state, cache);
+ process(cache, "variant_part {");
+ process_linebreak(cache, 1);
+ check(process_die_container(state, cache, die, process_type,
+ match_all));
+ process_linebreak(cache, -1);
+ process(cache, "}");
+}
+
+static int get_kabi_status(Dwarf_Die *die, const char **suffix)
+{
+ const char *name = get_name_attr(die);
+
+ if (suffix)
+ *suffix = NULL;
+
+ if (is_kabi_prefix(name)) {
+ name += KABI_PREFIX_LEN;
+
+ if (!strncmp(name, KABI_RESERVED_PREFIX,
+ KABI_RESERVED_PREFIX_LEN))
+ return KABI_RESERVED;
+ if (!strncmp(name, KABI_IGNORED_PREFIX,
+ KABI_IGNORED_PREFIX_LEN))
+ return KABI_IGNORED;
+
+ if (!strncmp(name, KABI_RENAMED_PREFIX,
+ KABI_RENAMED_PREFIX_LEN)) {
+ if (suffix) {
+ name += KABI_RENAMED_PREFIX_LEN;
+ *suffix = name;
+ }
+ return KABI_RESERVED;
+ }
+ }
+
+ return KABI_NORMAL;
+}
+
+static int check_struct_member_kabi_status(struct state *state,
+ struct die *__unused, Dwarf_Die *die)
+{
+ int res;
+
+ assert(dwarf_tag(die) == DW_TAG_member_type);
+
+ /*
+ * If the union member is a struct, expect the __kabi field to
+ * be the first member of the structure, i.e..:
+ *
+ * union {
+ * type new_member;
+ * struct {
+ * type __kabi_field;
+ * }
+ * };
+ */
+ res = get_kabi_status(die, &state->kabi.orig_name);
+
+ if (res == KABI_RESERVED &&
+ !get_ref_die_attr(die, DW_AT_type, &state->kabi.placeholder))
+ error("structure member missing a type?");
+
+ return res;
+}
+
+static int check_union_member_kabi_status(struct state *state,
+ struct die *__unused, Dwarf_Die *die)
+{
+ Dwarf_Die type;
+ int res;
+
+ assert(dwarf_tag(die) == DW_TAG_member_type);
+
+ if (!get_ref_die_attr(die, DW_AT_type, &type))
+ error("union member missing a type?");
+
+ /*
+ * We expect a union with two members. Check if either of them
+ * has a __kabi name prefix, i.e.:
+ *
+ * union {
+ * ...
+ * type memberN; // <- type, N = {0,1}
+ * ...
+ * };
+ *
+ * The member can also be a structure type, in which case we'll
+ * check the first structure member.
+ *
+ * In any case, stop processing after we've seen two members.
+ */
+ res = get_kabi_status(die, &state->kabi.orig_name);
+
+ if (res == KABI_RESERVED)
+ state->kabi.placeholder = type;
+ if (res != KABI_NORMAL)
+ return res;
+
+ if (dwarf_tag(&type) == DW_TAG_structure_type)
+ res = checkp(process_die_container(
+ state, NULL, &type, check_struct_member_kabi_status,
+ match_member_type));
+
+ if (res <= KABI_NORMAL && ++state->kabi.members < 2)
+ return 0; /* Continue */
+
+ return res;
+}
+
+static int get_union_kabi_status(Dwarf_Die *die, Dwarf_Die *placeholder,
+ const char **orig_name)
+{
+ struct state state;
+ int res;
+
+ if (!stable)
+ return KABI_NORMAL;
+
+ /*
+ * To maintain a stable kABI, distributions may choose to reserve
+ * space in structs for later use by adding placeholder members,
+ * for example:
+ *
+ * struct s {
+ * u32 a;
+ * // an 8-byte placeholder for future use
+ * u64 __kabi_reserved_0;
+ * };
+ *
+ * When the reserved member is taken into use, the type change
+ * would normally cause the symbol version to change as well, but
+ * if the replacement uses the following convention, gendwarfksyms
+ * continues to use the placeholder type for versioning instead,
+ * thus maintaining the same symbol version:
+ *
+ * struct s {
+ * u32 a;
+ * union {
+ * // placeholder replaced with a new member `b`
+ * struct t b;
+ * struct {
+ * // the placeholder type that is still
+ * // used for versioning
+ * u64 __kabi_reserved_0;
+ * };
+ * };
+ * };
+ *
+ * I.e., as long as the replaced member is in a union, and the
+ * placeholder has a __kabi_reserved name prefix, we'll continue
+ * to use the placeholder type (here u64) for version calculation
+ * instead of the union type.
+ *
+ * It's also possible to ignore new members from versioning if
+ * they've been added to alignment holes, for example, by
+ * including them in a union with another member that uses the
+ * __kabi_ignored name prefix:
+ *
+ * struct s {
+ * u32 a;
+ * // an alignment hole is used to add `n`
+ * union {
+ * u32 n;
+ * // hide the entire union member from versioning
+ * u8 __kabi_ignored_0;
+ * };
+ * u64 b;
+ * };
+ *
+ * Note that the user of this feature is responsible for ensuring
+ * that the structure actually remains ABI compatible.
+ */
+ memset(&state.kabi, 0, sizeof(struct kabi_state));
+
+ res = checkp(process_die_container(&state, NULL, die,
+ check_union_member_kabi_status,
+ match_member_type));
+
+ if (res == KABI_RESERVED) {
+ if (placeholder)
+ *placeholder = state.kabi.placeholder;
+ if (orig_name)
+ *orig_name = state.kabi.orig_name;
+ }
+
+ return res;
+}
+
+static bool is_kabi_ignored(Dwarf_Die *die)
+{
+ Dwarf_Die type;
+
+ if (!stable)
+ return false;
+
+ if (!get_ref_die_attr(die, DW_AT_type, &type))
+ error("member missing a type?");
+
+ return dwarf_tag(&type) == DW_TAG_union_type &&
+ checkp(get_union_kabi_status(&type, NULL, NULL)) == KABI_IGNORED;
+}
+
+static int ___process_structure_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ switch (dwarf_tag(die)) {
+ case DW_TAG_member:
+ if (is_kabi_ignored(die))
+ return 0;
+ return check(process_type(state, cache, die));
+ case DW_TAG_variant_part:
+ return check(process_type(state, cache, die));
+ case DW_TAG_class_type:
+ case DW_TAG_enumeration_type:
+ case DW_TAG_structure_type:
+ case DW_TAG_template_type_parameter:
+ case DW_TAG_union_type:
+ case DW_TAG_subprogram:
+ /* Skip non-member types, including member functions */
+ return 0;
+ default:
+ error("unexpected structure_type child: %x", dwarf_tag(die));
+ }
+}
+
+static void __process_structure_type(struct state *state, struct die *cache,
+ Dwarf_Die *die, const char *type,
+ die_callback_t process_func,
+ die_match_callback_t match_func)
+{
+ bool expand;
+
+ process(cache, type);
+ process_fqn(cache, die);
+ process(cache, " {");
+ process_linebreak(cache, 1);
+
+ expand = state->expand.expand && is_kabi_definition(cache, die);
+
+ if (expand) {
+ state->expand.current_fqn = cache->fqn;
+ check(process_die_container(state, cache, die, process_func,
+ match_func));
+ }
+
+ process_linebreak(cache, -1);
+ process(cache, "}");
+
+ if (expand) {
+ process_byte_size_attr(cache, die);
+ process_alignment_attr(cache, die);
+ }
+}
+
+#define DEFINE_PROCESS_STRUCTURE_TYPE(structure) \
+ static void process_##structure##_type( \
+ struct state *state, struct die *cache, Dwarf_Die *die) \
+ { \
+ __process_structure_type(state, cache, die, \
+ #structure "_type", \
+ ___process_structure_type, \
+ match_all); \
+ }
+
+DEFINE_PROCESS_STRUCTURE_TYPE(class)
+DEFINE_PROCESS_STRUCTURE_TYPE(structure)
+
+static void process_union_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ Dwarf_Die placeholder;
+
+ int res = checkp(get_union_kabi_status(die, &placeholder,
+ &state->kabi.orig_name));
+
+ if (res == KABI_RESERVED)
+ check(process_type(state, cache, &placeholder));
+ if (res > KABI_NORMAL)
+ return;
+
+ __process_structure_type(state, cache, die, "union_type",
+ ___process_structure_type, match_all);
+}
+
+static void process_enumerator_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ bool overridden = false;
+ Dwarf_Word value;
+
+ if (stable) {
+ /* Get the fqn before we process anything */
+ update_fqn(cache, die);
+
+ if (kabi_is_enumerator_ignored(state->expand.current_fqn,
+ cache->fqn))
+ return;
+
+ overridden = kabi_get_enumerator_value(
+ state->expand.current_fqn, cache->fqn, &value);
+ }
+
+ process_list_comma(state, cache);
+ process(cache, "enumerator");
+ process_fqn(cache, die);
+
+ if (overridden || get_udata_attr(die, DW_AT_const_value, &value)) {
+ process(cache, " = ");
+ process_fmt(cache, "%" PRIu64, value);
+ }
+}
+
+static void process_enumeration_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ __process_structure_type(state, cache, die, "enumeration_type",
+ process_type, match_enumerator_type);
+}
+
+static void process_base_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ process(cache, "base_type");
+ process_fqn(cache, die);
+ process_byte_size_attr(cache, die);
+ process_encoding_attr(cache, die);
+ process_alignment_attr(cache, die);
+}
+
+static void process_unspecified_type(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ /*
+ * These can be emitted for stand-alone assembly code, which means we
+ * might run into them in vmlinux.o.
+ */
+ process(cache, "unspecified_type");
+}
+
+static void process_cached(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ struct die_fragment *df;
+ Dwarf_Die child;
+
+ list_for_each_entry(df, &cache->fragments, list) {
+ switch (df->type) {
+ case FRAGMENT_STRING:
+ die_debug_b("cache %p STRING '%s'", cache,
+ df->data.str);
+ process(NULL, df->data.str);
+ break;
+ case FRAGMENT_LINEBREAK:
+ process_linebreak(NULL, df->data.linebreak);
+ break;
+ case FRAGMENT_DIE:
+ if (!dwarf_die_addr_die(dwarf_cu_getdwarf(die->cu),
+ (void *)df->data.addr, &child))
+ error("dwarf_die_addr_die failed");
+ die_debug_b("cache %p DIE addr %" PRIxPTR " tag %x",
+ cache, df->data.addr, dwarf_tag(&child));
+ check(process_type(state, NULL, &child));
+ break;
+ default:
+ error("empty die_fragment");
+ }
+ }
+}
+
+static void state_init(struct state *state)
+{
+ state->expand.expand = true;
+ state->expand.current_fqn = NULL;
+ cache_init(&state->expansion_cache);
+}
+
+static void expansion_state_restore(struct expansion_state *state,
+ struct expansion_state *saved)
+{
+ state->expand = saved->expand;
+ state->current_fqn = saved->current_fqn;
+}
+
+static void expansion_state_save(struct expansion_state *state,
+ struct expansion_state *saved)
+{
+ expansion_state_restore(saved, state);
+}
+
+static bool is_expanded_type(int tag)
+{
+ return tag == DW_TAG_class_type || tag == DW_TAG_structure_type ||
+ tag == DW_TAG_union_type || tag == DW_TAG_enumeration_type;
+}
+
+#define PROCESS_TYPE(type) \
+ case DW_TAG_##type##_type: \
+ process_##type##_type(state, cache, die); \
+ break;
+
+static int process_type(struct state *state, struct die *parent, Dwarf_Die *die)
+{
+ enum die_state want_state = DIE_COMPLETE;
+ struct die *cache;
+ struct expansion_state saved;
+ int tag = dwarf_tag(die);
+
+ expansion_state_save(&state->expand, &saved);
+
+ /*
+ * Structures and enumeration types are expanded only once per
+ * exported symbol. This is sufficient for detecting ABI changes
+ * within the structure.
+ */
+ if (is_expanded_type(tag)) {
+ if (cache_was_expanded(&state->expansion_cache, die->addr))
+ state->expand.expand = false;
+
+ if (state->expand.expand)
+ cache_mark_expanded(&state->expansion_cache, die->addr);
+ else
+ want_state = DIE_UNEXPANDED;
+ }
+
+ /*
+ * If we have want_state already cached, use it instead of walking
+ * through DWARF.
+ */
+ cache = die_map_get(die, want_state);
+
+ if (cache->state == want_state) {
+ die_debug_g("cached addr %p tag %x -- %s", die->addr, tag,
+ die_state_name(cache->state));
+
+ process_cached(state, cache, die);
+ die_map_add_die(parent, cache);
+
+ expansion_state_restore(&state->expand, &saved);
+ return 0;
+ }
+
+ die_debug_g("addr %p tag %x -- %s -> %s", die->addr, tag,
+ die_state_name(cache->state), die_state_name(want_state));
+
+ switch (tag) {
+ /* Type modifiers */
+ PROCESS_TYPE(atomic)
+ PROCESS_TYPE(const)
+ PROCESS_TYPE(immutable)
+ PROCESS_TYPE(packed)
+ PROCESS_TYPE(pointer)
+ PROCESS_TYPE(reference)
+ PROCESS_TYPE(restrict)
+ PROCESS_TYPE(rvalue_reference)
+ PROCESS_TYPE(shared)
+ PROCESS_TYPE(volatile)
+ /* Container types */
+ PROCESS_TYPE(class)
+ PROCESS_TYPE(structure)
+ PROCESS_TYPE(union)
+ PROCESS_TYPE(enumeration)
+ /* Subtypes */
+ PROCESS_TYPE(enumerator)
+ PROCESS_TYPE(formal_parameter)
+ PROCESS_TYPE(member)
+ PROCESS_TYPE(subrange)
+ PROCESS_TYPE(template_type_parameter)
+ PROCESS_TYPE(variant)
+ PROCESS_TYPE(variant_part)
+ /* Other types */
+ PROCESS_TYPE(array)
+ PROCESS_TYPE(base)
+ PROCESS_TYPE(subroutine)
+ PROCESS_TYPE(typedef)
+ PROCESS_TYPE(unspecified)
+ default:
+ error("unexpected type: %x", tag);
+ }
+
+ die_debug_r("parent %p cache %p die addr %p tag %x", parent, cache,
+ die->addr, tag);
+
+ /* Update cache state and append to the parent (if any) */
+ cache->tag = tag;
+ cache->state = want_state;
+ die_map_add_die(parent, cache);
+
+ expansion_state_restore(&state->expand, &saved);
+ return 0;
+}
+
+/*
+ * Exported symbol processing
+ */
+static struct die *get_symbol_cache(struct state *state, Dwarf_Die *die)
+{
+ struct die *cache;
+
+ cache = die_map_get(die, DIE_SYMBOL);
+
+ if (cache->state != DIE_INCOMPLETE)
+ return NULL; /* We already processed a symbol for this DIE */
+
+ cache->tag = dwarf_tag(die);
+ return cache;
+}
+
+static void process_symbol(struct state *state, Dwarf_Die *die,
+ die_callback_t process_func)
+{
+ struct die *cache;
+
+ symbol_set_die(state->sym, die);
+
+ cache = get_symbol_cache(state, die);
+ if (!cache)
+ return;
+
+ debug("%s", state->sym->name);
+ check(process_func(state, cache, die));
+ cache->state = DIE_SYMBOL;
+ if (dump_dies)
+ fputs("\n", stderr);
+}
+
+static int __process_subprogram(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ __process_subroutine_type(state, cache, die, "subprogram");
+ return 0;
+}
+
+static void process_subprogram(struct state *state, Dwarf_Die *die)
+{
+ process_symbol(state, die, __process_subprogram);
+}
+
+static int __process_variable(struct state *state, struct die *cache,
+ Dwarf_Die *die)
+{
+ process(cache, "variable ");
+ process_type_attr(state, cache, die);
+ return 0;
+}
+
+static void process_variable(struct state *state, Dwarf_Die *die)
+{
+ process_symbol(state, die, __process_variable);
+}
+
+static void save_symbol_ptr(struct state *state)
+{
+ Dwarf_Die ptr_type;
+ Dwarf_Die type;
+
+ if (!get_ref_die_attr(&state->die, DW_AT_type, &ptr_type) ||
+ dwarf_tag(&ptr_type) != DW_TAG_pointer_type)
+ error("%s must be a pointer type!",
+ get_symbol_name(&state->die));
+
+ if (!get_ref_die_attr(&ptr_type, DW_AT_type, &type))
+ error("%s pointer missing a type attribute?",
+ get_symbol_name(&state->die));
+
+ /*
+ * Save the symbol pointer DIE in case the actual symbol is
+ * missing from the DWARF. Clang, for example, intentionally
+ * omits external symbols from the debugging information.
+ */
+ if (dwarf_tag(&type) == DW_TAG_subroutine_type)
+ symbol_set_ptr(state->sym, &type);
+ else
+ symbol_set_ptr(state->sym, &ptr_type);
+}
+
+static int process_exported_symbols(struct state *unused, struct die *cache,
+ Dwarf_Die *die)
+{
+ int tag = dwarf_tag(die);
+
+ switch (tag) {
+ /* Possible containers of exported symbols */
+ case DW_TAG_namespace:
+ case DW_TAG_class_type:
+ case DW_TAG_structure_type:
+ return check(process_die_container(
+ NULL, cache, die, process_exported_symbols, match_all));
+
+ /* Possible exported symbols */
+ case DW_TAG_subprogram:
+ case DW_TAG_variable: {
+ struct state state;
+
+ if (!match_export_symbol(&state, die))
+ return 0;
+
+ state_init(&state);
+
+ if (is_symbol_ptr(get_symbol_name(&state.die)))
+ save_symbol_ptr(&state);
+ else if (tag == DW_TAG_subprogram)
+ process_subprogram(&state, &state.die);
+ else
+ process_variable(&state, &state.die);
+
+ cache_free(&state.expansion_cache);
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static void process_symbol_ptr(struct symbol *sym, void *arg)
+{
+ struct state state;
+ Dwarf *dwarf = arg;
+
+ if (sym->state != SYMBOL_UNPROCESSED || !sym->ptr_die_addr)
+ return;
+
+ debug("%s", sym->name);
+ state_init(&state);
+ state.sym = sym;
+
+ if (!dwarf_die_addr_die(dwarf, (void *)sym->ptr_die_addr, &state.die))
+ error("dwarf_die_addr_die failed for symbol ptr: '%s'",
+ sym->name);
+
+ if (dwarf_tag(&state.die) == DW_TAG_subroutine_type)
+ process_subprogram(&state, &state.die);
+ else
+ process_variable(&state, &state.die);
+
+ cache_free(&state.expansion_cache);
+}
+
+void process_cu(Dwarf_Die *cudie)
+{
+ check(process_die_container(NULL, NULL, cudie, process_exported_symbols,
+ match_all));
+
+ symbol_for_each(process_symbol_ptr, dwarf_cu_getdwarf(cudie->cu));
+
+ cache_free(&srcfile_cache);
+}
diff --git a/scripts/gendwarfksyms/examples/kabi.h b/scripts/gendwarfksyms/examples/kabi.h
new file mode 100644
index 000000000000..97a5669b083d
--- /dev/null
+++ b/scripts/gendwarfksyms/examples/kabi.h
@@ -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__ */
diff --git a/scripts/gendwarfksyms/examples/kabi_ex.c b/scripts/gendwarfksyms/examples/kabi_ex.c
new file mode 100644
index 000000000000..0b7ffd830541
--- /dev/null
+++ b/scripts/gendwarfksyms/examples/kabi_ex.c
@@ -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;
diff --git a/scripts/gendwarfksyms/examples/kabi_ex.h b/scripts/gendwarfksyms/examples/kabi_ex.h
new file mode 100644
index 000000000000..1736e0f65208
--- /dev/null
+++ b/scripts/gendwarfksyms/examples/kabi_ex.h
@@ -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__ */
diff --git a/scripts/gendwarfksyms/examples/symbolptr.c b/scripts/gendwarfksyms/examples/symbolptr.c
new file mode 100644
index 000000000000..88bc1bd60da8
--- /dev/null
+++ b/scripts/gendwarfksyms/examples/symbolptr.c
@@ -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);
diff --git a/scripts/gendwarfksyms/gendwarfksyms.c b/scripts/gendwarfksyms/gendwarfksyms.c
new file mode 100644
index 000000000000..08ae61eb327e
--- /dev/null
+++ b/scripts/gendwarfksyms/gendwarfksyms.c
@@ -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;
+}
diff --git a/scripts/gendwarfksyms/gendwarfksyms.h b/scripts/gendwarfksyms/gendwarfksyms.h
new file mode 100644
index 000000000000..197a1a8123c6
--- /dev/null
+++ b/scripts/gendwarfksyms/gendwarfksyms.h
@@ -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 */
diff --git a/scripts/gendwarfksyms/kabi.c b/scripts/gendwarfksyms/kabi.c
new file mode 100644
index 000000000000..66f01fcd1607
--- /dev/null
+++ b/scripts/gendwarfksyms/kabi.c
@@ -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);
+}
diff --git a/scripts/gendwarfksyms/symbols.c b/scripts/gendwarfksyms/symbols.c
new file mode 100644
index 000000000000..327f87389c34
--- /dev/null
+++ b/scripts/gendwarfksyms/symbols.c
@@ -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);
+}
diff --git a/scripts/gendwarfksyms/types.c b/scripts/gendwarfksyms/types.c
new file mode 100644
index 000000000000..6c03265f4d10
--- /dev/null
+++ b/scripts/gendwarfksyms/types.c
@@ -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();
+}
diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py
index 09e1d166d8d2..adae71544cbd 100755
--- a/scripts/generate_rust_analyzer.py
+++ b/scripts/generate_rust_analyzer.py
@@ -8,6 +8,7 @@ import json
import logging
import os
import pathlib
+import subprocess
import sys
def args_crates_cfgs(cfgs):
@@ -35,8 +36,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
crates_cfgs = args_crates_cfgs(cfgs)
def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False):
- crates_indexes[display_name] = len(crates)
- crates.append({
+ crate = {
"display_name": display_name,
"root_module": str(root_module),
"is_workspace_member": is_workspace_member,
@@ -47,16 +47,36 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
"env": {
"RUST_MODFILE": "This is only for rust-analyzer"
}
- })
-
- # First, the ones in `rust/` since they are a bit special.
- append_crate(
- "core",
- sysroot_src / "core" / "src" / "lib.rs",
- [],
- cfg=crates_cfgs.get("core", []),
- is_workspace_member=False,
- )
+ }
+ if is_proc_macro:
+ proc_macro_dylib_name = subprocess.check_output(
+ [os.environ["RUSTC"], "--print", "file-names", "--crate-name", display_name, "--crate-type", "proc-macro", "-"],
+ stdin=subprocess.DEVNULL,
+ ).decode('utf-8').strip()
+ crate["proc_macro_dylib_path"] = f"{objtree}/rust/{proc_macro_dylib_name}"
+ crates_indexes[display_name] = len(crates)
+ crates.append(crate)
+
+ def append_sysroot_crate(
+ display_name,
+ deps,
+ cfg=[],
+ ):
+ append_crate(
+ display_name,
+ sysroot_src / display_name / "src" / "lib.rs",
+ deps,
+ cfg,
+ is_workspace_member=False,
+ )
+
+ # NB: sysroot crates reexport items from one another so setting up our transitive dependencies
+ # here is important for ensuring that rust-analyzer can resolve symbols. The sources of truth
+ # for this dependency graph are `(sysroot_src / crate / "Cargo.toml" for crate in crates)`.
+ append_sysroot_crate("core", [], cfg=crates_cfgs.get("core", []))
+ append_sysroot_crate("alloc", ["core"])
+ append_sysroot_crate("std", ["alloc", "core"])
+ append_sysroot_crate("proc_macro", ["core", "std"])
append_crate(
"compiler_builtins",
@@ -67,10 +87,9 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
append_crate(
"macros",
srctree / "rust" / "macros" / "lib.rs",
- [],
+ ["std", "proc_macro"],
is_proc_macro=True,
)
- crates[-1]["proc_macro_dylib_path"] = f"{objtree}/rust/libmacros.so"
append_crate(
"build_error",
@@ -78,27 +97,28 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
["core", "compiler_builtins"],
)
- append_crate(
- "bindings",
- srctree / "rust"/ "bindings" / "lib.rs",
- ["core"],
- cfg=cfg,
- )
- crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True))
-
- append_crate(
- "kernel",
- srctree / "rust" / "kernel" / "lib.rs",
- ["core", "macros", "build_error", "bindings"],
- cfg=cfg,
- )
- crates[-1]["source"] = {
- "include_dirs": [
- str(srctree / "rust" / "kernel"),
- str(objtree / "rust")
- ],
- "exclude_dirs": [],
- }
+ def append_crate_with_generated(
+ display_name,
+ deps,
+ ):
+ append_crate(
+ display_name,
+ srctree / "rust"/ display_name / "lib.rs",
+ deps,
+ cfg=cfg,
+ )
+ crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True))
+ crates[-1]["source"] = {
+ "include_dirs": [
+ str(srctree / "rust" / display_name),
+ str(objtree / "rust")
+ ],
+ "exclude_dirs": [],
+ }
+
+ append_crate_with_generated("bindings", ["core"])
+ append_crate_with_generated("uapi", ["core"])
+ append_crate_with_generated("kernel", ["core", "macros", "build_error", "bindings", "uapi"])
def is_root_crate(build_file, target):
try:
diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs
index 0d00ac3723b5..4fd6b6ab3e32 100644
--- a/scripts/generate_rust_target.rs
+++ b/scripts/generate_rust_target.rs
@@ -165,6 +165,18 @@ impl KernelConfig {
let option = "CONFIG_".to_owned() + option;
self.0.contains_key(&option)
}
+
+ /// Is the rustc version at least `major.minor.patch`?
+ fn rustc_version_atleast(&self, major: u32, minor: u32, patch: u32) -> bool {
+ let check_version = 100000 * major + 100 * minor + patch;
+ let actual_version = self
+ .0
+ .get("CONFIG_RUSTC_VERSION")
+ .unwrap()
+ .parse::<u32>()
+ .unwrap();
+ check_version <= actual_version
+ }
}
fn main() {
@@ -182,6 +194,9 @@ fn main() {
}
} else if cfg.has("X86_64") {
ts.push("arch", "x86_64");
+ if cfg.rustc_version_atleast(1, 86, 0) {
+ ts.push("rustc-abi", "x86-softfloat");
+ }
ts.push(
"data-layout",
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
@@ -215,6 +230,9 @@ fn main() {
panic!("32-bit x86 only works under UML");
}
ts.push("arch", "x86");
+ if cfg.rustc_version_atleast(1, 86, 0) {
+ ts.push("rustc-abi", "x86-softfloat");
+ }
ts.push(
"data-layout",
"e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
diff --git a/scripts/genksyms/Makefile b/scripts/genksyms/Makefile
index 312edccda736..4350311fb7b3 100644
--- a/scripts/genksyms/Makefile
+++ b/scripts/genksyms/Makefile
@@ -4,24 +4,6 @@ hostprogs-always-y += genksyms
genksyms-objs := genksyms.o parse.tab.o lex.lex.o
-# FIXME: fix the ambiguous grammar in parse.y and delete this hack
-#
-# Suppress shift/reduce, reduce/reduce conflicts warnings
-# unless W=1 is specified.
-#
-# Just in case, run "$(YACC) --version" without suppressing stderr
-# so that 'bison: not found' will be displayed if it is missing.
-ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),)
-
-quiet_cmd_bison_no_warn = $(quiet_cmd_bison)
- cmd_bison_no_warn = $(YACC) --version >/dev/null; \
- $(cmd_bison) 2>/dev/null
-
-$(obj)/pars%.tab.c $(obj)/pars%.tab.h: $(src)/pars%.y FORCE
- $(call if_changed,bison_no_warn)
-
-endif
-
# -I needed for generated C source to include headers in source tree
HOSTCFLAGS_parse.tab.o := -I $(src)
HOSTCFLAGS_lex.lex.o := -I $(src)
diff --git a/scripts/genksyms/genksyms.c b/scripts/genksyms/genksyms.c
index 07f9b8cfb233..8b0d7ac73dbb 100644
--- a/scripts/genksyms/genksyms.c
+++ b/scripts/genksyms/genksyms.c
@@ -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)
diff --git a/scripts/genksyms/genksyms.h b/scripts/genksyms/genksyms.h
index 21ed2ec2d98c..0c355075f0e6 100644
--- a/scripts/genksyms/genksyms.h
+++ b/scripts/genksyms/genksyms.h
@@ -12,8 +12,11 @@
#ifndef MODUTILS_GENKSYMS_H
#define MODUTILS_GENKSYMS_H 1
+#include <stdbool.h>
#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 +34,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;
@@ -64,6 +67,8 @@ struct string_list *copy_list_range(struct string_list *start,
int yylex(void);
int yyparse(void);
+extern bool dont_want_type_specifier;
+
void error_with_pos(const char *, ...) __attribute__ ((format(printf, 1, 2)));
/*----------------------------------------------------------------------*/
diff --git a/scripts/genksyms/lex.l b/scripts/genksyms/lex.l
index a4d7495eaf75..22aeb57649d9 100644
--- a/scripts/genksyms/lex.l
+++ b/scripts/genksyms/lex.l
@@ -12,6 +12,7 @@
%{
#include <limits.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
@@ -50,6 +51,7 @@ MC_TOKEN ([~%^&*+=|<>/-]=)|(&&)|("||")|(->)|(<<)|(>>)
%%
+u?int(8|16|32|64)x(1|2|4|8|16)_t return BUILTIN_INT_KEYW;
/* Keep track of our location in the original source files. */
^#[ \t]+{INT}[ \t]+\"[^\"\n]+\".*\n return FILENAME;
@@ -113,6 +115,12 @@ MC_TOKEN ([~%^&*+=|<>/-]=)|(&&)|("||")|(->)|(<<)|(>>)
/* The second stage lexer. Here we incorporate knowledge of the state
of the parser to tailor the tokens that are returned. */
+/*
+ * The lexer cannot distinguish whether a typedef'ed string is a TYPE or an
+ * IDENT. We need a hint from the parser to handle this accurately.
+ */
+bool dont_want_type_specifier;
+
int
yylex(void)
{
@@ -207,7 +215,7 @@ repeat:
goto repeat;
}
}
- if (!suppress_type_lookup)
+ if (!suppress_type_lookup && !dont_want_type_specifier)
{
if (find_symbol(yytext, SYM_TYPEDEF, 1))
token = TYPE;
@@ -431,7 +439,12 @@ fini:
if (suppress_type_lookup > 0)
--suppress_type_lookup;
- if (dont_want_brace_phrase > 0)
+
+ /*
+ * __attribute__() can be placed immediately after the 'struct' keyword.
+ * e.g.) struct __attribute__((__packed__)) foo { ... };
+ */
+ if (token != ATTRIBUTE_PHRASE && dont_want_brace_phrase > 0)
--dont_want_brace_phrase;
yylval = &next_node->next;
diff --git a/scripts/genksyms/parse.y b/scripts/genksyms/parse.y
index 8e9b5e69e8f0..ee600a804fa1 100644
--- a/scripts/genksyms/parse.y
+++ b/scripts/genksyms/parse.y
@@ -12,6 +12,7 @@
%{
#include <assert.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "genksyms.h"
@@ -148,32 +149,45 @@ simple_declaration:
current_name = NULL;
}
$$ = $3;
+ dont_want_type_specifier = false;
}
;
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;
$$ = $1;
+ dont_want_type_specifier = true;
}
- | init_declarator_list ',' init_declarator
- { struct string_list *decl = *$3;
- *$3 = NULL;
+ | init_declarator_list ',' attribute_opt init_declarator
+ { struct string_list *decl = *$4;
+ *$4 = 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;
- $$ = $3;
+ $$ = $4;
+ dont_want_type_specifier = true;
}
;
@@ -189,8 +203,9 @@ decl_specifier_seq_opt:
;
decl_specifier_seq:
- decl_specifier { decl_spec = *$1; }
+ attribute_opt decl_specifier { decl_spec = *$2; }
| decl_specifier_seq decl_specifier { decl_spec = *$2; }
+ | decl_specifier_seq ATTRIBUTE_PHRASE { decl_spec = *$2; }
;
decl_specifier:
@@ -200,7 +215,8 @@ decl_specifier:
remove_node($1);
$$ = $1;
}
- | type_specifier
+ | type_specifier { dont_want_type_specifier = true; $$ = $1; }
+ | type_qualifier
;
storage_class_specifier:
@@ -213,24 +229,23 @@ storage_class_specifier:
type_specifier:
simple_type_specifier
- | cvar_qualifier
| TYPEOF_KEYW '(' parameter_declaration ')'
| TYPEOF_PHRASE
/* References to s/u/e's defined elsewhere. Rearrange things
so that it is easier to expand the definition fully later. */
- | STRUCT_KEYW IDENT
- { remove_node($1); (*$2)->tag = SYM_STRUCT; $$ = $2; }
- | UNION_KEYW IDENT
- { remove_node($1); (*$2)->tag = SYM_UNION; $$ = $2; }
+ | STRUCT_KEYW attribute_opt IDENT
+ { remove_node($1); (*$3)->tag = SYM_STRUCT; $$ = $3; }
+ | UNION_KEYW attribute_opt IDENT
+ { remove_node($1); (*$3)->tag = SYM_UNION; $$ = $3; }
| ENUM_KEYW IDENT
{ remove_node($1); (*$2)->tag = SYM_ENUM; $$ = $2; }
/* Full definitions of an s/u/e. Record it. */
- | STRUCT_KEYW IDENT class_body
- { record_compound($1, $2, $3, SYM_STRUCT); $$ = $3; }
- | UNION_KEYW IDENT class_body
- { record_compound($1, $2, $3, SYM_UNION); $$ = $3; }
+ | STRUCT_KEYW attribute_opt IDENT class_body
+ { record_compound($1, $3, $4, SYM_STRUCT); $$ = $4; }
+ | UNION_KEYW attribute_opt IDENT class_body
+ { record_compound($1, $3, $4, SYM_UNION); $$ = $4; }
| ENUM_KEYW IDENT enum_body
{ record_compound($1, $2, $3, SYM_ENUM); $$ = $3; }
/*
@@ -239,8 +254,8 @@ type_specifier:
| ENUM_KEYW enum_body
{ add_symbol(NULL, SYM_ENUM, NULL, 0); $$ = $2; }
/* Anonymous s/u definitions. Nothing needs doing. */
- | STRUCT_KEYW class_body { $$ = $2; }
- | UNION_KEYW class_body { $$ = $2; }
+ | STRUCT_KEYW attribute_opt class_body { $$ = $3; }
+ | UNION_KEYW attribute_opt class_body { $$ = $3; }
;
simple_type_specifier:
@@ -260,22 +275,24 @@ simple_type_specifier:
;
ptr_operator:
- '*' cvar_qualifier_seq_opt
+ '*' type_qualifier_seq_opt
{ $$ = $2 ? $2 : $1; }
;
-cvar_qualifier_seq_opt:
+type_qualifier_seq_opt:
/* empty */ { $$ = NULL; }
- | cvar_qualifier_seq
+ | type_qualifier_seq
;
-cvar_qualifier_seq:
- cvar_qualifier
- | cvar_qualifier_seq cvar_qualifier { $$ = $2; }
+type_qualifier_seq:
+ type_qualifier
+ | ATTRIBUTE_PHRASE
+ | type_qualifier_seq type_qualifier { $$ = $2; }
+ | type_qualifier_seq ATTRIBUTE_PHRASE { $$ = $2; }
;
-cvar_qualifier:
- CONST_KEYW | VOLATILE_KEYW | ATTRIBUTE_PHRASE
+type_qualifier:
+ CONST_KEYW | VOLATILE_KEYW
| RESTRICT_KEYW
{ /* restrict has no effect in prototypes so ignore it */
remove_node($1);
@@ -297,15 +314,7 @@ direct_declarator:
current_name = (*$1)->string;
$$ = $1;
}
- }
- | TYPE
- { if (current_name != NULL) {
- error_with_pos("unexpected second declaration name");
- YYERROR;
- } else {
- current_name = (*$1)->string;
- $$ = $1;
- }
+ dont_want_type_specifier = false;
}
| direct_declarator '(' parameter_declaration_clause ')'
{ $$ = $4; }
@@ -325,16 +334,19 @@ nested_declarator:
;
direct_nested_declarator:
- IDENT
- | TYPE
- | direct_nested_declarator '(' parameter_declaration_clause ')'
+ direct_nested_declarator1
+ | direct_nested_declarator1 '(' parameter_declaration_clause ')'
{ $$ = $4; }
- | direct_nested_declarator '(' error ')'
+ ;
+
+direct_nested_declarator1:
+ IDENT { $$ = $1; dont_want_type_specifier = false; }
+ | direct_nested_declarator1 '(' error ')'
{ $$ = $4; }
- | direct_nested_declarator BRACKET_PHRASE
+ | direct_nested_declarator1 BRACKET_PHRASE
{ $$ = $2; }
- | '(' nested_declarator ')'
- { $$ = $3; }
+ | '(' attribute_opt nested_declarator ')'
+ { $$ = $4; }
| '(' error ')'
{ $$ = $3; }
;
@@ -352,45 +364,57 @@ parameter_declaration_list_opt:
parameter_declaration_list:
parameter_declaration
+ { $$ = $1; dont_want_type_specifier = false; }
| parameter_declaration_list ',' parameter_declaration
- { $$ = $3; }
+ { $$ = $3; dont_want_type_specifier = false; }
;
parameter_declaration:
- decl_specifier_seq m_abstract_declarator
+ decl_specifier_seq abstract_declarator_opt
{ $$ = $2 ? $2 : $1; }
;
-m_abstract_declarator:
- ptr_operator m_abstract_declarator
+abstract_declarator_opt:
+ /* empty */ { $$ = NULL; }
+ | abstract_declarator
+ ;
+
+abstract_declarator:
+ ptr_operator
+ | ptr_operator abstract_declarator
{ $$ = $2 ? $2 : $1; }
- | direct_m_abstract_declarator
+ | direct_abstract_declarator attribute_opt
+ { $$ = $2; dont_want_type_specifier = false; }
;
-direct_m_abstract_declarator:
- /* empty */ { $$ = NULL; }
- | IDENT
+direct_abstract_declarator:
+ direct_abstract_declarator1
+ | direct_abstract_declarator1 open_paren parameter_declaration_clause ')'
+ { $$ = $4; }
+ | open_paren parameter_declaration_clause ')'
+ { $$ = $3; }
+ ;
+
+direct_abstract_declarator1:
+ IDENT
{ /* For version 2 checksums, we don't want to remember
private parameter names. */
remove_node($1);
$$ = $1;
}
- /* This wasn't really a typedef name but an identifier that
- shadows one. */
- | TYPE
- { remove_node($1);
- $$ = $1;
- }
- | direct_m_abstract_declarator '(' parameter_declaration_clause ')'
- { $$ = $4; }
- | direct_m_abstract_declarator '(' error ')'
+ | direct_abstract_declarator1 open_paren error ')'
{ $$ = $4; }
- | direct_m_abstract_declarator BRACKET_PHRASE
+ | direct_abstract_declarator1 BRACKET_PHRASE
{ $$ = $2; }
- | '(' m_abstract_declarator ')'
- { $$ = $3; }
- | '(' error ')'
+ | open_paren attribute_opt abstract_declarator ')'
+ { $$ = $4; }
+ | open_paren error ')'
{ $$ = $3; }
+ | BRACKET_PHRASE
+ ;
+
+open_paren:
+ '(' { $$ = $1; dont_want_type_specifier = false; }
;
function_definition:
@@ -430,9 +454,9 @@ member_specification:
member_declaration:
decl_specifier_seq_opt member_declarator_list_opt ';'
- { $$ = $3; }
+ { $$ = $3; dont_want_type_specifier = false; }
| error ';'
- { $$ = $2; }
+ { $$ = $2; dont_want_type_specifier = false; }
;
member_declarator_list_opt:
@@ -442,7 +466,9 @@ member_declarator_list_opt:
member_declarator_list:
member_declarator
- | member_declarator_list ',' member_declarator { $$ = $3; }
+ { $$ = $1; dont_want_type_specifier = true; }
+ | member_declarator_list ',' member_declarator
+ { $$ = $3; dont_want_type_specifier = true; }
;
member_declarator:
@@ -457,7 +483,7 @@ member_bitfield_declarator:
attribute_opt:
/* empty */ { $$ = NULL; }
- | attribute_opt ATTRIBUTE_PHRASE
+ | attribute_opt ATTRIBUTE_PHRASE { $$ = $2; }
;
enum_body:
@@ -472,12 +498,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);
}
diff --git a/scripts/get_abi.pl b/scripts/get_abi.pl
deleted file mode 100755
index de1c0354b50c..000000000000
--- a/scripts/get_abi.pl
+++ /dev/null
@@ -1,1103 +0,0 @@
-#!/usr/bin/env perl
-# SPDX-License-Identifier: GPL-2.0
-
-BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Termcap'; }
-
-use strict;
-use warnings;
-use utf8;
-use Pod::Usage qw(pod2usage);
-use Getopt::Long;
-use File::Find;
-use IO::Handle;
-use Fcntl ':mode';
-use Cwd 'abs_path';
-use Data::Dumper;
-
-my $help = 0;
-my $hint = 0;
-my $man = 0;
-my $debug = 0;
-my $enable_lineno = 0;
-my $show_warnings = 1;
-my $prefix="Documentation/ABI";
-my $sysfs_prefix="/sys";
-my $search_string;
-
-# Debug options
-my $dbg_what_parsing = 1;
-my $dbg_what_open = 2;
-my $dbg_dump_abi_structs = 4;
-my $dbg_undefined = 8;
-
-$Data::Dumper::Indent = 1;
-$Data::Dumper::Terse = 1;
-
-#
-# If true, assumes that the description is formatted with ReST
-#
-my $description_is_rst = 1;
-
-GetOptions(
- "debug=i" => \$debug,
- "enable-lineno" => \$enable_lineno,
- "rst-source!" => \$description_is_rst,
- "dir=s" => \$prefix,
- 'help|?' => \$help,
- "show-hints" => \$hint,
- "search-string=s" => \$search_string,
- man => \$man
-) or pod2usage(2);
-
-pod2usage(1) if $help;
-pod2usage(-exitstatus => 0, -noperldoc, -verbose => 2) if $man;
-
-pod2usage(2) if (scalar @ARGV < 1 || @ARGV > 2);
-
-my ($cmd, $arg) = @ARGV;
-
-pod2usage(2) if ($cmd ne "search" && $cmd ne "rest" && $cmd ne "validate" && $cmd ne "undefined");
-pod2usage(2) if ($cmd eq "search" && !$arg);
-
-require Data::Dumper if ($debug & $dbg_dump_abi_structs);
-
-my %data;
-my %symbols;
-
-#
-# Displays an error message, printing file name and line
-#
-sub parse_error($$$$) {
- my ($file, $ln, $msg, $data) = @_;
-
- return if (!$show_warnings);
-
- $data =~ s/\s+$/\n/;
-
- print STDERR "Warning: file $file#$ln:\n\t$msg";
-
- if ($data ne "") {
- print STDERR ". Line\n\t\t$data";
- } else {
- print STDERR "\n";
- }
-}
-
-#
-# Parse an ABI file, storing its contents at %data
-#
-sub parse_abi {
- my $file = $File::Find::name;
-
- my $mode = (stat($file))[2];
- return if ($mode & S_IFDIR);
- return if ($file =~ m,/README,);
- return if ($file =~ m,/\.,);
- return if ($file =~ m,\.(rej|org|orig|bak)$,);
-
- my $name = $file;
- $name =~ s,.*/,,;
-
- my $fn = $file;
- $fn =~ s,.*Documentation/ABI/,,;
-
- my $nametag = "File $fn";
- $data{$nametag}->{what} = "File $name";
- $data{$nametag}->{type} = "File";
- $data{$nametag}->{file} = $name;
- $data{$nametag}->{filepath} = $file;
- $data{$nametag}->{is_file} = 1;
- $data{$nametag}->{line_no} = 1;
-
- my $type = $file;
- $type =~ s,.*/(.*)/.*,$1,;
-
- my $what;
- my $new_what;
- my $tag = "";
- my $ln;
- my $xrefs;
- my $space;
- my @labels;
- my $label = "";
-
- print STDERR "Opening $file\n" if ($debug & $dbg_what_open);
- open IN, $file;
- while(<IN>) {
- $ln++;
- if (m/^(\S+)(:\s*)(.*)/i) {
- my $new_tag = lc($1);
- my $sep = $2;
- my $content = $3;
-
- if (!($new_tag =~ m/(what|where|date|kernelversion|contact|description|users)/)) {
- if ($tag eq "description") {
- # New "tag" is actually part of
- # description. Don't consider it a tag
- $new_tag = "";
- } elsif ($tag ne "") {
- parse_error($file, $ln, "tag '$tag' is invalid", $_);
- }
- }
-
- # Invalid, but it is a common mistake
- if ($new_tag eq "where") {
- parse_error($file, $ln, "tag 'Where' is invalid. Should be 'What:' instead", "");
- $new_tag = "what";
- }
-
- if ($new_tag =~ m/what/) {
- $space = "";
- $content =~ s/[,.;]$//;
-
- push @{$symbols{$content}->{file}}, " $file:" . ($ln - 1);
-
- if ($tag =~ m/what/) {
- $what .= "\xac" . $content;
- } else {
- if ($what) {
- parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description});
-
- foreach my $w(split /\xac/, $what) {
- $symbols{$w}->{xref} = $what;
- };
- }
-
- $what = $content;
- $label = $content;
- $new_what = 1;
- }
- push @labels, [($content, $label)];
- $tag = $new_tag;
-
- push @{$data{$nametag}->{symbols}}, $content if ($data{$nametag}->{what});
- next;
- }
-
- if ($tag ne "" && $new_tag) {
- $tag = $new_tag;
-
- if ($new_what) {
- @{$data{$what}->{label_list}} = @labels if ($data{$nametag}->{what});
- @labels = ();
- $label = "";
- $new_what = 0;
-
- $data{$what}->{type} = $type;
- if (!defined($data{$what}->{file})) {
- $data{$what}->{file} = $name;
- $data{$what}->{filepath} = $file;
- } else {
- $data{$what}->{description} .= "\n\n" if (defined($data{$what}->{description}));
- if ($name ne $data{$what}->{file}) {
- $data{$what}->{file} .= " " . $name;
- $data{$what}->{filepath} .= " " . $file;
- }
- }
- print STDERR "\twhat: $what\n" if ($debug & $dbg_what_parsing);
- $data{$what}->{line_no} = $ln;
- } else {
- $data{$what}->{line_no} = $ln if (!defined($data{$what}->{line_no}));
- }
-
- if (!$what) {
- parse_error($file, $ln, "'What:' should come first:", $_);
- next;
- }
- if ($new_tag eq "description") {
- $sep =~ s,:, ,;
- $content = ' ' x length($new_tag) . $sep . $content;
- while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}
- if ($content =~ m/^(\s*)(\S.*)$/) {
- # Preserve initial spaces for the first line
- $space = $1;
- $content = "$2\n";
- $data{$what}->{$tag} .= $content;
- } else {
- undef($space);
- }
-
- } else {
- $data{$what}->{$tag} = $content;
- }
- next;
- }
- }
-
- # Store any contents before tags at the database
- if (!$tag && $data{$nametag}->{what}) {
- $data{$nametag}->{description} .= $_;
- next;
- }
-
- if ($tag eq "description") {
- my $content = $_;
- while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}
- if (m/^\s*\n/) {
- $data{$what}->{$tag} .= "\n";
- next;
- }
-
- if (!defined($space)) {
- # Preserve initial spaces for the first line
- if ($content =~ m/^(\s*)(\S.*)$/) {
- $space = $1;
- $content = "$2\n";
- }
- } else {
- $space = "" if (!($content =~ s/^($space)//));
- }
- $data{$what}->{$tag} .= $content;
-
- next;
- }
- if (m/^\s*(.*)/) {
- $data{$what}->{$tag} .= "\n$1";
- $data{$what}->{$tag} =~ s/\n+$//;
- next;
- }
-
- # Everything else is error
- parse_error($file, $ln, "Unexpected content", $_);
- }
- $data{$nametag}->{description} =~ s/^\n+// if ($data{$nametag}->{description});
- if ($what) {
- parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description});
-
- foreach my $w(split /\xac/,$what) {
- $symbols{$w}->{xref} = $what;
- };
- }
- close IN;
-}
-
-sub create_labels {
- my %labels;
-
- foreach my $what (keys %data) {
- next if ($data{$what}->{file} eq "File");
-
- foreach my $p (@{$data{$what}->{label_list}}) {
- my ($content, $label) = @{$p};
- $label = "abi_" . $label . " ";
- $label =~ tr/A-Z/a-z/;
-
- # Convert special chars to "_"
- $label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g;
- $label =~ s,_+,_,g;
- $label =~ s,_$,,;
-
- # Avoid duplicated labels
- while (defined($labels{$label})) {
- my @chars = ("A".."Z", "a".."z");
- $label .= $chars[rand @chars];
- }
- $labels{$label} = 1;
-
- $data{$what}->{label} = $label;
-
- # only one label is enough
- last;
- }
- }
-}
-
-#
-# Outputs the book on ReST format
-#
-
-# \b doesn't work well with paths. So, we need to define something else:
-# Boundaries are punct characters, spaces and end-of-line
-my $start = qr {(^|\s|\() }x;
-my $bondary = qr { ([,.:;\)\s]|\z) }x;
-my $xref_match = qr { $start(\/(sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)$bondary }x;
-my $symbols = qr { ([\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff]) }x;
-
-sub output_rest {
- create_labels();
-
- my $part = "";
-
- foreach my $what (sort {
- ($data{$a}->{type} eq "File") cmp ($data{$b}->{type} eq "File") ||
- $a cmp $b
- } keys %data) {
- my $type = $data{$what}->{type};
-
- my @file = split / /, $data{$what}->{file};
- my @filepath = split / /, $data{$what}->{filepath};
-
- if ($enable_lineno) {
- printf ".. LINENO %s%s#%s\n\n",
- $prefix, $file[0],
- $data{$what}->{line_no};
- }
-
- my $w = $what;
-
- if ($type ne "File") {
- my $cur_part = $what;
- if ($what =~ '/') {
- if ($what =~ m#^(\/?(?:[\w\-]+\/?){1,2})#) {
- $cur_part = "Symbols under $1";
- $cur_part =~ s,/$,,;
- }
- }
-
- if ($cur_part ne "" && $part ne $cur_part) {
- $part = $cur_part;
- my $bar = $part;
- $bar =~ s/./-/g;
- print "$part\n$bar\n\n";
- }
-
- printf ".. _%s:\n\n", $data{$what}->{label};
-
- my @names = split /\xac/,$w;
- my $len = 0;
-
- foreach my $name (@names) {
- $name =~ s/$symbols/\\$1/g;
- $name = "**$name**";
- $len = length($name) if (length($name) > $len);
- }
-
- print "+-" . "-" x $len . "-+\n";
- foreach my $name (@names) {
- printf "| %s", $name . " " x ($len - length($name)) . " |\n";
- print "+-" . "-" x $len . "-+\n";
- }
-
- print "\n";
- }
-
- for (my $i = 0; $i < scalar(@filepath); $i++) {
- my $path = $filepath[$i];
- my $f = $file[$i];
-
- $path =~ s,.*/(.*/.*),$1,;;
- $path =~ s,[/\-],_,g;;
- my $fileref = "abi_file_".$path;
-
- if ($type eq "File") {
- print ".. _$fileref:\n\n";
- } else {
- print "Defined on file :ref:`$f <$fileref>`\n\n";
- }
- }
-
- if ($type eq "File") {
- my $bar = $w;
- $bar =~ s/./-/g;
- print "$w\n$bar\n\n";
- }
-
- my $desc = "";
- $desc = $data{$what}->{description} if (defined($data{$what}->{description}));
- $desc =~ s/\s+$/\n/;
-
- if (!($desc =~ /^\s*$/)) {
- if ($description_is_rst) {
- # Remove title markups from the description
- # Having titles inside ABI files will only work if extra
- # care would be taken in order to strictly follow the same
- # level order for each markup.
- $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g;
-
- # Enrich text by creating cross-references
-
- my $new_desc = "";
- my $init_indent = -1;
- my $literal_indent = -1;
-
- open(my $fh, "+<", \$desc);
- while (my $d = <$fh>) {
- my $indent = $d =~ m/^(\s+)/;
- my $spaces = length($indent);
- $init_indent = $indent if ($init_indent < 0);
- if ($literal_indent >= 0) {
- if ($spaces > $literal_indent) {
- $new_desc .= $d;
- next;
- } else {
- $literal_indent = -1;
- }
- } else {
- if ($d =~ /()::$/ && !($d =~ /^\s*\.\./)) {
- $literal_indent = $spaces;
- }
- }
-
- $d =~ s,Documentation/(?!devicetree)(\S+)\.rst,:doc:`/$1`,g;
-
- my @matches = $d =~ m,Documentation/ABI/([\w\/\-]+),g;
- foreach my $f (@matches) {
- my $xref = $f;
- my $path = $f;
- $path =~ s,.*/(.*/.*),$1,;;
- $path =~ s,[/\-],_,g;;
- $xref .= " <abi_file_" . $path . ">";
- $d =~ s,\bDocumentation/ABI/$f\b,:ref:`$xref`,g;
- }
-
- # Seek for cross reference symbols like /sys/...
- @matches = $d =~ m/$xref_match/g;
-
- foreach my $s (@matches) {
- next if (!($s =~ m,/,));
- if (defined($data{$s}) && defined($data{$s}->{label})) {
- my $xref = $s;
-
- $xref =~ s/$symbols/\\$1/g;
- $xref = ":ref:`$xref <" . $data{$s}->{label} . ">`";
-
- $d =~ s,$start$s$bondary,$1$xref$2,g;
- }
- }
- $new_desc .= $d;
- }
- close $fh;
-
-
- print "$new_desc\n\n";
- } else {
- $desc =~ s/^\s+//;
-
- # Remove title markups from the description, as they won't work
- $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g;
-
- if ($desc =~ m/\:\n/ || $desc =~ m/\n[\t ]+/ || $desc =~ m/[\x00-\x08\x0b-\x1f\x7b-\xff]/) {
- # put everything inside a code block
- $desc =~ s/\n/\n /g;
-
- print "::\n\n";
- print " $desc\n\n";
- } else {
- # Escape any special chars from description
- $desc =~s/([\x00-\x08\x0b-\x1f\x21-\x2a\x2d\x2f\x3c-\x40\x5c\x5e-\x60\x7b-\xff])/\\$1/g;
- print "$desc\n\n";
- }
- }
- } else {
- print "DESCRIPTION MISSING for $what\n\n" if (!$data{$what}->{is_file});
- }
-
- if ($data{$what}->{symbols}) {
- printf "Has the following ABI:\n\n";
-
- foreach my $content(@{$data{$what}->{symbols}}) {
- my $label = $data{$symbols{$content}->{xref}}->{label};
-
- # Escape special chars from content
- $content =~s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g;
-
- print "- :ref:`$content <$label>`\n\n";
- }
- }
-
- if (defined($data{$what}->{users})) {
- my $users = $data{$what}->{users};
-
- $users =~ s/\n/\n\t/g;
- printf "Users:\n\t%s\n\n", $users if ($users ne "");
- }
-
- }
-}
-
-#
-# Searches for ABI symbols
-#
-sub search_symbols {
- foreach my $what (sort keys %data) {
- next if (!($what =~ m/($arg)/));
-
- my $type = $data{$what}->{type};
- next if ($type eq "File");
-
- my $file = $data{$what}->{filepath};
-
- $what =~ s/\xac/, /g;
- my $bar = $what;
- $bar =~ s/./-/g;
-
- print "\n$what\n$bar\n\n";
-
- my $kernelversion = $data{$what}->{kernelversion} if (defined($data{$what}->{kernelversion}));
- my $contact = $data{$what}->{contact} if (defined($data{$what}->{contact}));
- my $users = $data{$what}->{users} if (defined($data{$what}->{users}));
- my $date = $data{$what}->{date} if (defined($data{$what}->{date}));
- my $desc = $data{$what}->{description} if (defined($data{$what}->{description}));
-
- $kernelversion =~ s/^\s+// if ($kernelversion);
- $contact =~ s/^\s+// if ($contact);
- if ($users) {
- $users =~ s/^\s+//;
- $users =~ s/\n//g;
- }
- $date =~ s/^\s+// if ($date);
- $desc =~ s/^\s+// if ($desc);
-
- printf "Kernel version:\t\t%s\n", $kernelversion if ($kernelversion);
- printf "Date:\t\t\t%s\n", $date if ($date);
- printf "Contact:\t\t%s\n", $contact if ($contact);
- printf "Users:\t\t\t%s\n", $users if ($users);
- print "Defined on file(s):\t$file\n\n";
- print "Description:\n\n$desc";
- }
-}
-
-# Exclude /sys/kernel/debug and /sys/kernel/tracing from the search path
-sub dont_parse_special_attributes {
- if (($File::Find::dir =~ m,^/sys/kernel,)) {
- return grep {!/(debug|tracing)/ } @_;
- }
-
- if (($File::Find::dir =~ m,^/sys/fs,)) {
- return grep {!/(pstore|bpf|fuse)/ } @_;
- }
-
- return @_
-}
-
-my %leaf;
-my %aliases;
-my @files;
-my %root;
-
-sub graph_add_file {
- my $file = shift;
- my $type = shift;
-
- my $dir = $file;
- $dir =~ s,^(.*/).*,$1,;
- $file =~ s,.*/,,;
-
- my $name;
- my $file_ref = \%root;
- foreach my $edge(split "/", $dir) {
- $name .= "$edge/";
- if (!defined ${$file_ref}{$edge}) {
- ${$file_ref}{$edge} = { };
- }
- $file_ref = \%{$$file_ref{$edge}};
- ${$file_ref}{"__name"} = [ $name ];
- }
- $name .= "$file";
- ${$file_ref}{$file} = {
- "__name" => [ $name ]
- };
-
- return \%{$$file_ref{$file}};
-}
-
-sub graph_add_link {
- my $file = shift;
- my $link = shift;
-
- # Traverse graph to find the reference
- my $file_ref = \%root;
- foreach my $edge(split "/", $file) {
- $file_ref = \%{$$file_ref{$edge}} || die "Missing node!";
- }
-
- # do a BFS
-
- my @queue;
- my %seen;
- my $st;
-
- push @queue, $file_ref;
- $seen{$start}++;
-
- while (@queue) {
- my $v = shift @queue;
- my @child = keys(%{$v});
-
- foreach my $c(@child) {
- next if $seen{$$v{$c}};
- next if ($c eq "__name");
-
- if (!defined($$v{$c}{"__name"})) {
- printf STDERR "Error: Couldn't find a non-empty name on a children of $file/.*: ";
- print STDERR Dumper(%{$v});
- exit;
- }
-
- # Add new name
- my $name = @{$$v{$c}{"__name"}}[0];
- if ($name =~ s#^$file/#$link/#) {
- push @{$$v{$c}{"__name"}}, $name;
- }
- # Add child to the queue and mark as seen
- push @queue, $$v{$c};
- $seen{$c}++;
- }
- }
-}
-
-my $escape_symbols = qr { ([\x01-\x08\x0e-\x1f\x21-\x29\x2b-\x2d\x3a-\x40\x7b-\xfe]) }x;
-sub parse_existing_sysfs {
- my $file = $File::Find::name;
-
- my $mode = (lstat($file))[2];
- my $abs_file = abs_path($file);
-
- my @tmp;
- push @tmp, $file;
- push @tmp, $abs_file if ($abs_file ne $file);
-
- foreach my $f(@tmp) {
- # Ignore cgroup, as this is big and has zero docs under ABI
- return if ($f =~ m#^/sys/fs/cgroup/#);
-
- # Ignore firmware as it is documented elsewhere
- # Either ACPI or under Documentation/devicetree/bindings/
- return if ($f =~ m#^/sys/firmware/#);
-
- # Ignore some sysfs nodes that aren't actually part of ABI
- return if ($f =~ m#/sections|notes/#);
-
- # Would need to check at
- # Documentation/admin-guide/kernel-parameters.txt, but this
- # is not easily parseable.
- return if ($f =~ m#/parameters/#);
- }
-
- if (S_ISLNK($mode)) {
- $aliases{$file} = $abs_file;
- return;
- }
-
- return if (S_ISDIR($mode));
-
- # Trivial: file is defined exactly the same way at ABI What:
- return if (defined($data{$file}));
- return if (defined($data{$abs_file}));
-
- push @files, graph_add_file($abs_file, "file");
-}
-
-sub get_leave($)
-{
- my $what = shift;
- my $leave;
-
- my $l = $what;
- my $stop = 1;
-
- $leave = $l;
- $leave =~ s,/$,,;
- $leave =~ s,.*/,,;
- $leave =~ s/[\(\)]//g;
-
- # $leave is used to improve search performance at
- # check_undefined_symbols, as the algorithm there can seek
- # for a small number of "what". It also allows giving a
- # hint about a leave with the same name somewhere else.
- # However, there are a few occurences where the leave is
- # either a wildcard or a number. Just group such cases
- # altogether.
- if ($leave =~ m/\.\*/ || $leave eq "" || $leave =~ /\\d/) {
- $leave = "others";
- }
-
- return $leave;
-}
-
-my @not_found;
-
-sub check_file($$)
-{
- my $file_ref = shift;
- my $names_ref = shift;
- my @names = @{$names_ref};
- my $file = $names[0];
-
- my $found_string;
-
- my $leave = get_leave($file);
- if (!defined($leaf{$leave})) {
- $leave = "others";
- }
- my @expr = @{$leaf{$leave}->{expr}};
- die ("\rmissing rules for $leave") if (!defined($leaf{$leave}));
-
- my $path = $file;
- $path =~ s,(.*/).*,$1,;
-
- if ($search_string) {
- return if (!($file =~ m#$search_string#));
- $found_string = 1;
- }
-
- for (my $i = 0; $i < @names; $i++) {
- if ($found_string && $hint) {
- if (!$i) {
- print STDERR "--> $names[$i]\n";
- } else {
- print STDERR " $names[$i]\n";
- }
- }
- foreach my $re (@expr) {
- print STDERR "$names[$i] =~ /^$re\$/\n" if ($debug && $dbg_undefined);
- if ($names[$i] =~ $re) {
- return;
- }
- }
- }
-
- if ($leave ne "others") {
- my @expr = @{$leaf{"others"}->{expr}};
- for (my $i = 0; $i < @names; $i++) {
- foreach my $re (@expr) {
- print STDERR "$names[$i] =~ /^$re\$/\n" if ($debug && $dbg_undefined);
- if ($names[$i] =~ $re) {
- return;
- }
- }
- }
- }
-
- push @not_found, $file if (!$search_string || $found_string);
-
- if ($hint && (!$search_string || $found_string)) {
- my $what = $leaf{$leave}->{what};
- $what =~ s/\xac/\n\t/g;
- if ($leave ne "others") {
- print STDERR "\r more likely regexes:\n\t$what\n";
- } else {
- print STDERR "\r tested regexes:\n\t$what\n";
- }
- }
-}
-
-sub check_undefined_symbols {
- my $num_files = scalar @files;
- my $next_i = 0;
- my $start_time = times;
-
- @files = sort @files;
-
- my $last_time = $start_time;
-
- # When either debug or hint is enabled, there's no sense showing
- # progress, as the progress will be overriden.
- if ($hint || ($debug && $dbg_undefined)) {
- $next_i = $num_files;
- }
-
- my $is_console;
- $is_console = 1 if (-t STDERR);
-
- for (my $i = 0; $i < $num_files; $i++) {
- my $file_ref = $files[$i];
- my @names = @{$$file_ref{"__name"}};
-
- check_file($file_ref, \@names);
-
- my $cur_time = times;
-
- if ($i == $next_i || $cur_time > $last_time + 1) {
- my $percent = $i * 100 / $num_files;
-
- my $tm = $cur_time - $start_time;
- my $time = sprintf "%d:%02d", int($tm), 60 * ($tm - int($tm));
-
- printf STDERR "\33[2K\r", if ($is_console);
- printf STDERR "%s: processing sysfs files... %i%%: $names[0]", $time, $percent;
- printf STDERR "\n", if (!$is_console);
- STDERR->flush();
-
- $next_i = int (($percent + 1) * $num_files / 100);
- $last_time = $cur_time;
- }
- }
-
- my $cur_time = times;
- my $tm = $cur_time - $start_time;
- my $time = sprintf "%d:%02d", int($tm), 60 * ($tm - int($tm));
-
- printf STDERR "\33[2K\r", if ($is_console);
- printf STDERR "%s: processing sysfs files... done\n", $time;
-
- foreach my $file (@not_found) {
- print "$file not found.\n";
- }
-}
-
-sub undefined_symbols {
- print STDERR "Reading $sysfs_prefix directory contents...";
- find({
- wanted =>\&parse_existing_sysfs,
- preprocess =>\&dont_parse_special_attributes,
- no_chdir => 1
- }, $sysfs_prefix);
- print STDERR "done.\n";
-
- $leaf{"others"}->{what} = "";
-
- print STDERR "Converting ABI What fields into regexes...";
- foreach my $w (sort keys %data) {
- foreach my $what (split /\xac/,$w) {
- next if (!($what =~ m/^$sysfs_prefix/));
-
- # Convert what into regular expressions
-
- # Escape dot characters
- $what =~ s/\./\xf6/g;
-
- # Temporarily change [0-9]+ type of patterns
- $what =~ s/\[0\-9\]\+/\xff/g;
-
- # Temporarily change [\d+-\d+] type of patterns
- $what =~ s/\[0\-\d+\]/\xff/g;
- $what =~ s/\[(\d+)\]/\xf4$1\xf5/g;
-
- # Temporarily change [0-9] type of patterns
- $what =~ s/\[(\d)\-(\d)\]/\xf4$1-$2\xf5/g;
-
- # Handle multiple option patterns
- $what =~ s/[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]/($1|$2)/g;
-
- # Handle wildcards
- $what =~ s,\*,.*,g;
- $what =~ s,/\xf6..,/.*,g;
- $what =~ s/\<[^\>]+\>/.*/g;
- $what =~ s/\{[^\}]+\}/.*/g;
- $what =~ s/\[[^\]]+\]/.*/g;
-
- $what =~ s/[XYZ]/.*/g;
-
- # Recover [0-9] type of patterns
- $what =~ s/\xf4/[/g;
- $what =~ s/\xf5/]/g;
-
- # Remove duplicated spaces
- $what =~ s/\s+/ /g;
-
- # Special case: this ABI has a parenthesis on it
- $what =~ s/sqrt\(x^2\+y^2\+z^2\)/sqrt\(x^2\+y^2\+z^2\)/;
-
- # Special case: drop comparition as in:
- # What: foo = <something>
- # (this happens on a few IIO definitions)
- $what =~ s,\s*\=.*$,,;
-
- # Escape all other symbols
- $what =~ s/$escape_symbols/\\$1/g;
- $what =~ s/\\\\/\\/g;
- $what =~ s/\\([\[\]\(\)\|])/$1/g;
- $what =~ s/(\d+)\\(-\d+)/$1$2/g;
-
- $what =~ s/\xff/\\d+/g;
-
- # Special case: IIO ABI which a parenthesis.
- $what =~ s/sqrt(.*)/sqrt\(.*\)/;
-
- # Simplify regexes with multiple .*
- $what =~ s#(?:\.\*){2,}##g;
-# $what =~ s#\.\*/\.\*#.*#g;
-
- # Recover dot characters
- $what =~ s/\xf6/\./g;
-
- my $leave = get_leave($what);
-
- my $added = 0;
- foreach my $l (split /\|/, $leave) {
- if (defined($leaf{$l})) {
- next if ($leaf{$l}->{what} =~ m/\b$what\b/);
- $leaf{$l}->{what} .= "\xac" . $what;
- $added = 1;
- } else {
- $leaf{$l}->{what} = $what;
- $added = 1;
- }
- }
- if ($search_string && $added) {
- print STDERR "What: $what\n" if ($what =~ m#$search_string#);
- }
-
- }
- }
- # Compile regexes
- foreach my $l (sort keys %leaf) {
- my @expr;
- foreach my $w(sort split /\xac/, $leaf{$l}->{what}) {
- push @expr, qr /^$w$/;
- }
- $leaf{$l}->{expr} = \@expr;
- }
-
- # Take links into account
- foreach my $link (sort keys %aliases) {
- my $abs_file = $aliases{$link};
- graph_add_link($abs_file, $link);
- }
- print STDERR "done.\n";
-
- check_undefined_symbols;
-}
-
-# Ensure that the prefix will always end with a slash
-# While this is not needed for find, it makes the patch nicer
-# with --enable-lineno
-$prefix =~ s,/?$,/,;
-
-if ($cmd eq "undefined" || $cmd eq "search") {
- $show_warnings = 0;
-}
-#
-# Parses all ABI files located at $prefix dir
-#
-find({wanted =>\&parse_abi, no_chdir => 1}, $prefix);
-
-print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug & $dbg_dump_abi_structs);
-
-#
-# Handles the command
-#
-if ($cmd eq "undefined") {
- undefined_symbols;
-} elsif ($cmd eq "search") {
- search_symbols;
-} else {
- if ($cmd eq "rest") {
- output_rest;
- }
-
- # Warn about duplicated ABI entries
- foreach my $what(sort keys %symbols) {
- my @files = @{$symbols{$what}->{file}};
-
- next if (scalar(@files) == 1);
-
- printf STDERR "Warning: $what is defined %d times: @files\n",
- scalar(@files);
- }
-}
-
-__END__
-
-=head1 NAME
-
-get_abi.pl - parse the Linux ABI files and produce a ReST book.
-
-=head1 SYNOPSIS
-
-B<get_abi.pl> [--debug <level>] [--enable-lineno] [--man] [--help]
- [--(no-)rst-source] [--dir=<dir>] [--show-hints]
- [--search-string <regex>]
- <COMMAND> [<ARGUMENT>]
-
-Where B<COMMAND> can be:
-
-=over 8
-
-B<search> I<SEARCH_REGEX> - search for I<SEARCH_REGEX> inside ABI
-
-B<rest> - output the ABI in ReST markup language
-
-B<validate> - validate the ABI contents
-
-B<undefined> - existing symbols at the system that aren't
- defined at Documentation/ABI
-
-=back
-
-=head1 OPTIONS
-
-=over 8
-
-=item B<--dir>
-
-Changes the location of the ABI search. By default, it uses
-the Documentation/ABI directory.
-
-=item B<--rst-source> and B<--no-rst-source>
-
-The input file may be using ReST syntax or not. Those two options allow
-selecting between a rst-compliant source ABI (B<--rst-source>), or a
-plain text that may be violating ReST spec, so it requres some escaping
-logic (B<--no-rst-source>).
-
-=item B<--enable-lineno>
-
-Enable output of .. LINENO lines.
-
-=item B<--debug> I<debug level>
-
-Print debug information according with the level, which is given by the
-following bitmask:
-
- - 1: Debug parsing What entries from ABI files;
- - 2: Shows what files are opened from ABI files;
- - 4: Dump the structs used to store the contents of the ABI files.
-
-=item B<--show-hints>
-
-Show hints about possible definitions for the missing ABI symbols.
-Used only when B<undefined>.
-
-=item B<--search-string> I<regex string>
-
-Show only occurences that match a search string.
-Used only when B<undefined>.
-
-=item B<--help>
-
-Prints a brief help message and exits.
-
-=item B<--man>
-
-Prints the manual page and exits.
-
-=back
-
-=head1 DESCRIPTION
-
-Parse the Linux ABI files from ABI DIR (usually located at Documentation/ABI),
-allowing to search for ABI symbols or to produce a ReST book containing
-the Linux ABI documentation.
-
-=head1 EXAMPLES
-
-Search for all stable symbols with the word "usb":
-
-=over 8
-
-$ scripts/get_abi.pl search usb --dir Documentation/ABI/stable
-
-=back
-
-Search for all symbols that match the regex expression "usb.*cap":
-
-=over 8
-
-$ scripts/get_abi.pl search usb.*cap
-
-=back
-
-Output all obsoleted symbols in ReST format
-
-=over 8
-
-$ scripts/get_abi.pl rest --dir Documentation/ABI/obsolete
-
-=back
-
-=head1 BUGS
-
-Report bugs to Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
-
-=head1 COPYRIGHT
-
-Copyright (c) 2016-2021 by Mauro Carvalho Chehab <mchehab+huawei@kernel.org>.
-
-License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>.
-
-This is free software: you are free to change and redistribute it.
-There is NO WARRANTY, to the extent permitted by law.
-
-=cut
diff --git a/scripts/get_abi.py b/scripts/get_abi.py
new file mode 100755
index 000000000000..7ce4748a46d2
--- /dev/null
+++ b/scripts/get_abi.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python3
+# pylint: disable=R0903
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Parse ABI documentation and produce results from it.
+"""
+
+import argparse
+import logging
+import os
+import sys
+
+# Import Python modules
+
+LIB_DIR = "lib/abi"
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+
+sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
+
+from abi_parser import AbiParser # pylint: disable=C0413
+from abi_regex import AbiRegex # pylint: disable=C0413
+from helpers import ABI_DIR, DEBUG_HELP # pylint: disable=C0413
+from system_symbols import SystemSymbols # pylint: disable=C0413
+
+# Command line classes
+
+
+REST_DESC = """
+Produce output in ReST format.
+
+The output is done on two sections:
+
+- Symbols: show all parsed symbols in alphabetic order;
+- Files: cross reference the content of each file with the symbols on it.
+"""
+
+class AbiRest:
+ """Initialize an argparse subparser for rest output"""
+
+ def __init__(self, subparsers):
+ """Initialize argparse subparsers"""
+
+ parser = subparsers.add_parser("rest",
+ formatter_class=argparse.RawTextHelpFormatter,
+ description=REST_DESC)
+
+ parser.add_argument("--enable-lineno", action="store_true",
+ help="enable lineno")
+ parser.add_argument("--raw", action="store_true",
+ help="output text as contained in the ABI files. "
+ "It not used, output will contain dynamically"
+ " generated cross references when possible.")
+ parser.add_argument("--no-file", action="store_true",
+ help="Don't the files section")
+ parser.add_argument("--show-hints", help="Show-hints")
+
+ parser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Run subparser"""
+
+ parser = AbiParser(args.dir, debug=args.debug)
+ parser.parse_abi()
+ parser.check_issues()
+
+ for t in parser.doc(args.raw, not args.no_file):
+ if args.enable_lineno:
+ print (f".. LINENO {t[1]}#{t[2]}\n\n")
+
+ print(t[0])
+
+class AbiValidate:
+ """Initialize an argparse subparser for ABI validation"""
+
+ def __init__(self, subparsers):
+ """Initialize argparse subparsers"""
+
+ parser = subparsers.add_parser("validate",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ description="list events")
+
+ parser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Run subparser"""
+
+ parser = AbiParser(args.dir, debug=args.debug)
+ parser.parse_abi()
+ parser.check_issues()
+
+
+class AbiSearch:
+ """Initialize an argparse subparser for ABI search"""
+
+ def __init__(self, subparsers):
+ """Initialize argparse subparsers"""
+
+ parser = subparsers.add_parser("search",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ description="Search ABI using a regular expression")
+
+ parser.add_argument("expression",
+ help="Case-insensitive search pattern for the ABI symbol")
+
+ parser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Run subparser"""
+
+ parser = AbiParser(args.dir, debug=args.debug)
+ parser.parse_abi()
+ parser.search_symbols(args.expression)
+
+UNDEFINED_DESC="""
+Check undefined ABIs on local machine.
+
+Read sysfs devnodes and check if the devnodes there are defined inside
+ABI documentation.
+
+The search logic tries to minimize the number of regular expressions to
+search per each symbol.
+
+By default, it runs on a single CPU, as Python support for CPU threads
+is still experimental, and multi-process runs on Python is very slow.
+
+On experimental tests, if the number of ABI symbols to search per devnode
+is contained on a limit of ~150 regular expressions, using a single CPU
+is a lot faster than using multiple processes. However, if the number of
+regular expressions to check is at the order of ~30000, using multiple
+CPUs speeds up the check.
+"""
+
+class AbiUndefined:
+ """
+ Initialize an argparse subparser for logic to check undefined ABI at
+ the current machine's sysfs
+ """
+
+ def __init__(self, subparsers):
+ """Initialize argparse subparsers"""
+
+ parser = subparsers.add_parser("undefined",
+ formatter_class=argparse.RawTextHelpFormatter,
+ description=UNDEFINED_DESC)
+
+ parser.add_argument("-S", "--sysfs-dir", default="/sys",
+ help="directory where sysfs is mounted")
+ parser.add_argument("-s", "--search-string",
+ help="search string regular expression to limit symbol search")
+ parser.add_argument("-H", "--show-hints", action="store_true",
+ help="Hints about definitions for missing ABI symbols.")
+ parser.add_argument("-j", "--jobs", "--max-workers", type=int, default=1,
+ help="If bigger than one, enables multiprocessing.")
+ parser.add_argument("-c", "--max-chunk-size", type=int, default=50,
+ help="Maximum number of chunk size")
+ parser.add_argument("-f", "--found", action="store_true",
+ help="Also show found items. "
+ "Helpful to debug the parser."),
+ parser.add_argument("-d", "--dry-run", action="store_true",
+ help="Don't actually search for undefined. "
+ "Helpful to debug the parser."),
+
+ parser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Run subparser"""
+
+ abi = AbiRegex(args.dir, debug=args.debug,
+ search_string=args.search_string)
+
+ abi_symbols = SystemSymbols(abi=abi, hints=args.show_hints,
+ sysfs=args.sysfs_dir)
+
+ abi_symbols.check_undefined_symbols(dry_run=args.dry_run,
+ found=args.found,
+ max_workers=args.jobs,
+ chunk_size=args.max_chunk_size)
+
+
+def main():
+ """Main program"""
+
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
+
+ parser.add_argument("-d", "--debug", type=int, default=0, help="debug level")
+ parser.add_argument("-D", "--dir", default=ABI_DIR, help=DEBUG_HELP)
+
+ subparsers = parser.add_subparsers()
+
+ AbiRest(subparsers)
+ AbiValidate(subparsers)
+ AbiSearch(subparsers)
+ AbiUndefined(subparsers)
+
+ args = parser.parse_args()
+
+ if args.debug:
+ level = logging.DEBUG
+ else:
+ level = logging.INFO
+
+ logging.basicConfig(level=level, format="[%(levelname)s] %(message)s")
+
+ if "func" in args:
+ args.func(args)
+ else:
+ sys.exit(f"Please specify a valid command for {sys.argv[0]}")
+
+
+# Call main method
+if __name__ == "__main__":
+ main()
diff --git a/scripts/get_feat.pl b/scripts/get_feat.pl
index 5c5397eeb237..40fb28c8424e 100755
--- a/scripts/get_feat.pl
+++ b/scripts/get_feat.pl
@@ -512,13 +512,13 @@ print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug);
# Handles the command
#
if ($cmd eq "current") {
- $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/');
+ $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/');
$arch =~s/\s+$//;
}
if ($cmd eq "ls" or $cmd eq "list") {
if (!$arch) {
- $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/');
+ $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/');
$arch =~s/\s+$//;
}
diff --git a/scripts/integer-wrap-ignore.scl b/scripts/integer-wrap-ignore.scl
new file mode 100644
index 000000000000..431c3053a4a2
--- /dev/null
+++ b/scripts/integer-wrap-ignore.scl
@@ -0,0 +1,3 @@
+[{unsigned-integer-overflow,signed-integer-overflow,implicit-signed-integer-truncation,implicit-unsigned-integer-truncation}]
+type:*
+type:size_t=sanitize
diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index 03852da3d249..4b0234e4b12f 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -5,7 +5,7 @@
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
- * Usage: kallsyms [--all-symbols] [--absolute-percpu] in.map > out.S
+ * Usage: kallsyms [--all-symbols] in.map > out.S
*
* Table compression uses all the unused char codes on the symbols and
* maps these to the most used substrings (tokens). For instance, it might
@@ -37,7 +37,6 @@ struct sym_entry {
unsigned long long addr;
unsigned int len;
unsigned int seq;
- bool percpu_absolute;
unsigned char sym[];
};
@@ -55,14 +54,9 @@ static struct addr_range text_ranges[] = {
#define text_range_text (&text_ranges[0])
#define text_range_inittext (&text_ranges[1])
-static struct addr_range percpu_range = {
- "__per_cpu_start", "__per_cpu_end", -1ULL, 0
-};
-
static struct sym_entry **table;
static unsigned int table_size, table_cnt;
static int all_symbols;
-static int absolute_percpu;
static int token_profit[0x10000];
@@ -73,7 +67,7 @@ static unsigned char best_table_len[256];
static void usage(void)
{
- fprintf(stderr, "Usage: kallsyms [--all-symbols] [--absolute-percpu] in.map > out.S\n");
+ fprintf(stderr, "Usage: kallsyms [--all-symbols] in.map > out.S\n");
exit(1);
}
@@ -164,7 +158,6 @@ static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len)
return NULL;
check_symbol_range(name, addr, text_ranges, ARRAY_SIZE(text_ranges));
- check_symbol_range(name, addr, &percpu_range, 1);
/* include the type field in the symbol name, so that it gets
* compressed together */
@@ -175,7 +168,6 @@ static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len)
sym->len = len;
sym->sym[0] = type;
strcpy(sym_name(sym), name);
- sym->percpu_absolute = false;
return sym;
}
@@ -319,11 +311,6 @@ static int expand_symbol(const unsigned char *data, int len, char *result)
return total;
}
-static bool symbol_absolute(const struct sym_entry *s)
-{
- return s->percpu_absolute;
-}
-
static int compare_names(const void *a, const void *b)
{
int ret;
@@ -455,22 +442,11 @@ static void write_src(void)
*/
long long offset;
- bool overflow;
-
- if (!absolute_percpu) {
- offset = table[i]->addr - relative_base;
- overflow = offset < 0 || offset > UINT_MAX;
- } else if (symbol_absolute(table[i])) {
- offset = table[i]->addr;
- overflow = offset < 0 || offset > INT_MAX;
- } else {
- offset = relative_base - table[i]->addr - 1;
- overflow = offset < INT_MIN || offset >= 0;
- }
- if (overflow) {
+
+ offset = table[i]->addr - relative_base;
+ if (offset < 0 || offset > UINT_MAX) {
fprintf(stderr, "kallsyms failure: "
- "%s symbol value %#llx out of range in relative mode\n",
- symbol_absolute(table[i]) ? "absolute" : "relative",
+ "relative symbol value %#llx out of range\n",
table[i]->addr);
exit(EXIT_FAILURE);
}
@@ -725,36 +701,15 @@ static void sort_symbols(void)
qsort(table, table_cnt, sizeof(table[0]), compare_symbols);
}
-static void make_percpus_absolute(void)
-{
- unsigned int i;
-
- for (i = 0; i < table_cnt; i++)
- if (symbol_in_range(table[i], &percpu_range, 1)) {
- /*
- * Keep the 'A' override for percpu symbols to
- * ensure consistent behavior compared to older
- * versions of this tool.
- */
- table[i]->sym[0] = 'A';
- table[i]->percpu_absolute = true;
- }
-}
-
/* find the minimum non-absolute symbol address */
static void record_relative_base(void)
{
- unsigned int i;
-
- for (i = 0; i < table_cnt; i++)
- if (!symbol_absolute(table[i])) {
- /*
- * The table is sorted by address.
- * Take the first non-absolute symbol value.
- */
- relative_base = table[i]->addr;
- return;
- }
+ /*
+ * The table is sorted by address.
+ * Take the first symbol value.
+ */
+ if (table_cnt)
+ relative_base = table[0]->addr;
}
int main(int argc, char **argv)
@@ -762,7 +717,6 @@ int main(int argc, char **argv)
while (1) {
static const struct option long_options[] = {
{"all-symbols", no_argument, &all_symbols, 1},
- {"absolute-percpu", no_argument, &absolute_percpu, 1},
{},
};
@@ -779,8 +733,6 @@ int main(int argc, char **argv)
read_map(argv[optind]);
shrink_table();
- if (absolute_percpu)
- make_percpus_absolute();
sort_symbols();
record_relative_base();
optimize_token_table();
diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile
index a0a0be38cbdc..fb50bd4f4103 100644
--- a/scripts/kconfig/Makefile
+++ b/scripts/kconfig/Makefile
@@ -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
diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c
index 4286d5e7f95d..3b55e7a4131d 100644
--- a/scripts/kconfig/confdata.c
+++ b/scripts/kconfig/confdata.c
@@ -360,10 +360,12 @@ int conf_read_simple(const char *name, int def)
*p = '\0';
- in = zconf_fopen(env);
+ name = env;
+
+ in = zconf_fopen(name);
if (in) {
conf_message("using defaults found in %s",
- env);
+ name);
goto load;
}
diff --git a/scripts/kconfig/qconf.cc b/scripts/kconfig/qconf.cc
index 6c92ef1e16ef..eaa465b0ccf9 100644
--- a/scripts/kconfig/qconf.cc
+++ b/scripts/kconfig/qconf.cc
@@ -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())) {
diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c
index 89b84bf8e21f..7beb59dec5a0 100644
--- a/scripts/kconfig/symbol.c
+++ b/scripts/kconfig/symbol.c
@@ -388,6 +388,7 @@ static void sym_warn_unmet_dep(const struct symbol *sym)
" Selected by [m]:\n");
fputs(str_get(&gs), stderr);
+ str_free(&gs);
sym_warnings++;
}
diff --git a/scripts/kernel-doc b/scripts/kernel-doc
index 4ee843d3600e..af6cf408b96d 100755
--- a/scripts/kernel-doc
+++ b/scripts/kernel-doc
@@ -26,7 +26,7 @@ kernel-doc - Print formatted kernel documentation to stdout
kernel-doc [-h] [-v] [-Werror] [-Wall] [-Wreturn] [-Wshort-desc[ription]] [-Wcontents-before-sections]
[ -man |
- -rst [-sphinx-version VERSION] [-enable-lineno] |
+ -rst [-enable-lineno] |
-none
]
[
@@ -130,7 +130,6 @@ if ($#ARGV == -1) {
}
my $kernelversion;
-my ($sphinx_major, $sphinx_minor, $sphinx_patch);
my $dohighlight = "";
@@ -138,7 +137,6 @@ my $verbose = 0;
my $Werror = 0;
my $Wreturn = 0;
my $Wshort_desc = 0;
-my $Wcontents_before_sections = 0;
my $output_mode = "rst";
my $output_preformatted = 0;
my $no_doc_sections = 0;
@@ -179,7 +177,7 @@ my ($function, %function_table, %parametertypes, $declaration_purpose);
my %nosymbol_table = ();
my $declaration_start_line;
my ($type, $declaration_name, $return_type);
-my ($newsection, $newcontents, $prototype, $brcount, %source_map);
+my ($newsection, $newcontents, $prototype, $brcount);
if (defined($ENV{'KBUILD_VERBOSE'}) && $ENV{'KBUILD_VERBOSE'} =~ '1') {
$verbose = 1;
@@ -224,7 +222,6 @@ use constant {
STATE_INLINE => 7, # gathering doc outside main block
};
my $state;
-my $in_doc_sect;
my $leading_space;
# Inline documentation state
@@ -333,12 +330,9 @@ while ($ARGV[0] =~ m/^--?(.*)/) {
$Wreturn = 1;
} elsif ($cmd eq "Wshort-desc" or $cmd eq "Wshort-description") {
$Wshort_desc = 1;
- } elsif ($cmd eq "Wcontents-before-sections") {
- $Wcontents_before_sections = 1;
} elsif ($cmd eq "Wall") {
$Wreturn = 1;
$Wshort_desc = 1;
- $Wcontents_before_sections = 1;
} elsif (($cmd eq "h") || ($cmd eq "help")) {
pod2usage(-exitval => 0, -verbose => 2);
} elsif ($cmd eq 'no-doc-sections') {
@@ -347,23 +341,6 @@ while ($ARGV[0] =~ m/^--?(.*)/) {
$enable_lineno = 1;
} elsif ($cmd eq 'show-not-found') {
$show_not_found = 1; # A no-op but don't fail
- } elsif ($cmd eq "sphinx-version") {
- my $ver_string = shift @ARGV;
- if ($ver_string =~ m/^(\d+)(\.\d+)?(\.\d+)?/) {
- $sphinx_major = $1;
- if (defined($2)) {
- $sphinx_minor = substr($2,1);
- } else {
- $sphinx_minor = 0;
- }
- if (defined($3)) {
- $sphinx_patch = substr($3,1)
- } else {
- $sphinx_patch = 0;
- }
- } else {
- die "Sphinx version should either major.minor or major.minor.patch format\n";
- }
} else {
# Unknown argument
pod2usage(
@@ -387,8 +364,6 @@ while ($ARGV[0] =~ m/^--?(.*)/) {
# continue execution near EOF;
-# The C domain dialect changed on Sphinx 3. So, we need to check the
-# version in order to produce the right tags.
sub findprog($)
{
foreach(split(/:/, $ENV{PATH})) {
@@ -396,42 +371,6 @@ sub findprog($)
}
}
-sub get_sphinx_version()
-{
- my $ver;
-
- my $cmd = "sphinx-build";
- if (!findprog($cmd)) {
- my $cmd = "sphinx-build3";
- if (!findprog($cmd)) {
- $sphinx_major = 1;
- $sphinx_minor = 2;
- $sphinx_patch = 0;
- printf STDERR "Warning: Sphinx version not found. Using default (Sphinx version %d.%d.%d)\n",
- $sphinx_major, $sphinx_minor, $sphinx_patch;
- return;
- }
- }
-
- open IN, "$cmd --version 2>&1 |";
- while (<IN>) {
- if (m/^\s*sphinx-build\s+([\d]+)\.([\d\.]+)(\+\/[\da-f]+)?$/) {
- $sphinx_major = $1;
- $sphinx_minor = $2;
- $sphinx_patch = $3;
- last;
- }
- # Sphinx 1.2.x uses a different format
- if (m/^\s*Sphinx.*\s+([\d]+)\.([\d\.]+)$/) {
- $sphinx_major = $1;
- $sphinx_minor = $2;
- $sphinx_patch = $3;
- last;
- }
- }
- close IN;
-}
-
# get kernel version from env
sub get_kernel_version() {
my $version = 'unknown kernel version';
@@ -816,6 +755,10 @@ sub output_highlight_rst {
if ($block) {
$output .= highlight_block($block);
}
+
+ $output =~ s/^\n+//g;
+ $output =~ s/\n+$//g;
+
foreach $line (split "\n", $output) {
print $lineprefix . $line . "\n";
}
@@ -859,9 +802,10 @@ sub output_function_rst(%) {
$signature .= ")";
}
- if ($sphinx_major < 3) {
+ if ($args{'typedef'} || $args{'functiontype'} eq "") {
+ print ".. c:macro:: ". $args{'function'} . "\n\n";
+
if ($args{'typedef'}) {
- print ".. c:type:: ". $args{'function'} . "\n\n";
print_lineno($declaration_start_line);
print " **Typedef**: ";
$lineprefix = "";
@@ -869,25 +813,10 @@ sub output_function_rst(%) {
print "\n\n**Syntax**\n\n";
print " ``$signature``\n\n";
} else {
- print ".. c:function:: $signature\n\n";
+ print "``$signature``\n\n";
}
} else {
- if ($args{'typedef'} || $args{'functiontype'} eq "") {
- print ".. c:macro:: ". $args{'function'} . "\n\n";
-
- if ($args{'typedef'}) {
- print_lineno($declaration_start_line);
- print " **Typedef**: ";
- $lineprefix = "";
- output_highlight_rst($args{'purpose'});
- print "\n\n**Syntax**\n\n";
- print " ``$signature``\n\n";
- } else {
- print "``$signature``\n\n";
- }
- } else {
- print ".. c:function:: $signature\n\n";
- }
+ print ".. c:function:: $signature\n\n";
}
if (!$args{'typedef'}) {
@@ -955,13 +884,9 @@ sub output_enum_rst(%) {
my $count;
my $outer;
- if ($sphinx_major < 3) {
- my $name = "enum " . $args{'enum'};
- print "\n\n.. c:type:: " . $name . "\n\n";
- } else {
- my $name = $args{'enum'};
- print "\n\n.. c:enum:: " . $name . "\n\n";
- }
+ my $name = $args{'enum'};
+ print "\n\n.. c:enum:: " . $name . "\n\n";
+
print_lineno($declaration_start_line);
$lineprefix = " ";
output_highlight_rst($args{'purpose'});
@@ -992,11 +917,8 @@ sub output_typedef_rst(%) {
my $oldprefix = $lineprefix;
my $name;
- if ($sphinx_major < 3) {
- $name = "typedef " . $args{'typedef'};
- } else {
- $name = $args{'typedef'};
- }
+ $name = $args{'typedef'};
+
print "\n\n.. c:type:: " . $name . "\n\n";
print_lineno($declaration_start_line);
$lineprefix = " ";
@@ -1012,17 +934,13 @@ sub output_struct_rst(%) {
my ($parameter);
my $oldprefix = $lineprefix;
- if ($sphinx_major < 3) {
- my $name = $args{'type'} . " " . $args{'struct'};
- print "\n\n.. c:type:: " . $name . "\n\n";
+ my $name = $args{'struct'};
+ if ($args{'type'} eq 'union') {
+ print "\n\n.. c:union:: " . $name . "\n\n";
} else {
- my $name = $args{'struct'};
- if ($args{'type'} eq 'union') {
- print "\n\n.. c:union:: " . $name . "\n\n";
- } else {
- print "\n\n.. c:struct:: " . $name . "\n\n";
- }
+ print "\n\n.. c:struct:: " . $name . "\n\n";
}
+
print_lineno($declaration_start_line);
$lineprefix = " ";
output_highlight_rst($args{'purpose'});
@@ -2005,10 +1923,6 @@ sub map_filename($) {
$file = $orig_file;
}
- if (defined($source_map{$file})) {
- $file = $source_map{$file};
- }
-
return $file;
}
@@ -2044,7 +1958,6 @@ sub process_export_file($) {
sub process_normal() {
if (/$doc_start/o) {
$state = STATE_NAME; # next line is always the function name
- $in_doc_sect = 0;
$declaration_start_line = $. + 1;
}
}
@@ -2085,7 +1998,7 @@ sub process_name($$) {
# Look for foo() or static void foo() - description; or misspelt
# identifier
elsif (/^$decl_start$fn_type?(\w+)\s*$parenthesis?\s*$decl_end?$/ ||
- /^$decl_start$fn_type?(\w+.*)$parenthesis?\s*$decl_end$/) {
+ /^$decl_start$fn_type?(\w+[^-:]*)$parenthesis?\s*$decl_end$/) {
$identifier = $1;
$decl_type = 'function';
$identifier =~ s/^define\s+//;
@@ -2149,7 +2062,6 @@ sub process_body($$) {
}
if (/$doc_sect/i) { # case insensitive for supported section names
- $in_doc_sect = 1;
$newsection = $1;
$newcontents = $2;
@@ -2166,14 +2078,10 @@ sub process_body($$) {
}
if (($contents ne "") && ($contents ne "\n")) {
- if (!$in_doc_sect && $Wcontents_before_sections) {
- emit_warning("${file}:$.", "contents before sections\n");
- }
dump_section($file, $section, $contents);
$section = $section_default;
}
- $in_doc_sect = 1;
$state = STATE_BODY;
$contents = $newcontents;
$new_start_line = $.;
@@ -2387,11 +2295,6 @@ sub process_file($) {
close IN_FILE;
}
-
-if ($output_mode eq "rst") {
- get_sphinx_version() if (!$sphinx_major);
-}
-
$kernelversion = get_kernel_version();
# generate a sequence of code that will splice in highlighting information
@@ -2403,19 +2306,6 @@ for (my $k = 0; $k < @highlights; $k++) {
$dohighlight .= "\$contents =~ s:$pattern:$result:gs;\n";
}
-# Read the file that maps relative names to absolute names for
-# separate source and object directories and for shadow trees.
-if (open(SOURCE_MAP, "<.tmp_filelist.txt")) {
- my ($relname, $absname);
- while(<SOURCE_MAP>) {
- chop();
- ($relname, $absname) = (split())[0..1];
- $relname =~ s:^/+::;
- $source_map{$relname} = $absname;
- }
- close(SOURCE_MAP);
-}
-
if ($output_selection == OUTPUT_EXPORTED ||
$output_selection == OUTPUT_INTERNAL) {
@@ -2471,17 +2361,6 @@ Do not output documentation, only warnings.
=head3 reStructuredText only
-=over 8
-
-=item -sphinx-version VERSION
-
-Use the ReST C domain dialect compatible with a specific Sphinx Version.
-
-If not specified, kernel-doc will auto-detect using the sphinx-build version
-found on PATH.
-
-=back
-
=head2 Output selection (mutually exclusive):
=over 8
diff --git a/scripts/lib/abi/abi_parser.py b/scripts/lib/abi/abi_parser.py
new file mode 100644
index 000000000000..66a738013ce1
--- /dev/null
+++ b/scripts/lib/abi/abi_parser.py
@@ -0,0 +1,628 @@
+#!/usr/bin/env python3
+# pylint: disable=R0902,R0903,R0911,R0912,R0913,R0914,R0915,R0917,C0302
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Parse ABI documentation and produce results from it.
+"""
+
+from argparse import Namespace
+import logging
+import os
+import re
+
+from pprint import pformat
+from random import randrange, seed
+
+# Import Python modules
+
+from helpers import AbiDebug, ABI_DIR
+
+
+class AbiParser:
+ """Main class to parse ABI files"""
+
+ TAGS = r"(what|where|date|kernelversion|contact|description|users)"
+ XREF = r"(?:^|\s|\()(\/(?:sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)(?:[,.:;\)\s]|\Z)"
+
+ def __init__(self, directory, logger=None,
+ enable_lineno=False, show_warnings=True, debug=0):
+ """Stores arguments for the class and initialize class vars"""
+
+ self.directory = directory
+ self.enable_lineno = enable_lineno
+ self.show_warnings = show_warnings
+ self.debug = debug
+
+ if not logger:
+ self.log = logging.getLogger("get_abi")
+ else:
+ self.log = logger
+
+ self.data = {}
+ self.what_symbols = {}
+ self.file_refs = {}
+ self.what_refs = {}
+
+ # Ignore files that contain such suffixes
+ self.ignore_suffixes = (".rej", ".org", ".orig", ".bak", "~")
+
+ # Regular expressions used on parser
+ self.re_abi_dir = re.compile(r"(.*)" + ABI_DIR)
+ self.re_tag = re.compile(r"(\S+)(:\s*)(.*)", re.I)
+ self.re_valid = re.compile(self.TAGS)
+ self.re_start_spc = re.compile(r"(\s*)(\S.*)")
+ self.re_whitespace = re.compile(r"^\s+")
+
+ # Regular used on print
+ self.re_what = re.compile(r"(\/?(?:[\w\-]+\/?){1,2})")
+ self.re_escape = re.compile(r"([\.\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])")
+ self.re_unprintable = re.compile(r"([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff]+)")
+ self.re_title_mark = re.compile(r"\n[\-\*\=\^\~]+\n")
+ self.re_doc = re.compile(r"Documentation/(?!devicetree)(\S+)\.rst")
+ self.re_abi = re.compile(r"(Documentation/ABI/)([\w\/\-]+)")
+ self.re_xref_node = re.compile(self.XREF)
+
+ def warn(self, fdata, msg, extra=None):
+ """Displays a parse error if warning is enabled"""
+
+ if not self.show_warnings:
+ return
+
+ msg = f"{fdata.fname}:{fdata.ln}: {msg}"
+ if extra:
+ msg += "\n\t\t" + extra
+
+ self.log.warning(msg)
+
+ def add_symbol(self, what, fname, ln=None, xref=None):
+ """Create a reference table describing where each 'what' is located"""
+
+ if what not in self.what_symbols:
+ self.what_symbols[what] = {"file": {}}
+
+ if fname not in self.what_symbols[what]["file"]:
+ self.what_symbols[what]["file"][fname] = []
+
+ if ln and ln not in self.what_symbols[what]["file"][fname]:
+ self.what_symbols[what]["file"][fname].append(ln)
+
+ if xref:
+ self.what_symbols[what]["xref"] = xref
+
+ def _parse_line(self, fdata, line):
+ """Parse a single line of an ABI file"""
+
+ new_what = False
+ new_tag = False
+ content = None
+
+ match = self.re_tag.match(line)
+ if match:
+ new = match.group(1).lower()
+ sep = match.group(2)
+ content = match.group(3)
+
+ match = self.re_valid.search(new)
+ if match:
+ new_tag = match.group(1)
+ else:
+ if fdata.tag == "description":
+ # New "tag" is actually part of description.
+ # Don't consider it a tag
+ new_tag = False
+ elif fdata.tag != "":
+ self.warn(fdata, f"tag '{fdata.tag}' is invalid", line)
+
+ if new_tag:
+ # "where" is Invalid, but was a common mistake. Warn if found
+ if new_tag == "where":
+ self.warn(fdata, "tag 'Where' is invalid. Should be 'What:' instead")
+ new_tag = "what"
+
+ if new_tag == "what":
+ fdata.space = None
+
+ if content not in self.what_symbols:
+ self.add_symbol(what=content, fname=fdata.fname, ln=fdata.ln)
+
+ if fdata.tag == "what":
+ fdata.what.append(content.strip("\n"))
+ else:
+ if fdata.key:
+ if "description" not in self.data.get(fdata.key, {}):
+ self.warn(fdata, f"{fdata.key} doesn't have a description")
+
+ for w in fdata.what:
+ self.add_symbol(what=w, fname=fdata.fname,
+ ln=fdata.what_ln, xref=fdata.key)
+
+ fdata.label = content
+ new_what = True
+
+ key = "abi_" + content.lower()
+ fdata.key = self.re_unprintable.sub("_", key).strip("_")
+
+ # Avoid duplicated keys but using a defined seed, to make
+ # the namespace identical if there aren't changes at the
+ # ABI symbols
+ seed(42)
+
+ while fdata.key in self.data:
+ char = randrange(0, 51) + ord("A")
+ if char > ord("Z"):
+ char += ord("a") - ord("Z") - 1
+
+ fdata.key += chr(char)
+
+ if fdata.key and fdata.key not in self.data:
+ self.data[fdata.key] = {
+ "what": [content],
+ "file": [fdata.file_ref],
+ "path": fdata.ftype,
+ "line_no": fdata.ln,
+ }
+
+ fdata.what = self.data[fdata.key]["what"]
+
+ self.what_refs[content] = fdata.key
+ fdata.tag = new_tag
+ fdata.what_ln = fdata.ln
+
+ if fdata.nametag["what"]:
+ t = (content, fdata.key)
+ if t not in fdata.nametag["symbols"]:
+ fdata.nametag["symbols"].append(t)
+
+ return
+
+ if fdata.tag and new_tag:
+ fdata.tag = new_tag
+
+ if new_what:
+ fdata.label = ""
+
+ if "description" in self.data[fdata.key]:
+ self.data[fdata.key]["description"] += "\n\n"
+
+ if fdata.file_ref not in self.data[fdata.key]["file"]:
+ self.data[fdata.key]["file"].append(fdata.file_ref)
+
+ if self.debug == AbiDebug.WHAT_PARSING:
+ self.log.debug("what: %s", fdata.what)
+
+ if not fdata.what:
+ self.warn(fdata, "'What:' should come first:", line)
+ return
+
+ if new_tag == "description":
+ fdata.space = None
+
+ if content:
+ sep = sep.replace(":", " ")
+
+ c = " " * len(new_tag) + sep + content
+ c = c.expandtabs()
+
+ match = self.re_start_spc.match(c)
+ if match:
+ # Preserve initial spaces for the first line
+ fdata.space = match.group(1)
+ content = match.group(2) + "\n"
+
+ self.data[fdata.key][fdata.tag] = content
+
+ return
+
+ # Store any contents before tags at the database
+ if not fdata.tag and "what" in fdata.nametag:
+ fdata.nametag["description"] += line
+ return
+
+ if fdata.tag == "description":
+ content = line.expandtabs()
+
+ if self.re_whitespace.sub("", content) == "":
+ self.data[fdata.key][fdata.tag] += "\n"
+ return
+
+ if fdata.space is None:
+ match = self.re_start_spc.match(content)
+ if match:
+ # Preserve initial spaces for the first line
+ fdata.space = match.group(1)
+
+ content = match.group(2) + "\n"
+ else:
+ if content.startswith(fdata.space):
+ content = content[len(fdata.space):]
+
+ else:
+ fdata.space = ""
+
+ if fdata.tag == "what":
+ w = content.strip("\n")
+ if w:
+ self.data[fdata.key][fdata.tag].append(w)
+ else:
+ self.data[fdata.key][fdata.tag] += content
+ return
+
+ content = line.strip()
+ if fdata.tag:
+ if fdata.tag == "what":
+ w = content.strip("\n")
+ if w:
+ self.data[fdata.key][fdata.tag].append(w)
+ else:
+ self.data[fdata.key][fdata.tag] += "\n" + content.rstrip("\n")
+ return
+
+ # Everything else is error
+ if content:
+ self.warn(fdata, "Unexpected content", line)
+
+ def parse_readme(self, nametag, fname):
+ """Parse ABI README file"""
+
+ nametag["what"] = ["Introduction"]
+ nametag["path"] = "README"
+ with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp:
+ for line in fp:
+ match = self.re_tag.match(line)
+ if match:
+ new = match.group(1).lower()
+
+ match = self.re_valid.search(new)
+ if match:
+ nametag["description"] += "\n:" + line
+ continue
+
+ nametag["description"] += line
+
+ def parse_file(self, fname, path, basename):
+ """Parse a single file"""
+
+ ref = f"abi_file_{path}_{basename}"
+ ref = self.re_unprintable.sub("_", ref).strip("_")
+
+ # Store per-file state into a namespace variable. This will be used
+ # by the per-line parser state machine and by the warning function.
+ fdata = Namespace
+
+ fdata.fname = fname
+ fdata.name = basename
+
+ pos = fname.find(ABI_DIR)
+ if pos > 0:
+ f = fname[pos:]
+ else:
+ f = fname
+
+ fdata.file_ref = (f, ref)
+ self.file_refs[f] = ref
+
+ fdata.ln = 0
+ fdata.what_ln = 0
+ fdata.tag = ""
+ fdata.label = ""
+ fdata.what = []
+ fdata.key = None
+ fdata.xrefs = None
+ fdata.space = None
+ fdata.ftype = path.split("/")[0]
+
+ fdata.nametag = {}
+ fdata.nametag["what"] = [f"ABI file {path}/{basename}"]
+ fdata.nametag["type"] = "File"
+ fdata.nametag["path"] = fdata.ftype
+ fdata.nametag["file"] = [fdata.file_ref]
+ fdata.nametag["line_no"] = 1
+ fdata.nametag["description"] = ""
+ fdata.nametag["symbols"] = []
+
+ self.data[ref] = fdata.nametag
+
+ if self.debug & AbiDebug.WHAT_OPEN:
+ self.log.debug("Opening file %s", fname)
+
+ if basename == "README":
+ self.parse_readme(fdata.nametag, fname)
+ return
+
+ with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp:
+ for line in fp:
+ fdata.ln += 1
+
+ self._parse_line(fdata, line)
+
+ if "description" in fdata.nametag:
+ fdata.nametag["description"] = fdata.nametag["description"].lstrip("\n")
+
+ if fdata.key:
+ if "description" not in self.data.get(fdata.key, {}):
+ self.warn(fdata, f"{fdata.key} doesn't have a description")
+
+ for w in fdata.what:
+ self.add_symbol(what=w, fname=fname, xref=fdata.key)
+
+ def _parse_abi(self, root=None):
+ """Internal function to parse documentation ABI recursively"""
+
+ if not root:
+ root = self.directory
+
+ with os.scandir(root) as obj:
+ for entry in obj:
+ name = os.path.join(root, entry.name)
+
+ if entry.is_dir():
+ self._parse_abi(name)
+ continue
+
+ if not entry.is_file():
+ continue
+
+ basename = os.path.basename(name)
+
+ if basename.startswith("."):
+ continue
+
+ if basename.endswith(self.ignore_suffixes):
+ continue
+
+ path = self.re_abi_dir.sub("", os.path.dirname(name))
+
+ self.parse_file(name, path, basename)
+
+ def parse_abi(self, root=None):
+ """Parse documentation ABI"""
+
+ self._parse_abi(root)
+
+ if self.debug & AbiDebug.DUMP_ABI_STRUCTS:
+ self.log.debug(pformat(self.data))
+
+ def desc_txt(self, desc):
+ """Print description as found inside ABI files"""
+
+ desc = desc.strip(" \t\n")
+
+ return desc + "\n\n"
+
+ def xref(self, fname):
+ """
+ Converts a Documentation/ABI + basename into a ReST cross-reference
+ """
+
+ xref = self.file_refs.get(fname)
+ if not xref:
+ return None
+ else:
+ return xref
+
+ def desc_rst(self, desc):
+ """Enrich ReST output by creating cross-references"""
+
+ # Remove title markups from the description
+ # Having titles inside ABI files will only work if extra
+ # care would be taken in order to strictly follow the same
+ # level order for each markup.
+ desc = self.re_title_mark.sub("\n\n", "\n" + desc)
+ desc = desc.rstrip(" \t\n").lstrip("\n")
+
+ # Python's regex performance for non-compiled expressions is a lot
+ # than Perl, as Perl automatically caches them at their
+ # first usage. Here, we'll need to do the same, as otherwise the
+ # performance penalty is be high
+
+ new_desc = ""
+ for d in desc.split("\n"):
+ if d == "":
+ new_desc += "\n"
+ continue
+
+ # Use cross-references for doc files where needed
+ d = self.re_doc.sub(r":doc:`/\1`", d)
+
+ # Use cross-references for ABI generated docs where needed
+ matches = self.re_abi.findall(d)
+ for m in matches:
+ abi = m[0] + m[1]
+
+ xref = self.file_refs.get(abi)
+ if not xref:
+ # This may happen if ABI is on a separate directory,
+ # like parsing ABI testing and symbol is at stable.
+ # The proper solution is to move this part of the code
+ # for it to be inside sphinx/kernel_abi.py
+ self.log.info("Didn't find ABI reference for '%s'", abi)
+ else:
+ new = self.re_escape.sub(r"\\\1", m[1])
+ d = re.sub(fr"\b{abi}\b", f":ref:`{new} <{xref}>`", d)
+
+ # Seek for cross reference symbols like /sys/...
+ # Need to be careful to avoid doing it on a code block
+ if d[0] not in [" ", "\t"]:
+ matches = self.re_xref_node.findall(d)
+ for m in matches:
+ # Finding ABI here is more complex due to wildcards
+ xref = self.what_refs.get(m)
+ if xref:
+ new = self.re_escape.sub(r"\\\1", m)
+ d = re.sub(fr"\b{m}\b", f":ref:`{new} <{xref}>`", d)
+
+ new_desc += d + "\n"
+
+ return new_desc + "\n\n"
+
+ def doc(self, output_in_txt=False, show_symbols=True, show_file=True,
+ filter_path=None):
+ """Print ABI at stdout"""
+
+ part = None
+ for key, v in sorted(self.data.items(),
+ key=lambda x: (x[1].get("type", ""),
+ x[1].get("what"))):
+
+ wtype = v.get("type", "Symbol")
+ file_ref = v.get("file")
+ names = v.get("what", [""])
+
+ if wtype == "File":
+ if not show_file:
+ continue
+ else:
+ if not show_symbols:
+ continue
+
+ if filter_path:
+ if v.get("path") != filter_path:
+ continue
+
+ msg = ""
+
+ if wtype != "File":
+ cur_part = names[0]
+ if cur_part.find("/") >= 0:
+ match = self.re_what.match(cur_part)
+ if match:
+ symbol = match.group(1).rstrip("/")
+ cur_part = "Symbols under " + symbol
+
+ if cur_part and cur_part != part:
+ part = cur_part
+ msg += part + "\n"+ "-" * len(part) +"\n\n"
+
+ msg += f".. _{key}:\n\n"
+
+ max_len = 0
+ for i in range(0, len(names)): # pylint: disable=C0200
+ names[i] = "**" + self.re_escape.sub(r"\\\1", names[i]) + "**"
+
+ max_len = max(max_len, len(names[i]))
+
+ msg += "+-" + "-" * max_len + "-+\n"
+ for name in names:
+ msg += f"| {name}" + " " * (max_len - len(name)) + " |\n"
+ msg += "+-" + "-" * max_len + "-+\n"
+ msg += "\n"
+
+ for ref in file_ref:
+ if wtype == "File":
+ msg += f".. _{ref[1]}:\n\n"
+ else:
+ base = os.path.basename(ref[0])
+ msg += f"Defined on file :ref:`{base} <{ref[1]}>`\n\n"
+
+ if wtype == "File":
+ msg += names[0] +"\n" + "-" * len(names[0]) +"\n\n"
+
+ desc = v.get("description")
+ if not desc and wtype != "File":
+ msg += f"DESCRIPTION MISSING for {names[0]}\n\n"
+
+ if desc:
+ if output_in_txt:
+ msg += self.desc_txt(desc)
+ else:
+ msg += self.desc_rst(desc)
+
+ symbols = v.get("symbols")
+ if symbols:
+ msg += "Has the following ABI:\n\n"
+
+ for w, label in symbols:
+ # Escape special chars from content
+ content = self.re_escape.sub(r"\\\1", w)
+
+ msg += f"- :ref:`{content} <{label}>`\n\n"
+
+ users = v.get("users")
+ if users and users.strip(" \t\n"):
+ users = users.strip("\n").replace('\n', '\n\t')
+ msg += f"Users:\n\t{users}\n\n"
+
+ ln = v.get("line_no", 1)
+
+ yield (msg, file_ref[0][0], ln)
+
+ def check_issues(self):
+ """Warn about duplicated ABI entries"""
+
+ for what, v in self.what_symbols.items():
+ files = v.get("file")
+ if not files:
+ # Should never happen if the parser works properly
+ self.log.warning("%s doesn't have a file associated", what)
+ continue
+
+ if len(files) == 1:
+ continue
+
+ f = []
+ for fname, lines in sorted(files.items()):
+ if not lines:
+ f.append(f"{fname}")
+ elif len(lines) == 1:
+ f.append(f"{fname}:{lines[0]}")
+ else:
+ m = fname + "lines "
+ m += ", ".join(str(x) for x in lines)
+ f.append(m)
+
+ self.log.warning("%s is defined %d times: %s", what, len(f), "; ".join(f))
+
+ def search_symbols(self, expr):
+ """ Searches for ABI symbols """
+
+ regex = re.compile(expr, re.I)
+
+ found_keys = 0
+ for t in sorted(self.data.items(), key=lambda x: [0]):
+ v = t[1]
+
+ wtype = v.get("type", "")
+ if wtype == "File":
+ continue
+
+ for what in v.get("what", [""]):
+ if regex.search(what):
+ found_keys += 1
+
+ kernelversion = v.get("kernelversion", "").strip(" \t\n")
+ date = v.get("date", "").strip(" \t\n")
+ contact = v.get("contact", "").strip(" \t\n")
+ users = v.get("users", "").strip(" \t\n")
+ desc = v.get("description", "").strip(" \t\n")
+
+ files = []
+ for f in v.get("file", ()):
+ files.append(f[0])
+
+ what = str(found_keys) + ". " + what
+ title_tag = "-" * len(what)
+
+ print(f"\n{what}\n{title_tag}\n")
+
+ if kernelversion:
+ print(f"Kernel version:\t\t{kernelversion}")
+
+ if date:
+ print(f"Date:\t\t\t{date}")
+
+ if contact:
+ print(f"Contact:\t\t{contact}")
+
+ if users:
+ print(f"Users:\t\t\t{users}")
+
+ print("Defined on file(s):\t" + ", ".join(files))
+
+ if desc:
+ desc = desc.strip("\n")
+ print(f"\n{desc}\n")
+
+ if not found_keys:
+ print(f"Regular expression /{expr}/ not found.")
diff --git a/scripts/lib/abi/abi_regex.py b/scripts/lib/abi/abi_regex.py
new file mode 100644
index 000000000000..8a57846cbc69
--- /dev/null
+++ b/scripts/lib/abi/abi_regex.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python3
+# xxpylint: disable=R0903
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Convert ABI what into regular expressions
+"""
+
+import re
+import sys
+
+from pprint import pformat
+
+from abi_parser import AbiParser
+from helpers import AbiDebug
+
+class AbiRegex(AbiParser):
+ """Extends AbiParser to search ABI nodes with regular expressions"""
+
+ # Escape only ASCII visible characters
+ escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])"
+ leave_others = "others"
+
+ # Tuples with regular expressions to be compiled and replacement data
+ re_whats = [
+ # Drop escape characters that might exist
+ (re.compile("\\\\"), ""),
+
+ # Temporarily escape dot characters
+ (re.compile(r"\."), "\xf6"),
+
+ # Temporarily change [0-9]+ type of patterns
+ (re.compile(r"\[0\-9\]\+"), "\xff"),
+
+ # Temporarily change [\d+-\d+] type of patterns
+ (re.compile(r"\[0\-\d+\]"), "\xff"),
+ (re.compile(r"\[0:\d+\]"), "\xff"),
+ (re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"),
+
+ # Temporarily change [0-9] type of patterns
+ (re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"),
+
+ # Handle multiple option patterns
+ (re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"),
+
+ # Handle wildcards
+ (re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"),
+ (re.compile(r"/\*/"), "/.*/"),
+ (re.compile(r"/\xf6\xf6\xf6"), "/.*"),
+ (re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"),
+ (re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"),
+ (re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"),
+
+ (re.compile(r"XX+"), "\\\\w\xf7"),
+ (re.compile(r"([^A-Z])[XYZ]([^A-Z])"), "\\1\\\\w\xf7\\2"),
+ (re.compile(r"([^A-Z])[XYZ]$"), "\\1\\\\w\xf7"),
+ (re.compile(r"_[AB]_"), "_\\\\w\xf7_"),
+
+ # Recover [0-9] type of patterns
+ (re.compile(r"\xf4"), "["),
+ (re.compile(r"\xf5"), "]"),
+
+ # Remove duplicated spaces
+ (re.compile(r"\s+"), r" "),
+
+ # Special case: drop comparison as in:
+ # What: foo = <something>
+ # (this happens on a few IIO definitions)
+ (re.compile(r"\s*\=.*$"), ""),
+
+ # Escape all other symbols
+ (re.compile(escape_symbols), r"\\\1"),
+ (re.compile(r"\\\\"), r"\\"),
+ (re.compile(r"\\([\[\]\(\)\|])"), r"\1"),
+ (re.compile(r"(\d+)\\(-\d+)"), r"\1\2"),
+
+ (re.compile(r"\xff"), r"\\d+"),
+
+ # Special case: IIO ABI which a parenthesis.
+ (re.compile(r"sqrt(.*)"), r"sqrt(.*)"),
+
+ # Simplify regexes with multiple .*
+ (re.compile(r"(?:\.\*){2,}"), ""),
+
+ # Recover dot characters
+ (re.compile(r"\xf6"), "\\."),
+ # Recover plus characters
+ (re.compile(r"\xf7"), "+"),
+ ]
+ re_has_num = re.compile(r"\\d")
+
+ # Symbol name after escape_chars that are considered a devnode basename
+ re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$")
+
+ # List of popular group names to be skipped to minimize regex group size
+ # Use AbiDebug.SUBGROUP_SIZE to detect those
+ skip_names = set(["devices", "hwmon"])
+
+ def regex_append(self, what, new):
+ """
+ Get a search group for a subset of regular expressions.
+
+ As ABI may have thousands of symbols, using a for to search all
+ regular expressions is at least O(n^2). When there are wildcards,
+ the complexity increases substantially, eventually becoming exponential.
+
+ To avoid spending too much time on them, use a logic to split
+ them into groups. The smaller the group, the better, as it would
+ mean that searches will be confined to a small number of regular
+ expressions.
+
+ The conversion to a regex subset is tricky, as we need something
+ that can be easily obtained from the sysfs symbol and from the
+ regular expression. So, we need to discard nodes that have
+ wildcards.
+
+ If it can't obtain a subgroup, place the regular expression inside
+ a special group (self.leave_others).
+ """
+
+ search_group = None
+
+ for search_group in reversed(new.split("/")):
+ if not search_group or search_group in self.skip_names:
+ continue
+ if self.re_symbol_name.match(search_group):
+ break
+
+ if not search_group:
+ search_group = self.leave_others
+
+ if self.debug & AbiDebug.SUBGROUP_MAP:
+ self.log.debug("%s: mapped as %s", what, search_group)
+
+ try:
+ if search_group not in self.regex_group:
+ self.regex_group[search_group] = []
+
+ self.regex_group[search_group].append(re.compile(new))
+ if self.search_string:
+ if what.find(self.search_string) >= 0:
+ print(f"What: {what}")
+ except re.PatternError:
+ self.log.warning("Ignoring '%s' as it produced an invalid regex:\n"
+ " '%s'", what, new)
+
+ def get_regexes(self, what):
+ """
+ Given an ABI devnode, return a list of all regular expressions that
+ may match it, based on the sub-groups created by regex_append()
+ """
+
+ re_list = []
+
+ patches = what.split("/")
+ patches.reverse()
+ patches.append(self.leave_others)
+
+ for search_group in patches:
+ if search_group in self.regex_group:
+ re_list += self.regex_group[search_group]
+
+ return re_list
+
+ def __init__(self, *args, **kwargs):
+ """
+ Override init method to get verbose argument
+ """
+
+ self.regex_group = None
+ self.search_string = None
+ self.re_string = None
+
+ if "search_string" in kwargs:
+ self.search_string = kwargs.get("search_string")
+ del kwargs["search_string"]
+
+ if self.search_string:
+
+ try:
+ self.re_string = re.compile(self.search_string)
+ except re.PatternError as e:
+ msg = f"{self.search_string} is not a valid regular expression"
+ raise ValueError(msg) from e
+
+ super().__init__(*args, **kwargs)
+
+ def parse_abi(self, *args, **kwargs):
+
+ super().parse_abi(*args, **kwargs)
+
+ self.regex_group = {}
+
+ print("Converting ABI What fields into regexes...", file=sys.stderr)
+
+ for t in sorted(self.data.items(), key=lambda x: x[0]):
+ v = t[1]
+ if v.get("type") == "File":
+ continue
+
+ v["regex"] = []
+
+ for what in v.get("what", []):
+ if not what.startswith("/sys"):
+ continue
+
+ new = what
+ for r, s in self.re_whats:
+ try:
+ new = r.sub(s, new)
+ except re.PatternError as e:
+ # Help debugging troubles with new regexes
+ raise re.PatternError(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e
+
+ v["regex"].append(new)
+
+ if self.debug & AbiDebug.REGEX:
+ self.log.debug("%-90s <== %s", new, what)
+
+ # Store regex into a subgroup to speedup searches
+ self.regex_append(what, new)
+
+ if self.debug & AbiDebug.SUBGROUP_DICT:
+ self.log.debug("%s", pformat(self.regex_group))
+
+ if self.debug & AbiDebug.SUBGROUP_SIZE:
+ biggestd_keys = sorted(self.regex_group.keys(),
+ key= lambda k: len(self.regex_group[k]),
+ reverse=True)
+
+ print("Top regex subgroups:", file=sys.stderr)
+ for k in biggestd_keys[:10]:
+ print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr)
diff --git a/scripts/lib/abi/helpers.py b/scripts/lib/abi/helpers.py
new file mode 100644
index 000000000000..639b23e4ca33
--- /dev/null
+++ b/scripts/lib/abi/helpers.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+# pylint: disable=R0903
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Helper classes for ABI parser
+"""
+
+ABI_DIR = "Documentation/ABI/"
+
+
+class AbiDebug:
+ """Debug levels"""
+
+ WHAT_PARSING = 1
+ WHAT_OPEN = 2
+ DUMP_ABI_STRUCTS = 4
+ UNDEFINED = 8
+ REGEX = 16
+ SUBGROUP_MAP = 32
+ SUBGROUP_DICT = 64
+ SUBGROUP_SIZE = 128
+ GRAPH = 256
+
+
+DEBUG_HELP = """
+1 - enable debug parsing logic
+2 - enable debug messages on file open
+4 - enable debug for ABI parse data
+8 - enable extra debug information to identify troubles
+ with ABI symbols found at the local machine that
+ weren't found on ABI documentation (used only for
+ undefined subcommand)
+16 - enable debug for what to regex conversion
+32 - enable debug for symbol regex subgroups
+64 - enable debug for sysfs graph tree variable
+"""
diff --git a/scripts/lib/abi/system_symbols.py b/scripts/lib/abi/system_symbols.py
new file mode 100644
index 000000000000..f15c94a6e33c
--- /dev/null
+++ b/scripts/lib/abi/system_symbols.py
@@ -0,0 +1,378 @@
+#!/usr/bin/env python3
+# pylint: disable=R0902,R0912,R0914,R0915,R1702
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Parse ABI documentation and produce results from it.
+"""
+
+import os
+import re
+import sys
+
+from concurrent import futures
+from datetime import datetime
+from random import shuffle
+
+from helpers import AbiDebug
+
+class SystemSymbols:
+ """Stores arguments for the class and initialize class vars"""
+
+ def graph_add_file(self, path, link=None):
+ """
+ add a file path to the sysfs graph stored at self.root
+ """
+
+ if path in self.files:
+ return
+
+ name = ""
+ ref = self.root
+ for edge in path.split("/"):
+ name += edge + "/"
+ if edge not in ref:
+ ref[edge] = {"__name": [name.rstrip("/")]}
+
+ ref = ref[edge]
+
+ if link and link not in ref["__name"]:
+ ref["__name"].append(link.rstrip("/"))
+
+ self.files.add(path)
+
+ def print_graph(self, root_prefix="", root=None, level=0):
+ """Prints a reference tree graph using UTF-8 characters"""
+
+ if not root:
+ root = self.root
+ level = 0
+
+ # Prevent endless traverse
+ if level > 5:
+ return
+
+ if level > 0:
+ prefix = "├──"
+ last_prefix = "└──"
+ else:
+ prefix = ""
+ last_prefix = ""
+
+ items = list(root.items())
+
+ names = root.get("__name", [])
+ for k, edge in items:
+ if k == "__name":
+ continue
+
+ if not k:
+ k = "/"
+
+ if len(names) > 1:
+ k += " links: " + ",".join(names[1:])
+
+ if edge == items[-1][1]:
+ print(root_prefix + last_prefix + k)
+ p = root_prefix
+ if level > 0:
+ p += " "
+ self.print_graph(p, edge, level + 1)
+ else:
+ print(root_prefix + prefix + k)
+ p = root_prefix + "│ "
+ self.print_graph(p, edge, level + 1)
+
+ def _walk(self, root):
+ """
+ Walk through sysfs to get all devnodes that aren't ignored.
+
+ By default, uses /sys as sysfs mounting point. If another
+ directory is used, it replaces them to /sys at the patches.
+ """
+
+ with os.scandir(root) as obj:
+ for entry in obj:
+ path = os.path.join(root, entry.name)
+ if self.sysfs:
+ p = path.replace(self.sysfs, "/sys", count=1)
+ else:
+ p = path
+
+ if self.re_ignore.search(p):
+ return
+
+ # Handle link first to avoid directory recursion
+ if entry.is_symlink():
+ real = os.path.realpath(path)
+ if not self.sysfs:
+ self.aliases[path] = real
+ else:
+ real = real.replace(self.sysfs, "/sys", count=1)
+
+ # Add absfile location to graph if it doesn't exist
+ if not self.re_ignore.search(real):
+ # Add link to the graph
+ self.graph_add_file(real, p)
+
+ elif entry.is_file():
+ self.graph_add_file(p)
+
+ elif entry.is_dir():
+ self._walk(path)
+
+ def __init__(self, abi, sysfs="/sys", hints=False):
+ """
+ Initialize internal variables and get a list of all files inside
+ sysfs that can currently be parsed.
+
+ Please notice that there are several entries on sysfs that aren't
+ documented as ABI. Ignore those.
+
+ The real paths will be stored under self.files. Aliases will be
+ stored in separate, as self.aliases.
+ """
+
+ self.abi = abi
+ self.log = abi.log
+
+ if sysfs != "/sys":
+ self.sysfs = sysfs.rstrip("/")
+ else:
+ self.sysfs = None
+
+ self.hints = hints
+
+ self.root = {}
+ self.aliases = {}
+ self.files = set()
+
+ dont_walk = [
+ # Those require root access and aren't documented at ABI
+ f"^{sysfs}/kernel/debug",
+ f"^{sysfs}/kernel/tracing",
+ f"^{sysfs}/fs/pstore",
+ f"^{sysfs}/fs/bpf",
+ f"^{sysfs}/fs/fuse",
+
+ # This is not documented at ABI
+ f"^{sysfs}/module",
+
+ f"^{sysfs}/fs/cgroup", # this is big and has zero docs under ABI
+ f"^{sysfs}/firmware", # documented elsewhere: ACPI, DT bindings
+ "sections|notes", # aren't actually part of ABI
+
+ # kernel-parameters.txt - not easy to parse
+ "parameters",
+ ]
+
+ self.re_ignore = re.compile("|".join(dont_walk))
+
+ print(f"Reading {sysfs} directory contents...", file=sys.stderr)
+ self._walk(sysfs)
+
+ def check_file(self, refs, found):
+ """Check missing ABI symbols for a given sysfs file"""
+
+ res_list = []
+
+ try:
+ for names in refs:
+ fname = names[0]
+
+ res = {
+ "found": False,
+ "fname": fname,
+ "msg": "",
+ }
+ res_list.append(res)
+
+ re_what = self.abi.get_regexes(fname)
+ if not re_what:
+ self.abi.log.warning(f"missing rules for {fname}")
+ continue
+
+ for name in names:
+ for r in re_what:
+ if self.abi.debug & AbiDebug.UNDEFINED:
+ self.log.debug("check if %s matches '%s'", name, r.pattern)
+ if r.match(name):
+ res["found"] = True
+ if found:
+ res["msg"] += f" {fname}: regex:\n\t"
+ continue
+
+ if self.hints and not res["found"]:
+ res["msg"] += f" {fname} not found. Tested regexes:\n"
+ for r in re_what:
+ res["msg"] += " " + r.pattern + "\n"
+
+ except KeyboardInterrupt:
+ pass
+
+ return res_list
+
+ def _ref_interactor(self, root):
+ """Recursive function to interact over the sysfs tree"""
+
+ for k, v in root.items():
+ if isinstance(v, dict):
+ yield from self._ref_interactor(v)
+
+ if root == self.root or k == "__name":
+ continue
+
+ if self.abi.re_string:
+ fname = v["__name"][0]
+ if self.abi.re_string.search(fname):
+ yield v
+ else:
+ yield v
+
+
+ def get_fileref(self, all_refs, chunk_size):
+ """Interactor to group refs into chunks"""
+
+ n = 0
+ refs = []
+
+ for ref in all_refs:
+ refs.append(ref)
+
+ n += 1
+ if n >= chunk_size:
+ yield refs
+ n = 0
+ refs = []
+
+ yield refs
+
+ def check_undefined_symbols(self, max_workers=None, chunk_size=50,
+ found=None, dry_run=None):
+ """Seach ABI for sysfs symbols missing documentation"""
+
+ self.abi.parse_abi()
+
+ if self.abi.debug & AbiDebug.GRAPH:
+ self.print_graph()
+
+ all_refs = []
+ for ref in self._ref_interactor(self.root):
+ all_refs.append(ref["__name"])
+
+ if dry_run:
+ print("Would check", file=sys.stderr)
+ for ref in all_refs:
+ print(", ".join(ref))
+
+ return
+
+ print("Starting to search symbols (it may take several minutes):",
+ file=sys.stderr)
+ start = datetime.now()
+ old_elapsed = None
+
+ # Python doesn't support multithreading due to limitations on its
+ # global lock (GIL). While Python 3.13 finally made GIL optional,
+ # there are still issues related to it. Also, we want to have
+ # backward compatibility with older versions of Python.
+ #
+ # So, use instead multiprocess. However, Python is very slow passing
+ # data from/to multiple processes. Also, it may consume lots of memory
+ # if the data to be shared is not small. So, we need to group workload
+ # in chunks that are big enough to generate performance gains while
+ # not being so big that would cause out-of-memory.
+
+ num_refs = len(all_refs)
+ print(f"Number of references to parse: {num_refs}", file=sys.stderr)
+
+ if not max_workers:
+ max_workers = os.cpu_count()
+ elif max_workers > os.cpu_count():
+ max_workers = os.cpu_count()
+
+ max_workers = max(max_workers, 1)
+
+ max_chunk_size = int((num_refs + max_workers - 1) / max_workers)
+ chunk_size = min(chunk_size, max_chunk_size)
+ chunk_size = max(1, chunk_size)
+
+ if max_workers > 1:
+ executor = futures.ProcessPoolExecutor
+
+ # Place references in a random order. This may help improving
+ # performance, by mixing complex/simple expressions when creating
+ # chunks
+ shuffle(all_refs)
+ else:
+ # Python has a high overhead with processes. When there's just
+ # one worker, it is faster to not create a new process.
+ # Yet, User still deserves to have a progress print. So, use
+ # python's "thread", which is actually a single process, using
+ # an internal schedule to switch between tasks. No performance
+ # gains for non-IO tasks, but still it can be quickly interrupted
+ # from time to time to display progress.
+ executor = futures.ThreadPoolExecutor
+
+ not_found = []
+ f_list = []
+ with executor(max_workers=max_workers) as exe:
+ for refs in self.get_fileref(all_refs, chunk_size):
+ if refs:
+ try:
+ f_list.append(exe.submit(self.check_file, refs, found))
+
+ except KeyboardInterrupt:
+ return
+
+ total = len(f_list)
+
+ if not total:
+ if self.abi.re_string:
+ print(f"No ABI symbol matches {self.abi.search_string}")
+ else:
+ self.abi.log.warning("No ABI symbols found")
+ return
+
+ print(f"{len(f_list):6d} jobs queued on {max_workers} workers",
+ file=sys.stderr)
+
+ while f_list:
+ try:
+ t = futures.wait(f_list, timeout=1,
+ return_when=futures.FIRST_COMPLETED)
+
+ done = t[0]
+
+ for fut in done:
+ res_list = fut.result()
+
+ for res in res_list:
+ if not res["found"]:
+ not_found.append(res["fname"])
+ if res["msg"]:
+ print(res["msg"])
+
+ f_list.remove(fut)
+ except KeyboardInterrupt:
+ return
+
+ except RuntimeError as e:
+ self.abi.log.warning(f"Future: {e}")
+ break
+
+ if sys.stderr.isatty():
+ elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0]
+ if len(f_list) < total:
+ elapsed += f" ({total - len(f_list)}/{total} jobs completed). "
+ if elapsed != old_elapsed:
+ print(elapsed + "\r", end="", flush=True,
+ file=sys.stderr)
+ old_elapsed = elapsed
+
+ elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0]
+ print(elapsed, file=sys.stderr)
+
+ for f in sorted(not_found):
+ print(f"{f} not found.")
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index d853ddb3b28c..67e66333bd2a 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -144,10 +144,6 @@ kallsyms()
kallsymopt="${kallsymopt} --all-symbols"
fi
- if is_enabled CONFIG_KALLSYMS_ABSOLUTE_PERCPU; then
- kallsymopt="${kallsymopt} --absolute-percpu"
- fi
-
info KSYMS "${2}.S"
scripts/kallsyms ${kallsymopt} "${1}" > "${2}.S"
@@ -283,7 +279,11 @@ vmlinux_link vmlinux
# fill in BTF IDs
if is_enabled CONFIG_DEBUG_INFO_BTF; then
info BTFIDS vmlinux
- ${RESOLVE_BTFIDS} vmlinux
+ RESOLVE_BTFIDS_ARGS=""
+ if is_enabled CONFIG_WERROR; then
+ RESOLVE_BTFIDS_ARGS=" --fatal_warnings "
+ fi
+ ${RESOLVE_BTFIDS} ${RESOLVE_BTFIDS_ARGS} vmlinux
fi
mksysmap vmlinux System.map
diff --git a/scripts/min-tool-version.sh b/scripts/min-tool-version.sh
index 91c91201212c..787868183b84 100755
--- a/scripts/min-tool-version.sh
+++ b/scripts/min-tool-version.sh
@@ -19,12 +19,14 @@ binutils)
gcc)
if [ "$ARCH" = parisc64 ]; then
echo 12.0.0
+ elif [ "$SRCARCH" = x86 ]; then
+ echo 8.1.0
else
echo 5.1.0
fi
;;
llvm)
- if [ "$SRCARCH" = s390 ]; then
+ if [ "$SRCARCH" = s390 -o "$SRCARCH" = x86 ]; then
echo 15.0.0
elif [ "$SRCARCH" = loongarch ]; then
echo 18.0.0
diff --git a/scripts/mksysmap b/scripts/mksysmap
index c12723a04655..3accbdb269ac 100755
--- a/scripts/mksysmap
+++ b/scripts/mksysmap
@@ -26,7 +26,7 @@
# (do not forget a space before each pattern)
# local symbols for ARM, MIPS, etc.
-/ \\$/d
+/ \$/d
# local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc.
/ \.L/d
@@ -39,7 +39,7 @@
/ __pi_\.L/d
# arm64 local symbols in non-VHE KVM namespace
-/ __kvm_nvhe_\\$/d
+/ __kvm_nvhe_\$/d
/ __kvm_nvhe_\.L/d
# lld arm/aarch64/mips thunks
diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c
index 9c7b404defbd..d3d00e85edf7 100644
--- a/scripts/mod/devicetable-offsets.c
+++ b/scripts/mod/devicetable-offsets.c
@@ -237,7 +237,6 @@ int main(void)
DEVID(typec_device_id);
DEVID_FIELD(typec_device_id, svid);
- DEVID_FIELD(typec_device_id, mode);
DEVID(tee_client_device_id);
DEVID_FIELD(tee_client_device_id, uuid);
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 5b5745f00eb3..00586119a25b 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -132,7 +132,8 @@ struct devtable {
* based at address m.
*/
#define DEF_FIELD(m, devid, f) \
- typeof(((struct devid *)0)->f) f = TO_NATIVE(*(typeof(f) *)((m) + OFF_##devid##_##f))
+ typeof(((struct devid *)0)->f) f = \
+ get_unaligned_native((typeof(f) *)((m) + OFF_##devid##_##f))
/* Define a variable f that holds the address of field f of struct devid
* based at address m. Due to the way typeof works, for a field of type
@@ -600,7 +601,7 @@ static void do_pnp_card_entry(struct module *mod, void *symval)
static void do_pcmcia_entry(struct module *mod, void *symval)
{
char alias[256] = {};
- unsigned int i;
+
DEF_FIELD(symval, pcmcia_device_id, match_flags);
DEF_FIELD(symval, pcmcia_device_id, manf_id);
DEF_FIELD(symval, pcmcia_device_id, card_id);
@@ -609,10 +610,6 @@ static void do_pcmcia_entry(struct module *mod, void *symval)
DEF_FIELD(symval, pcmcia_device_id, device_no);
DEF_FIELD_ADDR(symval, pcmcia_device_id, prod_id_hash);
- for (i=0; i<4; i++) {
- (*prod_id_hash)[i] = TO_NATIVE((*prod_id_hash)[i]);
- }
-
ADD(alias, "m", match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID,
manf_id);
ADD(alias, "c", match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID,
@@ -623,10 +620,14 @@ static void do_pcmcia_entry(struct module *mod, void *symval)
function);
ADD(alias, "pfn", match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO,
device_no);
- ADD(alias, "pa", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1, (*prod_id_hash)[0]);
- ADD(alias, "pb", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2, (*prod_id_hash)[1]);
- ADD(alias, "pc", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3, (*prod_id_hash)[2]);
- ADD(alias, "pd", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4, (*prod_id_hash)[3]);
+ ADD(alias, "pa", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1,
+ get_unaligned_native(*prod_id_hash + 0));
+ ADD(alias, "pb", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2,
+ get_unaligned_native(*prod_id_hash + 1));
+ ADD(alias, "pc", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3,
+ get_unaligned_native(*prod_id_hash + 2));
+ ADD(alias, "pd", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4,
+ get_unaligned_native(*prod_id_hash + 3));
module_alias_printf(mod, true, "pcmcia:%s", alias);
}
@@ -654,10 +655,9 @@ static void do_input(char *alias,
{
unsigned int i;
- for (i = min / BITS_PER_LONG; i < max / BITS_PER_LONG + 1; i++)
- arr[i] = TO_NATIVE(arr[i]);
- for (i = min; i < max; i++)
- if (arr[i / BITS_PER_LONG] & (1ULL << (i%BITS_PER_LONG)))
+ for (i = min; i <= max; i++)
+ if (get_unaligned_native(arr + i / BITS_PER_LONG) &
+ (1ULL << (i % BITS_PER_LONG)))
sprintf(alias + strlen(alias), "%X,*", i);
}
@@ -812,15 +812,13 @@ static void do_virtio_entry(struct module *mod, void *symval)
* Each byte of the guid will be represented by two hex characters
* in the name.
*/
-
static void do_vmbus_entry(struct module *mod, void *symval)
{
- int i;
DEF_FIELD_ADDR(symval, hv_vmbus_device_id, guid);
- char guid_name[(sizeof(*guid) + 1) * 2];
+ char guid_name[sizeof(*guid) * 2 + 1];
- for (i = 0; i < (sizeof(*guid) * 2); i += 2)
- sprintf(&guid_name[i], "%02x", TO_NATIVE((guid->b)[i/2]));
+ for (int i = 0; i < sizeof(*guid); i++)
+ sprintf(&guid_name[i * 2], "%02x", guid->b[i]);
module_alias_printf(mod, false, "vmbus:%s", guid_name);
}
@@ -1221,17 +1219,12 @@ static void do_tbsvc_entry(struct module *mod, void *symval)
module_alias_printf(mod, true, "tbsvc:%s", alias);
}
-/* Looks like: typec:idNmN */
+/* Looks like: typec:idN */
static void do_typec_entry(struct module *mod, void *symval)
{
- char alias[256] = {};
-
DEF_FIELD(symval, typec_device_id, svid);
- DEF_FIELD(symval, typec_device_id, mode);
-
- ADD(alias, "m", mode != TYPEC_ANY_MODE, mode);
- module_alias_printf(mod, false, "typec:id%04X%s", svid, alias);
+ module_alias_printf(mod, false, "typec:id%04X", svid);
}
/* Looks like: tee:uuid */
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index 94ee49207a45..c35d22607978 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -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 */
@@ -186,8 +190,8 @@ static struct module *new_module(const char *name, size_t namelen)
/*
* Set mod->is_gpl_compatible to true by default. If MODULE_LICENSE()
- * is missing, do not check the use for EXPORT_SYMBOL_GPL() becasue
- * modpost will exit wiht error anyway.
+ * is missing, do not check the use for EXPORT_SYMBOL_GPL() because
+ * modpost will exit with an error anyway.
*/
mod->is_gpl_compatible = true;
@@ -503,6 +507,9 @@ static int parse_elf(struct elf_info *info, const char *filename)
info->modinfo_len = sechdrs[i].sh_size;
} else if (!strcmp(secname, ".export_symbol")) {
info->export_symbol_secndx = i;
+ } else if (!strcmp(secname, ".no_trim_symbol")) {
+ info->no_trim_symbol = (void *)hdr + sechdrs[i].sh_offset;
+ info->no_trim_symbol_len = sechdrs[i].sh_size;
}
if (sechdrs[i].sh_type == SHT_SYMTAB) {
@@ -1138,9 +1145,9 @@ static Elf_Addr addend_386_rel(uint32_t *location, unsigned int r_type)
{
switch (r_type) {
case R_386_32:
- return TO_NATIVE(*location);
+ return get_unaligned_native(location);
case R_386_PC32:
- return TO_NATIVE(*location) + 4;
+ return get_unaligned_native(location) + 4;
}
return (Elf_Addr)(-1);
@@ -1161,24 +1168,24 @@ static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type)
switch (r_type) {
case R_ARM_ABS32:
case R_ARM_REL32:
- inst = TO_NATIVE(*(uint32_t *)loc);
+ inst = get_unaligned_native((uint32_t *)loc);
return inst + sym->st_value;
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_ABS:
- inst = TO_NATIVE(*(uint32_t *)loc);
+ inst = get_unaligned_native((uint32_t *)loc);
offset = sign_extend32(((inst & 0xf0000) >> 4) | (inst & 0xfff),
15);
return offset + sym->st_value;
case R_ARM_PC24:
case R_ARM_CALL:
case R_ARM_JUMP24:
- inst = TO_NATIVE(*(uint32_t *)loc);
+ inst = get_unaligned_native((uint32_t *)loc);
offset = sign_extend32((inst & 0x00ffffff) << 2, 25);
return offset + sym->st_value + 8;
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_ABS:
- upper = TO_NATIVE(*(uint16_t *)loc);
- lower = TO_NATIVE(*((uint16_t *)loc + 1));
+ upper = get_unaligned_native((uint16_t *)loc);
+ lower = get_unaligned_native((uint16_t *)loc + 1);
offset = sign_extend32(((upper & 0x000f) << 12) |
((upper & 0x0400) << 1) |
((lower & 0x7000) >> 4) |
@@ -1195,8 +1202,8 @@ static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type)
* imm11 = lower[10:0]
* imm32 = SignExtend(S:J2:J1:imm6:imm11:'0')
*/
- upper = TO_NATIVE(*(uint16_t *)loc);
- lower = TO_NATIVE(*((uint16_t *)loc + 1));
+ upper = get_unaligned_native((uint16_t *)loc);
+ lower = get_unaligned_native((uint16_t *)loc + 1);
sign = (upper >> 10) & 1;
j1 = (lower >> 13) & 1;
@@ -1219,8 +1226,8 @@ static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type)
* I2 = NOT(J2 XOR S)
* imm32 = SignExtend(S:I1:I2:imm10:imm11:'0')
*/
- upper = TO_NATIVE(*(uint16_t *)loc);
- lower = TO_NATIVE(*((uint16_t *)loc + 1));
+ upper = get_unaligned_native((uint16_t *)loc);
+ lower = get_unaligned_native((uint16_t *)loc + 1);
sign = (upper >> 10) & 1;
j1 = (lower >> 13) & 1;
@@ -1241,7 +1248,7 @@ static Elf_Addr addend_mips_rel(uint32_t *location, unsigned int r_type)
{
uint32_t inst;
- inst = TO_NATIVE(*location);
+ inst = get_unaligned_native(location);
switch (r_type) {
case R_MIPS_LO16:
return inst & 0xffff;
@@ -1562,6 +1569,14 @@ static void read_symbols(const char *modname)
/* strip trailing .o */
mod = new_module(modname, strlen(modname) - strlen(".o"));
+ /* save .no_trim_symbol section for later use */
+ if (info.no_trim_symbol_len) {
+ mod->no_trim_symbol = xmalloc(info.no_trim_symbol_len);
+ memcpy(mod->no_trim_symbol, info.no_trim_symbol,
+ info.no_trim_symbol_len);
+ mod->no_trim_symbol_len = info.no_trim_symbol_len;
+ }
+
if (!mod->is_vmlinux) {
license = get_modinfo(&info, "license");
if (!license)
@@ -1724,6 +1739,28 @@ static void handle_white_list_exports(const char *white_list)
free(buf);
}
+/*
+ * Keep symbols recorded in the .no_trim_symbol section. This is necessary to
+ * prevent CONFIG_TRIM_UNUSED_KSYMS from dropping EXPORT_SYMBOL because
+ * symbol_get() relies on the symbol being present in the ksymtab for lookups.
+ */
+static void keep_no_trim_symbols(struct module *mod)
+{
+ unsigned long size = mod->no_trim_symbol_len;
+
+ for (char *s = mod->no_trim_symbol; s; s = next_string(s , &size)) {
+ struct symbol *sym;
+
+ /*
+ * If find_symbol() returns NULL, this symbol is not provided
+ * by any module, and symbol_get() will fail.
+ */
+ sym = find_symbol(s);
+ if (sym)
+ sym->used = true;
+ }
+}
+
static void check_modname_len(struct module *mod)
{
const char *mod_name;
@@ -1806,13 +1843,56 @@ 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
**/
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 +1908,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 +2046,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 +2212,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 +2261,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);
}
@@ -2195,6 +2287,8 @@ int main(int argc, char **argv)
read_symbols_from_files(files_source);
list_for_each_entry(mod, &modules, list) {
+ keep_no_trim_symbols(mod);
+
if (mod->dump_file || mod->is_vmlinux)
continue;
diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h
index 8b72c227ebf4..59366f456b76 100644
--- a/scripts/mod/modpost.h
+++ b/scripts/mod/modpost.h
@@ -65,6 +65,20 @@
#define TO_NATIVE(x) \
(target_is_big_endian == host_is_big_endian ? x : bswap(x))
+#define __get_unaligned_t(type, ptr) ({ \
+ const struct { type x; } __attribute__((__packed__)) *__pptr = \
+ (typeof(__pptr))(ptr); \
+ __pptr->x; \
+})
+
+#define get_unaligned(ptr) __get_unaligned_t(typeof(*(ptr)), (ptr))
+
+#define get_unaligned_native(ptr) \
+({ \
+ typeof(*(ptr)) _val = get_unaligned(ptr); \
+ TO_NATIVE(_val); \
+})
+
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define strstarts(str, prefix) (strncmp(str, prefix, strlen(prefix)) == 0)
@@ -97,6 +111,8 @@ struct module_alias {
*
* @dump_file: path to the .symvers file if loaded from a file
* @aliases: list head for module_aliases
+ * @no_trim_symbol: .no_trim_symbol section data
+ * @no_trim_symbol_len: length of the .no_trim_symbol section
*/
struct module {
struct list_head list;
@@ -114,6 +130,8 @@ struct module {
// Actual imported namespaces
struct list_head imported_namespaces;
struct list_head aliases;
+ char *no_trim_symbol;
+ unsigned int no_trim_symbol_len;
char name[];
};
@@ -127,6 +145,8 @@ struct elf_info {
char *strtab;
char *modinfo;
unsigned int modinfo_len;
+ char *no_trim_symbol;
+ unsigned int no_trim_symbol_len;
/* support for 32bit section numbers */
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index c2f80f9141d4..450f1088d5fd 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -16,6 +16,7 @@ SECTIONS {
*(.discard)
*(.discard.*)
*(.export_symbol)
+ *(.no_trim_symbol)
}
__ksymtab 0 : ALIGN(8) { *(SORT(___ksymtab+*)) }
diff --git a/scripts/package/PKGBUILD b/scripts/package/PKGBUILD
index f83493838cf9..0cf3a55b05e1 100644
--- a/scripts/package/PKGBUILD
+++ b/scripts/package/PKGBUILD
@@ -22,7 +22,6 @@ license=(GPL-2.0-only)
makedepends=(
bc
bison
- cpio
flex
gettext
kmod
@@ -103,7 +102,7 @@ _package-headers() {
_package-api-headers() {
pkgdesc="Kernel headers sanitized for use in userspace"
- provides=(linux-api-headers)
+ provides=(linux-api-headers="${pkgver}")
conflicts=(linux-api-headers)
_prologue
diff --git a/scripts/package/builddeb b/scripts/package/builddeb
index ad7aba0f268e..3627ca227e5a 100755
--- a/scripts/package/builddeb
+++ b/scripts/package/builddeb
@@ -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}"
diff --git a/scripts/package/install-extmod-build b/scripts/package/install-extmod-build
index d3c5b104c063..b96538787f3d 100755
--- a/scripts/package/install-extmod-build
+++ b/scripts/package/install-extmod-build
@@ -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/
-
- 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/
+ # Clear VPATH and srcroot because the source files reside in the output
+ # directory.
+ # shellcheck disable=SC2016 # $(MAKE) and $(build) will be expanded by Make
+ "${MAKE}" run-command KBUILD_RUN_COMMAND='+$(MAKE) HOSTCC='"${CC}"' VPATH= srcroot=. $(build)='"$(realpath --relative-base=. "${destdir}")"/scripts
- rm -f "${destdir}/Kbuild" "${destdir}/scripts/Kbuild"
+ rm -f "${destdir}/scripts/Kbuild"
fi
find "${destdir}" \( -name '.*.cmd' -o -name '*.o' \) -delete
diff --git a/scripts/package/mkdebian b/scripts/package/mkdebian
index b038a1380b8a..b6dd98ca860b 100755
--- a/scripts/package/mkdebian
+++ b/scripts/package/mkdebian
@@ -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>,
diff --git a/scripts/rust_is_available.sh b/scripts/rust_is_available.sh
index 93c0ef7fb3fb..d2323de0692c 100755
--- a/scripts/rust_is_available.sh
+++ b/scripts/rust_is_available.sh
@@ -123,8 +123,10 @@ fi
# Non-stable and distributions' versions may have a version suffix, e.g. `-dev`.
#
# The dummy parameter `workaround-for-0.69.0` is required to support 0.69.0
-# (https://github.com/rust-lang/rust-bindgen/pull/2678). It can be removed when
-# the minimum version is upgraded past that (0.69.1 already fixed the issue).
+# (https://github.com/rust-lang/rust-bindgen/pull/2678) and 0.71.0
+# (https://github.com/rust-lang/rust-bindgen/pull/3040). It can be removed when
+# the minimum version is upgraded past the latter (0.69.1 and 0.71.1 both fixed
+# the issue).
rust_bindings_generator_output=$( \
LC_ALL=C "$BINDGEN" --version workaround-for-0.69.0 2>/dev/null
) || rust_bindings_generator_code=$?
diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs
index 5ebd42ae4a3f..76aaa8329413 100644
--- a/scripts/rustdoc_test_gen.rs
+++ b/scripts/rustdoc_test_gen.rs
@@ -15,8 +15,8 @@
//! - Test code should be able to define functions and call them, without having to carry
//! the context.
//!
-//! - Later on, we may want to be able to test non-kernel code (e.g. `core`, `alloc` or
-//! third-party crates) which likely use the standard library `assert*!` macros.
+//! - Later on, we may want to be able to test non-kernel code (e.g. `core` or third-party
+//! crates) which likely use the standard library `assert*!` macros.
//!
//! For this reason, instead of the passed context, `kunit_get_current_test()` is used instead
//! (i.e. `current->kunit_test`).
diff --git a/scripts/sorttable.c b/scripts/sorttable.c
index 83cdb843d92f..9f41575afd7a 100644
--- a/scripts/sorttable.c
+++ b/scripts/sorttable.c
@@ -64,14 +64,204 @@
#define EM_LOONGARCH 258
#endif
+typedef union {
+ Elf32_Ehdr e32;
+ Elf64_Ehdr e64;
+} Elf_Ehdr;
+
+typedef union {
+ Elf32_Shdr e32;
+ Elf64_Shdr e64;
+} Elf_Shdr;
+
+typedef union {
+ Elf32_Sym e32;
+ Elf64_Sym e64;
+} Elf_Sym;
+
static uint32_t (*r)(const uint32_t *);
static uint16_t (*r2)(const uint16_t *);
static uint64_t (*r8)(const uint64_t *);
static void (*w)(uint32_t, uint32_t *);
-static void (*w2)(uint16_t, uint16_t *);
-static void (*w8)(uint64_t, uint64_t *);
typedef void (*table_sort_t)(char *, int);
+static struct elf_funcs {
+ int (*compare_extable)(const void *a, const void *b);
+ uint64_t (*ehdr_shoff)(Elf_Ehdr *ehdr);
+ uint16_t (*ehdr_shstrndx)(Elf_Ehdr *ehdr);
+ uint16_t (*ehdr_shentsize)(Elf_Ehdr *ehdr);
+ uint16_t (*ehdr_shnum)(Elf_Ehdr *ehdr);
+ uint64_t (*shdr_addr)(Elf_Shdr *shdr);
+ uint64_t (*shdr_offset)(Elf_Shdr *shdr);
+ uint64_t (*shdr_size)(Elf_Shdr *shdr);
+ uint64_t (*shdr_entsize)(Elf_Shdr *shdr);
+ uint32_t (*shdr_link)(Elf_Shdr *shdr);
+ uint32_t (*shdr_name)(Elf_Shdr *shdr);
+ uint32_t (*shdr_type)(Elf_Shdr *shdr);
+ uint8_t (*sym_type)(Elf_Sym *sym);
+ uint32_t (*sym_name)(Elf_Sym *sym);
+ uint64_t (*sym_value)(Elf_Sym *sym);
+ uint16_t (*sym_shndx)(Elf_Sym *sym);
+} e;
+
+static uint64_t ehdr64_shoff(Elf_Ehdr *ehdr)
+{
+ return r8(&ehdr->e64.e_shoff);
+}
+
+static uint64_t ehdr32_shoff(Elf_Ehdr *ehdr)
+{
+ return r(&ehdr->e32.e_shoff);
+}
+
+static uint64_t ehdr_shoff(Elf_Ehdr *ehdr)
+{
+ return e.ehdr_shoff(ehdr);
+}
+
+#define EHDR_HALF(fn_name) \
+static uint16_t ehdr64_##fn_name(Elf_Ehdr *ehdr) \
+{ \
+ return r2(&ehdr->e64.e_##fn_name); \
+} \
+ \
+static uint16_t ehdr32_##fn_name(Elf_Ehdr *ehdr) \
+{ \
+ return r2(&ehdr->e32.e_##fn_name); \
+} \
+ \
+static uint16_t ehdr_##fn_name(Elf_Ehdr *ehdr) \
+{ \
+ return e.ehdr_##fn_name(ehdr); \
+}
+
+EHDR_HALF(shentsize)
+EHDR_HALF(shstrndx)
+EHDR_HALF(shnum)
+
+#define SHDR_WORD(fn_name) \
+static uint32_t shdr64_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return r(&shdr->e64.sh_##fn_name); \
+} \
+ \
+static uint32_t shdr32_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return r(&shdr->e32.sh_##fn_name); \
+} \
+ \
+static uint32_t shdr_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return e.shdr_##fn_name(shdr); \
+}
+
+#define SHDR_ADDR(fn_name) \
+static uint64_t shdr64_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return r8(&shdr->e64.sh_##fn_name); \
+} \
+ \
+static uint64_t shdr32_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return r(&shdr->e32.sh_##fn_name); \
+} \
+ \
+static uint64_t shdr_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return e.shdr_##fn_name(shdr); \
+}
+
+#define SHDR_WORD(fn_name) \
+static uint32_t shdr64_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return r(&shdr->e64.sh_##fn_name); \
+} \
+ \
+static uint32_t shdr32_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return r(&shdr->e32.sh_##fn_name); \
+} \
+static uint32_t shdr_##fn_name(Elf_Shdr *shdr) \
+{ \
+ return e.shdr_##fn_name(shdr); \
+}
+
+SHDR_ADDR(addr)
+SHDR_ADDR(offset)
+SHDR_ADDR(size)
+SHDR_ADDR(entsize)
+
+SHDR_WORD(link)
+SHDR_WORD(name)
+SHDR_WORD(type)
+
+#define SYM_ADDR(fn_name) \
+static uint64_t sym64_##fn_name(Elf_Sym *sym) \
+{ \
+ return r8(&sym->e64.st_##fn_name); \
+} \
+ \
+static uint64_t sym32_##fn_name(Elf_Sym *sym) \
+{ \
+ return r(&sym->e32.st_##fn_name); \
+} \
+ \
+static uint64_t sym_##fn_name(Elf_Sym *sym) \
+{ \
+ return e.sym_##fn_name(sym); \
+}
+
+#define SYM_WORD(fn_name) \
+static uint32_t sym64_##fn_name(Elf_Sym *sym) \
+{ \
+ return r(&sym->e64.st_##fn_name); \
+} \
+ \
+static uint32_t sym32_##fn_name(Elf_Sym *sym) \
+{ \
+ return r(&sym->e32.st_##fn_name); \
+} \
+ \
+static uint32_t sym_##fn_name(Elf_Sym *sym) \
+{ \
+ return e.sym_##fn_name(sym); \
+}
+
+#define SYM_HALF(fn_name) \
+static uint16_t sym64_##fn_name(Elf_Sym *sym) \
+{ \
+ return r2(&sym->e64.st_##fn_name); \
+} \
+ \
+static uint16_t sym32_##fn_name(Elf_Sym *sym) \
+{ \
+ return r2(&sym->e32.st_##fn_name); \
+} \
+ \
+static uint16_t sym_##fn_name(Elf_Sym *sym) \
+{ \
+ return e.sym_##fn_name(sym); \
+}
+
+static uint8_t sym64_type(Elf_Sym *sym)
+{
+ return ELF64_ST_TYPE(sym->e64.st_info);
+}
+
+static uint8_t sym32_type(Elf_Sym *sym)
+{
+ return ELF32_ST_TYPE(sym->e32.st_info);
+}
+
+static uint8_t sym_type(Elf_Sym *sym)
+{
+ return e.sym_type(sym);
+}
+
+SYM_ADDR(value)
+SYM_WORD(name)
+SYM_HALF(shndx)
+
/*
* Get the whole file as a programming convenience in order to avoid
* malloc+lseek+read+free of many pieces. If successful, then mmap
@@ -146,31 +336,11 @@ static void wbe(uint32_t val, uint32_t *x)
put_unaligned_be32(val, x);
}
-static void w2be(uint16_t val, uint16_t *x)
-{
- put_unaligned_be16(val, x);
-}
-
-static void w8be(uint64_t val, uint64_t *x)
-{
- put_unaligned_be64(val, x);
-}
-
static void wle(uint32_t val, uint32_t *x)
{
put_unaligned_le32(val, x);
}
-static void w2le(uint16_t val, uint16_t *x)
-{
- put_unaligned_le16(val, x);
-}
-
-static void w8le(uint64_t val, uint64_t *x)
-{
- put_unaligned_le64(val, x);
-}
-
/*
* Move reserved section indices SHN_LORESERVE..SHN_HIRESERVE out of
* the way to -256..-1, to avoid conflicting with real section
@@ -195,10 +365,430 @@ static inline unsigned int get_secindex(unsigned int shndx,
return r(&symtab_shndx_start[sym_offs]);
}
-/* 32 bit and 64 bit are very similar */
-#include "sorttable.h"
-#define SORTTABLE_64
-#include "sorttable.h"
+static int compare_extable_32(const void *a, const void *b)
+{
+ Elf32_Addr av = r(a);
+ Elf32_Addr bv = r(b);
+
+ if (av < bv)
+ return -1;
+ return av > bv;
+}
+
+static int compare_extable_64(const void *a, const void *b)
+{
+ Elf64_Addr av = r8(a);
+ Elf64_Addr bv = r8(b);
+
+ if (av < bv)
+ return -1;
+ return av > bv;
+}
+
+static int compare_extable(const void *a, const void *b)
+{
+ return e.compare_extable(a, b);
+}
+
+static inline void *get_index(void *start, int entsize, int index)
+{
+ return start + (entsize * index);
+}
+
+static int extable_ent_size;
+static int long_size;
+
+
+#ifdef UNWINDER_ORC_ENABLED
+/* ORC unwinder only support X86_64 */
+#include <asm/orc_types.h>
+
+#define ERRSTR_MAXSZ 256
+
+static char g_err[ERRSTR_MAXSZ];
+static int *g_orc_ip_table;
+static struct orc_entry *g_orc_table;
+
+static pthread_t orc_sort_thread;
+
+static inline unsigned long orc_ip(const int *ip)
+{
+ return (unsigned long)ip + *ip;
+}
+
+static int orc_sort_cmp(const void *_a, const void *_b)
+{
+ struct orc_entry *orc_a, *orc_b;
+ const int *a = g_orc_ip_table + *(int *)_a;
+ const int *b = g_orc_ip_table + *(int *)_b;
+ unsigned long a_val = orc_ip(a);
+ unsigned long b_val = orc_ip(b);
+
+ if (a_val > b_val)
+ return 1;
+ if (a_val < b_val)
+ return -1;
+
+ /*
+ * The "weak" section terminator entries need to always be on the left
+ * to ensure the lookup code skips them in favor of real entries.
+ * These terminator entries exist to handle any gaps created by
+ * whitelisted .o files which didn't get objtool generation.
+ */
+ orc_a = g_orc_table + (a - g_orc_ip_table);
+ orc_b = g_orc_table + (b - g_orc_ip_table);
+ if (orc_a->type == ORC_TYPE_UNDEFINED && orc_b->type == ORC_TYPE_UNDEFINED)
+ return 0;
+ return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
+}
+
+static void *sort_orctable(void *arg)
+{
+ int i;
+ int *idxs = NULL;
+ int *tmp_orc_ip_table = NULL;
+ struct orc_entry *tmp_orc_table = NULL;
+ unsigned int *orc_ip_size = (unsigned int *)arg;
+ unsigned int num_entries = *orc_ip_size / sizeof(int);
+ unsigned int orc_size = num_entries * sizeof(struct orc_entry);
+
+ idxs = (int *)malloc(*orc_ip_size);
+ if (!idxs) {
+ snprintf(g_err, ERRSTR_MAXSZ, "malloc idxs: %s",
+ strerror(errno));
+ pthread_exit(g_err);
+ }
+
+ tmp_orc_ip_table = (int *)malloc(*orc_ip_size);
+ if (!tmp_orc_ip_table) {
+ snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_ip_table: %s",
+ strerror(errno));
+ pthread_exit(g_err);
+ }
+
+ tmp_orc_table = (struct orc_entry *)malloc(orc_size);
+ if (!tmp_orc_table) {
+ snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_table: %s",
+ strerror(errno));
+ pthread_exit(g_err);
+ }
+
+ /* initialize indices array, convert ip_table to absolute address */
+ for (i = 0; i < num_entries; i++) {
+ idxs[i] = i;
+ tmp_orc_ip_table[i] = g_orc_ip_table[i] + i * sizeof(int);
+ }
+ memcpy(tmp_orc_table, g_orc_table, orc_size);
+
+ qsort(idxs, num_entries, sizeof(int), orc_sort_cmp);
+
+ for (i = 0; i < num_entries; i++) {
+ if (idxs[i] == i)
+ continue;
+
+ /* convert back to relative address */
+ g_orc_ip_table[i] = tmp_orc_ip_table[idxs[i]] - i * sizeof(int);
+ g_orc_table[i] = tmp_orc_table[idxs[i]];
+ }
+
+ free(idxs);
+ free(tmp_orc_ip_table);
+ free(tmp_orc_table);
+ pthread_exit(NULL);
+}
+#endif
+
+#ifdef MCOUNT_SORT_ENABLED
+static pthread_t mcount_sort_thread;
+
+struct elf_mcount_loc {
+ Elf_Ehdr *ehdr;
+ Elf_Shdr *init_data_sec;
+ uint64_t start_mcount_loc;
+ uint64_t stop_mcount_loc;
+};
+
+/* Sort the addresses stored between __start_mcount_loc to __stop_mcount_loc in vmlinux */
+static void *sort_mcount_loc(void *arg)
+{
+ struct elf_mcount_loc *emloc = (struct elf_mcount_loc *)arg;
+ uint64_t offset = emloc->start_mcount_loc - shdr_addr(emloc->init_data_sec)
+ + shdr_offset(emloc->init_data_sec);
+ uint64_t count = emloc->stop_mcount_loc - emloc->start_mcount_loc;
+ unsigned char *start_loc = (void *)emloc->ehdr + offset;
+
+ qsort(start_loc, count/long_size, long_size, compare_extable);
+ return NULL;
+}
+
+/* Get the address of __start_mcount_loc and __stop_mcount_loc in System.map */
+static void get_mcount_loc(struct elf_mcount_loc *emloc, Elf_Shdr *symtab_sec,
+ const char *strtab)
+{
+ Elf_Sym *sym, *end_sym;
+ int symentsize = shdr_entsize(symtab_sec);
+ int found = 0;
+
+ sym = (void *)emloc->ehdr + shdr_offset(symtab_sec);
+ end_sym = (void *)sym + shdr_size(symtab_sec);
+
+ while (sym < end_sym) {
+ if (!strcmp(strtab + sym_name(sym), "__start_mcount_loc")) {
+ emloc->start_mcount_loc = sym_value(sym);
+ if (++found == 2)
+ break;
+ } else if (!strcmp(strtab + sym_name(sym), "__stop_mcount_loc")) {
+ emloc->stop_mcount_loc = sym_value(sym);
+ if (++found == 2)
+ break;
+ }
+ sym = (void *)sym + symentsize;
+ }
+
+ if (!emloc->start_mcount_loc) {
+ fprintf(stderr, "get start_mcount_loc error!");
+ return;
+ }
+
+ if (!emloc->stop_mcount_loc) {
+ fprintf(stderr, "get stop_mcount_loc error!");
+ return;
+ }
+}
+#endif
+
+static int do_sort(Elf_Ehdr *ehdr,
+ char const *const fname,
+ table_sort_t custom_sort)
+{
+ int rc = -1;
+ Elf_Shdr *shdr_start;
+ Elf_Shdr *strtab_sec = NULL;
+ Elf_Shdr *symtab_sec = NULL;
+ Elf_Shdr *extab_sec = NULL;
+ Elf_Shdr *string_sec;
+ Elf_Sym *sym;
+ const Elf_Sym *symtab;
+ Elf32_Word *symtab_shndx = NULL;
+ Elf_Sym *sort_needed_sym = NULL;
+ Elf_Shdr *sort_needed_sec;
+ uint32_t *sort_needed_loc;
+ void *sym_start;
+ void *sym_end;
+ const char *secstrings;
+ const char *strtab;
+ char *extab_image;
+ int sort_need_index;
+ int symentsize;
+ int shentsize;
+ int idx;
+ int i;
+ unsigned int shnum;
+ unsigned int shstrndx;
+#ifdef MCOUNT_SORT_ENABLED
+ struct elf_mcount_loc mstruct = {0};
+#endif
+#ifdef UNWINDER_ORC_ENABLED
+ unsigned int orc_ip_size = 0;
+ unsigned int orc_size = 0;
+ unsigned int orc_num_entries = 0;
+#endif
+
+ shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr));
+ shentsize = ehdr_shentsize(ehdr);
+
+ shstrndx = ehdr_shstrndx(ehdr);
+ if (shstrndx == SHN_XINDEX)
+ shstrndx = shdr_link(shdr_start);
+ string_sec = get_index(shdr_start, shentsize, shstrndx);
+ secstrings = (const char *)ehdr + shdr_offset(string_sec);
+
+ shnum = ehdr_shnum(ehdr);
+ if (shnum == SHN_UNDEF)
+ shnum = shdr_size(shdr_start);
+
+ for (i = 0; i < shnum; i++) {
+ Elf_Shdr *shdr = get_index(shdr_start, shentsize, i);
+
+ idx = shdr_name(shdr);
+ if (!strcmp(secstrings + idx, "__ex_table"))
+ extab_sec = shdr;
+ if (!strcmp(secstrings + idx, ".symtab"))
+ symtab_sec = shdr;
+ if (!strcmp(secstrings + idx, ".strtab"))
+ strtab_sec = shdr;
+
+ if (shdr_type(shdr) == SHT_SYMTAB_SHNDX)
+ symtab_shndx = (Elf32_Word *)((const char *)ehdr +
+ shdr_offset(shdr));
+
+#ifdef MCOUNT_SORT_ENABLED
+ /* locate the .init.data section in vmlinux */
+ if (!strcmp(secstrings + idx, ".init.data"))
+ mstruct.init_data_sec = shdr;
+#endif
+
+#ifdef UNWINDER_ORC_ENABLED
+ /* locate the ORC unwind tables */
+ if (!strcmp(secstrings + idx, ".orc_unwind_ip")) {
+ orc_ip_size = shdr_size(shdr);
+ g_orc_ip_table = (int *)((void *)ehdr +
+ shdr_offset(shdr));
+ }
+ if (!strcmp(secstrings + idx, ".orc_unwind")) {
+ orc_size = shdr_size(shdr);
+ g_orc_table = (struct orc_entry *)((void *)ehdr +
+ shdr_offset(shdr));
+ }
+#endif
+ } /* for loop */
+
+#ifdef UNWINDER_ORC_ENABLED
+ if (!g_orc_ip_table || !g_orc_table) {
+ fprintf(stderr,
+ "incomplete ORC unwind tables in file: %s\n", fname);
+ goto out;
+ }
+
+ orc_num_entries = orc_ip_size / sizeof(int);
+ if (orc_ip_size % sizeof(int) != 0 ||
+ orc_size % sizeof(struct orc_entry) != 0 ||
+ orc_num_entries != orc_size / sizeof(struct orc_entry)) {
+ fprintf(stderr,
+ "inconsistent ORC unwind table entries in file: %s\n",
+ fname);
+ goto out;
+ }
+
+ /* create thread to sort ORC unwind tables concurrently */
+ if (pthread_create(&orc_sort_thread, NULL,
+ sort_orctable, &orc_ip_size)) {
+ fprintf(stderr,
+ "pthread_create orc_sort_thread failed '%s': %s\n",
+ strerror(errno), fname);
+ goto out;
+ }
+#endif
+ if (!extab_sec) {
+ fprintf(stderr, "no __ex_table in file: %s\n", fname);
+ goto out;
+ }
+
+ if (!symtab_sec) {
+ fprintf(stderr, "no .symtab in file: %s\n", fname);
+ goto out;
+ }
+
+ if (!strtab_sec) {
+ fprintf(stderr, "no .strtab in file: %s\n", fname);
+ goto out;
+ }
+
+ extab_image = (void *)ehdr + shdr_offset(extab_sec);
+ strtab = (const char *)ehdr + shdr_offset(strtab_sec);
+ symtab = (const Elf_Sym *)((const char *)ehdr + shdr_offset(symtab_sec));
+
+#ifdef MCOUNT_SORT_ENABLED
+ mstruct.ehdr = ehdr;
+ get_mcount_loc(&mstruct, symtab_sec, strtab);
+
+ if (!mstruct.init_data_sec || !mstruct.start_mcount_loc || !mstruct.stop_mcount_loc) {
+ fprintf(stderr,
+ "incomplete mcount's sort in file: %s\n",
+ fname);
+ goto out;
+ }
+
+ /* create thread to sort mcount_loc concurrently */
+ if (pthread_create(&mcount_sort_thread, NULL, &sort_mcount_loc, &mstruct)) {
+ fprintf(stderr,
+ "pthread_create mcount_sort_thread failed '%s': %s\n",
+ strerror(errno), fname);
+ goto out;
+ }
+#endif
+
+ if (custom_sort) {
+ custom_sort(extab_image, shdr_size(extab_sec));
+ } else {
+ int num_entries = shdr_size(extab_sec) / extable_ent_size;
+ qsort(extab_image, num_entries,
+ extable_ent_size, compare_extable);
+ }
+
+ /* find the flag main_extable_sort_needed */
+ sym_start = (void *)ehdr + shdr_offset(symtab_sec);
+ sym_end = sym_start + shdr_size(symtab_sec);
+ symentsize = shdr_entsize(symtab_sec);
+
+ for (sym = sym_start; (void *)sym + symentsize < sym_end;
+ sym = (void *)sym + symentsize) {
+ if (sym_type(sym) != STT_OBJECT)
+ continue;
+ if (!strcmp(strtab + sym_name(sym),
+ "main_extable_sort_needed")) {
+ sort_needed_sym = sym;
+ break;
+ }
+ }
+
+ if (!sort_needed_sym) {
+ fprintf(stderr,
+ "no main_extable_sort_needed symbol in file: %s\n",
+ fname);
+ goto out;
+ }
+
+ sort_need_index = get_secindex(sym_shndx(sym),
+ ((void *)sort_needed_sym - (void *)symtab) / symentsize,
+ symtab_shndx);
+ sort_needed_sec = get_index(shdr_start, shentsize, sort_need_index);
+ sort_needed_loc = (void *)ehdr +
+ shdr_offset(sort_needed_sec) +
+ sym_value(sort_needed_sym) - shdr_addr(sort_needed_sec);
+
+ /* extable has been sorted, clear the flag */
+ w(0, sort_needed_loc);
+ rc = 0;
+
+out:
+#ifdef UNWINDER_ORC_ENABLED
+ if (orc_sort_thread) {
+ void *retval = NULL;
+ /* wait for ORC tables sort done */
+ rc = pthread_join(orc_sort_thread, &retval);
+ if (rc) {
+ fprintf(stderr,
+ "pthread_join failed '%s': %s\n",
+ strerror(errno), fname);
+ } else if (retval) {
+ rc = -1;
+ fprintf(stderr,
+ "failed to sort ORC tables '%s': %s\n",
+ (char *)retval, fname);
+ }
+ }
+#endif
+
+#ifdef MCOUNT_SORT_ENABLED
+ if (mcount_sort_thread) {
+ void *retval = NULL;
+ /* wait for mcount sort done */
+ rc = pthread_join(mcount_sort_thread, &retval);
+ if (rc) {
+ fprintf(stderr,
+ "pthread_join failed '%s': %s\n",
+ strerror(errno), fname);
+ } else if (retval) {
+ rc = -1;
+ fprintf(stderr,
+ "failed to sort mcount '%s': %s\n",
+ (char *)retval, fname);
+ }
+ }
+#endif
+ return rc;
+}
static int compare_relative_table(const void *a, const void *b)
{
@@ -267,41 +857,36 @@ static void sort_relative_table_with_data(char *extab_image, int image_size)
static int do_file(char const *const fname, void *addr)
{
- int rc = -1;
- Elf32_Ehdr *ehdr = addr;
+ Elf_Ehdr *ehdr = addr;
table_sort_t custom_sort = NULL;
- switch (ehdr->e_ident[EI_DATA]) {
+ switch (ehdr->e32.e_ident[EI_DATA]) {
case ELFDATA2LSB:
r = rle;
r2 = r2le;
r8 = r8le;
w = wle;
- w2 = w2le;
- w8 = w8le;
break;
case ELFDATA2MSB:
r = rbe;
r2 = r2be;
r8 = r8be;
w = wbe;
- w2 = w2be;
- w8 = w8be;
break;
default:
fprintf(stderr, "unrecognized ELF data encoding %d: %s\n",
- ehdr->e_ident[EI_DATA], fname);
+ ehdr->e32.e_ident[EI_DATA], fname);
return -1;
}
- if (memcmp(ELFMAG, ehdr->e_ident, SELFMAG) != 0 ||
- (r2(&ehdr->e_type) != ET_EXEC && r2(&ehdr->e_type) != ET_DYN) ||
- ehdr->e_ident[EI_VERSION] != EV_CURRENT) {
+ if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 ||
+ (r2(&ehdr->e32.e_type) != ET_EXEC && r2(&ehdr->e32.e_type) != ET_DYN) ||
+ ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) {
fprintf(stderr, "unrecognized ET_EXEC/ET_DYN file %s\n", fname);
return -1;
}
- switch (r2(&ehdr->e_machine)) {
+ switch (r2(&ehdr->e32.e_machine)) {
case EM_386:
case EM_AARCH64:
case EM_LOONGARCH:
@@ -324,40 +909,85 @@ static int do_file(char const *const fname, void *addr)
break;
default:
fprintf(stderr, "unrecognized e_machine %d %s\n",
- r2(&ehdr->e_machine), fname);
+ r2(&ehdr->e32.e_machine), fname);
return -1;
}
- switch (ehdr->e_ident[EI_CLASS]) {
- case ELFCLASS32:
- if (r2(&ehdr->e_ehsize) != sizeof(Elf32_Ehdr) ||
- r2(&ehdr->e_shentsize) != sizeof(Elf32_Shdr)) {
+ switch (ehdr->e32.e_ident[EI_CLASS]) {
+ case ELFCLASS32: {
+ struct elf_funcs efuncs = {
+ .compare_extable = compare_extable_32,
+ .ehdr_shoff = ehdr32_shoff,
+ .ehdr_shentsize = ehdr32_shentsize,
+ .ehdr_shstrndx = ehdr32_shstrndx,
+ .ehdr_shnum = ehdr32_shnum,
+ .shdr_addr = shdr32_addr,
+ .shdr_offset = shdr32_offset,
+ .shdr_link = shdr32_link,
+ .shdr_size = shdr32_size,
+ .shdr_name = shdr32_name,
+ .shdr_type = shdr32_type,
+ .shdr_entsize = shdr32_entsize,
+ .sym_type = sym32_type,
+ .sym_name = sym32_name,
+ .sym_value = sym32_value,
+ .sym_shndx = sym32_shndx,
+ };
+
+ e = efuncs;
+ long_size = 4;
+ extable_ent_size = 8;
+
+ if (r2(&ehdr->e32.e_ehsize) != sizeof(Elf32_Ehdr) ||
+ r2(&ehdr->e32.e_shentsize) != sizeof(Elf32_Shdr)) {
fprintf(stderr,
"unrecognized ET_EXEC/ET_DYN file: %s\n", fname);
- break;
+ return -1;
+ }
+
}
- rc = do_sort_32(ehdr, fname, custom_sort);
break;
- case ELFCLASS64:
- {
- Elf64_Ehdr *const ghdr = (Elf64_Ehdr *)ehdr;
- if (r2(&ghdr->e_ehsize) != sizeof(Elf64_Ehdr) ||
- r2(&ghdr->e_shentsize) != sizeof(Elf64_Shdr)) {
+ case ELFCLASS64: {
+ struct elf_funcs efuncs = {
+ .compare_extable = compare_extable_64,
+ .ehdr_shoff = ehdr64_shoff,
+ .ehdr_shentsize = ehdr64_shentsize,
+ .ehdr_shstrndx = ehdr64_shstrndx,
+ .ehdr_shnum = ehdr64_shnum,
+ .shdr_addr = shdr64_addr,
+ .shdr_offset = shdr64_offset,
+ .shdr_link = shdr64_link,
+ .shdr_size = shdr64_size,
+ .shdr_name = shdr64_name,
+ .shdr_type = shdr64_type,
+ .shdr_entsize = shdr64_entsize,
+ .sym_type = sym64_type,
+ .sym_name = sym64_name,
+ .sym_value = sym64_value,
+ .sym_shndx = sym64_shndx,
+ };
+
+ e = efuncs;
+ long_size = 8;
+ extable_ent_size = 16;
+
+ if (r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) ||
+ r2(&ehdr->e64.e_shentsize) != sizeof(Elf64_Shdr)) {
fprintf(stderr,
"unrecognized ET_EXEC/ET_DYN file: %s\n",
fname);
- break;
+ return -1;
}
- rc = do_sort_64(ghdr, fname, custom_sort);
+
}
break;
default:
fprintf(stderr, "unrecognized ELF class %d %s\n",
- ehdr->e_ident[EI_CLASS], fname);
- break;
+ ehdr->e32.e_ident[EI_CLASS], fname);
+ return -1;
}
- return rc;
+ return do_sort(ehdr, fname, custom_sort);
}
int main(int argc, char *argv[])
diff --git a/scripts/sorttable.h b/scripts/sorttable.h
deleted file mode 100644
index 7bd0184380d3..000000000000
--- a/scripts/sorttable.h
+++ /dev/null
@@ -1,497 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * sorttable.h
- *
- * Added ORC unwind tables sort support and other updates:
- * Copyright (C) 1999-2019 Alibaba Group Holding Limited. by:
- * Shile Zhang <shile.zhang@linux.alibaba.com>
- *
- * Copyright 2011 - 2012 Cavium, Inc.
- *
- * Some of code was taken out of arch/x86/kernel/unwind_orc.c, written by:
- * Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
- *
- * Some of this code was taken out of recordmcount.h written by:
- *
- * Copyright 2009 John F. Reiser <jreiser@BitWagon.com>. All rights reserved.
- * Copyright 2010 Steven Rostedt <srostedt@redhat.com>, Red Hat Inc.
- */
-
-#undef extable_ent_size
-#undef compare_extable
-#undef get_mcount_loc
-#undef sort_mcount_loc
-#undef elf_mcount_loc
-#undef do_sort
-#undef Elf_Addr
-#undef Elf_Ehdr
-#undef Elf_Shdr
-#undef Elf_Rel
-#undef Elf_Rela
-#undef Elf_Sym
-#undef ELF_R_SYM
-#undef Elf_r_sym
-#undef ELF_R_INFO
-#undef Elf_r_info
-#undef ELF_ST_BIND
-#undef ELF_ST_TYPE
-#undef fn_ELF_R_SYM
-#undef fn_ELF_R_INFO
-#undef uint_t
-#undef _r
-#undef _w
-
-#ifdef SORTTABLE_64
-# define extable_ent_size 16
-# define compare_extable compare_extable_64
-# define get_mcount_loc get_mcount_loc_64
-# define sort_mcount_loc sort_mcount_loc_64
-# define elf_mcount_loc elf_mcount_loc_64
-# define do_sort do_sort_64
-# define Elf_Addr Elf64_Addr
-# define Elf_Ehdr Elf64_Ehdr
-# define Elf_Shdr Elf64_Shdr
-# define Elf_Rel Elf64_Rel
-# define Elf_Rela Elf64_Rela
-# define Elf_Sym Elf64_Sym
-# define ELF_R_SYM ELF64_R_SYM
-# define Elf_r_sym Elf64_r_sym
-# define ELF_R_INFO ELF64_R_INFO
-# define Elf_r_info Elf64_r_info
-# define ELF_ST_BIND ELF64_ST_BIND
-# define ELF_ST_TYPE ELF64_ST_TYPE
-# define fn_ELF_R_SYM fn_ELF64_R_SYM
-# define fn_ELF_R_INFO fn_ELF64_R_INFO
-# define uint_t uint64_t
-# define _r r8
-# define _w w8
-#else
-# define extable_ent_size 8
-# define compare_extable compare_extable_32
-# define get_mcount_loc get_mcount_loc_32
-# define sort_mcount_loc sort_mcount_loc_32
-# define elf_mcount_loc elf_mcount_loc_32
-# define do_sort do_sort_32
-# define Elf_Addr Elf32_Addr
-# define Elf_Ehdr Elf32_Ehdr
-# define Elf_Shdr Elf32_Shdr
-# define Elf_Rel Elf32_Rel
-# define Elf_Rela Elf32_Rela
-# define Elf_Sym Elf32_Sym
-# define ELF_R_SYM ELF32_R_SYM
-# define Elf_r_sym Elf32_r_sym
-# define ELF_R_INFO ELF32_R_INFO
-# define Elf_r_info Elf32_r_info
-# define ELF_ST_BIND ELF32_ST_BIND
-# define ELF_ST_TYPE ELF32_ST_TYPE
-# define fn_ELF_R_SYM fn_ELF32_R_SYM
-# define fn_ELF_R_INFO fn_ELF32_R_INFO
-# define uint_t uint32_t
-# define _r r
-# define _w w
-#endif
-
-#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED)
-/* ORC unwinder only support X86_64 */
-#include <asm/orc_types.h>
-
-#define ERRSTR_MAXSZ 256
-
-char g_err[ERRSTR_MAXSZ];
-int *g_orc_ip_table;
-struct orc_entry *g_orc_table;
-
-pthread_t orc_sort_thread;
-
-static inline unsigned long orc_ip(const int *ip)
-{
- return (unsigned long)ip + *ip;
-}
-
-static int orc_sort_cmp(const void *_a, const void *_b)
-{
- struct orc_entry *orc_a;
- const int *a = g_orc_ip_table + *(int *)_a;
- const int *b = g_orc_ip_table + *(int *)_b;
- unsigned long a_val = orc_ip(a);
- unsigned long b_val = orc_ip(b);
-
- if (a_val > b_val)
- return 1;
- if (a_val < b_val)
- return -1;
-
- /*
- * The "weak" section terminator entries need to always be on the left
- * to ensure the lookup code skips them in favor of real entries.
- * These terminator entries exist to handle any gaps created by
- * whitelisted .o files which didn't get objtool generation.
- */
- orc_a = g_orc_table + (a - g_orc_ip_table);
- return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
-}
-
-static void *sort_orctable(void *arg)
-{
- int i;
- int *idxs = NULL;
- int *tmp_orc_ip_table = NULL;
- struct orc_entry *tmp_orc_table = NULL;
- unsigned int *orc_ip_size = (unsigned int *)arg;
- unsigned int num_entries = *orc_ip_size / sizeof(int);
- unsigned int orc_size = num_entries * sizeof(struct orc_entry);
-
- idxs = (int *)malloc(*orc_ip_size);
- if (!idxs) {
- snprintf(g_err, ERRSTR_MAXSZ, "malloc idxs: %s",
- strerror(errno));
- pthread_exit(g_err);
- }
-
- tmp_orc_ip_table = (int *)malloc(*orc_ip_size);
- if (!tmp_orc_ip_table) {
- snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_ip_table: %s",
- strerror(errno));
- pthread_exit(g_err);
- }
-
- tmp_orc_table = (struct orc_entry *)malloc(orc_size);
- if (!tmp_orc_table) {
- snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_table: %s",
- strerror(errno));
- pthread_exit(g_err);
- }
-
- /* initialize indices array, convert ip_table to absolute address */
- for (i = 0; i < num_entries; i++) {
- idxs[i] = i;
- tmp_orc_ip_table[i] = g_orc_ip_table[i] + i * sizeof(int);
- }
- memcpy(tmp_orc_table, g_orc_table, orc_size);
-
- qsort(idxs, num_entries, sizeof(int), orc_sort_cmp);
-
- for (i = 0; i < num_entries; i++) {
- if (idxs[i] == i)
- continue;
-
- /* convert back to relative address */
- g_orc_ip_table[i] = tmp_orc_ip_table[idxs[i]] - i * sizeof(int);
- g_orc_table[i] = tmp_orc_table[idxs[i]];
- }
-
- free(idxs);
- free(tmp_orc_ip_table);
- free(tmp_orc_table);
- pthread_exit(NULL);
-}
-#endif
-
-static int compare_extable(const void *a, const void *b)
-{
- Elf_Addr av = _r(a);
- Elf_Addr bv = _r(b);
-
- if (av < bv)
- return -1;
- if (av > bv)
- return 1;
- return 0;
-}
-#ifdef MCOUNT_SORT_ENABLED
-pthread_t mcount_sort_thread;
-
-struct elf_mcount_loc {
- Elf_Ehdr *ehdr;
- Elf_Shdr *init_data_sec;
- uint_t start_mcount_loc;
- uint_t stop_mcount_loc;
-};
-
-/* Sort the addresses stored between __start_mcount_loc to __stop_mcount_loc in vmlinux */
-static void *sort_mcount_loc(void *arg)
-{
- struct elf_mcount_loc *emloc = (struct elf_mcount_loc *)arg;
- uint_t offset = emloc->start_mcount_loc - _r(&(emloc->init_data_sec)->sh_addr)
- + _r(&(emloc->init_data_sec)->sh_offset);
- uint_t count = emloc->stop_mcount_loc - emloc->start_mcount_loc;
- unsigned char *start_loc = (void *)emloc->ehdr + offset;
-
- qsort(start_loc, count/sizeof(uint_t), sizeof(uint_t), compare_extable);
- return NULL;
-}
-
-/* Get the address of __start_mcount_loc and __stop_mcount_loc in System.map */
-static void get_mcount_loc(uint_t *_start, uint_t *_stop)
-{
- FILE *file_start, *file_stop;
- char start_buff[20];
- char stop_buff[20];
- int len = 0;
-
- file_start = popen(" grep start_mcount System.map | awk '{print $1}' ", "r");
- if (!file_start) {
- fprintf(stderr, "get start_mcount_loc error!");
- return;
- }
-
- file_stop = popen(" grep stop_mcount System.map | awk '{print $1}' ", "r");
- if (!file_stop) {
- fprintf(stderr, "get stop_mcount_loc error!");
- pclose(file_start);
- return;
- }
-
- while (fgets(start_buff, sizeof(start_buff), file_start) != NULL) {
- len = strlen(start_buff);
- start_buff[len - 1] = '\0';
- }
- *_start = strtoul(start_buff, NULL, 16);
-
- while (fgets(stop_buff, sizeof(stop_buff), file_stop) != NULL) {
- len = strlen(stop_buff);
- stop_buff[len - 1] = '\0';
- }
- *_stop = strtoul(stop_buff, NULL, 16);
-
- pclose(file_start);
- pclose(file_stop);
-}
-#endif
-static int do_sort(Elf_Ehdr *ehdr,
- char const *const fname,
- table_sort_t custom_sort)
-{
- int rc = -1;
- Elf_Shdr *s, *shdr = (Elf_Shdr *)((char *)ehdr + _r(&ehdr->e_shoff));
- Elf_Shdr *strtab_sec = NULL;
- Elf_Shdr *symtab_sec = NULL;
- Elf_Shdr *extab_sec = NULL;
- Elf_Sym *sym;
- const Elf_Sym *symtab;
- Elf32_Word *symtab_shndx = NULL;
- Elf_Sym *sort_needed_sym = NULL;
- Elf_Shdr *sort_needed_sec;
- Elf_Rel *relocs = NULL;
- int relocs_size = 0;
- uint32_t *sort_needed_loc;
- const char *secstrings;
- const char *strtab;
- char *extab_image;
- int extab_index = 0;
- int i;
- int idx;
- unsigned int shnum;
- unsigned int shstrndx;
-#ifdef MCOUNT_SORT_ENABLED
- struct elf_mcount_loc mstruct = {0};
- uint_t _start_mcount_loc = 0;
- uint_t _stop_mcount_loc = 0;
-#endif
-#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED)
- unsigned int orc_ip_size = 0;
- unsigned int orc_size = 0;
- unsigned int orc_num_entries = 0;
-#endif
-
- shstrndx = r2(&ehdr->e_shstrndx);
- if (shstrndx == SHN_XINDEX)
- shstrndx = r(&shdr[0].sh_link);
- secstrings = (const char *)ehdr + _r(&shdr[shstrndx].sh_offset);
-
- shnum = r2(&ehdr->e_shnum);
- if (shnum == SHN_UNDEF)
- shnum = _r(&shdr[0].sh_size);
-
- for (i = 0, s = shdr; s < shdr + shnum; i++, s++) {
- idx = r(&s->sh_name);
- if (!strcmp(secstrings + idx, "__ex_table")) {
- extab_sec = s;
- extab_index = i;
- }
- if (!strcmp(secstrings + idx, ".symtab"))
- symtab_sec = s;
- if (!strcmp(secstrings + idx, ".strtab"))
- strtab_sec = s;
-
- if ((r(&s->sh_type) == SHT_REL ||
- r(&s->sh_type) == SHT_RELA) &&
- r(&s->sh_info) == extab_index) {
- relocs = (void *)ehdr + _r(&s->sh_offset);
- relocs_size = _r(&s->sh_size);
- }
- if (r(&s->sh_type) == SHT_SYMTAB_SHNDX)
- symtab_shndx = (Elf32_Word *)((const char *)ehdr +
- _r(&s->sh_offset));
-
-#ifdef MCOUNT_SORT_ENABLED
- /* locate the .init.data section in vmlinux */
- if (!strcmp(secstrings + idx, ".init.data")) {
- get_mcount_loc(&_start_mcount_loc, &_stop_mcount_loc);
- mstruct.ehdr = ehdr;
- mstruct.init_data_sec = s;
- mstruct.start_mcount_loc = _start_mcount_loc;
- mstruct.stop_mcount_loc = _stop_mcount_loc;
- }
-#endif
-
-#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED)
- /* locate the ORC unwind tables */
- if (!strcmp(secstrings + idx, ".orc_unwind_ip")) {
- orc_ip_size = s->sh_size;
- g_orc_ip_table = (int *)((void *)ehdr +
- s->sh_offset);
- }
- if (!strcmp(secstrings + idx, ".orc_unwind")) {
- orc_size = s->sh_size;
- g_orc_table = (struct orc_entry *)((void *)ehdr +
- s->sh_offset);
- }
-#endif
- } /* for loop */
-
-#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED)
- if (!g_orc_ip_table || !g_orc_table) {
- fprintf(stderr,
- "incomplete ORC unwind tables in file: %s\n", fname);
- goto out;
- }
-
- orc_num_entries = orc_ip_size / sizeof(int);
- if (orc_ip_size % sizeof(int) != 0 ||
- orc_size % sizeof(struct orc_entry) != 0 ||
- orc_num_entries != orc_size / sizeof(struct orc_entry)) {
- fprintf(stderr,
- "inconsistent ORC unwind table entries in file: %s\n",
- fname);
- goto out;
- }
-
- /* create thread to sort ORC unwind tables concurrently */
- if (pthread_create(&orc_sort_thread, NULL,
- sort_orctable, &orc_ip_size)) {
- fprintf(stderr,
- "pthread_create orc_sort_thread failed '%s': %s\n",
- strerror(errno), fname);
- goto out;
- }
-#endif
-
-#ifdef MCOUNT_SORT_ENABLED
- if (!mstruct.init_data_sec || !_start_mcount_loc || !_stop_mcount_loc) {
- fprintf(stderr,
- "incomplete mcount's sort in file: %s\n",
- fname);
- goto out;
- }
-
- /* create thread to sort mcount_loc concurrently */
- if (pthread_create(&mcount_sort_thread, NULL, &sort_mcount_loc, &mstruct)) {
- fprintf(stderr,
- "pthread_create mcount_sort_thread failed '%s': %s\n",
- strerror(errno), fname);
- goto out;
- }
-#endif
- if (!extab_sec) {
- fprintf(stderr, "no __ex_table in file: %s\n", fname);
- goto out;
- }
-
- if (!symtab_sec) {
- fprintf(stderr, "no .symtab in file: %s\n", fname);
- goto out;
- }
-
- if (!strtab_sec) {
- fprintf(stderr, "no .strtab in file: %s\n", fname);
- goto out;
- }
-
- extab_image = (void *)ehdr + _r(&extab_sec->sh_offset);
- strtab = (const char *)ehdr + _r(&strtab_sec->sh_offset);
- symtab = (const Elf_Sym *)((const char *)ehdr +
- _r(&symtab_sec->sh_offset));
-
- if (custom_sort) {
- custom_sort(extab_image, _r(&extab_sec->sh_size));
- } else {
- int num_entries = _r(&extab_sec->sh_size) / extable_ent_size;
- qsort(extab_image, num_entries,
- extable_ent_size, compare_extable);
- }
-
- /* If there were relocations, we no longer need them. */
- if (relocs)
- memset(relocs, 0, relocs_size);
-
- /* find the flag main_extable_sort_needed */
- for (sym = (void *)ehdr + _r(&symtab_sec->sh_offset);
- sym < sym + _r(&symtab_sec->sh_size) / sizeof(Elf_Sym);
- sym++) {
- if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT)
- continue;
- if (!strcmp(strtab + r(&sym->st_name),
- "main_extable_sort_needed")) {
- sort_needed_sym = sym;
- break;
- }
- }
-
- if (!sort_needed_sym) {
- fprintf(stderr,
- "no main_extable_sort_needed symbol in file: %s\n",
- fname);
- goto out;
- }
-
- sort_needed_sec = &shdr[get_secindex(r2(&sym->st_shndx),
- sort_needed_sym - symtab,
- symtab_shndx)];
- sort_needed_loc = (void *)ehdr +
- _r(&sort_needed_sec->sh_offset) +
- _r(&sort_needed_sym->st_value) -
- _r(&sort_needed_sec->sh_addr);
-
- /* extable has been sorted, clear the flag */
- w(0, sort_needed_loc);
- rc = 0;
-
-out:
-#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED)
- if (orc_sort_thread) {
- void *retval = NULL;
- /* wait for ORC tables sort done */
- rc = pthread_join(orc_sort_thread, &retval);
- if (rc) {
- fprintf(stderr,
- "pthread_join failed '%s': %s\n",
- strerror(errno), fname);
- } else if (retval) {
- rc = -1;
- fprintf(stderr,
- "failed to sort ORC tables '%s': %s\n",
- (char *)retval, fname);
- }
- }
-#endif
-
-#ifdef MCOUNT_SORT_ENABLED
- if (mcount_sort_thread) {
- void *retval = NULL;
- /* wait for mcount sort done */
- rc = pthread_join(mcount_sort_thread, &retval);
- if (rc) {
- fprintf(stderr,
- "pthread_join failed '%s': %s\n",
- strerror(errno), fname);
- } else if (retval) {
- rc = -1;
- fprintf(stderr,
- "failed to sort mcount '%s': %s\n",
- (char *)retval, fname);
- }
- }
-#endif
- return rc;
-}
diff --git a/scripts/spdxcheck.py b/scripts/spdxcheck.py
index 8b8fb115fc81..8d608f61bf37 100755
--- a/scripts/spdxcheck.py
+++ b/scripts/spdxcheck.py
@@ -214,9 +214,15 @@ class id_parser(object):
# Remove trailing xml comment closure
if line.strip().endswith('-->'):
expr = expr.rstrip('-->').strip()
+ # Remove trailing Jinja2 comment closure
+ if line.strip().endswith('#}'):
+ expr = expr.rstrip('#}').strip()
# Special case for SH magic boot code files
if line.startswith('LIST \"'):
expr = expr.rstrip('\"').strip()
+ # Remove j2 comment closure
+ if line.startswith('{#'):
+ expr = expr.rstrip('#}').strip()
self.parse(expr)
self.spdx_valid += 1
#
diff --git a/scripts/spelling.txt b/scripts/spelling.txt
index 05bd9ca1fbfa..a290db720b0f 100644
--- a/scripts/spelling.txt
+++ b/scripts/spelling.txt
@@ -222,6 +222,7 @@ autonymous||autonomous
auxillary||auxiliary
auxilliary||auxiliary
avaiable||available
+avaialable||available
avaible||available
availabe||available
availabled||available
@@ -267,6 +268,7 @@ broadcase||broadcast
broadcat||broadcast
bufer||buffer
bufferred||buffered
+bufferur||buffer
bufufer||buffer
cacluated||calculated
caculate||calculate
@@ -405,6 +407,7 @@ configutation||configuration
congiuration||configuration
conider||consider
conjuction||conjunction
+connction||connection
connecetd||connected
connectinos||connections
connetor||connector
@@ -413,6 +416,7 @@ connnections||connections
consistancy||consistency
consistant||consistent
consits||consists
+constructred||constructed
containes||contains
containts||contains
contaisn||contains
@@ -450,6 +454,7 @@ creationg||creating
cryptocraphic||cryptographic
cummulative||cumulative
cunter||counter
+curent||current
curently||currently
cylic||cyclic
dafault||default
@@ -461,6 +466,7 @@ decendant||descendant
decendants||descendants
decompres||decompress
decsribed||described
+decrese||decrease
decription||description
detault||default
dectected||detected
@@ -485,6 +491,7 @@ delare||declare
delares||declares
delaring||declaring
delemiter||delimiter
+deley||delay
delibrately||deliberately
delievered||delivered
demodualtor||demodulator
@@ -551,6 +558,7 @@ disgest||digest
disired||desired
dispalying||displaying
dissable||disable
+dissapeared||disappeared
diplay||display
directon||direction
direcly||directly
@@ -606,6 +614,7 @@ eigth||eight
elementry||elementary
eletronic||electronic
embeded||embedded
+emtpy||empty
enabledi||enabled
enbale||enable
enble||enable
@@ -669,6 +678,7 @@ exmaple||example
expecially||especially
experies||expires
explicite||explicit
+explicity||explicitly
explicitely||explicitly
explict||explicit
explictely||explicitly
@@ -723,10 +733,12 @@ followign||following
followings||following
follwing||following
fonud||found
+forcebly||forcibly
forseeable||foreseeable
forse||force
fortan||fortran
forwardig||forwarding
+forwared||forwarded
frambuffer||framebuffer
framming||framing
framwork||framework
@@ -767,6 +779,7 @@ grahpical||graphical
granularty||granularity
grapic||graphic
grranted||granted
+grups||groups
guage||gauge
guarenteed||guaranteed
guarentee||guarantee
@@ -780,6 +793,7 @@ hardare||hardware
harware||hardware
hardward||hardware
havind||having
+heigth||height
heirarchically||hierarchically
heirarchy||hierarchy
heirachy||hierarchy
@@ -788,9 +802,11 @@ hearbeat||heartbeat
heterogenous||heterogeneous
hexdecimal||hexadecimal
hybernate||hibernate
+hiearchy||hierarchy
hierachy||hierarchy
hierarchie||hierarchy
homogenous||homogeneous
+horizental||horizontal
howver||however
hsould||should
hypervior||hypervisor
@@ -842,6 +858,7 @@ independed||independent
indiate||indicate
indicat||indicate
inexpect||inexpected
+infalte||inflate
inferface||interface
infinit||infinite
infomation||information
@@ -861,6 +878,7 @@ initators||initiators
initialiazation||initialization
initializationg||initialization
initializiation||initialization
+initializtion||initialization
initialze||initialize
initialzed||initialized
initialzing||initializing
@@ -877,6 +895,7 @@ instanciate||instantiate
instanciated||instantiated
instuments||instruments
insufficent||insufficient
+intead||instead
inteface||interface
integreated||integrated
integrety||integrity
@@ -1081,6 +1100,7 @@ notications||notifications
notifcations||notifications
notifed||notified
notity||notify
+notfify||notify
nubmer||number
numebr||number
numer||number
@@ -1122,6 +1142,7 @@ orientatied||orientated
orientied||oriented
orignal||original
originial||original
+orphanded||orphaned
otherise||otherwise
ouput||output
oustanding||outstanding
@@ -1184,9 +1205,11 @@ peroid||period
persistance||persistence
persistant||persistent
phoneticly||phonetically
+pipline||pipeline
plaform||platform
plalform||platform
platfoem||platform
+platfomr||platform
platfrom||platform
plattform||platform
pleaes||please
@@ -1211,6 +1234,7 @@ preceeding||preceding
preceed||precede
precendence||precedence
precission||precision
+predicition||prediction
preemptable||preemptible
prefered||preferred
prefferably||preferably
@@ -1289,6 +1313,7 @@ querrying||querying
queus||queues
randomally||randomly
raoming||roaming
+readyness||readiness
reasearcher||researcher
reasearchers||researchers
reasearch||research
@@ -1305,8 +1330,10 @@ recieves||receives
recieving||receiving
recogniced||recognised
recognizeable||recognizable
+recompte||recompute
recommanded||recommended
recyle||recycle
+redect||reject
redircet||redirect
redirectrion||redirection
redundacy||redundancy
@@ -1314,6 +1341,7 @@ reename||rename
refcounf||refcount
refence||reference
refered||referred
+referencce||reference
referenace||reference
refererence||reference
refering||referring
@@ -1348,11 +1376,13 @@ replys||replies
reponse||response
representaion||representation
repsonse||response
+reqested||requested
reqeust||request
reqister||register
requed||requeued
requestied||requested
requiere||require
+requieres||requires
requirment||requirement
requred||required
requried||required
@@ -1440,6 +1470,7 @@ sequencial||sequential
serivce||service
serveral||several
servive||service
+sesion||session
setts||sets
settting||setting
shapshot||snapshot
@@ -1602,11 +1633,13 @@ trys||tries
thses||these
tiggers||triggers
tiggered||triggered
+tiggerring||triggering
tipically||typically
timeing||timing
timming||timing
timout||timeout
tmis||this
+tolarance||tolerance
toogle||toggle
torerable||tolerable
torlence||tolerance
@@ -1633,6 +1666,7 @@ trasfer||transfer
trasmission||transmission
trasmitter||transmitter
treshold||threshold
+trigged||triggered
triggerd||triggered
trigerred||triggered
trigerring||triggering
@@ -1648,6 +1682,7 @@ uknown||unknown
usccess||success
uncommited||uncommitted
uncompatible||incompatible
+uncomressed||uncompressed
unconditionaly||unconditionally
undeflow||underflow
undelying||underlying
@@ -1715,6 +1750,7 @@ utitity||utility
utitlty||utility
vaid||valid
vaild||valid
+validationg||validating
valide||valid
variantions||variations
varible||variable
@@ -1724,6 +1760,7 @@ verbse||verbose
veify||verify
verfication||verification
veriosn||version
+versoin||version
verisons||versions
verison||version
veritical||vertical
diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl
index ebbdb3c42e9f..580b4e246aec 100644
--- a/scripts/syscall.tbl
+++ b/scripts/syscall.tbl
@@ -407,3 +407,4 @@
464 common getxattrat sys_getxattrat
465 common listxattrat sys_listxattrat
466 common removexattrat sys_removexattrat
+467 common open_tree_attr sys_open_tree_attr
diff --git a/scripts/tags.sh b/scripts/tags.sh
index b21236377998..45eaf35f5bff 100755
--- a/scripts/tags.sh
+++ b/scripts/tags.sh
@@ -188,6 +188,7 @@ regex_c=(
'/^PCI_OP_WRITE([[:space:]]*\(\w*\).*[1-4])/pci_bus_write_config_\1/'
'/\<DEFINE_\(RT_MUTEX\|MUTEX\|SEMAPHORE\|SPINLOCK\)([[:space:]]*\([[:alnum:]_]*\)/\2/v/'
'/\<DEFINE_\(RAW_SPINLOCK\|RWLOCK\|SEQLOCK\)([[:space:]]*\([[:alnum:]_]*\)/\2/v/'
+ '/\<DEFINE_TIMER(\([^,)]*\),/\1/'
'/\<DECLARE_\(RWSEM\|COMPLETION\)([[:space:]]*\([[:alnum:]_]\+\)/\2/v/'
'/\<DECLARE_BITMAP([[:space:]]*\([[:alnum:]_]\+\)/\1/v/'
'/\(^\|\s\)\(\|L\|H\)LIST_HEAD([[:space:]]*\([[:alnum:]_]*\)/\3/v/'
@@ -212,6 +213,13 @@ regex_c=(
'/^SEQCOUNT_LOCKTYPE(\([^,]*\),[[:space:]]*\([^,]*\),[^)]*)/seqcount_\2_init/'
'/^\<DECLARE_IDTENTRY[[:alnum:]_]*([^,)]*,[[:space:]]*\([[:alnum:]_]\+\)/\1/'
'/^\<DEFINE_IDTENTRY[[:alnum:]_]*([[:space:]]*\([[:alnum:]_]\+\)/\1/'
+ '/^\<DEFINE_FREE(\([[:alnum:]_]\+\)/cleanup_\1/'
+ '/^\<DEFINE_CLASS(\([[:alnum:]_]\+\)/class_\1/'
+ '/^\<EXTEND_CLASS(\([[:alnum:]_]\+\),[[:space:]]*\([[:alnum:]_]\+\)/class_\1\2/'
+ '/^\<DEFINE_GUARD(\([[:alnum:]_]\+\)/class_\1/'
+ '/^\<DEFINE_GUARD_COND(\([[:alnum:]_]\+\),[[:space:]]*\([[:alnum:]_]\+\)/class_\1\2/'
+ '/^\<DEFINE_LOCK_GUARD_[[:digit:]](\([[:alnum:]_]\+\)/class_\1/'
+ '/^\<DEFINE_LOCK_GUARD_[[:digit:]]_COND(\([[:alnum:]_]\+\),[[:space:]]*\([[:alnum:]_]\+\)/class_\1\2/'
)
regex_kconfig=(
'/^[[:blank:]]*\(menu\|\)config[[:blank:]]\+\([[:alnum:]_]\+\)/\2/'
@@ -260,7 +268,8 @@ exuberant()
# identifiers to ignore by ctags
local ign=(
ACPI_EXPORT_SYMBOL
- DEFINE_{TRACE,MUTEX}
+ DECLARE_BITMAP
+ DEFINE_{TRACE,MUTEX,TIMER}
EXPORT_SYMBOL EXPORT_SYMBOL_GPL
EXPORT_TRACEPOINT_SYMBOL EXPORT_TRACEPOINT_SYMBOL_GPL
____cacheline_aligned ____cacheline_aligned_in_smp