/root/src/xen/xen/arch/x86/extable.c
Line | Count | Source (jump to first uncovered line) |
1 | | |
2 | | #include <xen/init.h> |
3 | | #include <xen/list.h> |
4 | | #include <xen/perfc.h> |
5 | | #include <xen/rcupdate.h> |
6 | | #include <xen/sort.h> |
7 | | #include <xen/spinlock.h> |
8 | | #include <asm/uaccess.h> |
9 | | #include <xen/domain_page.h> |
10 | | #include <xen/virtual_region.h> |
11 | | #include <xen/livepatch.h> |
12 | | |
13 | 3.44k | #define EX_FIELD(ptr, field) ((unsigned long)&(ptr)->field + (ptr)->field) |
14 | | |
15 | | static inline unsigned long ex_addr(const struct exception_table_entry *x) |
16 | 3.43k | { |
17 | 3.43k | return EX_FIELD(x, addr); |
18 | 3.43k | } |
19 | | |
20 | | static inline unsigned long ex_cont(const struct exception_table_entry *x) |
21 | 4 | { |
22 | 4 | return EX_FIELD(x, cont); |
23 | 4 | } |
24 | | |
25 | | static int init_or_livepatch cmp_ex(const void *a, const void *b) |
26 | 1.70k | { |
27 | 1.70k | const struct exception_table_entry *l = a, *r = b; |
28 | 1.70k | unsigned long lip = ex_addr(l); |
29 | 1.70k | unsigned long rip = ex_addr(r); |
30 | 1.70k | |
31 | 1.70k | /* avoid overflow */ |
32 | 1.70k | if (lip > rip) |
33 | 458 | return 1; |
34 | 1.24k | if (lip < rip) |
35 | 1.24k | return -1; |
36 | 0 | return 0; |
37 | 1.24k | } |
38 | | |
39 | | #ifndef swap_ex |
40 | | static void init_or_livepatch swap_ex(void *a, void *b, int size) |
41 | 995 | { |
42 | 995 | struct exception_table_entry *l = a, *r = b, tmp; |
43 | 995 | long delta = b - a; |
44 | 995 | |
45 | 995 | tmp = *l; |
46 | 995 | l->addr = r->addr + delta; |
47 | 995 | l->cont = r->cont + delta; |
48 | 995 | r->addr = tmp.addr - delta; |
49 | 995 | r->cont = tmp.cont - delta; |
50 | 995 | } |
51 | | #endif |
52 | | |
53 | | void init_or_livepatch sort_exception_table(struct exception_table_entry *start, |
54 | | const struct exception_table_entry *stop) |
55 | 2 | { |
56 | 2 | sort(start, stop - start, |
57 | 2 | sizeof(struct exception_table_entry), cmp_ex, swap_ex); |
58 | 2 | } |
59 | | |
60 | | void __init sort_exception_tables(void) |
61 | 1 | { |
62 | 1 | sort_exception_table(__start___ex_table, __stop___ex_table); |
63 | 1 | sort_exception_table(__start___pre_ex_table, __stop___pre_ex_table); |
64 | 1 | } |
65 | | |
66 | | static unsigned long |
67 | | search_one_extable(const struct exception_table_entry *first, |
68 | | const struct exception_table_entry *last, |
69 | | unsigned long value) |
70 | 4 | { |
71 | 4 | const struct exception_table_entry *mid; |
72 | 4 | long diff; |
73 | 4 | |
74 | 28 | while ( first <= last ) |
75 | 28 | { |
76 | 28 | mid = (last - first) / 2 + first; |
77 | 28 | diff = ex_addr(mid) - value; |
78 | 28 | if (diff == 0) |
79 | 4 | return ex_cont(mid); |
80 | 24 | else if (diff < 0) |
81 | 20 | first = mid+1; |
82 | 24 | else |
83 | 4 | last = mid-1; |
84 | 28 | } |
85 | 0 | return 0; |
86 | 4 | } |
87 | | |
88 | | unsigned long |
89 | | search_exception_table(const struct cpu_user_regs *regs) |
90 | 4 | { |
91 | 4 | const struct virtual_region *region = find_text_region(regs->rip); |
92 | 4 | unsigned long stub = this_cpu(stubs.addr); |
93 | 4 | |
94 | 4 | if ( region && region->ex ) |
95 | 0 | return search_one_extable(region->ex, region->ex_end - 1, regs->rip); |
96 | 4 | |
97 | 4 | if ( regs->rip >= stub + STUB_BUF_SIZE / 2 && |
98 | 4 | regs->rip < stub + STUB_BUF_SIZE && |
99 | 4 | regs->rsp > (unsigned long)regs && |
100 | 4 | regs->rsp < (unsigned long)get_cpu_info() ) |
101 | 4 | { |
102 | 4 | unsigned long retptr = *(unsigned long *)regs->rsp; |
103 | 4 | |
104 | 4 | region = find_text_region(retptr); |
105 | 4 | retptr = region && region->ex |
106 | 4 | ? search_one_extable(region->ex, region->ex_end - 1, retptr) |
107 | 0 | : 0; |
108 | 4 | if ( retptr ) |
109 | 4 | { |
110 | 4 | /* |
111 | 4 | * Put trap number and error code on the stack (in place of the |
112 | 4 | * original return address) for recovery code to pick up. |
113 | 4 | */ |
114 | 4 | union stub_exception_token token = { |
115 | 4 | .fields.ec = regs->error_code, |
116 | 4 | .fields.trapnr = regs->entry_vector, |
117 | 4 | }; |
118 | 4 | |
119 | 4 | *(unsigned long *)regs->rsp = token.raw; |
120 | 4 | return retptr; |
121 | 4 | } |
122 | 4 | } |
123 | 4 | |
124 | 0 | return 0; |
125 | 4 | } |
126 | | |
127 | | #ifndef NDEBUG |
128 | | static int __init stub_selftest(void) |
129 | 1 | { |
130 | 1 | static const struct { |
131 | 1 | uint8_t opc[4]; |
132 | 1 | uint64_t rax; |
133 | 1 | union stub_exception_token res; |
134 | 1 | } tests[] __initconst = { |
135 | 1 | { .opc = { 0x0f, 0xb9, 0xc3, 0xc3 }, /* ud1 */ |
136 | 1 | .res.fields.trapnr = TRAP_invalid_op }, |
137 | 1 | { .opc = { 0x90, 0x02, 0x00, 0xc3 }, /* nop; add (%rax),%al */ |
138 | 1 | .rax = 0x0123456789abcdef, |
139 | 1 | .res.fields.trapnr = TRAP_gp_fault }, |
140 | 1 | { .opc = { 0x02, 0x04, 0x04, 0xc3 }, /* add (%rsp,%rax),%al */ |
141 | 1 | .rax = 0xfedcba9876543210, |
142 | 1 | .res.fields.trapnr = TRAP_stack_error }, |
143 | 1 | { .opc = { 0xcc, 0xc3, 0xc3, 0xc3 }, /* int3 */ |
144 | 1 | .res.fields.trapnr = TRAP_int3 }, |
145 | 1 | }; |
146 | 1 | unsigned long addr = this_cpu(stubs.addr) + STUB_BUF_SIZE / 2; |
147 | 1 | unsigned int i; |
148 | 1 | |
149 | 1 | printk("Running stub recovery selftests...\n"); |
150 | 1 | |
151 | 5 | for ( i = 0; i < ARRAY_SIZE(tests); ++i ) |
152 | 4 | { |
153 | 4 | uint8_t *ptr = map_domain_page(_mfn(this_cpu(stubs.mfn))) + |
154 | 4 | (addr & ~PAGE_MASK); |
155 | 4 | unsigned long res = ~0; |
156 | 4 | |
157 | 4 | memset(ptr, 0xcc, STUB_BUF_SIZE / 2); |
158 | 4 | memcpy(ptr, tests[i].opc, ARRAY_SIZE(tests[i].opc)); |
159 | 4 | unmap_domain_page(ptr); |
160 | 4 | |
161 | 4 | asm volatile ( "call *%[stb]\n" |
162 | 4 | ".Lret%=:\n\t" |
163 | 4 | ".pushsection .fixup,\"ax\"\n" |
164 | 4 | ".Lfix%=:\n\t" |
165 | 4 | "pop %[exn]\n\t" |
166 | 4 | "jmp .Lret%=\n\t" |
167 | 4 | ".popsection\n\t" |
168 | 4 | _ASM_EXTABLE(.Lret%=, .Lfix%=) |
169 | 4 | : [exn] "+m" (res) |
170 | 4 | : [stb] "rm" (addr), "a" (tests[i].rax)); |
171 | 4 | ASSERT(res == tests[i].res.raw); |
172 | 4 | } |
173 | 1 | |
174 | 1 | return 0; |
175 | 1 | } |
176 | | __initcall(stub_selftest); |
177 | | #endif |
178 | | |
179 | | unsigned long |
180 | | search_pre_exception_table(struct cpu_user_regs *regs) |
181 | 0 | { |
182 | 0 | unsigned long addr = regs->rip; |
183 | 0 | unsigned long fixup = search_one_extable( |
184 | 0 | __start___pre_ex_table, __stop___pre_ex_table-1, addr); |
185 | 0 | if ( fixup ) |
186 | 0 | { |
187 | 0 | dprintk(XENLOG_INFO, "Pre-exception: %p -> %p\n", _p(addr), _p(fixup)); |
188 | 0 | perfc_incr(exception_fixed); |
189 | 0 | } |
190 | 0 | return fixup; |
191 | 0 | } |