aboutsummaryrefslogtreecommitdiffstats
path: root/arch/loongarch/kernel/module-sections.c
blob: d296a70b758fd60a5963d1298bbd1189e645c93c (plain) (blame)
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
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
 */

#include <linux/elf.h>
#include <linux/kernel.h>
#include <linux/module.h>

Elf_Addr module_emit_got_entry(struct module *mod, Elf_Addr val)
{
	struct mod_section *got_sec = &mod->arch.got;
	int i = got_sec->num_entries;
	struct got_entry *got = get_got_entry(val, got_sec);

	if (got)
		return (Elf_Addr)got;

	/* There is no GOT entry for val yet, create a new one. */
	got = (struct got_entry *)got_sec->shdr->sh_addr;
	got[i] = emit_got_entry(val);

	got_sec->num_entries++;
	if (got_sec->num_entries > got_sec->max_entries) {
		/*
		 * This may happen when the module contains a GOT_HI20 without
		 * a paired GOT_LO12. Such a module is broken, reject it.
		 */
		pr_err("%s: module contains bad GOT relocation\n", mod->name);
		return 0;
	}

	return (Elf_Addr)&got[i];
}

Elf_Addr module_emit_plt_entry(struct module *mod, Elf_Addr val)
{
	int nr;
	struct mod_section *plt_sec = &mod->arch.plt;
	struct mod_section *plt_idx_sec = &mod->arch.plt_idx;
	struct plt_entry *plt = get_plt_entry(val, plt_sec, plt_idx_sec);
	struct plt_idx_entry *plt_idx;

	if (plt)
		return (Elf_Addr)plt;

	nr = plt_sec->num_entries;

	/* There is no duplicate entry, create a new one */
	plt = (struct plt_entry *)plt_sec->shdr->sh_addr;
	plt[nr] = emit_plt_entry(val);
	plt_idx = (struct plt_idx_entry *)plt_idx_sec->shdr->sh_addr;
	plt_idx[nr] = emit_plt_idx_entry(val);

	plt_sec->num_entries++;
	plt_idx_sec->num_entries++;
	BUG_ON(plt_sec->num_entries > plt_sec->max_entries);

	return (Elf_Addr)&plt[nr];
}

static int is_rela_equal(const Elf_Rela *x, const Elf_Rela *y)
{
	return x->r_info == y->r_info && x->r_addend == y->r_addend;
}

static bool duplicate_rela(const Elf_Rela *rela, int idx)
{
	int i;

	for (i = 0; i < idx; i++) {
		if (is_rela_equal(&rela[i], &rela[idx]))
			return true;
	}

	return false;
}

static void count_max_entries(Elf_Rela *relas, int num,
			      unsigned int *plts, unsigned int *gots)
{
	unsigned int i, type;

	for (i = 0; i < num; i++) {
		type = ELF_R_TYPE(relas[i].r_info);
		switch (type) {
		case R_LARCH_SOP_PUSH_PLT_PCREL:
		case R_LARCH_B26:
			if (!duplicate_rela(relas, i))
				(*plts)++;
			break;
		case R_LARCH_GOT_PC_HI20:
			if (!duplicate_rela(relas, i))
				(*gots)++;
			break;
		default:
			break; /* Do nothing. */
		}
	}
}

int module_frob_arch_sections(Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
			      char *secstrings, struct module *mod)
{
	unsigned int i, num_plts = 0, num_gots = 0;

	/*
	 * Find the empty .plt sections.
	 */
	for (i = 0; i < ehdr->e_shnum; i++) {
		if (!strcmp(secstrings + sechdrs[i].sh_name, ".got"))
			mod->arch.got.shdr = sechdrs + i;
		else if (!strcmp(secstrings + sechdrs[i].sh_name, ".plt"))
			mod->arch.plt.shdr = sechdrs + i;
		else if (!strcmp(secstrings + sechdrs[i].sh_name, ".plt.idx"))
			mod->arch.plt_idx.shdr = sechdrs + i;
	}

	if (!mod->arch.got.shdr) {
		pr_err("%s: module GOT section(s) missing\n", mod->name);
		return -ENOEXEC;
	}
	if (!mod->arch.plt.shdr) {
		pr_err("%s: module PLT section(s) missing\n", mod->name);
		return -ENOEXEC;
	}
	if (!mod->arch.plt_idx.shdr) {
		pr_err("%s: module PLT.IDX section(s) missing\n", mod->name);
		return -ENOEXEC;
	}

	/* Calculate the maxinum number of entries */
	for (i = 0; i < ehdr->e_shnum; i++) {
		int num_rela = sechdrs[i].sh_size / sizeof(Elf_Rela);
		Elf_Rela *relas = (void *)ehdr + sechdrs[i].sh_offset;
		Elf_Shdr *dst_sec = sechdrs + sechdrs[i].sh_info;

		if (sechdrs[i].sh_type != SHT_RELA)
			continue;

		/* ignore relocations that operate on non-exec sections */
		if (!(dst_sec->sh_flags & SHF_EXECINSTR))
			continue;

		count_max_entries(relas, num_rela, &num_plts, &num_gots);
	}

	mod->arch.got.shdr->sh_type = SHT_NOBITS;
	mod->arch.got.shdr->sh_flags = SHF_ALLOC;
	mod->arch.got.shdr->sh_addralign = L1_CACHE_BYTES;
	mod->arch.got.shdr->sh_size = (num_gots + 1) * sizeof(struct got_entry);
	mod->arch.got.num_entries = 0;
	mod->arch.got.max_entries = num_gots;

	mod->arch.plt.shdr->sh_type = SHT_NOBITS;
	mod->arch.plt.shdr->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
	mod->arch.plt.shdr->sh_addralign = L1_CACHE_BYTES;
	mod->arch.plt.shdr->sh_size = (num_plts + 1) * sizeof(struct plt_entry);
	mod->arch.plt.num_entries = 0;
	mod->arch.plt.max_entries = num_plts;

	mod->arch.plt_idx.shdr->sh_type = SHT_NOBITS;
	mod->arch.plt_idx.shdr->sh_flags = SHF_ALLOC;
	mod->arch.plt_idx.shdr->sh_addralign = L1_CACHE_BYTES;
	mod->arch.plt_idx.shdr->sh_size = (num_plts + 1) * sizeof(struct plt_idx_entry);
	mod->arch.plt_idx.num_entries = 0;
	mod->arch.plt_idx.max_entries = num_plts;

	return 0;
}