]>
Commit | Line | Data |
---|---|---|
3fa2d384 VP |
1 | /* |
2 | * Copyright (c) 2018 Virtuozzo International GmbH | |
3 | * | |
4 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
5 | * | |
6 | */ | |
7 | ||
8 | #include "qemu/osdep.h" | |
9de37c28 | 9 | #include "qemu/host-utils.h" |
3fa2d384 VP |
10 | #include "err.h" |
11 | #include "qemu_elf.h" | |
12 | ||
13 | #define QEMU_NOTE_NAME "QEMU" | |
14 | ||
15 | #ifndef ROUND_UP | |
16 | #define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d))) | |
17 | #endif | |
18 | ||
3fa2d384 VP |
19 | int is_system(QEMUCPUState *s) |
20 | { | |
21 | return s->gs.base >> 63; | |
22 | } | |
23 | ||
3fa2d384 VP |
24 | Elf64_Phdr *elf64_getphdr(void *map) |
25 | { | |
26 | Elf64_Ehdr *ehdr = map; | |
27 | Elf64_Phdr *phdr = (void *)((uint8_t *)map + ehdr->e_phoff); | |
28 | ||
29 | return phdr; | |
30 | } | |
31 | ||
32 | Elf64_Half elf_getphdrnum(void *map) | |
33 | { | |
34 | Elf64_Ehdr *ehdr = map; | |
35 | ||
36 | return ehdr->e_phnum; | |
37 | } | |
38 | ||
9de37c28 AO |
39 | static bool advance_note_offset(uint64_t *offsetp, uint64_t size, uint64_t end) |
40 | { | |
41 | uint64_t offset = *offsetp; | |
42 | ||
43 | if (uadd64_overflow(offset, size, &offset) || offset > UINT64_MAX - 3) { | |
44 | return false; | |
45 | } | |
46 | ||
47 | offset = ROUND_UP(offset, 4); | |
48 | ||
49 | if (offset > end) { | |
50 | return false; | |
51 | } | |
52 | ||
53 | *offsetp = offset; | |
54 | ||
55 | return true; | |
56 | } | |
57 | ||
49760ccf | 58 | static bool init_states(QEMU_Elf *qe) |
3fa2d384 VP |
59 | { |
60 | Elf64_Phdr *phdr = elf64_getphdr(qe->map); | |
3fa2d384 | 61 | Elf64_Nhdr *nhdr; |
0c94e32d | 62 | GPtrArray *states; |
9de37c28 AO |
63 | QEMUCPUState *state; |
64 | uint32_t state_size; | |
65 | uint64_t offset; | |
66 | uint64_t end_offset; | |
67 | char *name; | |
3fa2d384 VP |
68 | |
69 | if (phdr[0].p_type != PT_NOTE) { | |
70 | eprintf("Failed to find PT_NOTE\n"); | |
49760ccf | 71 | return false; |
3fa2d384 VP |
72 | } |
73 | ||
74 | qe->has_kernel_gs_base = 1; | |
9de37c28 | 75 | offset = phdr[0].p_offset; |
0c94e32d | 76 | states = g_ptr_array_new(); |
3fa2d384 | 77 | |
9de37c28 AO |
78 | if (uadd64_overflow(offset, phdr[0].p_memsz, &end_offset) || |
79 | end_offset > qe->size) { | |
80 | end_offset = qe->size; | |
81 | } | |
82 | ||
83 | while (offset < end_offset) { | |
84 | nhdr = (void *)((uint8_t *)qe->map + offset); | |
85 | ||
86 | if (!advance_note_offset(&offset, sizeof(*nhdr), end_offset)) { | |
87 | break; | |
88 | } | |
89 | ||
90 | name = (char *)qe->map + offset; | |
91 | ||
92 | if (!advance_note_offset(&offset, nhdr->n_namesz, end_offset)) { | |
93 | break; | |
94 | } | |
95 | ||
96 | state = (void *)((uint8_t *)qe->map + offset); | |
97 | ||
98 | if (!advance_note_offset(&offset, nhdr->n_descsz, end_offset)) { | |
99 | break; | |
100 | } | |
101 | ||
102 | if (!strcmp(name, QEMU_NOTE_NAME) && | |
103 | nhdr->n_descsz >= offsetof(QEMUCPUState, kernel_gs_base)) { | |
104 | state_size = MIN(state->size, nhdr->n_descsz); | |
3fa2d384 | 105 | |
9de37c28 | 106 | if (state_size < sizeof(*state)) { |
0c94e32d | 107 | eprintf("CPU #%u: QEMU CPU state size %u doesn't match\n", |
9de37c28 | 108 | states->len, state_size); |
3fa2d384 VP |
109 | /* |
110 | * We assume either every QEMU CPU state has KERNEL_GS_BASE or | |
111 | * no one has. | |
112 | */ | |
113 | qe->has_kernel_gs_base = 0; | |
114 | } | |
0c94e32d | 115 | g_ptr_array_add(states, state); |
3fa2d384 VP |
116 | } |
117 | } | |
118 | ||
0c94e32d | 119 | printf("%u CPU states has been found\n", states->len); |
3fa2d384 | 120 | |
0c94e32d AO |
121 | qe->state_nr = states->len; |
122 | qe->state = (void *)g_ptr_array_free(states, FALSE); | |
3fa2d384 | 123 | |
49760ccf | 124 | return true; |
3fa2d384 VP |
125 | } |
126 | ||
127 | static void exit_states(QEMU_Elf *qe) | |
128 | { | |
2a052b4e | 129 | g_free(qe->state); |
3fa2d384 VP |
130 | } |
131 | ||
c06ebc0f VP |
132 | static bool check_ehdr(QEMU_Elf *qe) |
133 | { | |
134 | Elf64_Ehdr *ehdr = qe->map; | |
135 | ||
136 | if (sizeof(Elf64_Ehdr) > qe->size) { | |
137 | eprintf("Invalid input dump file size\n"); | |
138 | return false; | |
139 | } | |
140 | ||
141 | if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { | |
142 | eprintf("Invalid ELF signature, input file is not ELF\n"); | |
143 | return false; | |
144 | } | |
145 | ||
146 | if (ehdr->e_ident[EI_CLASS] != ELFCLASS64 || | |
147 | ehdr->e_ident[EI_DATA] != ELFDATA2LSB) { | |
148 | eprintf("Invalid ELF class or byte order, must be 64-bit LE\n"); | |
149 | return false; | |
150 | } | |
151 | ||
152 | if (ehdr->e_ident[EI_VERSION] != EV_CURRENT) { | |
153 | eprintf("Invalid ELF version\n"); | |
154 | return false; | |
155 | } | |
156 | ||
157 | if (ehdr->e_machine != EM_X86_64) { | |
158 | eprintf("Invalid input dump architecture, only x86_64 is supported\n"); | |
159 | return false; | |
160 | } | |
161 | ||
162 | if (ehdr->e_type != ET_CORE) { | |
163 | eprintf("Invalid ELF type, must be core file\n"); | |
164 | return false; | |
165 | } | |
166 | ||
167 | /* | |
168 | * ELF dump file must contain one PT_NOTE and at least one PT_LOAD to | |
169 | * restore physical address space. | |
170 | */ | |
171 | if (ehdr->e_phnum < 2) { | |
172 | eprintf("Invalid number of ELF program headers\n"); | |
173 | return false; | |
174 | } | |
175 | ||
176 | return true; | |
177 | } | |
178 | ||
49760ccf | 179 | static bool QEMU_Elf_map(QEMU_Elf *qe, const char *filename) |
3fa2d384 | 180 | { |
df7a7556 VP |
181 | #ifdef CONFIG_LINUX |
182 | struct stat st; | |
183 | int fd; | |
184 | ||
185 | printf("Using Linux mmap\n"); | |
186 | ||
187 | fd = open(filename, O_RDONLY, 0); | |
188 | if (fd == -1) { | |
189 | eprintf("Failed to open ELF dump file \'%s\'\n", filename); | |
49760ccf | 190 | return false; |
df7a7556 VP |
191 | } |
192 | ||
193 | if (fstat(fd, &st)) { | |
194 | eprintf("Failed to get size of ELF dump file\n"); | |
195 | close(fd); | |
49760ccf | 196 | return false; |
df7a7556 VP |
197 | } |
198 | qe->size = st.st_size; | |
199 | ||
200 | qe->map = mmap(NULL, qe->size, PROT_READ | PROT_WRITE, | |
201 | MAP_PRIVATE | MAP_NORESERVE, fd, 0); | |
202 | if (qe->map == MAP_FAILED) { | |
203 | eprintf("Failed to map ELF file\n"); | |
204 | close(fd); | |
49760ccf | 205 | return false; |
df7a7556 VP |
206 | } |
207 | ||
208 | close(fd); | |
209 | #else | |
bd4d0da7 | 210 | GError *gerr = NULL; |
df7a7556 VP |
211 | |
212 | printf("Using GLib mmap\n"); | |
3fa2d384 | 213 | |
bd4d0da7 VP |
214 | qe->gmf = g_mapped_file_new(filename, TRUE, &gerr); |
215 | if (gerr) { | |
216 | eprintf("Failed to map ELF dump file \'%s\'\n", filename); | |
514284d7 | 217 | g_error_free(gerr); |
49760ccf | 218 | return false; |
3fa2d384 VP |
219 | } |
220 | ||
bd4d0da7 VP |
221 | qe->map = g_mapped_file_get_contents(qe->gmf); |
222 | qe->size = g_mapped_file_get_length(qe->gmf); | |
df7a7556 VP |
223 | #endif |
224 | ||
49760ccf | 225 | return true; |
df7a7556 VP |
226 | } |
227 | ||
228 | static void QEMU_Elf_unmap(QEMU_Elf *qe) | |
229 | { | |
230 | #ifdef CONFIG_LINUX | |
231 | munmap(qe->map, qe->size); | |
232 | #else | |
233 | g_mapped_file_unref(qe->gmf); | |
234 | #endif | |
235 | } | |
236 | ||
49760ccf | 237 | bool QEMU_Elf_init(QEMU_Elf *qe, const char *filename) |
df7a7556 | 238 | { |
49760ccf AO |
239 | if (!QEMU_Elf_map(qe, filename)) { |
240 | return false; | |
df7a7556 | 241 | } |
3fa2d384 | 242 | |
c06ebc0f VP |
243 | if (!check_ehdr(qe)) { |
244 | eprintf("Input file has the wrong format\n"); | |
df7a7556 | 245 | QEMU_Elf_unmap(qe); |
49760ccf | 246 | return false; |
c06ebc0f VP |
247 | } |
248 | ||
49760ccf | 249 | if (!init_states(qe)) { |
3fa2d384 | 250 | eprintf("Failed to extract QEMU CPU states\n"); |
df7a7556 | 251 | QEMU_Elf_unmap(qe); |
49760ccf | 252 | return false; |
3fa2d384 VP |
253 | } |
254 | ||
49760ccf | 255 | return true; |
3fa2d384 VP |
256 | } |
257 | ||
258 | void QEMU_Elf_exit(QEMU_Elf *qe) | |
259 | { | |
260 | exit_states(qe); | |
df7a7556 | 261 | QEMU_Elf_unmap(qe); |
3fa2d384 | 262 | } |