diff --git a/Documentation/dontdiff b/Documentation/dontdiff
index 17686f59039c..72c089eea111 100644
--- a/Documentation/dontdiff
+++ b/Documentation/dontdiff
@@ -79,6 +79,7 @@ SCCS
 System.map*
 TAGS
 aconf
+addaddrs
 af_names.h
 aic7*reg.h*
 aic7*reg_print.c*
diff --git a/init/Kconfig b/init/Kconfig
index c45935cd2f1f..160ec1370594 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1570,6 +1570,16 @@ config POSIX_TIMERS
 
 	  If unsure say y.
 
+config KALLMODSYMS
+	default y
+	bool "Enable support for /proc/kallmodsyms" if EXPERT
+	depends on KALLSYMS
+	select VMLINUX_MAP
+	help
+	  This option enables the /proc/kallmodsyms file, which unambiguously
+	  maps built-in kernel symbols and their associated object files and
+	  modules to addresses.
+
 config PRINTK
 	default y
 	bool "Enable support for printk" if EXPERT
diff --git a/scripts/.gitignore b/scripts/.gitignore
index b7aec8eb1bd4..d042f0e3d13f 100644
--- a/scripts/.gitignore
+++ b/scripts/.gitignore
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
+/addaddrs
 /asn1_compiler
 /bin2c
 /generate_rust_target
diff --git a/scripts/Makefile b/scripts/Makefile
index 1575af84d557..356cafcd313d 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -5,6 +5,7 @@
 
 hostprogs-always-$(CONFIG_BUILD_BIN2C)			+= bin2c
 hostprogs-always-$(CONFIG_KALLSYMS)			+= kallsyms
+hostprogs-always-$(CONFIG_KALLMODSYMS)			+= addaddrs
 hostprogs-always-$(BUILD_C_RECORDMCOUNT)		+= recordmcount
 hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT)		+= sorttable
 hostprogs-always-$(CONFIG_ASN1)				+= asn1_compiler
diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o
index 9b4ca83f0695..94c2ec366b91 100644
--- a/scripts/Makefile.vmlinux_o
+++ b/scripts/Makefile.vmlinux_o
@@ -27,6 +27,10 @@ ifdef CONFIG_LTO_CLANG
 initcalls-lds := .tmp_initcalls.lds
 endif
 
+ifneq ($(CONFIG_VMLINUX_MAP)$(CONFIG_KALLMODSYMS),)
+KBUILD_MAPFLAGS = -Map=$@.map
+endif
+
 # objtool for vmlinux.o
 # ---------------------------------------------------------------------------
 #
@@ -47,7 +51,7 @@ objtool-args = $(vmlinux-objtool-args-y) --link
 quiet_cmd_ld_vmlinux.o = LD      $@
       cmd_ld_vmlinux.o = \
 	$(LD) ${KBUILD_LDFLAGS} -r -o $@ \
-	$(addprefix -T , $(initcalls-lds)) \
+	$(KBUILD_MAPFLAGS) $(addprefix -T , $(initcalls-lds)) \
 	--whole-archive vmlinux.a --no-whole-archive \
 	--start-group $(KBUILD_VMLINUX_LIBS) --end-group \
 	$(cmd_objtool)
diff --git a/scripts/addaddrs.c b/scripts/addaddrs.c
new file mode 100644
index 000000000000..d15cbbf8d262
--- /dev/null
+++ b/scripts/addaddrs.c
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* Used only by link-vmlinux.sh */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+int main (void)
+{
+	int i = 0;
+	while (!feof(stdin)) {
+		uint64_t a, b;
+		int ret;
+		char *rest = NULL;
+
+		i++;
+		if ((ret = scanf("%" SCNx64 " %" SCNx64 " %m[^\n]\n", &a, &b, &rest)) < 3) {
+			fprintf(stderr,
+				"Syntax error: invalid line %i found in rangefile generation: at least three fields expected, %i converted\n", i, ret);
+			exit(1);
+		}
+
+		printf("0x%018" PRIx64 " %s\n", a+b, rest);
+		free(rest);
+	}
+	exit(0);
+}
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index a40d372b1289..3b3ea6214062 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -60,10 +60,7 @@ vmlinux_link()
 	# skip output file argument
 	shift
 
-	# kallmodsyms needs a linker mapfile that contains original object
-	# file names, so cannot use this optimization.
-	if { is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT; } && \
-	   ! is_enabled CONFIG_KALLMODSYMS; then
+	if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT; then
 		# Use vmlinux.o instead of performing the slow LTO link again.
 		objs=vmlinux.o
 		libs=
@@ -153,12 +150,31 @@ kallsyms()
 	#       - but sometimes there is a line break after the first field
 	#   - start reading at "Linker script and memory map"
 	#   - stop reading at ".brk"
+	# if there is a vmlinux.o.map and LTO_CLANG or KERNEL_IBT are
+	# turned on, we have used a vmlinux -r'ed .o for linking: use this
+	# as our primary information source, but acquire section addresses
+	# from the (later) linker map we were passed in.  This makes things
+	# a bit more complex, since we have to recognize and eliminate
+	# sections elided by the linker, and add together numbers larger
+	# than awk can portably handle.
 	if is_enabled CONFIG_KALLMODSYMS; then
-		${AWK} '
-		    /\.o$/ && start==1 { print $(NF-2), $(NF-1), $NF }
-		    /^Linker script and memory map/ { start = 1 }
-		    /^\.brk/ { exit(0) }
-		' ${3} | sort > .tmp_vmlinux.ranges
+		if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT; then
+			${AWK} 'BEGIN { addresses = 1 }
+			    /^Linker script and memory map/ { start = 1 }
+			    !start { next }
+			    { got_section = 0 }
+			    /^ \./ { section = $1; got_section = 1; if (NF == 1) { getline }}
+			    addresses && got_section && !(section in addrs) { addrs[section] = $2 }
+			    !addresses && got_section && section in addrs { print $(NF-2), addrs[section], $(NF-1), $NF }
+			    /^\.brk/ || /^\.bss\.\.brk/ { addresses = 0; start = 0; nextfile }
+			' ${3} vmlinux.o.map | scripts/addaddrs | sort > .tmp_vmlinux.ranges
+		else
+			${AWK} '
+			    start && /\.o$/ { print $(NF-2), $(NF-1), $NF }
+			    /^Linker script and memory map/ { start = 1 }
+			    /^\.brk/ { exit(0) }
+			' ${3} | sort > .tmp_vmlinux.ranges
+		fi
 	fi
 
 	# get kallsyms options
