1use log::info;
2
3use memflow::*;
4use memflow_derive::connector;
5
6use core::ffi::c_void;
7use libc::{c_ulong, iovec, pid_t, sysconf, _SC_IOV_MAX};
8
9#[derive(Clone, Copy)]
10#[repr(transparent)]
11struct IoSendVec(iovec);
12
13unsafe impl Send for IoSendVec {}
14
15fn qemu_arg_opt(args: &[String], argname: &str, argopt: &str) -> Option<String> {
16 for (idx, arg) in args.iter().enumerate() {
17 if arg == argname {
18 let name = args[idx + 1].split(',');
19 for (i, kv) in name.clone().enumerate() {
20 let kvsplt = kv.split('=').collect::<Vec<_>>();
21 if kvsplt.len() == 2 {
22 if kvsplt[0] == argopt {
23 return Some(kvsplt[1].to_string());
24 }
25 } else if i == 0 {
26 return Some(kv.to_string());
27 }
28 }
29 }
30 }
31
32 None
33}
34
35#[derive(Clone)]
36pub struct QemuProcfs {
37 pub pid: pid_t,
38 pub mem_map: MemoryMap<(Address, usize)>,
39 temp_iov: Box<[IoSendVec]>,
40}
41
42impl QemuProcfs {
43 pub fn new() -> Result<Self> {
44 let prcs = procfs::process::all_processes()
45 .map_err(|_| Error::Connector("unable to list procfs processes"))?;
46 let prc = prcs
47 .iter()
48 .find(|p| p.stat.comm == "qemu-system-x86")
49 .ok_or_else(|| Error::Connector("qemu process not found"))?;
50 info!("qemu process found with pid {:?}", prc.stat.pid);
51
52 Self::with_process(prc)
53 }
54
55 pub fn with_guest_name(name: &str) -> Result<Self> {
56 let prcs = procfs::process::all_processes()
57 .map_err(|_| Error::Connector("unable to list procefs processes"))?;
58 let (prc, _) = prcs
59 .iter()
60 .filter(|p| p.stat.comm == "qemu-system-x86")
61 .filter_map(|p| {
62 if let Ok(c) = p.cmdline() {
63 Some((p, c))
64 } else {
65 None
66 }
67 })
68 .find(|(_, c)| qemu_arg_opt(c, "-name", "guest").unwrap_or_default() == name)
69 .ok_or_else(|| Error::Connector("qemu process not found"))?;
70 info!(
71 "qemu process with name {} found with pid {:?}",
72 name, prc.stat.pid
73 );
74
75 Self::with_process(prc)
76 }
77
78 fn with_process(prc: &procfs::process::Process) -> Result<Self> {
79 let mut maps = prc
81 .maps()
82 .map_err(|_| Error::Connector("unable to get qemu memory maps"))?;
83 maps.sort_by(|b, a| {
84 (a.address.1 - a.address.0)
85 .partial_cmp(&(b.address.1 - b.address.0))
86 .unwrap()
87 });
88 let map = maps
89 .get(0)
90 .ok_or_else(|| Error::Connector("qemu memory map could not be read"))?;
91 info!("qemu memory map found {:?}", map);
92
93 let map_base = map.address.0 as usize;
94 let map_size = (map.address.1 - map.address.0) as usize;
95 info!("qemu memory map size: {:x}", map_size);
96
97 let machine = qemu_arg_opt(
102 &prc.cmdline()
103 .map_err(|_| Error::Connector("unable to parse qemu arguments"))?,
104 "-machine",
105 "type",
106 )
107 .unwrap_or_else(|| "pc".into());
108 info!("qemu process started with machine: {}", machine);
109
110 let mut mem_map = MemoryMap::new();
111 if machine.contains("q35") {
112 mem_map.push_range(Address::NULL, size::kb(640).into(), map_base.into()); if map_size >= size::mb(2816) {
124 mem_map.push_range(
125 size::mb(1).into(),
126 size::gb(2).into(),
127 (map_base + size::mb(1)).into(),
128 ); mem_map.push_range(
130 size::gb(4).into(),
131 (map_size + size::gb(2)).into(),
132 (map_base + size::gb(2)).into(),
133 ); } else {
135 mem_map.push_range(
136 size::mb(1).into(),
137 map_size.into(),
138 (map_base + size::mb(1)).into(),
139 ); }
141 } else {
142 mem_map.push_range(Address::NULL, size::kb(768).into(), map_base.into()); mem_map.push_range(
155 size::kb(812).into(),
156 size::kb(824).into(),
157 (map_base + size::kb(812)).into(),
158 ); mem_map.push_range(
160 size::kb(928).into(),
161 size::kb(960).into(),
162 (map_base + size::kb(928)).into(),
163 ); mem_map.push_range(
165 size::mb(1).into(),
166 size::gb(3).into(),
167 (map_base + size::mb(1)).into(),
168 ); mem_map.push_range(
170 size::gb(4).into(),
171 (map_size + size::gb(1)).into(),
172 (map_base + size::gb(3)).into(),
173 ); }
175 info!("qemu machine mem_map: {:?}", mem_map);
176
177 let iov_max = unsafe { sysconf(_SC_IOV_MAX) } as usize;
178
179 Ok(Self {
180 pid: prc.stat.pid,
181 mem_map,
182 temp_iov: vec![
183 IoSendVec {
184 0: iovec {
185 iov_base: std::ptr::null_mut::<c_void>(),
186 iov_len: 0
187 }
188 };
189 iov_max * 2
190 ]
191 .into_boxed_slice(),
192 })
193 }
194
195 fn fill_iovec(addr: &Address, data: &[u8], liov: &mut IoSendVec, riov: &mut IoSendVec) {
196 let iov_len = data.len();
197
198 liov.0 = iovec {
199 iov_base: data.as_ptr() as *mut c_void,
200 iov_len,
201 };
202
203 riov.0 = iovec {
204 iov_base: addr.as_u64() as *mut c_void,
205 iov_len,
206 };
207 }
208
209 fn vm_error() -> Error {
210 match unsafe { *libc::__errno_location() } {
211 libc::EFAULT => Error::Connector("process_vm_readv failed: EFAULT (remote memory address is invalid)"),
212 libc::ENOMEM => Error::Connector("process_vm_readv failed: ENOMEM (unable to allocate memory for internal copies)"),
213 libc::EPERM => Error::Connector("process_vm_readv failed: EPERM (insifficient permissions to access the target address space)"),
214 libc::ESRCH => Error::Connector("process_vm_readv failed: ESRCH (process not found)"),
215 libc::EINVAL => Error::Connector("process_vm_readv failed: EINVAL (invalid value)"),
216 _ => Error::Connector("process_vm_readv failed: unknown error")
217 }
218 }
219}
220
221impl PhysicalMemory for QemuProcfs {
222 fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> {
223 let mem_map = &self.mem_map;
224 let temp_iov = &mut self.temp_iov;
225
226 let mut void = FnExtend::void();
227 let mut iter = mem_map.map_iter(
228 data.iter_mut()
229 .map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)),
230 &mut void,
231 );
232
233 let max_iov = temp_iov.len() / 2;
234 let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
235
236 let mut elem = iter.next();
237
238 let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
239 let mut iov_next = iov_iter.next();
240
241 while let Some(((addr, _), out)) = elem {
242 let (cnt, (liov, riov)) = iov_next.unwrap();
243
244 Self::fill_iovec(&addr, out, liov, riov);
245
246 iov_next = iov_iter.next();
247 elem = iter.next();
248
249 if elem.is_none() || iov_next.is_none() {
250 if unsafe {
251 libc::process_vm_readv(
252 self.pid,
253 iov_local.as_ptr().cast(),
254 (cnt + 1) as c_ulong,
255 iov_remote.as_ptr().cast(),
256 (cnt + 1) as c_ulong,
257 0,
258 )
259 } == -1
260 {
261 return Err(Self::vm_error());
262 }
263
264 iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
265 iov_next = iov_iter.next();
266 }
267 }
268
269 Ok(())
270 }
271
272 fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> {
273 let mem_map = &self.mem_map;
274 let temp_iov = &mut self.temp_iov;
275
276 let mut void = FnExtend::void();
277 let mut iter = mem_map.map_iter(data.iter().copied().map(<_>::from), &mut void);
278 let max_iov = temp_iov.len() / 2;
281 let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
282
283 let mut elem = iter.next();
284
285 let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
286 let mut iov_next = iov_iter.next();
287
288 while let Some(((addr, _), out)) = elem {
289 let (cnt, (liov, riov)) = iov_next.unwrap();
290
291 Self::fill_iovec(&addr, out, liov, riov);
292
293 iov_next = iov_iter.next();
294 elem = iter.next();
295
296 if elem.is_none() || iov_next.is_none() {
297 if unsafe {
298 libc::process_vm_writev(
299 self.pid,
300 iov_local.as_ptr().cast(),
301 (cnt + 1) as c_ulong,
302 iov_remote.as_ptr().cast(),
303 (cnt + 1) as c_ulong,
304 0,
305 )
306 } == -1
307 {
308 return Err(Self::vm_error());
309 }
310
311 iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
312 iov_next = iov_iter.next();
313 }
314 }
315
316 Ok(())
317 }
318
319 fn metadata(&self) -> PhysicalMemoryMetadata {
320 PhysicalMemoryMetadata {
321 size: self
322 .mem_map
323 .as_ref()
324 .iter()
325 .last()
326 .map(|map| map.base().as_usize() + map.output().1)
327 .unwrap(),
328 readonly: false,
329 }
330 }
331}
332
333#[connector(name = "qemu_procfs")]
335pub fn create_connector(args: &ConnectorArgs) -> Result<QemuProcfs> {
336 if let Some(name) = args.get("name").or_else(|| args.get_default()) {
337 QemuProcfs::with_guest_name(name)
338 } else {
339 QemuProcfs::new()
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn test_name() {
349 assert_eq!(
350 qemu_arg_opt(
351 &["-name".to_string(), "win10-test".to_string()],
352 "-name",
353 "guest"
354 ),
355 Some("win10-test".into())
356 );
357 assert_eq!(
358 qemu_arg_opt(
359 &[
360 "-test".to_string(),
361 "-name".to_string(),
362 "win10-test".to_string()
363 ],
364 "-name",
365 "guest"
366 ),
367 Some("win10-test".into())
368 );
369 assert_eq!(
370 qemu_arg_opt(
371 &["-name".to_string(), "win10-test,arg=opt".to_string()],
372 "-name",
373 "guest"
374 ),
375 Some("win10-test".into())
376 );
377 assert_eq!(
378 qemu_arg_opt(
379 &["-name".to_string(), "guest=win10-test,arg=opt".to_string()],
380 "-name",
381 "guest"
382 ),
383 Some("win10-test".into())
384 );
385 assert_eq!(
386 qemu_arg_opt(
387 &["-name".to_string(), "arg=opt,guest=win10-test".to_string()],
388 "-name",
389 "guest"
390 ),
391 Some("win10-test".into())
392 );
393 assert_eq!(
394 qemu_arg_opt(
395 &["-name".to_string(), "arg=opt".to_string()],
396 "-name",
397 "guest"
398 ),
399 None
400 );
401 }
402
403 #[test]
404 fn test_machine() {
405 assert_eq!(
406 qemu_arg_opt(
407 &["-machine".to_string(), "q35".to_string()],
408 "-machine",
409 "type"
410 ),
411 Some("q35".into())
412 );
413 assert_eq!(
414 qemu_arg_opt(
415 &[
416 "-test".to_string(),
417 "-machine".to_string(),
418 "q35".to_string()
419 ],
420 "-machine",
421 "type"
422 ),
423 Some("q35".into())
424 );
425 assert_eq!(
426 qemu_arg_opt(
427 &["-machine".to_string(), "q35,arg=opt".to_string()],
428 "-machine",
429 "type"
430 ),
431 Some("q35".into())
432 );
433 assert_eq!(
434 qemu_arg_opt(
435 &["-machine".to_string(), "type=pc,arg=opt".to_string()],
436 "-machine",
437 "type"
438 ),
439 Some("pc".into())
440 );
441 assert_eq!(
442 qemu_arg_opt(
443 &[
444 "-machine".to_string(),
445 "arg=opt,type=pc-i1440fx".to_string()
446 ],
447 "-machine",
448 "type"
449 ),
450 Some("pc-i1440fx".into())
451 );
452 assert_eq!(
453 qemu_arg_opt(
454 &["-machine".to_string(), "arg=opt".to_string()],
455 "-machine",
456 "type"
457 ),
458 None
459 );
460 }
461}