diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index 2ce5f73ae80..b657ddc5666 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -3256,6 +3256,7 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       char tls_type;
       bfd_vma relocation, off, ie_off, desc_off;
       int i, j;
+      bool resolve_pcrel_undef_weak = false;
 
       /* When an unrecognized relocation is encountered, which usually
 	 occurs when using a newer assembler but an older linker, an error
@@ -4107,23 +4108,74 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 
 	  break;
 
+	case R_LARCH_PCALA64_HI12:
+	  pc -= 4;
+	  /* Fall through.  */
+	case R_LARCH_PCALA64_LO20:
+	  pc -= 8;
+	  /* Fall through.  */
 	case R_LARCH_PCREL20_S2:
-	  unresolved_reloc = false;
-	  if (h && h->plt.offset != MINUS_ONE)
-	    relocation = sec_addr (plt) + h->plt.offset;
-	  else
-	    relocation += rel->r_addend;
-	  relocation -= pc;
-	  break;
-
 	case R_LARCH_PCALA_HI20:
 	  unresolved_reloc = false;
+
+	  /* If sym is hidden undefined weak, (sym + addend) should be
+	     resolved to runtime address (0 + addend).  */
+	  resolve_pcrel_undef_weak =
+	    (is_undefweak
+	     && h
+	     && ELF_ST_VISIBILITY (h->other) != STV_DEFAULT);
+
+	  if (resolve_pcrel_undef_weak)
+	    pc = 0;
+
 	  if (h && h->plt.offset != MINUS_ONE)
 	    relocation = sec_addr (plt) + h->plt.offset;
 	  else
 	    relocation += rel->r_addend;
 
-	  RELOCATE_CALC_PC32_HI20 (relocation, pc);
+	  switch (r_type)
+	    {
+	    case R_LARCH_PCREL20_S2:
+	      relocation -= pc;
+	      if (resolve_pcrel_undef_weak)
+		{
+		  bfd_signed_vma addr = (bfd_signed_vma) relocation;
+		  if (addr >= 2048 || addr < -2048)
+		    {
+		      const char *msg =
+			_("cannot resolve R_LARCH_PCREL20_S2 against "
+			  "undefined weak symbol with addend out of "
+			  "[-2048, 2048)");
+		      fatal =
+			loongarch_reloc_is_fatal (info, input_bfd,
+						  input_section, rel,
+						  howto,
+						  bfd_reloc_notsupported,
+						  is_undefweak, name, msg);
+		      break;
+		    }
+
+		  uint32_t insn = bfd_get (32, input_bfd,
+					   contents + rel->r_offset);
+		  insn = LARCH_GET_RD (insn) | LARCH_OP_ADDI_W;
+		  insn |= (relocation & 0xfff) << 10;
+		  bfd_put_32 (input_bfd, insn, contents + rel->r_offset);
+		  r = bfd_reloc_continue;
+		}
+	      break;
+	    case R_LARCH_PCALA_HI20:
+	      RELOCATE_CALC_PC32_HI20 (relocation, pc);
+	      if (resolve_pcrel_undef_weak)
+		{
+		  uint32_t insn = bfd_get (32, input_bfd,
+					   contents + rel->r_offset);
+		  insn = LARCH_GET_RD (insn) | LARCH_OP_LU12I_W;
+		  bfd_put_32 (input_bfd, insn, contents + rel->r_offset);
+		}
+	      break;
+	    default:
+	      RELOCATE_CALC_PC64_HI32 (relocation, pc);
+	    }
 	  break;
 
 	case R_LARCH_TLS_LE_HI20_R:
@@ -4160,19 +4212,6 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	    }
 	  break;
 
-	case R_LARCH_PCALA64_HI12:
-	  pc -= 4;
-	  /* Fall through.  */
-	case R_LARCH_PCALA64_LO20:
-	  if (h && h->plt.offset != MINUS_ONE)
-	    relocation = sec_addr (plt) + h->plt.offset;
-	  else
-	    relocation += rel->r_addend;
-
-	  RELOCATE_CALC_PC64_HI32 (relocation, pc - 8);
-
-	  break;
-
 	case R_LARCH_GOT_PC_HI20:
 	case R_LARCH_GOT_HI20:
 	  /* Calc got offset.  */
diff --git a/include/opcode/loongarch.h b/include/opcode/loongarch.h
index 493fe9bcce3..35799696c55 100644
--- a/include/opcode/loongarch.h
+++ b/include/opcode/loongarch.h
@@ -36,6 +36,7 @@ extern "C"
 
   #define LARCH_MK_ADDI_D 0xffc00000
   #define LARCH_OP_ADDI_D 0x02c00000
+  #define LARCH_OP_ADDI_W 0x02800000
   #define LARCH_MK_PCADDI 0xfe000000
   #define LARCH_OP_PCADDI 0x18000000
   #define LARCH_MK_B 0xfc000000
