xref: /DragonStub/apps/elf.c (revision 823f04931913f01ee1fc0dc0c7876156ad150388)
1 #include "elf.h"
2 #include "dragonstub/linux/align.h"
3 #include <efi.h>
4 #include <efiapi.h>
5 #include <efilib.h>
6 #include <dragonstub/dragonstub.h>
7 #include <dragonstub/elfloader.h>
8 
9 /// @brief 校验ELF文件头
10 /// @param buf 缓冲区
11 /// @param bufsize 缓冲区大小
12 /// @return
13 static bool verify_ident(const void *buf, u64 bufsize)
14 {
15 	if (bufsize < EI_NIDENT) {
16 		// 太短,不是ELF
17 		return false;
18 	}
19 	// 检查magic number
20 	for (int i = 0; i < EI_CLASS; i++) {
21 		u8 c = *(u8 *)(buf + i);
22 		if (c != ELFMAG[i]) {
23 			// 不是ELF magic number,跳过
24 			efi_err("ELF magic number not match\n");
25 			return false;
26 		}
27 	}
28 
29 	// verify ELF Version
30 	u8 version = *(u8 *)(buf + EI_VERSION);
31 	if (version != EV_CURRENT) {
32 		efi_err("ELF version not match, expected EV_CURRENT(%d), got %d\n",
33 			EV_CURRENT, version);
34 		// 不是当前版本,跳过
35 		return false;
36 	}
37 
38 	// verify ELF Class
39 	u8 class = *(u8 *)(buf + EI_CLASS);
40 	if (class != ELFCLASS64) {
41 		efi_err("ELF class not match, expected ELFCLASS64(%d), got %d\n",
42 			ELFCLASS64, class);
43 		// 不是64位,跳过
44 		return false;
45 	}
46 
47 	return true;
48 }
49 
50 bool elf_check(const void *payload_start, u64 payload_size)
51 {
52 	// 校验ELF文件头
53 	if (!verify_ident(payload_start, payload_size)) {
54 		return false;
55 	}
56 	// 检查架构
57 	Elf64_Ehdr *ehdr = (Elf64_Ehdr *)payload_start;
58 #ifdef CONFIG_riscv64
59 	if (ehdr->e_machine != EM_RISCV) {
60 		efi_err("ELF machine not match, expected EM_RISCV(%d), got %d\n",
61 			EM_RISCV, ehdr->e_machine);
62 		return false;
63 	}
64 #else
65 // 还没有对当前架构进行检查,抛出编译错误
66 #error "Unimplement ELF arch test for current cross compile arch"
67 #endif
68 	return true;
69 }
70 
71 /// @brief 获取ELF文件头
72 /// @param payload_start 文件起始地址
73 /// @param payload_size 文件大小
74 /// @param ehdr 返回的ELF文件头
75 /// @return
76 efi_status_t elf_get_header(const void *payload_start, u64 payload_size,
77 			    Elf64_Ehdr **ehdr)
78 {
79 	if (!verify_ident(payload_start, payload_size)) {
80 		return EFI_INVALID_PARAMETER;
81 	}
82 	*ehdr = (Elf64_Ehdr *)payload_start;
83 	return EFI_SUCCESS;
84 }
85 
86 static void print_elf_info(Elf64_Ehdr *ehdr)
87 {
88 	efi_info("ELF header:\n");
89 	efi_printk("  e_type: %d\n", ehdr->e_type);
90 	efi_printk("  e_machine: %d\n", ehdr->e_machine);
91 	efi_printk("  e_version: %d\n", ehdr->e_version);
92 	efi_printk("  e_entry: %p\n", ehdr->e_entry);
93 	efi_printk("  e_phoff: %p\n", ehdr->e_phoff);
94 	efi_printk("  e_shoff: %p\n", ehdr->e_shoff);
95 	efi_printk("  e_flags: %d\n", ehdr->e_flags);
96 	efi_printk("  e_ehsize: %d\n", ehdr->e_ehsize);
97 	efi_printk("  e_phentsize: %d\n", ehdr->e_phentsize);
98 	efi_printk("  e_phnum: %d\n", ehdr->e_phnum);
99 	efi_printk("  e_shentsize: %d\n", ehdr->e_shentsize);
100 	efi_printk("  e_shnum: %d\n", ehdr->e_shnum);
101 	efi_printk("  e_shstrndx: %d\n", ehdr->e_shstrndx);
102 }
103 
104 static efi_status_t parse_phdrs(const void *payload_start, u64 payload_size,
105 				const Elf64_Ehdr *ehdr, u32 *ret_segments_nr,
106 				Elf64_Phdr **ret_phdr)
107 {
108 	if (ehdr->e_phnum == 0) {
109 		efi_err("No program header\n");
110 		return EFI_INVALID_PARAMETER;
111 	}
112 	if (ehdr->e_phentsize != sizeof(Elf64_Phdr)) {
113 		efi_err("Invalid program header size: %d, expected %d\n",
114 			ehdr->e_phentsize, sizeof(Elf64_Phdr));
115 		return EFI_INVALID_PARAMETER;
116 	}
117 
118 	u16 phnum = ehdr->e_phnum;
119 	if (phnum == PN_XNUM) {
120 		u64 shoff = ehdr->e_shoff;
121 		if (shoff == 0) {
122 			efi_err("No section header\n");
123 			return EFI_INVALID_PARAMETER;
124 		}
125 
126 		if (shoff + sizeof(Elf64_Shdr) > payload_size) {
127 			efi_err("Section header out of range\n");
128 			return EFI_INVALID_PARAMETER;
129 		}
130 
131 		Elf64_Shdr *shdr = (Elf64_Shdr *)(payload_start + shoff);
132 
133 		phnum = shdr[0].sh_info;
134 		if (phnum == 0) {
135 			efi_err("shdr[0].sh_info indicates no program header\n");
136 			return EFI_INVALID_PARAMETER;
137 		}
138 	}
139 
140 	size_t phoff = ehdr->e_phoff;
141 	size_t phsize = ehdr->e_phentsize;
142 	size_t total_size = phnum * phsize;
143 	if (phoff + total_size > payload_size) {
144 		efi_err("Program header out of range\n");
145 		return EFI_INVALID_PARAMETER;
146 	}
147 
148 	Elf64_Phdr *phdr = (Elf64_Phdr *)(payload_start + phoff);
149 
150 	*ret_segments_nr = phnum;
151 	*ret_phdr = phdr;
152 
153 	return EFI_SUCCESS;
154 }
155 
156 /*
157  * Distro versions of GRUB may ignore the BSS allocation entirely (i.e., fail
158  * to provide space, and fail to zero it). Check for this condition by double
159  * checking that the first and the last byte of the image are covered by the
160  * same EFI memory map entry.
161  */
162 static bool check_image_region(u64 base, u64 size)
163 {
164 	struct efi_boot_memmap *map;
165 	efi_status_t status;
166 	bool ret = false;
167 	u64 map_offset;
168 
169 	status = efi_get_memory_map(&map, false);
170 	if (status != EFI_SUCCESS)
171 		return false;
172 
173 	for (map_offset = 0; map_offset < map->map_size;
174 	     map_offset += map->desc_size) {
175 		efi_memory_desc_t *md = (void *)map->map + map_offset;
176 		u64 end = md->PhysicalStart + md->NumberOfPages * EFI_PAGE_SIZE;
177 
178 		/*
179 		 * Find the region that covers base, and return whether
180 		 * it covers base+size bytes.
181 		 */
182 		if (base >= md->PhysicalStart && base < end) {
183 			ret = (base + size) <= end;
184 			break;
185 		}
186 	}
187 
188 	efi_bs_call(FreePool, map);
189 
190 	return ret;
191 }
192 efi_status_t efi_allocate_kernel_memory(const Elf64_Phdr *phdr_start,
193 					u32 phdrs_nr, u64 *ret_paddr,
194 					u64 *ret_size, u64 *ret_min_paddr,
195 					u64 *ret_max_paddr)
196 {
197 	efi_status_t status = EFI_SUCCESS;
198 
199 	const Elf64_Phdr *phdr = phdr_start;
200 
201 	u64 min_paddr = UINT64_MAX;
202 	u64 max_paddr = 0;
203 	for (u32 i = 0; i < phdrs_nr; ++i, ++phdr) {
204 		if (phdr->p_type != PT_LOAD) {
205 			continue;
206 		}
207 
208 		if (phdr->p_align & !EFI_PAGE_SIZE) {
209 			efi_err("ELF segment alignment should be multiple of EFI_PAGE_SIZE(%d), but got %d\n",
210 				EFI_PAGE_SIZE, phdr->p_align);
211 			return EFI_INVALID_PARAMETER;
212 		}
213 		min_paddr = min(min_paddr, (u64)phdr->p_paddr);
214 		max_paddr =
215 			max(max_paddr, (u64)(phdr->p_paddr + phdr->p_memsz));
216 	}
217 
218 	u64 mem_size = ALIGN_UP(max_paddr - min_paddr, EFI_PAGE_SIZE);
219 
220 	status = efi_allocate_pages_aligned(mem_size, ret_paddr, UINT64_MAX,
221 					    EFI_PAGE_SIZE, EfiLoaderData);
222 	// status = efi_allocate_pages_exact(mem_size, paddr);
223 	if (status != EFI_SUCCESS) {
224 		efi_err("Failed to allocate pages for ELF segment: status: %d, page_size=%d, min_paddr=%p, max_paddr=%p, mem_size=%d. Maybe an OOM error or section overlaps.\n",
225 			status, EFI_PAGE_SIZE, ret_paddr, max_paddr, mem_size);
226 		return status;
227 	}
228 
229 	*ret_size = mem_size;
230 	*ret_min_paddr = min_paddr;
231 	*ret_max_paddr = max_paddr;
232 	efi_info("Allocated kernel memory: paddr=%p, mem_size= %d bytes\n",
233 		 *ret_paddr, mem_size);
234 	// zeroed the memory
235 	memset((void *)(*ret_paddr), 0, mem_size);
236 
237 	return EFI_SUCCESS;
238 }
239 
240 static efi_status_t load_program(const void *payload_start, u64 payload_size,
241 				 const Elf64_Phdr *phdr_start, u32 phdrs_nr,
242 				 u64 *ret_program_mem_paddr,
243 				 u64 *ret_program_mem_size, u64 *ret_min_paddr)
244 {
245 	efi_status_t status = EFI_SUCCESS;
246 
247 	u64 allocated_paddr = 0;
248 	u64 allocated_size = 0;
249 	u64 min_paddr = 0;
250 	u64 max_paddr = 0;
251 	status = efi_allocate_kernel_memory(phdr_start, phdrs_nr,
252 					    &allocated_paddr, &allocated_size,
253 					    &min_paddr, &max_paddr);
254 	if (status != EFI_SUCCESS) {
255 		efi_err("Failed to allocate kernel memory\n");
256 		return status;
257 	}
258 	const Elf64_Phdr *phdr = phdr_start;
259 
260 	for (u32 i = 0; i < phdrs_nr; ++i, ++phdr) {
261 		if (phdr->p_type != PT_LOAD) {
262 			continue;
263 		}
264 
265 		if (phdr->p_align & !EFI_PAGE_SIZE) {
266 			efi_err("ELF segment alignment should be multiple of EFI_PAGE_SIZE(%d), but got %d\n",
267 				EFI_PAGE_SIZE, phdr->p_align);
268 			status = EFI_INVALID_PARAMETER;
269 			goto failed;
270 		}
271 
272 		u64 paddr = phdr->p_paddr;
273 
274 		u64 mem_size = phdr->p_memsz;
275 		u64 file_size = phdr->p_filesz;
276 		u64 file_offset = phdr->p_offset;
277 		// efi_debug(
278 		// 	"loading segment: paddr=%p, mem_size=%d, file_size=%d\n",
279 		// 	paddr, mem_size, file_size);
280 
281 		if (file_offset + file_size > payload_size) {
282 			status = EFI_INVALID_PARAMETER;
283 			goto failed;
284 		}
285 
286 		if (mem_size < file_size) {
287 			status = EFI_INVALID_PARAMETER;
288 			goto failed;
289 		}
290 
291 		if (mem_size == 0) {
292 			continue;
293 		}
294 
295 		memcpy((void *)(allocated_paddr + (paddr - min_paddr)),
296 		       payload_start + file_offset, file_size);
297 
298 		// efi_debug(
299 		// 	"segment loaded: paddr=%p, mem_size=%d, file_size=%d\n",
300 		// 	paddr, mem_size, file_size);
301 	}
302 
303 	*ret_program_mem_paddr = allocated_paddr;
304 	*ret_program_mem_size = allocated_size;
305 	*ret_min_paddr = min_paddr;
306 
307 	return EFI_SUCCESS;
308 failed:
309 	efi_free(allocated_size, allocated_paddr);
310 	return status;
311 }
312 
313 efi_status_t load_elf(struct payload_info *payload_info)
314 {
315 	const void *payload_start = (void *)payload_info->payload_addr;
316 	u64 payload_size = payload_info->payload_size;
317 	Elf64_Ehdr *ehdr = NULL;
318 	efi_status_t status =
319 		elf_get_header(payload_start, payload_size, &ehdr);
320 	if (status != EFI_SUCCESS) {
321 		efi_err("Failed to get ELF header\n");
322 		return status;
323 	}
324 	ASSERT(ehdr != NULL);
325 
326 	print_elf_info(ehdr);
327 
328 	u32 phdrs_nr = 0;
329 	Elf64_Phdr *phdr_start = NULL;
330 
331 	status = parse_phdrs(payload_start, payload_size, ehdr, &phdrs_nr,
332 			     &phdr_start);
333 	if (status != EFI_SUCCESS) {
334 		efi_err("Failed to parse ELF segments\n");
335 		return status;
336 	}
337 
338 	efi_debug("program headers: %d\n", phdrs_nr);
339 
340 	u64 program_paddr = 0;
341 	u64 program_size = 0;
342 	u64 image_link_base_paddr = 0;
343 	load_program(payload_start, payload_size, phdr_start, phdrs_nr,
344 		     &program_paddr, &program_size, &image_link_base_paddr);
345 	payload_info->loaded_paddr = program_paddr;
346 	payload_info->loaded_size = program_size;
347 	payload_info->kernel_entry =
348 		ehdr->e_entry - image_link_base_paddr + program_paddr;
349 	return EFI_SUCCESS;
350 }