summaryrefslogtreecommitdiff
path: root/kexec/arch/ppc64/kexec-elf-rel-ppc64.c
blob: 9b191d0025a417131da83b24dbcca72a2c00d3cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <stdio.h>
#include <elf.h>
#include <string.h>
#include "../../kexec.h"
#include "../../kexec-elf.h"
#include "kexec-ppc64.h"

int machine_verify_elf_rel(struct mem_ehdr *ehdr)
{
	if (ehdr->ei_class != ELFCLASS64) {
		return 0;
	}
	if (ehdr->e_machine != EM_PPC64) {
		return 0;
	}

	return 1;
}

static struct mem_shdr *toc_section(const struct mem_ehdr *ehdr)
{
	struct mem_shdr *shdr, *shdr_end;
	unsigned char *strtab;

	strtab = (unsigned char *)ehdr->e_shdr[ehdr->e_shstrndx].sh_data;
	shdr_end = &ehdr->e_shdr[ehdr->e_shnum];
	for (shdr = ehdr->e_shdr; shdr != shdr_end; shdr++) {
		if (shdr->sh_size &&
			strcmp((char *)&strtab[shdr->sh_name], ".toc") == 0) {
			return shdr;
		}
	}

	return NULL;
}

/* r2 is the TOC pointer: it actually points 0x8000 into the TOC (this
   gives the value maximum span in an instruction which uses a signed
   offset) */
unsigned long my_r2(const struct mem_ehdr *ehdr)
{
	struct mem_shdr *shdr;

	shdr = toc_section(ehdr);
	if (!shdr) {
		die("TOC reloc without a toc section?");
	}

	return shdr->sh_addr + 0x8000;
}

static void do_relative_toc(unsigned long value, uint16_t *location,
	unsigned long mask, int complain_signed)
{
	if (complain_signed && (value + 0x8000 > 0xffff)) {
		die("TOC16 relocation overflows (%lu)\n", value);
	}

	if ((~mask & 0xffff) & value) {
		die("bad TOC16 relocation (%lu)\n", value);
	}

	*location = (*location & ~mask) | (value & mask);
}

void machine_apply_elf_rel(struct mem_ehdr *ehdr, unsigned long r_type,
	void *location, unsigned long address, unsigned long value)
{
	switch(r_type) {
	case R_PPC64_ADDR32:
		/* Simply set it */
		*(uint32_t *)location = value;
		break;

	case R_PPC64_ADDR64:
	case R_PPC64_REL64:
		/* Simply set it */
		*(uint64_t *)location = value;
		break;

	case R_PPC64_REL32:
		*(uint32_t *)location = value - (uint32_t)(uint64_t)location;
		break;

	case R_PPC64_TOC:
		*(uint64_t *)location = my_r2(ehdr);
		break;

	case R_PPC64_TOC16:
		do_relative_toc(value - my_r2(ehdr), location, 0xffff, 1);
		break;

	case R_PPC64_TOC16_DS:
		do_relative_toc(value - my_r2(ehdr), location, 0xfffc, 1);
		break;

	case R_PPC64_TOC16_LO:
		do_relative_toc(value - my_r2(ehdr), location, 0xffff, 0);
		break;

	case R_PPC64_TOC16_LO_DS:
		do_relative_toc(value - my_r2(ehdr), location, 0xfffc, 0);
		break;

	case R_PPC64_TOC16_HI:
		do_relative_toc((value - my_r2(ehdr)) >> 16, location,
			0xffff, 0);
		break;

	case R_PPC64_TOC16_HA:
		do_relative_toc((value - my_r2(ehdr) + 0x8000) >> 16, location,
			0xffff, 0);
		break;

	case R_PPC64_REL24:
		/* Convert value to relative */
		value -= address;
		if (value + 0x2000000 > 0x3ffffff || (value & 3) != 0) {
			die("REL24 %li out of range!\n", (long int)value);
		}

		/* Only replace bits 2 through 26 */
		*(uint32_t *)location = (*(uint32_t *)location & ~0x03fffffc) |
					(value & 0x03fffffc);
		break;

	case R_PPC64_ADDR16_LO:
		*(uint16_t *)location = value & 0xffff;
		break;

	case R_PPC64_ADDR16_HI:
		*(uint16_t *)location = (value >> 16) & 0xffff;
		break;

	case R_PPC64_ADDR16_HA:
		*(uint16_t *)location = (((value + 0x8000) >> 16) & 0xffff);
		break;

	case R_PPC64_ADDR16_HIGHER:
		*(uint16_t *)location = (((uint64_t)value >> 32) & 0xffff);
		break;

	case R_PPC64_ADDR16_HIGHEST:
		*(uint16_t *)location = (((uint64_t)value >> 48) & 0xffff);
		break;

		/* R_PPC64_REL16_HA and R_PPC64_REL16_LO are handled to support
		 * ABIv2 r2 assignment based on r12 for PIC executable.
		 * Here address is know so replace
		 *	0:	addis 2,12,.TOC.-0b@ha
		 *		addi 2,2,.TOC.-0b@l
		 * by
		 *		lis 2,.TOC.@ha
		 *		addi 2,2,.TOC.@l
		 */
	case R_PPC64_REL16_HA:
		/* check that we are dealing with the addis 2,12 instruction */
		if (((*(uint32_t*)location) & 0xffff0000) != 0x3c4c0000)
			die("Unexpected instruction for  R_PPC64_REL16_HA");
		value += my_r2(ehdr);
		/* replacing by lis 2 */
		*(uint32_t *)location = 0x3c400000 + ((value >> 16) & 0xffff);
		break;

	case R_PPC64_REL16_LO:
		/* check that we are dealing with the addi 2,2 instruction */
		if (((*(uint32_t*)location) & 0xffff0000) != 0x38420000)
			die("Unexpected instruction for R_PPC64_REL16_LO");

		value += my_r2(ehdr) - 4;
		*(uint16_t *)location = value & 0xffff;
		break;

	default:
		die("Unknown rela relocation: %lu\n", r_type);
		break;
	}
}