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_everything = false)
49 ?(external_cmd = true)
52 name summary description =
54 (name, (name, summary, description,
55 needs_ksyms, needs_utsname, needs_tasks, needs_everything,
56 run, external_cmd, extra_args, argcheck))
59 (* Main program, called from mem/virt_mem_main.ml when all the
60 * tools have had a chance to register themselves.
63 (* Get the registered tools, alphabetically. *)
65 let tools = List.sort ~cmp:(fun (a,_) (b,_) -> compare a b) tools in
67 (* Which tool did the user want to run? Look at the executable
68 * name (eg. 'virt-dmesg' => tool == dmesg). If we don't recognise
69 * the executable name then we must look for the first parameter
70 * which doesn't begin with a '-' character.
72 * Note that we must do all of this before using the OCaml Arg
73 * module to properly parse the command line (below), so that
74 * we can have a usage message ready.
76 let tool, ignore_first_anon_arg =
77 let prog = Sys.executable_name in (* eg. "/usr/bin/virt-dmesg.opt" *)
78 let prog = Filename.basename prog in(* eg. "virt-dmesg.opt" *)
79 let prog = (* eg. "virt-dmesg" *)
80 try Filename.chop_extension prog with Invalid_argument _ -> prog in
81 let prog = (* eg. "dmesg" *)
82 if String.starts_with prog "virt-" then
83 String.sub prog 5 (String.length prog - 5)
85 try Some (List.assoc prog tools), false
87 let arg1 = (* First non-option argument. *)
88 match Array.to_list Sys.argv with
91 let rec loop = function
93 | a::args when String.length a > 0 && a.[0] = '-' -> loop args
99 | Some prog -> (* Recognisable first argument? *)
101 try Filename.chop_extension prog with Invalid_argument _ -> prog in
103 if String.starts_with prog "virt-" then
104 String.sub prog 5 (String.length prog - 5)
106 (try Some (List.assoc prog tools), true
107 with Not_found -> None, false) in
109 (* Make a usage message. *)
112 | None -> (* Generic usage message. *)
113 let tools = List.map (
114 fun (name, (_, summary, _, _, _, _, _, _, external_cmd, _, _)) ->
115 if external_cmd then "virt-"^name, summary
116 else "virt-mem "^name, summary
118 (* Maximum width of field in the left hand column. *)
120 List.fold_left max 0 (List.map String.length (List.map fst tools)) in
121 let tools = List.map (fun (l,r) -> pad max_width l, r) tools in
122 let tools = List.map (fun (l,r) -> " " ^ l ^ " - " ^ r) tools in
123 let tools = String.concat "\n" tools in
127 virt-mem: Tools for providing information about virtual machines
129 Currently available tools include:
133 <tool> [-options] [domains...]
135 To display extra help for a single tool, do:
136 virt-mem --help <tool>
140 (* Tool-specific usage message. *)
141 | Some (name, summary, description, _, _, _, _, _, external_cmd, _, _) ->
143 if external_cmd then "virt-" ^ name else "virt-mem " ^ name in
152 Options:") cmd summary description in
154 (* Now begin proper parsing of the command line arguments. *)
155 let debug = ref false in
156 let testimages = ref [] in
158 let anon_args = ref [] in
160 (* Default wordsize (-W). *)
161 let def_wordsize = ref None in
162 let set_wordsize = function
163 | "32" -> def_wordsize := Some W32
164 | "64" -> def_wordsize := Some W64
165 | "auto" -> def_wordsize := None
166 | str -> failwith (sprintf (f_"set_wordsize: %s: unknown wordsize") str)
169 (* Default endianness (-E). *)
170 let def_endian = ref None in
171 let set_endian = function
172 | "auto" -> def_endian := None
173 | "le" | "little" | "littleendian" | "intel" ->
174 def_endian := Some Bitstring.LittleEndian
175 | "be" | "big" | "bigendian" | "motorola" ->
176 def_endian := Some Bitstring.BigEndian
177 | str -> failwith (sprintf (f_"set_endian: %s: unknown endianness") str)
180 (* Default architecture (-A). *)
181 let def_architecture = ref None in
182 let set_architecture = function
183 | "auto" -> def_architecture := None
185 let arch = architecture_of_string arch in
186 def_architecture := Some arch;
187 def_endian := Some (endian_of_architecture arch);
188 def_wordsize := Some (wordsize_of_architecture arch)
191 (* Default text address (-T). *)
192 let def_text_addr = ref 0L (* 0 = auto-detect *) in
193 let def_kernel_min = ref 0L in
194 let def_kernel_max = ref 0L in
195 let set_text_addr = function
196 | "auto" -> def_text_addr := 0L
198 (* common for x86, but we should be able to try a selection *)
199 def_text_addr := 0xc010_0000_L;
200 def_kernel_min := 0xc010_0000_L;
201 def_kernel_max := 0xffff_ffff_L
202 | "x86-64"|"x86_64" ->
203 def_text_addr := 0xffffffff_81000000_L;
204 def_kernel_min := 0xffffffff_81000000_L;
205 def_kernel_max := 0xffffffff_ffffffff_L;
207 let strs = String.nsplit str "," in
210 def_text_addr := Int64.of_string str;
211 def_kernel_min := !def_text_addr;
213 if !def_text_addr < 0x1_0000_0000_L
215 else 0xffffffff_ffffffff_L
216 | [str1;str2;str3] ->
217 def_text_addr := Int64.of_string str1;
218 def_kernel_min := Int64.of_string str2;
219 def_kernel_max := Int64.of_string str3
220 | _ -> failwith (sprintf (f_"set_text_addr: %s: incorrect number of parameters to -T option") str)
223 (* Handle -t option. *)
224 let memory_image filename =
226 (!def_wordsize, !def_endian, !def_architecture,
227 !def_text_addr, !def_kernel_min, !def_kernel_max, filename)
231 (* Handle --version option. *)
233 printf "virt-mem %s\n" Virt_mem_version.version;
235 let major, minor, release =
236 let v, _ = Libvirt.get_version () in
237 v / 1_000_000, (v / 1_000) mod 1_000, v mod 1_000 in
238 printf "libvirt %d.%d.%d\n" major minor release;
242 (* Function to collect up any anonymous args (domain names/IDs). *)
243 let anon_arg str = anon_args := str :: !anon_args in
245 (* Construct the argspec.
246 * May include extra arguments specified by the tool.
249 let extra_args = match tool with
251 | Some (_, _, _, _, _, _, _, _, _, extra_args, _) -> extra_args in
253 "-A", Arg.String set_architecture,
254 "arch " ^ s_"Set kernel architecture, endianness and word size";
255 "-E", Arg.String set_endian,
256 "endian " ^ s_"Set kernel endianness";
257 "-T", Arg.String set_text_addr,
258 "addr " ^ s_"Set kernel text address";
259 "-W", Arg.String set_wordsize,
260 "addr " ^ s_"Set kernel word size";
261 "-c", Arg.Set_string uri,
262 "uri " ^ s_ "Connect to URI";
263 "--connect", Arg.Set_string uri,
264 "uri " ^ s_ "Connect to URI";
265 "--debug", Arg.Set debug,
266 " " ^ s_"Debug mode (default: false)";
267 "-t", Arg.String memory_image,
268 "image " ^ s_"Use saved kernel memory image";
269 "--version", Arg.Unit version,
270 " " ^ s_"Display version and exit";
273 (* Sort options alphabetically on first alpha character. *)
274 let cmp (a,_,_) (b,_,_) =
276 let a = String.strip ~chars a and b = String.strip ~chars b in
279 let argspec = List.sort ~cmp argspec in
280 (* Make the options line up nicely. *)
283 (* Parse the command line. This will exit if --version or --help found. *)
284 Arg.parse argspec anon_arg usage_msg;
286 let testimages = !testimages in
287 let debug = !debug in
288 let uri = if !uri = "" then None else Some !uri in
290 (* Discard the first anonymous argument if, above, we previously
291 * found it contained the tool name.
293 let anon_args = List.rev !anon_args in
295 if ignore_first_anon_arg then List.tl anon_args else anon_args in
297 (* At this point, either --help was specified on the command line
298 * (and so the program has exited) or we must have determined tool,
299 * or the user didn't give us a valid tool (eg. "virt-mem foobar").
300 * Detect that final case now and give an error.
303 needs_ksyms, needs_utsname, needs_tasks, needs_everything,
304 run, external_cmd, extra_args, argcheck =
309 virt-mem: I could not work out which tool you are trying to run.
310 Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
313 if debug then eprintf "tool = %s\n%!" name;
315 (* Optional argument checking in the tool. *)
318 | Some argcheck -> argcheck debug
321 (* Get the kernel images. *)
323 if testimages = [] then (
326 try C.connect_readonly ?name ()
327 with Libvirt.Virterror err ->
328 prerr_endline (Libvirt.Virterror.to_string err);
329 (* If non-root and no explicit connection URI, print a warning. *)
330 if Unix.geteuid () <> 0 && name = None then (
331 print_endline (s_ "NB: If you want to monitor a local Xen hypervisor, you usually need to be root");
335 (* If we have a list of parameters, then it is the domain names / UUIDs /
336 * IDs ONLY that we wish to display. Otherwise, display all active.
339 if anon_args = [] then (
340 (* List of active domains. *)
341 let nr_active_doms = C.num_of_domains conn in
343 Array.to_list (C.list_domains conn nr_active_doms) in
344 List.map (D.lookup_by_id conn) active_doms
349 try D.lookup_by_uuid_string conn arg
351 try D.lookup_by_name conn arg
353 try D.lookup_by_id conn (int_of_string arg)
355 failwith (sprintf (f_"%s: unknown domain (not a UUID, name or ID of any active domain)") arg) in
357 (* XXX Primitive test to see if the domain is active. *)
358 let is_active = try D.get_id dom >= 0 with _ -> false in
359 if not is_active then
360 failwith (sprintf (f_"%s: domain is not running") arg);
367 let xmls = List.map (fun dom -> dom, D.get_xml_desc dom) doms in
370 let xmls = List.map (fun (dom, xml) ->
371 dom, Xml.parse_string xml) xmls in
373 (* XXX Do something with the XML XXX
374 * such as detecting arch, wordsize, endianness.
384 let domname = D.get_name dom in
387 match !def_wordsize with
390 (sprintf (f_"%s: use -W to define word size for this image")
394 match !def_endian with
397 (sprintf (f_"%s: use -E to define endianness for this image")
402 match !def_architecture with
403 | Some I386 -> I386 | Some X86_64 -> X86_64
406 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") domname) in
408 if !def_text_addr = 0L ||
409 !def_kernel_min = 0L ||
410 !def_kernel_max = 0L then
412 (sprintf (f_"%s: use -T to define kernel load address for this image") domname);
414 (* Download the static part of the kernel. *)
415 let start_t = gettimeofday () in
419 load_static_memory ~dom ~domname ~arch
421 ~kernel_min:!def_kernel_min ~kernel_max:!def_kernel_max
422 !def_text_addr kernel_size
424 | LoadMemoryError (AddressOutOfRange, _) ->
425 prerr_endline (s_"virt-mem: error loading kernel memory: address out of range
426 Possibly the '-T' command line parameter was used inconsistently.");
428 (* Allow any other exceptions to escape & kill the program. *) in
431 let end_t = gettimeofday () in
432 eprintf "timing: downloading kernel took %f seconds\n%!"
440 (* One or more -t options passed. *)
441 if anon_args <> [] then
442 failwith (s_"virt-mem: if -t given on command line, then no domain arguments should be listed");
445 fun (wordsize, endian, arch,
446 text_addr, kernel_min, kernel_max, filename) ->
447 (* Quite a lot of limitations on the kernel images we can
448 * handle at the moment ...
450 (* XXX We could auto-detect wordsize easily. *)
455 (sprintf (f_"%s: use -W to define word size for this image")
462 (sprintf (f_"%s: use -E to define endianness for this image")
468 | Some I386 -> I386 | Some X86_64 -> X86_64
471 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") filename) in
473 if text_addr = 0L then
475 (sprintf (f_"%s: use -T to define kernel load address for this image")
478 (* Map the virtual memory. *)
479 let fd = openfile filename [O_RDONLY] 0 in
480 let mem = Virt_mem_mmap.of_file fd text_addr in
482 (* Force the wordsize and endianness. *)
483 let mem = Virt_mem_mmap.set_wordsize mem wordsize in
484 let mem = Virt_mem_mmap.set_endian mem endian in
486 { dom = None; domname = filename; mem = mem; arch = arch;
487 kernel_min = kernel_min; kernel_max = kernel_max }
491 (* Now build the kdata, depending on what the tool asked for. *)
495 let kdata = { ksyms = None; utsname = None; tasks = None } in
498 (* Certain needs are dependent on others ... *)
500 if needs_utsname then true
502 let needs_ksyms, needs_utsname =
503 if needs_tasks then true, true
504 else needs_ksyms, needs_utsname in
505 let needs_ksyms, needs_utsname, needs_tasks =
506 if needs_everything then true, true, true
507 else needs_ksyms, needs_utsname, needs_tasks in
509 (* Do the kernel symbol analysis. *)
511 if not needs_ksyms then images
514 fun (image, kdata) ->
515 (* Look for ordinary kernel symbols: *)
517 Virt_mem_ksyms.find_kernel_symbols debug image in
520 | None -> image, kdata
522 (* Look for kallsyms: *)
523 let image, kallsyms =
524 Virt_mem_kallsyms.find_kallsyms debug image ksyms in
528 | None -> ksyms (* no kallsyms, just use module symbols *)
529 | Some kallsyms -> kallsyms (* ksyms + kallsyms *) in
531 image, { kdata with ksyms = Some ksyms }
534 (* Get the kernel version (utsname analysis). *)
536 if not needs_utsname then images
539 fun (image, ({ ksyms = ksyms } as kdata)) ->
541 | None -> image, kdata
544 Virt_mem_utsname.find_utsname debug image ksyms in
545 let kdata = { kdata with utsname = utsname } in
551 if not needs_tasks then images
554 fun (image, ({ ksyms = ksyms; utsname = utsname } as kdata)) ->
555 match ksyms, utsname with
556 | Some ksyms, Some { kernel_release = kversion } ->
558 Virt_mem_tasks.find_tasks debug image ksyms kversion in
559 let kdata = { kdata with tasks = tasks } in
561 | _, _ -> image, kdata
564 (* Run the tool's main function. *)
565 let errors = ref 0 in
567 fun (image, kdata) ->
569 if not needs_everything then (
570 if needs_ksyms && kdata.ksyms = None then
571 failwith (s_"could not read kernel symbols")
572 else if needs_utsname && kdata.utsname = None then
573 failwith (s_"could not read kernel version")
574 else if needs_tasks && kdata.tasks = None then
575 failwith (s_"could not read process table")
577 run debug image kdata
579 eprintf "%s: %s\n" image.domname (Printexc.to_string exn);
582 exit (if !errors > 0 then 1 else 0)