xref: /linux/arch/x86/mm/extable.c (revision 5e4e38446a62a4f50d77b0dd11d4b379dee08988)
1 #include <linux/module.h>
2 #include <linux/spinlock.h>
3 #include <linux/sort.h>
4 #include <asm/uaccess.h>
5 
6 typedef bool (*ex_handler_t)(const struct exception_table_entry *,
7 			    struct pt_regs *, int);
8 
9 static inline unsigned long
10 ex_insn_addr(const struct exception_table_entry *x)
11 {
12 	return (unsigned long)&x->insn + x->insn;
13 }
14 static inline unsigned long
15 ex_fixup_addr(const struct exception_table_entry *x)
16 {
17 	return (unsigned long)&x->fixup + x->fixup;
18 }
19 static inline ex_handler_t
20 ex_fixup_handler(const struct exception_table_entry *x)
21 {
22 	return (ex_handler_t)((unsigned long)&x->handler + x->handler);
23 }
24 
25 bool ex_handler_default(const struct exception_table_entry *fixup,
26 		       struct pt_regs *regs, int trapnr)
27 {
28 	regs->ip = ex_fixup_addr(fixup);
29 	return true;
30 }
31 EXPORT_SYMBOL(ex_handler_default);
32 
33 bool ex_handler_fault(const struct exception_table_entry *fixup,
34 		     struct pt_regs *regs, int trapnr)
35 {
36 	regs->ip = ex_fixup_addr(fixup);
37 	regs->ax = trapnr;
38 	return true;
39 }
40 EXPORT_SYMBOL_GPL(ex_handler_fault);
41 
42 bool ex_handler_ext(const struct exception_table_entry *fixup,
43 		   struct pt_regs *regs, int trapnr)
44 {
45 	/* Special hack for uaccess_err */
46 	current_thread_info()->uaccess_err = 1;
47 	regs->ip = ex_fixup_addr(fixup);
48 	return true;
49 }
50 EXPORT_SYMBOL(ex_handler_ext);
51 
52 bool ex_has_fault_handler(unsigned long ip)
53 {
54 	const struct exception_table_entry *e;
55 	ex_handler_t handler;
56 
57 	e = search_exception_tables(ip);
58 	if (!e)
59 		return false;
60 	handler = ex_fixup_handler(e);
61 
62 	return handler == ex_handler_fault;
63 }
64 
65 int fixup_exception(struct pt_regs *regs, int trapnr)
66 {
67 	const struct exception_table_entry *e;
68 	ex_handler_t handler;
69 
70 #ifdef CONFIG_PNPBIOS
71 	if (unlikely(SEGMENT_IS_PNP_CODE(regs->cs))) {
72 		extern u32 pnp_bios_fault_eip, pnp_bios_fault_esp;
73 		extern u32 pnp_bios_is_utter_crap;
74 		pnp_bios_is_utter_crap = 1;
75 		printk(KERN_CRIT "PNPBIOS fault.. attempting recovery.\n");
76 		__asm__ volatile(
77 			"movl %0, %%esp\n\t"
78 			"jmp *%1\n\t"
79 			: : "g" (pnp_bios_fault_esp), "g" (pnp_bios_fault_eip));
80 		panic("do_trap: can't hit this");
81 	}
82 #endif
83 
84 	e = search_exception_tables(regs->ip);
85 	if (!e)
86 		return 0;
87 
88 	handler = ex_fixup_handler(e);
89 	return handler(e, regs, trapnr);
90 }
91 
92 /* Restricted version used during very early boot */
93 int __init early_fixup_exception(unsigned long *ip)
94 {
95 	const struct exception_table_entry *e;
96 	unsigned long new_ip;
97 	ex_handler_t handler;
98 
99 	e = search_exception_tables(*ip);
100 	if (!e)
101 		return 0;
102 
103 	new_ip  = ex_fixup_addr(e);
104 	handler = ex_fixup_handler(e);
105 
106 	/* special handling not supported during early boot */
107 	if (handler != ex_handler_default)
108 		return 0;
109 
110 	*ip = new_ip;
111 	return 1;
112 }
113 
114 /*
115  * Search one exception table for an entry corresponding to the
116  * given instruction address, and return the address of the entry,
117  * or NULL if none is found.
118  * We use a binary search, and thus we assume that the table is
119  * already sorted.
120  */
121 const struct exception_table_entry *
122 search_extable(const struct exception_table_entry *first,
123 	       const struct exception_table_entry *last,
124 	       unsigned long value)
125 {
126 	while (first <= last) {
127 		const struct exception_table_entry *mid;
128 		unsigned long addr;
129 
130 		mid = ((last - first) >> 1) + first;
131 		addr = ex_insn_addr(mid);
132 		if (addr < value)
133 			first = mid + 1;
134 		else if (addr > value)
135 			last = mid - 1;
136 		else
137 			return mid;
138         }
139         return NULL;
140 }
141 
142 /*
143  * The exception table needs to be sorted so that the binary
144  * search that we use to find entries in it works properly.
145  * This is used both for the kernel exception table and for
146  * the exception tables of modules that get loaded.
147  *
148  */
149 static int cmp_ex(const void *a, const void *b)
150 {
151 	const struct exception_table_entry *x = a, *y = b;
152 
153 	/*
154 	 * This value will always end up fittin in an int, because on
155 	 * both i386 and x86-64 the kernel symbol-reachable address
156 	 * space is < 2 GiB.
157 	 *
158 	 * This compare is only valid after normalization.
159 	 */
160 	return x->insn - y->insn;
161 }
162 
163 void sort_extable(struct exception_table_entry *start,
164 		  struct exception_table_entry *finish)
165 {
166 	struct exception_table_entry *p;
167 	int i;
168 
169 	/* Convert all entries to being relative to the start of the section */
170 	i = 0;
171 	for (p = start; p < finish; p++) {
172 		p->insn += i;
173 		i += 4;
174 		p->fixup += i;
175 		i += 4;
176 		p->handler += i;
177 		i += 4;
178 	}
179 
180 	sort(start, finish - start, sizeof(struct exception_table_entry),
181 	     cmp_ex, NULL);
182 
183 	/* Denormalize all entries */
184 	i = 0;
185 	for (p = start; p < finish; p++) {
186 		p->insn -= i;
187 		i += 4;
188 		p->fixup -= i;
189 		i += 4;
190 		p->handler -= i;
191 		i += 4;
192 	}
193 }
194 
195 #ifdef CONFIG_MODULES
196 /*
197  * If the exception table is sorted, any referring to the module init
198  * will be at the beginning or the end.
199  */
200 void trim_init_extable(struct module *m)
201 {
202 	/*trim the beginning*/
203 	while (m->num_exentries &&
204 	       within_module_init(ex_insn_addr(&m->extable[0]), m)) {
205 		m->extable++;
206 		m->num_exentries--;
207 	}
208 	/*trim the end*/
209 	while (m->num_exentries &&
210 	       within_module_init(ex_insn_addr(&m->extable[m->num_exentries-1]), m))
211 		m->num_exentries--;
212 }
213 #endif /* CONFIG_MODULES */
214