[PATCH] powerpc/64s: Add load address to plt branch targets before moved to linked location for non-relocatable kernels

Jordan Niethe jniethe5 at gmail.com
Wed Apr 21 12:17:21 AEST 2021


Large branches will go through the plt which includes a stub that loads
a target address from the .branch_lt section. On a relocatable kernel the
targets in .branch_lt have relocations so they will be fixed up for
where the kernel is running by relocate().

For a non-relocatable kernel obviously there are no relocations.
However, until the kernel is moved down to its linked address it is
expected to be able to run where ever it is loaded. For pseries machines
prom_init() is called before running at the linked address.

Certain configs result in a large kernel such as STRICT_KERNEL_RWX
(because of the larger data shift):

config DATA_SHIFT
	int "Data shift" if DATA_SHIFT_BOOL
	default 24 if STRICT_KERNEL_RWX && PPC64

These large kernels lead to prom_init()'s final call to __start()
generating a plt branch:

bl      c000000002000018 <00000078.plt_branch.__start>

This results in the kernel jumping to the linked address of __start,
0xc000000000000000, when really it needs to jump to the
0xc000000000000000 + the runtime address because the kernel is still
running at the load address.

The first 256 bytes are already copied to address 0 so the kernel will
run until

b	__start_initialization_multiplatform

because there is nothing yet at __start_initialization_multiplatform
this will inevitably crash. At this point the exception handlers are
still OF's.

On phyp this will look like:

OF stdout device is: /vdevice/vty at 30000000
Preparing to boot Linux version 5.12.0-rc3-63029-gada7d7e600c0 (gcc (GCC) 8.4.1 20200928 (Red Hat 8.4.1-1), GNU ld version 2.30-93.el8) #1 SMP Wed Apr 7 07:24:20 EDT 2021
Detected machine type: 0000000000000101
command line: BOOT_IMAGE=/vmlinuz-5.12.0-rc3-63029-gada7d7e600c0
Max number of cores passed to firmware: 256 (NR_CPUS = 2048)
Calling ibm,client-architecture-support... done
memory layout at init:
  memory_limit : 0000000000000000 (16 MB aligned)
  alloc_bottom : 000000000edc0000
  alloc_top    : 0000000020000000
  alloc_top_hi : 0000000020000000
  rmo_top      : 0000000020000000
  ram_top      : 0000000020000000
instantiating rtas at 0x000000001ec30000... done
prom_hold_cpus: skipped
copying OF device tree...
Building dt strings...
Building dt structure...
Device tree strings 0x000000000edd0000 -> 0x000000000edd1809
Device tree struct  0x000000000ede0000 -> 0x000000000edf0000
Quiescing Open Firmware ...
Booting Linux via __start() @ 0x000000000a710000 ...
DEFAULT CATCH!, exception-handler=fffffffffffffff6
at   %SRR0: 0000000000000f20   %SRR1: 8000000000081000
Open Firmware exception handler entered from non-OF code
Client's Fix Pt Regs:
 00 000000000c713134 0000000008a9fc00 000000000caf9c00 000000000edc0000
 04 000000000a710000 0000000000000000 0000000000000000 0000000000000000
 08 0000000000000000 0000000000000000 000000000a7200fc 0000000000003003
 0c c000000000000000 0000000000000000 0000000000000000 000000000b5a9820
 10 000000000b5a9b38 000000000b5a9988 000000000b5a9f38 000000000b660c10
 14 000000000b5a9f60 00000000013d0000 000000001ec30000 000000001ec30000
 18 000000000b5a9840 000000000a710000 0000000000000028 000000000edc0008
 1c 000000000edc0000 000000000cb60000 0000000000000000 000000000edc0000
Special Regs:
    %IV: 00000700     %CR: 44000202    %XER: 00000000  %DSISR: 00000000
  %SRR0: 0000000000000f20   %SRR1: 8000000000081000
    %LR: 000000000c71326c    %CTR: c000000000000000
   %DAR: 0000000000000000