@@ -44,6 +45,7 @@ extern "C"
   #define LARCH_OP_BL 0x54000000
   #define LARCH_MK_ORI 0xffc00000
   #define LARCH_OP_ORI 0x03800000
+  #define LARCH_OP_OR 0x00150000
   #define LARCH_MK_LU12I_W 0xfe000000
   #define LARCH_OP_LU12I_W 0x14000000
   #define LARCH_MK_LD_D 0xffc00000
diff --git a/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp b/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
index d7c2b311f2f..c09815703d5 100644
--- a/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
+++ b/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
@@ -184,6 +184,7 @@ if [istarget "loongarch64-*-*"] {
     run_dump_test "bad_pcala_hi20_weak_pie"
     run_dump_test "bad_pcrel20_s2_global"
     run_dump_test "bad_pcrel20_s2_weak"
+    run_dump_test "weak-undef-hidden-shared"
   }
 
   if [check_pie_support] {
@@ -194,6 +195,7 @@ if [istarget "loongarch64-*-*"] {
     run_dump_test "relr-got-start"
     run_dump_test "relr-text-pie"
     run_dump_test "abssym_pie"
+    run_dump_test "weak-undef-hidden-pie"
   }
 
   run_dump_test "max_imm_b16"
diff --git a/ld/testsuite/ld-loongarch-elf/weak-undef-hidden-pie.d b/ld/testsuite/ld-loongarch-elf/weak-undef-hidden-pie.d
new file mode 100644
index 00000000000..913bc96f70b
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/weak-undef-hidden-pie.d
@@ -0,0 +1,14 @@
+#source: weak-undef-hidden.s
+#ld: -pie -Ttext=0x1111222233334ff0
+#objdump: -d
+
+#...
+1111222233334ff0:[ 	]+159999a4[ 	]+lu12i.w[ 	]+\$a0,[ 	]+-209715
+1111222233334ff4:[ 	]+02f77405[ 	]+li.d[ 	]+\$a1,[ 	]+-547
+1111222233334ff8:[ 	]+17777765[ 	]+lu32i.d[ 	]+\$a1,[ 	]+-279621
+1111222233334ffc:[ 	]+032aa8a5[ 	]+lu52i.d[ 	]+\$a1,[ 	]+\$a1,[ 	]+-1366
+1111222233335000:[ 	]+00109484[ 	]+add.d[ 	]+\$a0,[ 	]+\$a0,[ 	]+\$a1
+1111222233335004:[ 	]+029ffc06[ 	]+li.w[ 	]+\$a2,[ 	]+2047
+1111222233335008:[ 	]+02a00007[ 	]+li.w[ 	]+\$a3,[ 	]+-2048
+111122223333500c:[ 	]+4c000020[ 	]+ret
+#pass
diff --git a/ld/testsuite/ld-loongarch-elf/weak-undef-hidden-shared.d b/ld/testsuite/ld-loongarch-elf/weak-undef-hidden-shared.d
new file mode 100644
index 00000000000..fdb215e6dd4
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/weak-undef-hidden-shared.d
@@ -0,0 +1,14 @@
+#source: weak-undef-hidden.s
+#ld: -shared -Ttext=0x1111222233334ff0
+#objdump: -d
+
+#...
+1111222233334ff0:[ 	]+159999a4[ 	]+lu12i.w[ 	]+\$a0,[ 	]+-209715
+1111222233334ff4:[ 	]+02f77405[ 	]+li.d[ 	]+\$a1,[ 	]+-547
+1111222233334ff8:[ 	]+17777765[ 	]+lu32i.d[ 	]+\$a1,[ 	]+-279621
+1111222233334ffc:[ 	]+032aa8a5[ 	]+lu52i.d[ 	]+\$a1,[ 	]+\$a1,[ 	]+-1366
+1111222233335000:[ 	]+00109484[ 	]+add.d[ 	]+\$a0,[ 	]+\$a0,[ 	]+\$a1
+1111222233335004:[ 	]+029ffc06[ 	]+li.w[ 	]+\$a2,[ 	]+2047
+1111222233335008:[ 	]+02a00007[ 	]+li.w[ 	]+\$a3,[ 	]+-2048
+111122223333500c:[ 	]+4c000020[ 	]+ret
+#pass
diff --git a/ld/testsuite/ld-loongarch-elf/weak-undef-hidden.s b/ld/testsuite/ld-loongarch-elf/weak-undef-hidden.s
new file mode 100644
index 00000000000..ac67007da26
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/weak-undef-hidden.s
@@ -0,0 +1,9 @@
+.weak undef
+.hidden undef
+
+.globl _start
+_start:
+  la.pcrel $a0, $a1, undef + 0xaaabbbbbcccccddd
+  pcaddi   $a2, undef + 0x7ff
+  pcaddi   $a3, undef - 0x800
+  ret
