1use memf_core::object_reader::ObjectReader;
17use memf_format::PhysicalMemoryProvider;
18use serde::Serialize;
19
20#[derive(Debug, Clone, Serialize)]
22pub struct DmesgEntry {
23 pub timestamp_ns: u64,
25 pub level: u8,
27 pub facility: u8,
29 pub message: String,
31}
32
33const PRINTK_HEADER_SIZE: usize = 16;
35
36const MAX_ENTRIES: usize = 65_536;
38
39pub fn extract_dmesg<P: PhysicalMemoryProvider>(
51 reader: &ObjectReader<P>,
52) -> crate::Result<Vec<DmesgEntry>> {
53 let log_buf_sym = match reader.symbols().symbol_address("log_buf") {
55 Some(addr) => addr,
56 None => return Ok(Vec::new()),
57 };
58
59 let buf_vaddr: u64 = {
61 let mut buf = [0u8; 8];
62 reader.vas().read_virt(log_buf_sym, &mut buf)?;
63 u64::from_le_bytes(buf)
64 };
65
66 let log_buf_len_sym = match reader.symbols().symbol_address("log_buf_len") {
68 Some(addr) => addr,
69 None => return Ok(Vec::new()),
70 };
71 let buf_len: u32 = {
72 let mut buf = [0u8; 4];
73 reader.vas().read_virt(log_buf_len_sym, &mut buf)?;
74 u32::from_le_bytes(buf)
75 };
76 let buf_len = buf_len as usize;
77
78 if buf_len == 0 {
79 return Ok(Vec::new());
80 }
81
82 let ring = reader.read_bytes(buf_vaddr, buf_len)?;
84
85 let mut entries = Vec::new();
87 let mut offset: usize = 0;
88
89 while offset + PRINTK_HEADER_SIZE <= buf_len && entries.len() < MAX_ENTRIES {
90 let ts_nsec = ring[offset..offset + 8]
92 .try_into()
93 .map_or(0, u64::from_le_bytes);
94 let len = ring[offset + 8..offset + 10]
95 .try_into()
96 .map_or(0, u16::from_le_bytes) as usize;
97 let text_len = ring[offset + 10..offset + 12]
98 .try_into()
99 .map_or(0, u16::from_le_bytes) as usize;
100 let _dict_len = ring[offset + 12..offset + 14]
101 .try_into()
102 .map_or(0, u16::from_le_bytes);
103 let facility = ring[offset + 14];
104 let level = ring[offset + 15];
105
106 if len == 0 {
108 break;
109 }
110
111 if offset + len > buf_len {
113 break;
114 }
115
116 let text_start = offset + PRINTK_HEADER_SIZE;
118 let text_end = text_start + text_len.min(buf_len - text_start);
119 let message = String::from_utf8_lossy(&ring[text_start..text_end]).into_owned();
120
121 entries.push(DmesgEntry {
122 timestamp_ns: ts_nsec,
123 level,
124 facility,
125 message,
126 });
127
128 offset += len;
129 }
130
131 Ok(entries)
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use memf_core::object_reader::ObjectReader;
138 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
139 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
140 use memf_symbols::isf::IsfResolver;
141 use memf_symbols::test_builders::IsfBuilder;
142
143 fn make_reader(isf: &IsfBuilder, ptb: PageTableBuilder) -> ObjectReader<SyntheticPhysMem> {
145 let json = isf.build_json();
146 let resolver = IsfResolver::from_value(&json).unwrap();
147 let (cr3, mem) = ptb.build();
148 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
149 ObjectReader::new(vas, Box::new(resolver))
150 }
151
152 #[test]
154 fn extract_dmesg_no_symbol() {
155 let isf = IsfBuilder::new().add_struct("printk_log", 16);
156 let ptb = PageTableBuilder::new();
157 let reader = make_reader(&isf, ptb);
158
159 let entries = extract_dmesg(&reader).unwrap();
160 assert!(
161 entries.is_empty(),
162 "expected empty Vec when log_buf symbol is missing"
163 );
164 }
165
166 #[test]
168 fn extract_dmesg_empty_buffer() {
169 let log_buf_sym_vaddr: u64 = 0xFFFF_8000_0010_0000;
174 let log_buf_len_sym_vaddr: u64 = 0xFFFF_8000_0010_1000;
175 let buf_vaddr: u64 = 0xFFFF_8000_0020_0000;
176
177 let log_buf_sym_paddr: u64 = 0x0010_0000; let log_buf_len_sym_paddr: u64 = 0x0010_1000;
179 let buf_paddr: u64 = 0x0020_0000; let buf_len: u32 = 4096;
182
183 let isf = IsfBuilder::new()
184 .add_symbol("log_buf", log_buf_sym_vaddr)
185 .add_symbol("log_buf_len", log_buf_len_sym_vaddr);
186
187 let ptb = PageTableBuilder::new()
188 .map_4k(log_buf_sym_vaddr, log_buf_sym_paddr, flags::WRITABLE)
190 .map_4k(
191 log_buf_len_sym_vaddr,
192 log_buf_len_sym_paddr,
193 flags::WRITABLE,
194 )
195 .map_4k(buf_vaddr, buf_paddr, flags::WRITABLE)
197 .write_phys_u64(log_buf_sym_paddr, buf_vaddr)
199 .write_phys(log_buf_len_sym_paddr, &buf_len.to_le_bytes());
201
202 let reader = make_reader(&isf, ptb);
203 let entries = extract_dmesg(&reader).unwrap();
204 assert!(
205 entries.is_empty(),
206 "expected empty Vec for zero-filled buffer"
207 );
208 }
209
210 #[test]
212 fn extract_dmesg_single_entry() {
213 let log_buf_sym_vaddr: u64 = 0xFFFF_8000_0010_0000;
223 let log_buf_len_sym_vaddr: u64 = 0xFFFF_8000_0010_1000;
224 let buf_vaddr: u64 = 0xFFFF_8000_0020_0000;
225
226 let log_buf_sym_paddr: u64 = 0x0010_0000;
227 let log_buf_len_sym_paddr: u64 = 0x0010_1000;
228 let buf_paddr: u64 = 0x0020_0000;
229
230 let message = b"Hello from kernel";
231 let text_len = message.len() as u16; let dict_len: u16 = 0;
233 let record_len: u16 = (16 + text_len + dict_len).div_ceil(4) * 4;
235 let ts_nsec: u64 = 1_000_000_000; let facility: u8 = 0; let level: u8 = 6; let buf_len: u32 = 4096;
240
241 let isf = IsfBuilder::new()
242 .add_symbol("log_buf", log_buf_sym_vaddr)
243 .add_symbol("log_buf_len", log_buf_len_sym_vaddr);
244
245 let mut record = vec![0u8; record_len as usize];
247 record[0..8].copy_from_slice(&ts_nsec.to_le_bytes());
248 record[8..10].copy_from_slice(&record_len.to_le_bytes());
249 record[10..12].copy_from_slice(&text_len.to_le_bytes());
250 record[12..14].copy_from_slice(&dict_len.to_le_bytes());
251 record[14] = facility;
252 record[15] = level;
253 record[16..16 + message.len()].copy_from_slice(message);
254
255 let ptb = PageTableBuilder::new()
256 .map_4k(log_buf_sym_vaddr, log_buf_sym_paddr, flags::WRITABLE)
257 .map_4k(
258 log_buf_len_sym_vaddr,
259 log_buf_len_sym_paddr,
260 flags::WRITABLE,
261 )
262 .map_4k(buf_vaddr, buf_paddr, flags::WRITABLE)
263 .write_phys_u64(log_buf_sym_paddr, buf_vaddr)
265 .write_phys(log_buf_len_sym_paddr, &buf_len.to_le_bytes())
267 .write_phys(buf_paddr, &record);
269
270 let reader = make_reader(&isf, ptb);
271 let entries = extract_dmesg(&reader).unwrap();
272
273 assert_eq!(entries.len(), 1);
274 assert_eq!(entries[0].timestamp_ns, 1_000_000_000);
275 assert_eq!(entries[0].level, 6);
276 assert_eq!(entries[0].facility, 0);
277 assert_eq!(entries[0].message, "Hello from kernel");
278 }
279}