xref: /freebsd/stand/kboot/libkboot/dfk.c (revision f3c0d74e3b8e969b9f790f4ec2ac173243f21920)
1*f3c0d74eSWarner Losh /*
2*f3c0d74eSWarner Losh  * Copyright (c) 2025 Netflix, Inc
3*f3c0d74eSWarner Losh  *
4*f3c0d74eSWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
5*f3c0d74eSWarner Losh  */
6*f3c0d74eSWarner Losh 
7*f3c0d74eSWarner Losh /*
8*f3c0d74eSWarner Losh  * Common macros to allow compiling this as a Linux binary or in libsa.
9*f3c0d74eSWarner Losh  */
10*f3c0d74eSWarner Losh #ifdef _STANDALONE
11*f3c0d74eSWarner Losh #include "stand.h"
12*f3c0d74eSWarner Losh /* Not ideal, but these are missing in libsa */
13*f3c0d74eSWarner Losh #define perror(msg) printf("ERROR %d: %s\n", errno, msg)
14*f3c0d74eSWarner Losh #define fprintf(x, ...) printf( __VA_ARGS__ )
15*f3c0d74eSWarner Losh #include <machine/elf.h>
16*f3c0d74eSWarner Losh #include <sys/param.h>
17*f3c0d74eSWarner Losh #include "util.h"
18*f3c0d74eSWarner Losh #else
19*f3c0d74eSWarner Losh #include <elf.h>
20*f3c0d74eSWarner Losh #include <errno.h>
21*f3c0d74eSWarner Losh #include <fcntl.h>
22*f3c0d74eSWarner Losh #include <fcntl.h>
23*f3c0d74eSWarner Losh #include <stdbool.h>
24*f3c0d74eSWarner Losh #include <stdint.h>
25*f3c0d74eSWarner Losh #include <stdio.h>
26*f3c0d74eSWarner Losh #include <stdlib.h>
27*f3c0d74eSWarner Losh #include <string.h>
28*f3c0d74eSWarner Losh #include <unistd.h>
29*f3c0d74eSWarner Losh #include <asm/bootparam.h>
30*f3c0d74eSWarner Losh 
31*f3c0d74eSWarner Losh #define PAGE_SIZE 4096
32*f3c0d74eSWarner Losh #define	IS_ELF(ehdr)	((ehdr).e_ident[EI_MAG0] == ELFMAG0 && \
33*f3c0d74eSWarner Losh 			 (ehdr).e_ident[EI_MAG1] == ELFMAG1 && \
34*f3c0d74eSWarner Losh 			 (ehdr).e_ident[EI_MAG2] == ELFMAG2 && \
35*f3c0d74eSWarner Losh 			 (ehdr).e_ident[EI_MAG3] == ELFMAG3)
36*f3c0d74eSWarner Losh 
37*f3c0d74eSWarner Losh #define ELF_TARG_CLASS  ELFCLASS64
38*f3c0d74eSWarner Losh #define ELF_TARG_MACH   EM_X86_64
39*f3c0d74eSWarner Losh #define ELF_TARG_DATA	ELFDATA2LSB
40*f3c0d74eSWarner Losh #endif
41*f3c0d74eSWarner Losh 
42*f3c0d74eSWarner Losh #define KCORE_PATH "/proc/kcore"
43*f3c0d74eSWarner Losh #define KALLSYMS_PATH "/proc/kallsyms"
44*f3c0d74eSWarner Losh 
45*f3c0d74eSWarner Losh struct elf_file
46*f3c0d74eSWarner Losh {
47*f3c0d74eSWarner Losh 	uint8_t		buf[PAGE_SIZE];
48*f3c0d74eSWarner Losh 	int		fd;
49*f3c0d74eSWarner Losh };
50*f3c0d74eSWarner Losh 
51*f3c0d74eSWarner Losh // All the line_buffer stuff can be replaced by fgetstr()
52*f3c0d74eSWarner Losh 
53*f3c0d74eSWarner Losh struct line_buffer
54*f3c0d74eSWarner Losh {
55*f3c0d74eSWarner Losh 	int		fd;
56*f3c0d74eSWarner Losh 	char		buf[PAGE_SIZE];
57*f3c0d74eSWarner Losh 	char		*pos;
58*f3c0d74eSWarner Losh 	char		*eos;
59*f3c0d74eSWarner Losh };
60*f3c0d74eSWarner Losh 
61*f3c0d74eSWarner Losh /*
62*f3c0d74eSWarner Losh  * We just assume we have to fill if we are called.
63*f3c0d74eSWarner Losh  */
64*f3c0d74eSWarner Losh static bool
lb_fill(struct line_buffer * lb)65*f3c0d74eSWarner Losh lb_fill(struct line_buffer *lb)
66*f3c0d74eSWarner Losh {
67*f3c0d74eSWarner Losh 	ssize_t rv;
68*f3c0d74eSWarner Losh 
69*f3c0d74eSWarner Losh 	lb->pos = lb->eos = lb->buf;	// Reset to no data condition
70*f3c0d74eSWarner Losh 	rv = read(lb->fd, lb->buf, sizeof(lb->buf));
71*f3c0d74eSWarner Losh 	if (rv <= 0)
72*f3c0d74eSWarner Losh 		return (false);
73*f3c0d74eSWarner Losh 	lb->pos = lb->buf;
74*f3c0d74eSWarner Losh 	lb->eos = lb->buf + rv;
75*f3c0d74eSWarner Losh 	return (true);
76*f3c0d74eSWarner Losh }
77*f3c0d74eSWarner Losh 
78*f3c0d74eSWarner Losh static bool
lb_fini(struct line_buffer * lb)79*f3c0d74eSWarner Losh lb_fini(struct line_buffer *lb)
80*f3c0d74eSWarner Losh {
81*f3c0d74eSWarner Losh 	close(lb->fd);
82*f3c0d74eSWarner Losh 	return (true);
83*f3c0d74eSWarner Losh }
84*f3c0d74eSWarner Losh 
85*f3c0d74eSWarner Losh static bool
lb_init(struct line_buffer * lb,const char * fn)86*f3c0d74eSWarner Losh lb_init(struct line_buffer *lb, const char *fn)
87*f3c0d74eSWarner Losh {
88*f3c0d74eSWarner Losh 	lb->fd = open(fn, O_RDONLY);
89*f3c0d74eSWarner Losh 	if (lb->fd == -1)
90*f3c0d74eSWarner Losh 		return (false);
91*f3c0d74eSWarner Losh 	lb->pos = lb->eos = lb->buf;
92*f3c0d74eSWarner Losh 	if (!lb_fill(lb)) {
93*f3c0d74eSWarner Losh 		lb_fini(lb);
94*f3c0d74eSWarner Losh 		return (false);
95*f3c0d74eSWarner Losh 	}
96*f3c0d74eSWarner Losh 	return (true);
97*f3c0d74eSWarner Losh }
98*f3c0d74eSWarner Losh 
99*f3c0d74eSWarner Losh // True -> data returned
100*f3c0d74eSWarner Losh // False -> EOF / ERROR w/o data
101*f3c0d74eSWarner Losh static bool
lb_1line(struct line_buffer * lb,char * buffer,size_t buflen)102*f3c0d74eSWarner Losh lb_1line(struct line_buffer *lb, char *buffer, size_t buflen)
103*f3c0d74eSWarner Losh {
104*f3c0d74eSWarner Losh 	char *bufeos = buffer + buflen - 1;	// point at byte for NUL at eos
105*f3c0d74eSWarner Losh 	char *walker = buffer;
106*f3c0d74eSWarner Losh 
107*f3c0d74eSWarner Losh 	while (walker < bufeos) {		// < to exclude space for NUL
108*f3c0d74eSWarner Losh 		if (lb->pos >= lb->eos) {	// Refill empty buffer
109*f3c0d74eSWarner Losh 			if (!lb_fill(lb)) {	// Hit EOF / error
110*f3c0d74eSWarner Losh 				if (walker > buffer) // Have data? return it
111*f3c0d74eSWarner Losh 					break;
112*f3c0d74eSWarner Losh 				// No data, signal EOF/Error
113*f3c0d74eSWarner Losh 				return (false);
114*f3c0d74eSWarner Losh 			}
115*f3c0d74eSWarner Losh 		}
116*f3c0d74eSWarner Losh 		*walker = *lb->pos++;
117*f3c0d74eSWarner Losh 		if (*walker == '\n')
118*f3c0d74eSWarner Losh 			break;
119*f3c0d74eSWarner Losh 		walker++;
120*f3c0d74eSWarner Losh 	}
121*f3c0d74eSWarner Losh 	/*
122*f3c0d74eSWarner Losh 	 * We know walker <= bufeos, so NUL will fit.
123*f3c0d74eSWarner Losh 	 */
124*f3c0d74eSWarner Losh 	*++walker = '\0';
125*f3c0d74eSWarner Losh 	return (true);
126*f3c0d74eSWarner Losh }
127*f3c0d74eSWarner Losh 
128*f3c0d74eSWarner Losh /*
129*f3c0d74eSWarner Losh  * Scan /proc/kallsyms to find @symbol and return the value it finds there.
130*f3c0d74eSWarner Losh  */
131*f3c0d74eSWarner Losh unsigned long
symbol_addr(const char * symbol)132*f3c0d74eSWarner Losh symbol_addr(const char *symbol)
133*f3c0d74eSWarner Losh {
134*f3c0d74eSWarner Losh 	struct line_buffer lb;
135*f3c0d74eSWarner Losh 	unsigned long addr;
136*f3c0d74eSWarner Losh 	char line[256];
137*f3c0d74eSWarner Losh 
138*f3c0d74eSWarner Losh 	if (!lb_init(&lb, KALLSYMS_PATH))
139*f3c0d74eSWarner Losh 		return (0);
140*f3c0d74eSWarner Losh 	while (lb_1line(&lb, line, sizeof(line))) {
141*f3c0d74eSWarner Losh 		char *val, *name, *x, t;
142*f3c0d74eSWarner Losh 
143*f3c0d74eSWarner Losh 		/*
144*f3c0d74eSWarner Losh 		 * Parse lines of the form
145*f3c0d74eSWarner Losh 		 *	val<sp>t<sp>name\n
146*f3c0d74eSWarner Losh 		 * looking for one with t in [dDbB] (so data) name == symbol,
147*f3c0d74eSWarner Losh 		 * skipping lines that don't match the pattern.
148*f3c0d74eSWarner Losh 		 */
149*f3c0d74eSWarner Losh 		val = line;
150*f3c0d74eSWarner Losh 		x = strchr(val, ' ');
151*f3c0d74eSWarner Losh 		if (x == NULL)
152*f3c0d74eSWarner Losh 			continue;	/* No 1st <sp> */
153*f3c0d74eSWarner Losh 		*x++ = '\0';
154*f3c0d74eSWarner Losh 		t = *x++;
155*f3c0d74eSWarner Losh 		if (strchr("dDbB", t) == NULL)
156*f3c0d74eSWarner Losh 			continue;	/* Only data types */
157*f3c0d74eSWarner Losh 		if (*x++ != ' ')
158*f3c0d74eSWarner Losh 			continue;	/* No 2nd <sp> */
159*f3c0d74eSWarner Losh 		name = x;
160*f3c0d74eSWarner Losh 		x = strchr(x, '\n');
161*f3c0d74eSWarner Losh 		if (x == NULL)
162*f3c0d74eSWarner Losh 			continue;	/* No traling newline */
163*f3c0d74eSWarner Losh 		*x++ = '\0';
164*f3c0d74eSWarner Losh 		if (strcmp(name, symbol) == 0) {
165*f3c0d74eSWarner Losh 			unsigned long v;
166*f3c0d74eSWarner Losh 			char *eop = NULL;
167*f3c0d74eSWarner Losh 			lb_fini(&lb);
168*f3c0d74eSWarner Losh 			v = strtoul(val, &eop, 16);
169*f3c0d74eSWarner Losh 			if (*eop == '\0')
170*f3c0d74eSWarner Losh 				return (v);
171*f3c0d74eSWarner Losh 			return (0);	/* PARSE ERROR -- what to do? */
172*f3c0d74eSWarner Losh 		}
173*f3c0d74eSWarner Losh 		/* No match, try next */
174*f3c0d74eSWarner Losh 	}
175*f3c0d74eSWarner Losh 
176*f3c0d74eSWarner Losh 	lb_fini(&lb);
177*f3c0d74eSWarner Losh 	return (0);
178*f3c0d74eSWarner Losh }
179*f3c0d74eSWarner Losh 
180*f3c0d74eSWarner Losh /*
181*f3c0d74eSWarner Losh  * Parse /proc/kcore to find if we can get the data for @len bytes that are
182*f3c0d74eSWarner Losh  * mapped in the kernel at VA @addr. It's a CORE file in ELF format that the
183*f3c0d74eSWarner Losh  * kernel exports for the 'safe' areas to touch. We can read random kernel
184*f3c0d74eSWarner Losh  * varaibles, but we can't read arbitrary addresses since it doesn't export
185*f3c0d74eSWarner Losh  * the direct map.
186*f3c0d74eSWarner Losh  */
187*f3c0d74eSWarner Losh bool
read_at_address(unsigned long addr,void * buf,size_t len)188*f3c0d74eSWarner Losh read_at_address(unsigned long addr, void *buf, size_t len)
189*f3c0d74eSWarner Losh {
190*f3c0d74eSWarner Losh 	struct elf_file ef;
191*f3c0d74eSWarner Losh 	Elf64_Ehdr *hdr;
192*f3c0d74eSWarner Losh 	Elf64_Phdr *phdr;
193*f3c0d74eSWarner Losh 	ssize_t rv;
194*f3c0d74eSWarner Losh 
195*f3c0d74eSWarner Losh 	bzero(&ef, sizeof(ef));
196*f3c0d74eSWarner Losh 	ef.fd = open(KCORE_PATH, O_RDONLY);
197*f3c0d74eSWarner Losh 	if (ef.fd == -1) {
198*f3c0d74eSWarner Losh 		perror("open " KCORE_PATH "\n");
199*f3c0d74eSWarner Losh 		return (false);
200*f3c0d74eSWarner Losh 	}
201*f3c0d74eSWarner Losh 
202*f3c0d74eSWarner Losh 	/*
203*f3c0d74eSWarner Losh 	 * Read in the first page. ELF files have a header that says how many
204*f3c0d74eSWarner Losh 	 * sections are in the file, whre they are, etc. All the Phdr are in the
205*f3c0d74eSWarner Losh 	 * first page. Read it, verify the headers, then loop through these Phdr
206*f3c0d74eSWarner Losh 	 * to find the address where addr is mapped to read it.
207*f3c0d74eSWarner Losh 	 */
208*f3c0d74eSWarner Losh 	rv = read(ef.fd, ef.buf, sizeof(ef.buf));
209*f3c0d74eSWarner Losh 	if (rv != sizeof(ef.buf)) {
210*f3c0d74eSWarner Losh 		perror("short hdr read\n");
211*f3c0d74eSWarner Losh 		close(ef.fd);
212*f3c0d74eSWarner Losh 		return (false);
213*f3c0d74eSWarner Losh 	}
214*f3c0d74eSWarner Losh 	hdr = (Elf64_Ehdr *)&ef.buf;
215*f3c0d74eSWarner Losh 	if (!IS_ELF(*hdr)) {
216*f3c0d74eSWarner Losh 		fprintf(stderr, "Not Elf\n");
217*f3c0d74eSWarner Losh 		close(ef.fd);
218*f3c0d74eSWarner Losh 		return (false);
219*f3c0d74eSWarner Losh 	}
220*f3c0d74eSWarner Losh 	if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS ||	/* Layout ? */
221*f3c0d74eSWarner Losh 	    hdr->e_ident[EI_DATA] != ELF_TARG_DATA ||
222*f3c0d74eSWarner Losh 	    hdr->e_ident[EI_VERSION] != EV_CURRENT ||	/* Version ? */
223*f3c0d74eSWarner Losh 	    hdr->e_version != EV_CURRENT ||
224*f3c0d74eSWarner Losh 	    hdr->e_machine != ELF_TARG_MACH ||		/* Machine ? */
225*f3c0d74eSWarner Losh 	    hdr->e_type != ET_CORE) {
226*f3c0d74eSWarner Losh 		fprintf(stderr, "Not what I expect\n");
227*f3c0d74eSWarner Losh 		close(ef.fd);
228*f3c0d74eSWarner Losh 		return (false);
229*f3c0d74eSWarner Losh 	}
230*f3c0d74eSWarner Losh 
231*f3c0d74eSWarner Losh 	phdr = (Elf64_Phdr *)(ef.buf + hdr->e_phoff);
232*f3c0d74eSWarner Losh 	for (int i = 0; i < hdr->e_phnum; i++) {
233*f3c0d74eSWarner Losh 		if (phdr[i].p_type != PT_LOAD)
234*f3c0d74eSWarner Losh 			continue;
235*f3c0d74eSWarner Losh 		if (addr < phdr[i].p_vaddr ||
236*f3c0d74eSWarner Losh 		    addr >= phdr[i].p_vaddr + phdr[i].p_filesz)
237*f3c0d74eSWarner Losh 			continue;
238*f3c0d74eSWarner Losh 		lseek(ef.fd, (off_t)phdr[i].p_offset + addr - phdr[i].p_vaddr,
239*f3c0d74eSWarner Losh 			SEEK_SET);
240*f3c0d74eSWarner Losh 		rv = read(ef.fd, buf, len);
241*f3c0d74eSWarner Losh 		if (rv != len)
242*f3c0d74eSWarner Losh 			perror("Can't read buffer\n");
243*f3c0d74eSWarner Losh 		close(ef.fd);
244*f3c0d74eSWarner Losh 		return (rv == len);
245*f3c0d74eSWarner Losh 	}
246*f3c0d74eSWarner Losh 
247*f3c0d74eSWarner Losh 	close(ef.fd);
248*f3c0d74eSWarner Losh 	return (false);
249*f3c0d74eSWarner Losh }
250*f3c0d74eSWarner Losh 
251*f3c0d74eSWarner Losh /*
252*f3c0d74eSWarner Losh  * Read a value from the Linux kernel. We lookup @sym and read @len bytes into
253*f3c0d74eSWarner Losh  * @buf. Returns true if we got it, false on an error.
254*f3c0d74eSWarner Losh  */
255*f3c0d74eSWarner Losh bool
data_from_kernel(const char * sym,void * buf,size_t len)256*f3c0d74eSWarner Losh data_from_kernel(const char *sym, void *buf, size_t len)
257*f3c0d74eSWarner Losh {
258*f3c0d74eSWarner Losh 	unsigned long addr;
259*f3c0d74eSWarner Losh 
260*f3c0d74eSWarner Losh 	addr = symbol_addr(sym);
261*f3c0d74eSWarner Losh 	if (addr == 0) {
262*f3c0d74eSWarner Losh 		fprintf(stderr, "Can't find symbol %s", sym);
263*f3c0d74eSWarner Losh 		return (false);
264*f3c0d74eSWarner Losh 	}
265*f3c0d74eSWarner Losh 	if (!read_at_address(addr, buf, len)) {
266*f3c0d74eSWarner Losh 		fprintf(stderr, "Can't read from kernel");
267*f3c0d74eSWarner Losh 		return (false);
268*f3c0d74eSWarner Losh 	}
269*f3c0d74eSWarner Losh 	return (true);
270*f3c0d74eSWarner Losh }
271*f3c0d74eSWarner Losh 
272*f3c0d74eSWarner Losh #ifndef _STANDALONE
273*f3c0d74eSWarner Losh /*
274*f3c0d74eSWarner Losh  * Silly  little test case to test on a random Linux system.
275*f3c0d74eSWarner Losh  */
276*f3c0d74eSWarner Losh int
main(int argc,char ** argv)277*f3c0d74eSWarner Losh main(int argc, char **argv)
278*f3c0d74eSWarner Losh {
279*f3c0d74eSWarner Losh 	struct boot_params bp;
280*f3c0d74eSWarner Losh 
281*f3c0d74eSWarner Losh 	if (data_from_kernel("boot_params", &bp, sizeof(bp))) {
282*f3c0d74eSWarner Losh 		fprintf(stderr, "Something went wrong\n");
283*f3c0d74eSWarner Losh 	} else {
284*f3c0d74eSWarner Losh 		printf("sig %#x systab %#lx memmap %#lx mmapsize %d md_size %d md_vers %d\n",
285*f3c0d74eSWarner Losh 		    bp.efi_info.efi_loader_signature,
286*f3c0d74eSWarner Losh 		    (long)(bp.efi_info.efi_systab | ((long)bp.efi_info.efi_systab_hi << 32)),
287*f3c0d74eSWarner Losh 		    (long)(bp.efi_info.efi_memmap | ((long)bp.efi_info.efi_memmap_hi << 32)),
288*f3c0d74eSWarner Losh 		    bp.efi_info.efi_memmap_size, bp.efi_info.efi_memdesc_size,
289*f3c0d74eSWarner Losh 		    bp.efi_info.efi_memdesc_version);
290*f3c0d74eSWarner Losh 	}
291*f3c0d74eSWarner Losh }
292*f3c0d74eSWarner Losh #endif
293