Virtual PID = 0
DEFAULT CATCH!, throw-code=fffffffffffffff6
Call History
------------
throw  - c3f05c
$call-method  - c4f0b4
(poplocals)  - c40a00
key-fillq  - c4f4cc
?xoff  - c4f5b4
(poplocals)  - c40a00
(stdout-write)  - c4fa64
(emit)  - c4fb3c
space  - c4dfc8
quit  - c5336c
quit  - c53100
My Fix Pt Regs:
 00 800000000000b002 0000000000000000 00000000deadbeef 0000000000c4f0b0
 04 0000000008bfff80 00000000deadbeef 0000000000000004 0000000000c09010
 08 0000000000000005 0000000000000000 0000000000000000 0000000000000000
 0c 80000000072a40a8 0000000000000000 0000000000000000 0000000008d2cf30
 10 0000000000e7d968 0000000000e7d968 0000000000c4f0a8 0000000000c4f0b4
 14 fffffffffffffff6 0000000008bfff80 c8ff21fbd0ff41fb f8ffe1fbb1fd21f8
 18 0000000000c19000 0000000000c3e000 0000000000c1af80 0000000000c1cfc0
 1c 0000000000c26000 0000000000c460f0 0000000000c17fa8 0000000000c16fe0
Special Regs:
    %IV: 00000900     %CR: 84800208    %XER: 00040010  %DSISR: 00000000
  %SRR0: 0000000000c3eec8   %SRR1: 800000000000b002
    %LR: 0000000000c3f05c    %CTR: 0000000000c4f0b0
   %DAR: 0000000000000000
...

On qemu it will just appear to be stuck after
Booting Linux via __start() @ 0x0000000000400000 ...:

SLOF **********************************************************************
QEMU Starting
 Build Date = Apr  9 2021 14:13:31
 FW Version = git-33a7322de13e9dca
 Press "s" to enter Open Firmware.

Populating /vdevice methods
Populating /vdevice/vty at 71000000
Populating /vdevice/nvram at 71000001
Populating /vdevice/l-lan at 71000002
Populating /vdevice/v-scsi at 71000003
       SCSI: Looking for devices
          8200000000000000 CD-ROM   : "QEMU     QEMU CD-ROM      2.5+"
Populating /pci at 800000020000000
Scanning USB
Using default console: /vdevice/vty at 71000000
Detected RAM kernel at 400000 (25baa08 bytes)

  Welcome to Open Firmware

  Copyright (c) 2004, 2017 IBM Corporation All rights reserved.
  This program and the accompanying materials are made available
  under the terms of the BSD License available at
  http://www.opensource.org/licenses/bsd-license.php

