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. *)
45 let register ?(external_cmd = true) ?(extra_args = [])
46 ?argcheck ?beforeksyms ?beforeutsname ?run
47 name summary description =
49 (name, (name, summary, description, external_cmd, extra_args,
50 argcheck, beforeksyms, beforeutsname, run))
53 (* Main program, called from mem/virt_mem_main.ml when all the
54 * tools have had a chance to register themselves.
57 (* Get the registered tools, alphabetically. *)
59 let tools = List.sort ~cmp:(fun (a,_) (b,_) -> compare a b) tools in
61 (* Which tool did the user want to run? Look at the executable
62 * name (eg. 'virt-dmesg' => tool == dmesg). If we don't recognise
63 * the executable name then we must look for the first parameter
64 * which doesn't begin with a '-' character.
66 * Note that we must do all of this before using the OCaml Arg
67 * module to properly parse the command line (below), so that
68 * we can have a usage message ready.
70 let tool, ignore_first_anon_arg =
71 let prog = Sys.executable_name in (* eg. "/usr/bin/virt-dmesg.opt" *)
72 let prog = Filename.basename prog in(* eg. "virt-dmesg.opt" *)
73 let prog = (* eg. "virt-dmesg" *)
74 try Filename.chop_extension prog with Invalid_argument _ -> prog in
75 let prog = (* eg. "dmesg" *)
76 if String.starts_with prog "virt-" then
77 String.sub prog 5 (String.length prog - 5)
79 try Some (List.assoc prog tools), false
81 let arg1 = (* First non-option argument. *)
82 match Array.to_list Sys.argv with
85 let rec loop = function
87 | a::args when String.length a > 0 && a.[0] = '-' -> loop args
93 | Some prog -> (* Recognisable first argument? *)
95 try Filename.chop_extension prog with Invalid_argument _ -> prog in
97 if String.starts_with prog "virt-" then
98 String.sub prog 5 (String.length prog - 5)
100 (try Some (List.assoc prog tools), true
101 with Not_found -> None, false) in
103 (* Make a usage message. *)
106 | None -> (* Generic usage message. *)
107 let tools = List.map (
108 fun (name, (_, summary, _, external_cmd, _, _, _, _, _)) ->
109 if external_cmd then "virt-"^name, summary
110 else "virt-mem "^name, summary
112 (* Maximum width of field in the left hand column. *)
114 List.fold_left max 0 (List.map String.length (List.map fst tools)) in
115 let tools = List.map (fun (l,r) -> pad max_width l, r) tools in
116 let tools = List.map (fun (l,r) -> " " ^ l ^ " - " ^ r) tools in
117 let tools = String.concat "\n" tools in
121 virt-mem: Tools for providing information about virtual machines
123 Currently available tools include:
127 <tool> [-options] [domains...]
129 To display extra help for a single tool, do:
134 (* Tool-specific usage message. *)
135 | Some (name, summary, description, external_cmd, _, _, _, _, _) ->
137 if external_cmd then "virt-" ^ name else "virt-mem " ^ name in
146 Options:") cmd summary description in
148 (* Now begin proper parsing of the command line arguments. *)
149 let debug = ref false in
150 let testimages = ref [] in
152 let anon_args = ref [] in
154 (* Default wordsize (-W). *)
155 let def_wordsize = ref None in
156 let set_wordsize = function
157 | "32" -> def_wordsize := Some W32
158 | "64" -> def_wordsize := Some W64
159 | "auto" -> def_wordsize := None
160 | str -> failwith (sprintf (f_"set_wordsize: %s: unknown wordsize") str)
163 (* Default endianness (-E). *)
164 let def_endian = ref None in
165 let set_endian = function
166 | "auto" -> def_endian := None
167 | "le" | "little" | "littleendian" | "intel" ->
168 def_endian := Some Bitstring.LittleEndian
169 | "be" | "big" | "bigendian" | "motorola" ->
170 def_endian := Some Bitstring.BigEndian
171 | str -> failwith (sprintf (f_"set_endian: %s: unknown endianness") str)
174 (* Default architecture (-A). *)
175 let def_architecture = ref None in
176 let set_architecture = function
177 | "auto" -> def_architecture := None
179 let arch = architecture_of_string arch in
180 def_architecture := Some arch;
181 def_endian := Some (endian_of_architecture arch);
182 def_wordsize := Some (wordsize_of_architecture arch)
185 (* Default text address (-T). *)
186 let def_text_addr = ref 0L (* 0 = auto-detect *) in
187 let def_kernel_min = ref 0L in
188 let def_kernel_max = ref 0L in
189 let set_text_addr = function
190 | "auto" -> def_text_addr := 0L
192 (* common for x86, but we should be able to try a selection *)
193 def_text_addr := 0xc010_0000_L;
194 def_kernel_min := 0xc010_0000_L;
195 def_kernel_max := 0xffff_ffff_L
196 | "x86-64"|"x86_64" ->
197 def_text_addr := 0xffffffff_81000000_L;
198 def_kernel_min := 0xffffffff_81000000_L;
199 def_kernel_max := 0xffffffff_ffffffff_L;
201 let strs = String.nsplit str "," in
204 def_text_addr := Int64.of_string str;
205 def_kernel_min := !def_text_addr;
207 if !def_text_addr < 0x1_0000_0000_L
209 else 0xffffffff_ffffffff_L
210 | [str1;str2;str3] ->
211 def_text_addr := Int64.of_string str1;
212 def_kernel_min := Int64.of_string str2;
213 def_kernel_max := Int64.of_string str3
214 | _ -> failwith (sprintf (f_"set_text_addr: %s: incorrect number of parameters to -T option") str)
217 (* Handle -t option. *)
218 let memory_image filename =
220 (!def_wordsize, !def_endian, !def_architecture,
221 !def_text_addr, !def_kernel_min, !def_kernel_max, filename)
225 (* Handle --version option. *)
227 printf "virt-mem %s\n" Virt_mem_version.version;
229 let major, minor, release =
230 let v, _ = Libvirt.get_version () in
231 v / 1_000_000, (v / 1_000) mod 1_000, v mod 1_000 in
232 printf "libvirt %d.%d.%d\n" major minor release;
236 (* Function to collect up any anonymous args (domain names/IDs). *)
237 let anon_arg str = anon_args := str :: !anon_args in
239 (* Construct the argspec.
240 * May include extra arguments specified by the tool.
243 let extra_args = match tool with
245 | Some (_, _, _, _, extra_args, _, _, _, _) -> extra_args in
247 "-A", Arg.String set_architecture,
248 "arch " ^ s_"Set kernel architecture, endianness and word size";
249 "-E", Arg.String set_endian,
250 "endian " ^ s_"Set kernel endianness";
251 "-T", Arg.String set_text_addr,
252 "addr " ^ s_"Set kernel text address";
253 "-W", Arg.String set_wordsize,
254 "addr " ^ s_"Set kernel word size";
255 "-c", Arg.Set_string uri,
256 "uri " ^ s_ "Connect to URI";
257 "--connect", Arg.Set_string uri,
258 "uri " ^ s_ "Connect to URI";
259 "--debug", Arg.Set debug,
260 " " ^ s_"Debug mode (default: false)";
261 "-t", Arg.String memory_image,
262 "image " ^ s_"Use saved kernel memory image";
263 "--version", Arg.Unit version,
264 " " ^ s_"Display version and exit";
267 (* Sort options alphabetically on first alpha character. *)
268 let cmp (a,_,_) (b,_,_) =
270 let a = String.strip ~chars a and b = String.strip ~chars b in
273 let argspec = List.sort ~cmp argspec in
274 (* Make the options line up nicely. *)
277 (* Parse the command line. This will exit if --version or --help found. *)
278 Arg.parse argspec anon_arg usage_msg;
280 let testimages = !testimages in
281 let debug = !debug in
282 let uri = if !uri = "" then None else Some !uri in
284 (* Discard the first anonymous argument if, above, we previously
285 * found it contained the tool name.
287 let anon_args = List.rev !anon_args in
289 if ignore_first_anon_arg then List.tl anon_args else anon_args in
291 (* At this point, either --help was specified on the command line
292 * (and so the program has exited) or we must have determined tool,
293 * or the user didn't give us a valid tool (eg. "virt-mem foobar").
294 * Detect that final case now and give an error.
296 let name, _, _, _, _, argcheck, beforeksyms, beforeutsname, run =
301 virt-mem: I could not work out which tool you are trying to run.
302 Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
305 if debug then eprintf "tool = %s\n%!" name;
307 (* Optional argument checking in the tool. *)
310 | Some argcheck -> argcheck debug
313 (* Get the kernel images. *)
315 if testimages = [] then (
318 try C.connect_readonly ?name ()
319 with Libvirt.Virterror err ->
320 prerr_endline (Libvirt.Virterror.to_string err);
321 (* If non-root and no explicit connection URI, print a warning. *)
322 if Unix.geteuid () <> 0 && name = None then (
323 print_endline (s_ "NB: If you want to monitor a local Xen hypervisor, you usually need to be root");
327 (* If we have a list of parameters, then it is the domain names / UUIDs /
328 * IDs ONLY that we wish to display. Otherwise, display all active.
331 if anon_args = [] then (
332 (* List of active domains. *)
333 let nr_active_doms = C.num_of_domains conn in
335 Array.to_list (C.list_domains conn nr_active_doms) in
336 List.map (D.lookup_by_id conn) active_doms
341 try D.lookup_by_uuid_string conn arg
343 try D.lookup_by_name conn arg
345 try D.lookup_by_id conn (int_of_string arg)
347 failwith (sprintf (f_"%s: unknown domain (not a UUID, name or ID of any active domain)") arg) in
349 (* XXX Primitive test to see if the domain is active. *)
350 let is_active = try D.get_id dom >= 0 with _ -> false in
351 if not is_active then
352 failwith (sprintf (f_"%s: domain is not running") arg);
359 let xmls = List.map (fun dom -> dom, D.get_xml_desc dom) doms in
362 let xmls = List.map (fun (dom, xml) ->
363 dom, Xml.parse_string xml) xmls in
365 (* XXX Do something with the XML XXX
366 * such as detecting arch, wordsize, endianness.
376 let domname = D.get_name dom in
379 match !def_wordsize with
382 (sprintf (f_"%s: use -W to define word size for this image")
386 match !def_endian with
389 (sprintf (f_"%s: use -E to define endianness for this image")
394 match !def_architecture with
395 | Some I386 -> I386 | Some X86_64 -> X86_64
398 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") domname) in
400 if !def_text_addr = 0L ||
401 !def_kernel_min = 0L ||
402 !def_kernel_max = 0L then
404 (sprintf (f_"%s: use -T to define kernel load address for this image") domname);
406 (* Download the static part of the kernel. *)
407 let start_t = gettimeofday () in
411 load_static_memory ~dom ~domname ~arch
413 ~kernel_min:!def_kernel_min ~kernel_max:!def_kernel_max
414 !def_text_addr kernel_size
416 | LoadMemoryError (AddressOutOfRange, _) ->
417 prerr_endline (s_"virt-mem: error loading kernel memory: address out of range
418 Possibly the '-T' command line parameter was used inconsistently.");
420 (* Allow any other exceptions to escape & kill the program. *) in
423 let end_t = gettimeofday () in
424 eprintf "timing: downloading kernel took %f seconds\n%!"
432 (* One or more -t options passed. *)
433 if anon_args <> [] then
434 failwith (s_"virt-mem: if -t given on command line, then no domain arguments should be listed");
437 fun (wordsize, endian, arch,
438 text_addr, kernel_min, kernel_max, filename) ->
439 (* Quite a lot of limitations on the kernel images we can
440 * handle at the moment ...
442 (* XXX We could auto-detect wordsize easily. *)
447 (sprintf (f_"%s: use -W to define word size for this image")
454 (sprintf (f_"%s: use -E to define endianness for this image")
460 | Some I386 -> I386 | Some X86_64 -> X86_64
463 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") filename) in
465 if text_addr = 0L then
467 (sprintf (f_"%s: use -T to define kernel load address for this image")
470 (* Map the virtual memory. *)
471 let fd = openfile filename [O_RDONLY] 0 in
472 let mem = Virt_mem_mmap.of_file fd text_addr in
474 (* Force the wordsize and endianness. *)
475 let mem = Virt_mem_mmap.set_wordsize mem wordsize in
476 let mem = Virt_mem_mmap.set_endian mem endian in
478 { dom = None; domname = filename; mem = mem; arch = arch;
479 kernel_min = kernel_min; kernel_max = kernel_max }
483 (* Optional callback into the tool before we start looking for
486 (match beforeksyms with
488 | Some beforeksyms -> beforeksyms debug images
491 (* If there are no more callback functions, then there is no point
492 * continuing with the rest of the program (kernel symbol analysis) ...
494 if beforeutsname = None && run = None then exit 0;
496 (* Do the kernel symbol analysis. *)
500 (* Look for ordinary kernel symbols: *)
501 let image = Virt_mem_ksyms.find_kernel_symbols debug image in
502 (* Look for kallsyms: *)
503 let image = Virt_mem_kallsyms.find_kallsyms debug image in
506 (* Finally, just wrap the lookup_ksym call in something
507 * which prints the query when debug is set.
511 let (domid, name, arch, mem, lookup_ksym) = image in
512 let lookup_ksym sym =
514 let value = lookup_ksym sym in
515 eprintf "lookup_ksym %S = %Lx\n%!" sym value;
518 eprintf "lookup_ksym %S failed\n%!" sym;
521 (domid, name, arch, mem, lookup_ksym)
529 (* Before utsname analysis. *)
530 (match beforeutsname with
532 | Some beforeutsname -> List.iter (beforeutsname debug) images
535 (* If there are no more callback functions, then there is no point
536 * continuing with the rest of the program (kernel version analysis) ...
538 if run = None then exit 0;
540 (* Get the kernel version (utsname analysis). *)
541 let images = List.map (Virt_mem_utsname.find_utsname debug) images in
543 (* Run the tool's main function. *)
546 | Some run -> List.iter (run debug) images