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