Booting from memory...
OF stdout device is: /vdevice/vty at 71000000
Preparing to boot Linux version 5.12.0-rc3-00128-g87a8d2180282 (powerpc64le-linux-gnu-gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #85 SMP Sun Apr 18 19:30:55 AEST 2021
Detected machine type: 0000000000000101
command line: nokaslr
Max number of cores passed to firmware: 2048 (NR_CPUS = 2048)
Calling ibm,client-architecture-support... done
memory layout at init:
  memory_limit : 0000000000000000 (16 MB aligned)
  alloc_bottom : 0000000003df0000
  alloc_top    : 0000000030000000
  alloc_top_hi : 0000000080000000
  rmo_top      : 0000000030000000
  ram_top      : 0000000080000000
instantiating rtas at 0x000000002fff0000... done
prom_hold_cpus: skipped
copying OF device tree...
Building dt strings...
Building dt structure...
Device tree strings 0x0000000003e00000 -> 0x0000000003e00ab2
Device tree struct  0x0000000003e10000 -> 0x0000000003e20000
Quiescing Open Firmware ...
Booting Linux via __start() @ 0x0000000000400000 ...

To fix this do some "relocation" of the plt target addresses on
non-relocatable before running at the linked address. Before calling
prom_init() add the runtime address to all the targets in .branch_lt
with relocate_plt(). Have relocate_plt() save the offset added in
p_branch_lt_off.  After prom_init() calls __start() remove the offset
saved in p_branch_lt_off to return the targets to their original
addresses.

Signed-off-by: Jordan Niethe <jniethe5 at gmail.com>
---
 arch/powerpc/include/asm/sections.h |  2 +
 arch/powerpc/kernel/head_64.S       | 66 +++++++++++++++++++++++++++++
 arch/powerpc/kernel/vmlinux.lds.S   |  2 +
 3 files changed, 70 insertions(+)

diff --git a/arch/powerpc/include/asm/sections.h b/arch/powerpc/include/asm/sections.h
index 324d7b298ec3..f087f5cd5a50 100644
--- a/arch/powerpc/include/asm/sections.h
+++ b/arch/powerpc/include/asm/sections.h
@@ -30,6 +30,8 @@ extern char __end_interrupts[];
 
 extern char __prom_init_toc_start[];
 extern char __prom_init_toc_end[];
+extern char __branch_lt_start[];
+extern char __branch_lt_end[];
 
 #ifdef CONFIG_PPC_POWERNV
 extern char start_real_trampolines[];
diff --git a/arch/powerpc/kernel/head_64.S b/arch/powerpc/kernel/head_64.S
index ece7f97bafff..28a6c2abd3ab 100644
--- a/arch/powerpc/kernel/head_64.S
+++ b/arch/powerpc/kernel/head_64.S
@@ -560,8 +560,11 @@ __boot_from_prom:
 	/* Relocate code for where we are now */
 	mr	r3,r26
 	bl	relocate
+#else
+	bl	relocate_plt
 #endif
 
+
 	/* Restore parameters */
 	mr	r3,r31
 	mr	r4,r30
@@ -600,6 +603,8 @@ __after_prom_start:
 	/* IVPR needs to be set after relocation. */
 	bl	init_core_book3e
 #endif
+#else
+	bl	unrelocate_plt
 #endif
 
 /*
@@ -901,6 +906,67 @@ _GLOBAL(relative_toc)
 .balign 8
 p_toc:	.8byte	__toc_start + 0x8000 - 0b
 
+/*
+ * A large non relocatable kernel may generate branches that go though the plt,
+ * before the kernel is copied down to its link location, the target address in
+ * the .branch_lt section need to be offset with the run time address. The
+ * offset then needs to be removed before the kernel is running at the correct
+ * address.  When relocate_plt is called the current runtime address is added
+ * to all of the target address in .branch_lt and that address is stored in
+ * p_branch_lt_off.  When unrelocate_plt is called if there is an offset saved
+ * in p_branch_lt_off it is subtracted from the addresses in .branch_lt to
+ * return them to their original targets.
+ */
+#ifndef CONFIG_RELOCATABLE
+#define RELOCATE_MODE 0
+#define UNRELOCATE_MODE 1
+unrelocate_plt:
+	li	r16,UNRELOCATE_MODE
+	b	+8
+relocate_plt:
+	li	r16,RELOCATE_MODE
+	mflr	r0
+	bcl	20,31,$+4
+0:	mflr	r11
+
+	ld	r12,(p_branch_lt_start - 0b)(r11)
+	add	r12,r12,r11
+	ld	r14,(p_branch_lt_end - 0b)(r11)
+	add	r14,r14,r11
+	ld	r15,(p_branch_lt_off - 0b)(r11)
+
+	/* Adding runtime address or subtracting p_branch_lt_off? */
+	cmpdi	r16,UNRELOCATE_MODE
+	bne	5f
+	cmpdi	r15,0
+	beq	4f
+	mr	r10,r15
+	neg	r10,r10
+	b	2f
+5:	mr	r10,r26
+
+	/* Iterate over all targets in .branch_lt */
+2:	cmpd	r12,r14
+	bge	6f
+	ld	r13,0(r12)
+	add	r13,r13, r10
+	std	r13,0(r12)
+	addi	r12,r12, 8
+	b	2b
+
+6:	cmpdi	r16,RELOCATE_MODE
+	bne	4f
+	std	r26,(p_branch_lt_off - 0b)(r11)
+
+4:	mtlr	r0
+	blr
+
+.balign 8
+p_branch_lt_start:	.8byte	__branch_lt_start - 0b
+p_branch_lt_end:	.8byte	__branch_lt_end - 0b
+p_branch_lt_off:	.8byte	0
+#endif
+
 /*
  * This is where the main kernel code starts.
  */
diff --git a/arch/powerpc/kernel/vmlinux.lds.S b/arch/powerpc/kernel/vmlinux.lds.S
index 72fa3c00229a..99085558ad3a 100644
--- a/arch/powerpc/kernel/vmlinux.lds.S
+++ b/arch/powerpc/kernel/vmlinux.lds.S
@@ -317,7 +317,9 @@ SECTIONS
 #endif
 		*(.data.rel*)
 		*(.toc1)
+		__branch_lt_start = .;
 		*(.branch_lt)
+		__branch_lt_end = .;
 	}
 
 	.opd : AT(ADDR(.opd) - LOAD_OFFSET) {
-- 
2.25.1



More information about the Linuxppc-dev mailing list