|
@@ -271,6 +271,79 @@ static void error(char *x)
|
|
|
asm("hlt");
|
|
|
}
|
|
|
|
|
|
+#if CONFIG_X86_NEED_RELOCS
|
|
|
+static void handle_relocations(void *output, unsigned long output_len)
|
|
|
+{
|
|
|
+ int *reloc;
|
|
|
+ unsigned long delta, map, ptr;
|
|
|
+ unsigned long min_addr = (unsigned long)output;
|
|
|
+ unsigned long max_addr = min_addr + output_len;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Calculate the delta between where vmlinux was linked to load
|
|
|
+ * and where it was actually loaded.
|
|
|
+ */
|
|
|
+ delta = min_addr - LOAD_PHYSICAL_ADDR;
|
|
|
+ if (!delta) {
|
|
|
+ debug_putstr("No relocation needed... ");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ debug_putstr("Performing relocations... ");
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The kernel contains a table of relocation addresses. Those
|
|
|
+ * addresses have the final load address of the kernel in virtual
|
|
|
+ * memory. We are currently working in the self map. So we need to
|
|
|
+ * create an adjustment for kernel memory addresses to the self map.
|
|
|
+ * This will involve subtracting out the base address of the kernel.
|
|
|
+ */
|
|
|
+ map = delta - __START_KERNEL_map;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Process relocations: 32 bit relocations first then 64 bit after.
|
|
|
+ * Two sets of binary relocations are added to the end of the kernel
|
|
|
+ * before compression. Each relocation table entry is the kernel
|
|
|
+ * address of the location which needs to be updated stored as a
|
|
|
+ * 32-bit value which is sign extended to 64 bits.
|
|
|
+ *
|
|
|
+ * Format is:
|
|
|
+ *
|
|
|
+ * kernel bits...
|
|
|
+ * 0 - zero terminator for 64 bit relocations
|
|
|
+ * 64 bit relocation repeated
|
|
|
+ * 0 - zero terminator for 32 bit relocations
|
|
|
+ * 32 bit relocation repeated
|
|
|
+ *
|
|
|
+ * So we work backwards from the end of the decompressed image.
|
|
|
+ */
|
|
|
+ for (reloc = output + output_len - sizeof(*reloc); *reloc; reloc--) {
|
|
|
+ int extended = *reloc;
|
|
|
+ extended += map;
|
|
|
+
|
|
|
+ ptr = (unsigned long)extended;
|
|
|
+ if (ptr < min_addr || ptr > max_addr)
|
|
|
+ error("32-bit relocation outside of kernel!\n");
|
|
|
+
|
|
|
+ *(uint32_t *)ptr += delta;
|
|
|
+ }
|
|
|
+#ifdef CONFIG_X86_64
|
|
|
+ for (reloc--; *reloc; reloc--) {
|
|
|
+ long extended = *reloc;
|
|
|
+ extended += map;
|
|
|
+
|
|
|
+ ptr = (unsigned long)extended;
|
|
|
+ if (ptr < min_addr || ptr > max_addr)
|
|
|
+ error("64-bit relocation outside of kernel!\n");
|
|
|
+
|
|
|
+ *(uint64_t *)ptr += delta;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+}
|
|
|
+#else
|
|
|
+static inline void handle_relocations(void *output, unsigned long output_len)
|
|
|
+{ }
|
|
|
+#endif
|
|
|
+
|
|
|
static void parse_elf(void *output)
|
|
|
{
|
|
|
#ifdef CONFIG_X86_64
|
|
@@ -325,7 +398,8 @@ static void parse_elf(void *output)
|
|
|
asmlinkage void decompress_kernel(void *rmode, memptr heap,
|
|
|
unsigned char *input_data,
|
|
|
unsigned long input_len,
|
|
|
- unsigned char *output)
|
|
|
+ unsigned char *output,
|
|
|
+ unsigned long output_len)
|
|
|
{
|
|
|
real_mode = rmode;
|
|
|
|
|
@@ -365,6 +439,7 @@ asmlinkage void decompress_kernel(void *rmode, memptr heap,
|
|
|
debug_putstr("\nDecompressing Linux... ");
|
|
|
decompress(input_data, input_len, NULL, NULL, output, NULL, error);
|
|
|
parse_elf(output);
|
|
|
+ handle_relocations(output, output_len);
|
|
|
debug_putstr("done.\nBooting the kernel.\n");
|
|
|
return;
|
|
|
}
|