1use log::{error, info};
2
3use memflow::cglue;
4use memflow::connector::cpu_state::*;
5use memflow::mem::memory_view::RemapView;
6use memflow::mem::phys_mem::*;
7use memflow::os::root::Os;
8use memflow::prelude::v1::*;
9
10mod qemu_args;
11use qemu_args::{is_qemu, qemu_arg_opt};
12
13#[cfg(all(target_os = "linux", feature = "qmp"))]
14#[macro_use]
15extern crate scan_fmt;
16
17mod mem_map;
18use mem_map::qemu_mem_mappings;
19
20cglue_impl_group!(QemuProcfs<P: MemoryView + Clone>, ConnectorInstance, {
21 ConnectorCpuState
22});
23cglue_impl_group!(QemuProcfs<P: MemoryView + Clone>, IntoCpuState);
24
25#[derive(Clone)]
26pub struct QemuProcfs<P: MemoryView> {
27 view: RemapView<P>,
28}
29
30impl<P: MemoryView + Process> QemuProcfs<P> {
31 pub fn new<O: Os<IntoProcessType = P>>(
32 mut os: O,
33 map_override: Option<CTup2<Address, umem>>,
34 ) -> Result<Self> {
35 let mut proc = None;
36
37 let callback = &mut |info: ProcessInfo| {
38 if proc.is_none() && is_qemu(&info) {
39 proc = Some(info);
40 }
41
42 proc.is_none()
43 };
44
45 os.process_info_list_callback(callback.into())?;
46
47 Self::with_process(
48 os,
49 proc.ok_or_else(|| {
50 Error(ErrorOrigin::Connector, ErrorKind::TargetNotFound)
51 .log_error("No QEMU process could be found. Is QEMU running?")
52 })?,
53 map_override,
54 )
55 }
56
57 pub fn with_guest_name<O: Os<IntoProcessType = P>>(
58 mut os: O,
59 name: &str,
60 map_override: Option<CTup2<Address, umem>>,
61 ) -> Result<Self> {
62 let mut proc = None;
63
64 let callback = &mut |info: ProcessInfo| {
65 if proc.is_none()
66 && is_qemu(&info)
67 && qemu_arg_opt(info.command_line.split_whitespace(), "-name", "guest").as_deref()
68 == Some(name)
69 {
70 proc = Some(info);
71 }
72
73 proc.is_none()
74 };
75
76 os.process_info_list_callback(callback.into())?;
77
78 Self::with_process(
79 os,
80 proc.ok_or_else(||
81 Error(ErrorOrigin::Connector, ErrorKind::TargetNotFound)
82 .log_error("A QEMU process for the specified guest name could not be found. Is the QEMU process running?")
83 )?,
84 map_override,
85 )
86 }
87
88 pub fn with_pid<O: Os<IntoProcessType = P>>(
89 mut os: O,
90 pid: Pid,
91 map_override: Option<CTup2<Address, umem>>,
92 ) -> Result<Self> {
93 let proc = os.process_info_by_pid(pid)?;
94
95 Self::with_process(os, proc, map_override)
96 }
97
98 fn with_process<O: Os<IntoProcessType = P>>(
99 os: O,
100 info: ProcessInfo,
101 map_override: Option<CTup2<Address, umem>>,
102 ) -> Result<Self> {
103 info!(
104 "qemu process with name {} found with pid {:?}",
105 info.name, info.pid
106 );
107
108 let cmdline: String = info.command_line.to_string();
109
110 let mut prc = os.into_process_by_info(info)?;
111
112 let mut biggest_map = map_override;
113
114 let callback = &mut |range: MemoryRange| {
115 if biggest_map
116 .map(|CTup2(_, oldsize)| oldsize < range.1)
117 .unwrap_or(true)
118 {
119 biggest_map = Some(CTup2(range.0, range.1));
120 }
121
122 true
123 };
124
125 if map_override.is_none() {
126 prc.mapped_mem_range(
127 smem::mb(-1),
128 Address::NULL,
129 Address::INVALID,
130 callback.into(),
131 );
132 }
133
134 let qemu_map = biggest_map.ok_or_else(|| Error(ErrorOrigin::Connector, ErrorKind::NotFound)
135 .log_error("Unable to find the QEMU guest memory map. This usually indicates insufficient permissions to acquire the QEMU memory maps. Are you running with appropiate access rights?")
136 )?;
137
138 info!("qemu memory map found {:?}", qemu_map);
139
140 Self::with_cmdline_and_mem(prc, &cmdline, qemu_map)
141 }
142
143 fn with_cmdline_and_mem(prc: P, cmdline: &str, qemu_map: CTup2<Address, umem>) -> Result<Self> {
144 let mem_map = qemu_mem_mappings(cmdline, &qemu_map)?;
145 info!("qemu machine mem_map: {:?}", mem_map);
146
147 Ok(Self {
148 view: prc.into_remap_view(mem_map),
149 })
150 }
151}
152
153impl<P: MemoryView> PhysicalMemory for QemuProcfs<P> {
154 fn phys_read_raw_iter(
155 &mut self,
156 MemOps { inp, out, out_fail }: PhysicalReadMemOps,
157 ) -> Result<()> {
158 let inp = inp.map(|CTup3(addr, meta_addr, data)| CTup3(addr.into(), meta_addr, data));
159 MemOps::with_raw(inp, out, out_fail, |data| self.view.read_raw_iter(data))
160 }
161
162 fn phys_write_raw_iter(
163 &mut self,
164 MemOps { inp, out, out_fail }: PhysicalWriteMemOps,
165 ) -> Result<()> {
166 let inp = inp.map(|CTup3(addr, meta_addr, data)| CTup3(addr.into(), meta_addr, data));
167 MemOps::with_raw(inp, out, out_fail, |data| self.view.write_raw_iter(data))
168 }
169
170 fn metadata(&self) -> PhysicalMemoryMetadata {
171 let md = self.view.metadata();
172
173 PhysicalMemoryMetadata {
174 max_address: md.max_address,
175 real_size: md.real_size,
176 readonly: md.readonly,
177 ideal_batch_size: 4096,
178 }
179 }
180}
181
182impl<P: MemoryView + 'static> ConnectorCpuState for QemuProcfs<P> {
183 type CpuStateType<'a> = Fwd<&'a mut QemuProcfs<P>>;
184 type IntoCpuStateType = QemuProcfs<P>;
185
186 fn cpu_state(&mut self) -> Result<Self::CpuStateType<'_>> {
187 Ok(self.forward_mut())
188 }
189
190 fn into_cpu_state(self) -> Result<Self::IntoCpuStateType> {
191 Ok(self)
192 }
193}
194
195impl<P: MemoryView> CpuState for QemuProcfs<P> {
196 fn pause(&mut self) {}
197
198 fn resume(&mut self) {}
199}
200
201fn validator() -> ArgsValidator {
202 ArgsValidator::new()
203 .arg(ArgDescriptor::new("map_base").description("override of VM memory base"))
204 .arg(ArgDescriptor::new("map_size").description("override of VM memory size"))
205}
206
207#[connector(
209 name = "qemu",
210 help_fn = "help",
211 target_list_fn = "target_list",
212 accept_input = true,
213 return_wrapped = true
214)]
215fn create_plugin(
216 args: &ConnectorArgs,
217 os: Option<OsInstanceArcBox<'static>>,
218 lib: LibArc,
219) -> Result<ConnectorInstanceArcBox<'static>> {
220 let os = os.map(Result::Ok).unwrap_or_else(|| {
221 memflow_native::create_os(
222 &Default::default(),
223 Option::<std::sync::Arc<_>>::None.into(),
224 )
225 })?;
226
227 let qemu = create_connector_with_os(args, os)?;
228 Ok(memflow::plugins::connector::create_instance(
229 qemu, lib, args, false,
230 ))
231}
232
233pub fn create_connector(
234 args: &ConnectorArgs,
235) -> Result<QemuProcfs<IntoProcessInstanceArcBox<'static>>> {
236 create_connector_with_os(
237 args,
238 memflow_native::create_os(
239 &Default::default(),
240 Option::<std::sync::Arc<_>>::None.into(),
241 )?,
242 )
243}
244
245pub fn create_connector_with_os<O: Os>(
246 args: &ConnectorArgs,
247 os: O,
248) -> Result<QemuProcfs<O::IntoProcessType>> {
249 let validator = validator();
250
251 let name = args.target.as_deref();
252
253 let args = &args.extra_args;
254
255 let qemu = match validator.validate(args) {
256 Ok(_) => {
257 let map_override = args
258 .get("map_base")
259 .and_then(|base| umem::from_str_radix(base, 16).ok())
260 .zip(
261 args.get("map_size")
262 .and_then(|size| umem::from_str_radix(size, 16).ok()),
263 )
264 .map(|(start, size)| CTup2(Address::from(start), size));
265
266 if let Some(name) = name.or_else(|| args.get("name")) {
267 if let Ok(pid) = Pid::from_str_radix(name, 10) {
268 QemuProcfs::with_pid(os, pid, map_override)
269 } else {
270 QemuProcfs::with_guest_name(os, name, map_override)
271 }
272 } else {
273 QemuProcfs::new(os, map_override)
274 }
275 }
276 Err(err) => {
277 error!(
278 "unable to validate provided arguments, valid arguments are:\n{}",
279 validator
280 );
281 Err(err)
282 }
283 }?;
284
285 Ok(qemu)
286}
287
288pub fn help() -> String {
290 let validator = validator();
291 format!(
292 "\
293The `qemu` connector implements a memflow plugin interface
294for QEMU on top of the Process Filesystem on Linux.
295
296This connector requires access to the qemu process via the linux procfs.
297This means any process which loads this connector requires
298to have at least ptrace permissions set.
299
300The `target` argument specifies the target qemu virtual machine.
301The qemu virtual machine name can be specified when starting qemu with the -name flag.
302
303Alternatively, if `target` is a number, qemu process by PID will be accessed.
304
305Available arguments are:
306{validator}"
307 )
308}
309
310pub fn target_list() -> Result<Vec<TargetInfo>> {
312 let mut os = memflow_native::create_os(
313 &Default::default(),
314 Option::<std::sync::Arc<_>>::None.into(),
315 )?;
316
317 let mut out = vec![];
318
319 let callback = &mut |info: ProcessInfo| {
320 if is_qemu(&info) {
321 if let Some(n) = qemu_arg_opt(info.command_line.split_whitespace(), "-name", "guest") {
322 out.push(TargetInfo {
323 name: ReprCString::from(n),
324 });
325 }
326 }
327
328 true
329 };
330
331 os.process_info_list_callback(callback.into())?;
332
333 Ok(out)
334}