1 (* Memory info for virtual domains.
2 (C) Copyright 2008 Richard W.M. Jones, Red Hat Inc.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 module C = Libvirt.Connect
26 module D = Libvirt.Domain
28 open Virt_mem_gettext.Gettext
32 (* Make the kernel size around 16 MB, but just a bit smaller than
33 * maximum string length so we can still run this on a 32 bit platform.
36 if Sys.word_size = 32 then Sys.max_string_length
39 (* When tools register themselves, they are added to this list.
40 * Later, we will alphabetize the list.
44 (* Registration function used by the tools. *)
46 ?(needs_ksyms = false) ?(needs_utsname = false)
47 ?(needs_tasks = false) ?(needs_net_devices = false)
48 ?(needs_everything = false)
50 ?(external_cmd = true)
53 name summary description =
55 (name, (name, summary, description,
56 needs_ksyms, needs_utsname, needs_tasks, needs_net_devices,
58 run, external_cmd, extra_args, argcheck))
61 (* Main program, called from mem/virt_mem_main.ml when all the
62 * tools have had a chance to register themselves.
65 (* Get the registered tools, alphabetically. *)
67 let tools = List.sort ~cmp:(fun (a,_) (b,_) -> compare a b) tools in
69 (* Which tool did the user want to run? Look at the executable
70 * name (eg. 'virt-dmesg' => tool == dmesg). If we don't recognise
71 * the executable name then we must look for the first parameter
72 * which doesn't begin with a '-' character.
74 * Note that we must do all of this before using the OCaml Arg
75 * module to properly parse the command line (below), so that
76 * we can have a usage message ready.
78 let tool, ignore_first_anon_arg =
79 let prog = Sys.executable_name in (* eg. "/usr/bin/virt-dmesg.opt" *)
80 let prog = Filename.basename prog in(* eg. "virt-dmesg.opt" *)
81 let prog = (* eg. "virt-dmesg" *)
82 try Filename.chop_extension prog with Invalid_argument _ -> prog in
83 let prog = (* eg. "dmesg" *)
84 if String.starts_with prog "virt-" then
85 String.sub prog 5 (String.length prog - 5)
87 try Some (List.assoc prog tools), false
89 let arg1 = (* First non-option argument. *)
90 match Array.to_list Sys.argv with
93 let rec loop = function
95 | a::args when String.length a > 0 && a.[0] = '-' -> loop args
100 | None -> None, false
101 | Some prog -> (* Recognisable first argument? *)
103 try Filename.chop_extension prog with Invalid_argument _ -> prog in
105 if String.starts_with prog "virt-" then
106 String.sub prog 5 (String.length prog - 5)
108 (try Some (List.assoc prog tools), true
109 with Not_found -> None, false) in
111 (* Make a usage message. *)
114 | None -> (* Generic usage message. *)
115 let tools = List.map (
116 fun (name, (_, summary, _, _, _, _, _, _, _, external_cmd, _, _)) ->
117 if external_cmd then "virt-"^name, summary
118 else "virt-mem "^name, summary
120 (* Maximum width of field in the left hand column. *)
122 List.fold_left max 0 (List.map String.length (List.map fst tools)) in
123 let tools = List.map (fun (l,r) -> pad max_width l, r) tools in
124 let tools = List.map (fun (l,r) -> " " ^ l ^ " - " ^ r) tools in
125 let tools = String.concat "\n" tools in
129 virt-mem: Tools for providing information about virtual machines
131 Currently available tools include:
135 <tool> [-options] [domains...]
137 To display extra help for a single tool, do:
138 virt-mem --help <tool>
142 (* Tool-specific usage message. *)
143 | Some (name, summary, description, _, _, _, _, _, _, external_cmd, _, _) ->
145 if external_cmd then "virt-" ^ name else "virt-mem " ^ name in
154 Options:") cmd summary description in
156 (* Now begin proper parsing of the command line arguments. *)
157 let debug = ref false in
158 let testimages = ref [] in
160 let anon_args = ref [] in
162 (* Default wordsize (-W). *)
163 let def_wordsize = ref None in
164 let set_wordsize = function
165 | "32" -> def_wordsize := Some W32
166 | "64" -> def_wordsize := Some W64
167 | "auto" -> def_wordsize := None
168 | str -> failwith (sprintf (f_"set_wordsize: %s: unknown wordsize") str)
171 (* Default endianness (-E). *)
172 let def_endian = ref None in
173 let set_endian = function
174 | "auto" -> def_endian := None
175 | "le" | "little" | "littleendian" | "intel" ->
176 def_endian := Some Bitstring.LittleEndian
177 | "be" | "big" | "bigendian" | "motorola" ->
178 def_endian := Some Bitstring.BigEndian
179 | str -> failwith (sprintf (f_"set_endian: %s: unknown endianness") str)
182 (* Default architecture (-A). *)
183 let def_architecture = ref None in
184 let set_architecture = function
185 | "auto" -> def_architecture := None
187 let arch = architecture_of_string arch in
188 def_architecture := Some arch;
189 def_endian := Some (endian_of_architecture arch);
190 def_wordsize := Some (wordsize_of_architecture arch)
193 (* Default text address (-T). *)
194 let def_text_addr = ref 0L (* 0 = auto-detect *) in
195 let def_kernel_min = ref 0L in
196 let def_kernel_max = ref 0L in
197 let set_text_addr = function
198 | "auto" -> def_text_addr := 0L
200 (* common for x86, but we should be able to try a selection *)
201 def_text_addr := 0xc010_0000_L;
202 def_kernel_min := 0xc010_0000_L;
203 def_kernel_max := 0xffff_ffff_L
204 | "x86-64"|"x86_64" ->
205 def_text_addr := 0xffffffff_81000000_L;
206 def_kernel_min := 0xffffffff_81000000_L;
207 def_kernel_max := 0xffffffff_ffffffff_L;
209 let strs = String.nsplit str "," in
212 def_text_addr := Int64.of_string str;
213 def_kernel_min := !def_text_addr;
215 if !def_text_addr < 0x1_0000_0000_L
217 else 0xffffffff_ffffffff_L
218 | [str1;str2;str3] ->
219 def_text_addr := Int64.of_string str1;
220 def_kernel_min := Int64.of_string str2;
221 def_kernel_max := Int64.of_string str3
222 | _ -> failwith (sprintf (f_"set_text_addr: %s: incorrect number of parameters to -T option") str)
225 (* Handle -t option. *)
226 let memory_image filename =
228 (!def_wordsize, !def_endian, !def_architecture,
229 !def_text_addr, !def_kernel_min, !def_kernel_max, filename)
233 (* Handle --version option. *)
235 printf "virt-mem %s\n" Virt_mem_version.version;
237 let major, minor, release =
238 let v, _ = Libvirt.get_version () in
239 v / 1_000_000, (v / 1_000) mod 1_000, v mod 1_000 in
240 printf "libvirt %d.%d.%d\n" major minor release;
244 (* Handle --list-kernels option. *)
245 let list_kernels () =
246 List.iter print_endline Virt_mem_kernels.kernels;
250 (* Function to collect up any anonymous args (domain names/IDs). *)
251 let anon_arg str = anon_args := str :: !anon_args in
253 (* Construct the argspec.
254 * May include extra arguments specified by the tool.
257 let extra_args = match tool with
259 | Some (_, _,_, _, _, _, _, _, _, _, extra_args, _) -> extra_args in
261 "-A", Arg.String set_architecture,
262 "arch " ^ s_"Set kernel architecture, endianness and word size";
263 "-E", Arg.String set_endian,
264 "endian " ^ s_"Set kernel endianness";
265 "-T", Arg.String set_text_addr,
266 "addr " ^ s_"Set kernel text address";
267 "-W", Arg.String set_wordsize,
268 "addr " ^ s_"Set kernel word size";
269 "-c", Arg.Set_string uri,
270 "uri " ^ s_ "Connect to URI";
271 "--connect", Arg.Set_string uri,
272 "uri " ^ s_ "Connect to URI";
273 "--debug", Arg.Set debug,
274 " " ^ s_"Debug mode (default: false)";
275 "--list-kernels", Arg.Unit list_kernels,
276 " " ^ s_"List known kernels";
277 "-t", Arg.String memory_image,
278 "image " ^ s_"Use saved kernel memory image";
279 "--version", Arg.Unit version,
280 " " ^ s_"Display version and exit";
283 (* Sort options alphabetically on first alpha character. *)
284 let cmp (a,_,_) (b,_,_) =
286 let a = String.strip ~chars a and b = String.strip ~chars b in
289 let argspec = List.sort ~cmp argspec in
290 (* Make the options line up nicely. *)
293 (* Parse the command line. This will exit if --version or --help found. *)
294 Arg.parse argspec anon_arg usage_msg;
296 let testimages = !testimages in
297 let debug = !debug in
298 let uri = if !uri = "" then None else Some !uri in
300 (* Discard the first anonymous argument if, above, we previously
301 * found it contained the tool name.
303 let anon_args = List.rev !anon_args in
305 if ignore_first_anon_arg then List.tl anon_args else anon_args in
307 (* At this point, either --help was specified on the command line
308 * (and so the program has exited) or we must have determined tool,
309 * or the user didn't give us a valid tool (eg. "virt-mem foobar").
310 * Detect that final case now and give an error.
313 needs_ksyms, needs_utsname, needs_tasks, needs_net_devices,
315 run, external_cmd, extra_args, argcheck =
320 virt-mem: I could not work out which tool you are trying to run.
321 Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
324 if debug then eprintf "tool = %s\n%!" name;
326 (* Optional argument checking in the tool. *)
329 | Some argcheck -> argcheck debug
332 (* Get the kernel images. *)
334 if testimages = [] then (
337 try C.connect_readonly ?name ()
338 with Libvirt.Virterror err ->
339 prerr_endline (Libvirt.Virterror.to_string err);
340 (* If non-root and no explicit connection URI, print a warning. *)
341 if Unix.geteuid () <> 0 && name = None then (
342 print_endline (s_ "NB: If you want to monitor a local Xen hypervisor, you usually need to be root");
346 (* If we have a list of parameters, then it is the domain names / UUIDs /
347 * IDs ONLY that we wish to display. Otherwise, display all active.
350 if anon_args = [] then (
351 (* List of active domains. *)
352 let nr_active_doms = C.num_of_domains conn in
354 Array.to_list (C.list_domains conn nr_active_doms) in
355 List.map (D.lookup_by_id conn) active_doms
360 try D.lookup_by_uuid_string conn arg
362 try D.lookup_by_name conn arg
364 try D.lookup_by_id conn (int_of_string arg)
366 failwith (sprintf (f_"%s: unknown domain (not a UUID, name or ID of any active domain)") arg) in
368 (* XXX Primitive test to see if the domain is active. *)
369 let is_active = try D.get_id dom >= 0 with _ -> false in
370 if not is_active then
371 failwith (sprintf (f_"%s: domain is not running") arg);
378 let xmls = List.map (fun dom -> dom, D.get_xml_desc dom) doms in
381 let xmls = List.map (fun (dom, xml) ->
382 dom, Xml.parse_string xml) xmls in
384 (* XXX Do something with the XML XXX
385 * such as detecting arch, wordsize, endianness.
395 let domname = D.get_name dom in
398 match !def_wordsize with
401 (sprintf (f_"%s: use -W to define word size for this image")
405 match !def_endian with
408 (sprintf (f_"%s: use -E to define endianness for this image")
413 match !def_architecture with
414 | Some I386 -> I386 | Some X86_64 -> X86_64
417 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") domname) in
419 if !def_text_addr = 0L ||
420 !def_kernel_min = 0L ||
421 !def_kernel_max = 0L then
423 (sprintf (f_"%s: use -T to define kernel load address for this image") domname);
425 (* Download the static part of the kernel. *)
426 let start_t = gettimeofday () in
430 load_static_memory ~dom ~domname ~arch
432 ~kernel_min:!def_kernel_min ~kernel_max:!def_kernel_max
433 !def_text_addr kernel_size
435 | LoadMemoryError (AddressOutOfRange, _) ->
436 prerr_endline (s_"virt-mem: error loading kernel memory: address out of range
437 Possibly the '-T' command line parameter was used inconsistently.");
439 (* Allow any other exceptions to escape & kill the program. *) in
442 let end_t = gettimeofday () in
443 eprintf "timing: downloading kernel took %f seconds\n%!"
451 (* One or more -t options passed. *)
452 if anon_args <> [] then
453 failwith (s_"virt-mem: if -t given on command line, then no domain arguments should be listed");
456 fun (wordsize, endian, arch,
457 text_addr, kernel_min, kernel_max, filename) ->
458 (* Quite a lot of limitations on the kernel images we can
459 * handle at the moment ...
461 (* XXX We could auto-detect wordsize easily. *)
466 (sprintf (f_"%s: use -W to define word size for this image")
473 (sprintf (f_"%s: use -E to define endianness for this image")
479 | Some I386 -> I386 | Some X86_64 -> X86_64
482 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") filename) in
484 if text_addr = 0L then
486 (sprintf (f_"%s: use -T to define kernel load address for this image")
489 (* Map the virtual memory. *)
490 let fd = openfile filename [O_RDONLY] 0 in
491 let mem = Virt_mem_mmap.of_file fd text_addr in
493 (* Force the wordsize and endianness. *)
494 let mem = Virt_mem_mmap.set_wordsize mem wordsize in
495 let mem = Virt_mem_mmap.set_endian mem endian in
497 { dom = None; domname = filename; arch = arch;
498 kernel_min = kernel_min; kernel_max = kernel_max;
499 mem = mem; addrmap = Kernel.AddrMap.empty;
500 ksyms = Ksymmap.empty; have_ksyms = false; have_kallsyms = false;
502 have_tasks = false; have_net_devices = false }
506 (* Certain needs are dependent on others ... *)
508 if needs_utsname then true
510 let needs_ksyms, needs_utsname =
511 if needs_tasks then true, true
512 else needs_ksyms, needs_utsname in
513 let needs_ksyms, needs_utsname =
514 if needs_net_devices then true, true
515 else needs_ksyms, needs_utsname in
516 let needs_ksyms, needs_utsname, needs_tasks, needs_net_devices =
517 if needs_everything then true, true, true, true
518 else needs_ksyms, needs_utsname, needs_tasks, needs_net_devices in
520 let errors = ref 0 in
525 (* Do the kernel symbol analysis. *)
527 if not needs_ksyms then kimage
529 (* Look for ordinary kernel symbols: *)
530 let kimage = Virt_mem_ksyms.find_kernel_symbols debug kimage in
532 if kimage.have_ksyms then
533 (* Look for kallsyms: *)
534 Virt_mem_kallsyms.find_kallsyms debug kimage
538 (* Get the kernel version (utsname analysis). *)
540 if not needs_utsname then kimage
542 if kimage.have_ksyms then
543 Virt_mem_utsname.find_utsname debug kimage
550 if not needs_tasks then kimage
552 let { ksyms = ksyms; have_ksyms = have_ksyms; utsname = utsname;
553 addrmap = addrmap } = kimage in
554 match have_ksyms, utsname with
555 | true, Some { uts_kernel_release = kernel_version } ->
556 let kimage = ref kimage in
557 let load struct_name addr size =
559 eprintf "load for task, %s: %Lx %d ...\n"
560 struct_name addr size;
562 Virt_mem_mmap.is_mapped_range !kimage.mem addr size in
563 if not mapped then kimage := load_memory !kimage addr size;
564 let bits = Virt_mem_mmap.get_bytes !kimage.mem addr size in
565 Bitstring.bitstring_of_string bits
569 let init_task = Ksymmap.find "init_task" ksyms in
570 Some (Kernel.task_struct_follower debug kernel_version
571 load addrmap init_task)
574 eprintf (f_"%s: cannot find init_task symbol in kernel image.\n") !kimage.domname;
579 { !kimage with addrmap = addrmap; have_tasks = true }
584 (* Get the net devices. *)
586 if not needs_net_devices then kimage
588 let { ksyms = ksyms; have_ksyms = have_ksyms; utsname = utsname;
589 addrmap = addrmap } = kimage in
590 match have_ksyms, utsname with
591 | true, Some { uts_kernel_release = kernel_version } ->
592 let kimage = ref kimage in
593 let load struct_name addr size =
595 eprintf "load for net dev, %s: %Lx %d ...\n"
596 struct_name addr size;
598 Virt_mem_mmap.is_mapped_range !kimage.mem addr size in
599 if not mapped then kimage := load_memory !kimage addr size;
600 let bits = Virt_mem_mmap.get_bytes !kimage.mem addr size in
601 Bitstring.bitstring_of_string bits
605 let dev_base = Ksymmap.find "dev_base" ksyms in
606 Some (Kernel.net_device_follower debug kernel_version
607 load addrmap dev_base)
610 let dev_base_head = Ksymmap.find "dev_base_head" ksyms in
611 (* XXX adjust to get offset of start of net_device *)
615 let init_net = Ksymmap.find "init_net" ksyms in
616 Some (Kernel.net_follower debug kernel_version
617 load addrmap init_net)
619 eprintf (f_"%s: cannot find dev_base, dev_base_head or init_net symbols in kernel image.\n") !kimage.domname;
624 { !kimage with addrmap = addrmap; have_net_devices = true }
629 (* Run the tool's main function. *)
630 if not needs_everything then (
631 if needs_ksyms && kimage.have_ksyms = false then
632 failwith (s_"could not read kernel symbols")
633 else if needs_utsname && kimage.utsname = None then
634 failwith (s_"could not read kernel version")
635 else if needs_tasks && kimage.have_tasks = false then
636 failwith (s_"could not read process table")
637 else if needs_net_devices && kimage.have_net_devices = false then
638 failwith (s_"could not read net device table")
642 eprintf "%s: %s\n" kimage.domname (Printexc.to_string exn);
646 exit (if !errors > 0 then 1 else 0)