diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs
index a4e5749b3acb9..62fa2884e0f6f 100644
--- a/compiler/rustc_codegen_llvm/src/consts.rs
+++ b/compiler/rustc_codegen_llvm/src/consts.rs
@@ -5,6 +5,7 @@ use rustc_abi::{
 };
 use rustc_codegen_ssa::common;
 use rustc_codegen_ssa::traits::*;
+use rustc_hir::LangItem;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefId;
 use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
@@ -12,9 +13,9 @@ use rustc_middle::mir::interpret::{
     Allocation, ConstAllocation, ErrorHandled, InitChunk, Pointer, Scalar as InterpScalar,
     read_target_uint,
 };
-use rustc_middle::mir::mono::MonoItem;
-use rustc_middle::ty::Instance;
+use rustc_middle::mir::mono::{Linkage, MonoItem};
 use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf};
+use rustc_middle::ty::{self, Instance};
 use rustc_middle::{bug, span_bug};
 use tracing::{debug, instrument, trace};
 
@@ -171,8 +172,27 @@ fn check_and_apply_linkage<'ll, 'tcx>(
     if let Some(linkage) = attrs.import_linkage {
         debug!("get_static: sym={} linkage={:?}", sym, linkage);
 
-        // Declare a symbol `foo` with the desired linkage.
-        let g1 = cx.declare_global(sym, cx.type_i8());
+        // Declare a symbol `foo`. If `foo` is an extern_weak symbol, we declare
+        // an extern_weak function, otherwise a global with the desired linkage.
+        let g1 = if matches!(attrs.import_linkage, Some(Linkage::ExternalWeak)) {
+            // An `extern_weak` function is represented as an `Option<unsafe extern ...>`,
+            // we extract the function signature and declare it as an extern_weak function
+            // instead of an extern_weak i8.
+            let instance = Instance::mono(cx.tcx, def_id);
+            if let ty::Adt(struct_def, args) = instance.ty(cx.tcx, cx.typing_env()).kind()
+                && cx.tcx.is_lang_item(struct_def.did(), LangItem::Option)
+                && let ty::FnPtr(sig, header) = args.type_at(0).kind()
+            {
+                let fn_sig = sig.with(*header);
+
+                let fn_abi = cx.fn_abi_of_fn_ptr(fn_sig, ty::List::empty());
+                cx.declare_fn(sym, &fn_abi, None)
+            } else {
+                cx.declare_global(sym, cx.type_i8())
+            }
+        } else {
+            cx.declare_global(sym, cx.type_i8())
+        };
         llvm::set_linkage(g1, base::linkage_to_llvm(linkage));
 
         // Declare an internal global `extern_with_linkage_foo` which
diff --git a/tests/codegen/sanitizer/cfi/external_weak_symbols.rs b/tests/codegen/sanitizer/cfi/external_weak_symbols.rs
new file mode 100644
index 0000000000000..00e9b5029af8c
--- /dev/null
+++ b/tests/codegen/sanitizer/cfi/external_weak_symbols.rs
@@ -0,0 +1,24 @@
+// Verifies that type metadata identifiers for for weakly-linked symbols are
+// emitted correctly.
+//
+//@ needs-sanitizer-cfi
+//@ compile-flags: -Clinker-plugin-lto -Copt-level=0 -Zsanitizer=cfi -Ctarget-feature=-crt-static
+#![crate_type = "bin"]
+#![feature(linkage)]
+
+unsafe extern "C" {
+    #[linkage = "extern_weak"]
+    static FOO: Option<unsafe extern "C" fn(f64) -> ()>;
+}
+// CHECK: @_rust_extern_with_linkage_FOO = internal global ptr @FOO
+
+fn main() {
+    unsafe {
+        if let Some(method) = FOO {
+            method(4.2);
+            // CHECK: call i1 @llvm.type.test(ptr {{%method|%0}}, metadata !"_ZTSFvdE")
+        }
+    }
+}
+
+// CHECK: declare !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} extern_weak void @FOO(double) unnamed_addr #{{[0-9]+}}