(* Memory info for virtual domains. (C) Copyright 2008 Richard W.M. Jones, Red Hat Inc. http://libvirt.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *) open Unix open Printf open ExtList open ExtString module C = Libvirt.Connect module D = Libvirt.Domain open Virt_mem_gettext.Gettext open Virt_mem_utils open Virt_mem_types (* Make the kernel size around 16 MB, but just a bit smaller than * maximum string length so we can still run this on a 32 bit platform. *) let kernel_size = if Sys.word_size = 32 then Sys.max_string_length else 0x100_0000 (* When tools register themselves, they are added to this list. * Later, we will alphabetize the list. *) let tools = ref [] (* Registration function used by the tools. *) let register ?(needs_ksyms = false) ?(needs_utsname = false) ?(needs_tasks = false) ?(needs_net_devices = false) ?(needs_everything = false) ~run ?(external_cmd = true) ?(extra_args = []) ?argcheck name summary description = tools := (name, (name, summary, description, needs_ksyms, needs_utsname, needs_tasks, needs_net_devices, needs_everything, run, external_cmd, extra_args, argcheck)) :: !tools (* Main program, called from mem/virt_mem_main.ml when all the * tools have had a chance to register themselves. *) let main () = (* Get the registered tools, alphabetically. *) let tools = !tools in let tools = List.sort ~cmp:(fun (a,_) (b,_) -> compare a b) tools in (* Which tool did the user want to run? Look at the executable * name (eg. 'virt-dmesg' => tool == dmesg). If we don't recognise * the executable name then we must look for the first parameter * which doesn't begin with a '-' character. * * Note that we must do all of this before using the OCaml Arg * module to properly parse the command line (below), so that * we can have a usage message ready. *) let tool, ignore_first_anon_arg = let prog = Sys.executable_name in (* eg. "/usr/bin/virt-dmesg.opt" *) let prog = Filename.basename prog in(* eg. "virt-dmesg.opt" *) let prog = (* eg. "virt-dmesg" *) try Filename.chop_extension prog with Invalid_argument _ -> prog in let prog = (* eg. "dmesg" *) if String.starts_with prog "virt-" then String.sub prog 5 (String.length prog - 5) else prog in try Some (List.assoc prog tools), false with Not_found -> let arg1 = (* First non-option argument. *) match Array.to_list Sys.argv with | [] -> None | _::args -> let rec loop = function | [] -> None | a::args when String.length a > 0 && a.[0] = '-' -> loop args | a::_ -> Some a in loop args in match arg1 with | None -> None, false | Some prog -> (* Recognisable first argument? *) let prog = try Filename.chop_extension prog with Invalid_argument _ -> prog in let prog = if String.starts_with prog "virt-" then String.sub prog 5 (String.length prog - 5) else prog in (try Some (List.assoc prog tools), true with Not_found -> None, false) in (* Make a usage message. *) let usage_msg = match tool with | None -> (* Generic usage message. *) let tools = List.map ( fun (name, (_, summary, _, _, _, _, _, _, _, external_cmd, _, _)) -> if external_cmd then "virt-"^name, summary else "virt-mem "^name, summary ) tools in (* Maximum width of field in the left hand column. *) let max_width = List.fold_left max 0 (List.map String.length (List.map fst tools)) in let tools = List.map (fun (l,r) -> pad max_width l, r) tools in let tools = List.map (fun (l,r) -> " " ^ l ^ " - " ^ r) tools in let tools = String.concat "\n" tools in sprintf (f_"\ virt-mem: Tools for providing information about virtual machines Currently available tools include: %s General usage is: [-options] [domains...] To display extra help for a single tool, do: virt-mem --help Options:") tools (* Tool-specific usage message. *) | Some (name, summary, description, _, _, _, _, _, _, external_cmd, _, _) -> let cmd = if external_cmd then "virt-" ^ name else "virt-mem " ^ name in sprintf (f_"\ %s: %s Description: %s Options:") cmd summary description in (* Now begin proper parsing of the command line arguments. *) let debug = ref false in let testimages = ref [] in let uri = ref "" in let anon_args = ref [] in (* Default wordsize (-W). *) let def_wordsize = ref None in let set_wordsize = function | "32" -> def_wordsize := Some W32 | "64" -> def_wordsize := Some W64 | "auto" -> def_wordsize := None | str -> failwith (sprintf (f_"set_wordsize: %s: unknown wordsize") str) in (* Default endianness (-E). *) let def_endian = ref None in let set_endian = function | "auto" -> def_endian := None | "le" | "little" | "littleendian" | "intel" -> def_endian := Some Bitstring.LittleEndian | "be" | "big" | "bigendian" | "motorola" -> def_endian := Some Bitstring.BigEndian | str -> failwith (sprintf (f_"set_endian: %s: unknown endianness") str) in (* Default architecture (-A). *) let def_architecture = ref None in let set_architecture = function | "auto" -> def_architecture := None | arch -> let arch = architecture_of_string arch in def_architecture := Some arch; def_endian := Some (endian_of_architecture arch); def_wordsize := Some (wordsize_of_architecture arch) in (* Default text address (-T). *) let def_text_addr = ref 0L (* 0 = auto-detect *) in let def_kernel_min = ref 0L in let def_kernel_max = ref 0L in let set_text_addr = function | "auto" -> def_text_addr := 0L | "i386" -> (* common for x86, but we should be able to try a selection *) def_text_addr := 0xc010_0000_L; def_kernel_min := 0xc010_0000_L; def_kernel_max := 0xffff_ffff_L | "x86-64"|"x86_64" -> def_text_addr := 0xffffffff_81000000_L; def_kernel_min := 0xffffffff_81000000_L; def_kernel_max := 0xffffffff_ffffffff_L; | str -> let strs = String.nsplit str "," in match strs with | [str] -> def_text_addr := Int64.of_string str; def_kernel_min := !def_text_addr; def_kernel_max := if !def_text_addr < 0x1_0000_0000_L then 0xffff_ffff_L else 0xffffffff_ffffffff_L | [str1;str2;str3] -> def_text_addr := Int64.of_string str1; def_kernel_min := Int64.of_string str2; def_kernel_max := Int64.of_string str3 | _ -> failwith (sprintf (f_"set_text_addr: %s: incorrect number of parameters to -T option") str) in (* Handle -t option. *) let memory_image filename = testimages := (!def_wordsize, !def_endian, !def_architecture, !def_text_addr, !def_kernel_min, !def_kernel_max, filename) :: !testimages in (* Handle --version option. *) let version () = printf "virt-mem %s\n" Virt_mem_version.version; let major, minor, release = let v, _ = Libvirt.get_version () in v / 1_000_000, (v / 1_000) mod 1_000, v mod 1_000 in printf "libvirt %d.%d.%d\n" major minor release; exit 0 in (* Handle --list-kernels option. *) let list_kernels () = List.iter print_endline Virt_mem_kernels.kernels; exit 0 in (* Function to collect up any anonymous args (domain names/IDs). *) let anon_arg str = anon_args := str :: !anon_args in (* Construct the argspec. * May include extra arguments specified by the tool. *) let argspec = let extra_args = match tool with | None -> [] | Some (_, _,_, _, _, _, _, _, _, _, extra_args, _) -> extra_args in let argspec = [ "-A", Arg.String set_architecture, "arch " ^ s_"Set kernel architecture, endianness and word size"; "-E", Arg.String set_endian, "endian " ^ s_"Set kernel endianness"; "-T", Arg.String set_text_addr, "addr " ^ s_"Set kernel text address"; "-W", Arg.String set_wordsize, "addr " ^ s_"Set kernel word size"; "-c", Arg.Set_string uri, "uri " ^ s_ "Connect to URI"; "--connect", Arg.Set_string uri, "uri " ^ s_ "Connect to URI"; "--debug", Arg.Set debug, " " ^ s_"Debug mode (default: false)"; "--list-kernels", Arg.Unit list_kernels, " " ^ s_"List known kernels"; "-t", Arg.String memory_image, "image " ^ s_"Use saved kernel memory image"; "--version", Arg.Unit version, " " ^ s_"Display version and exit"; ] @ extra_args in (* Sort options alphabetically on first alpha character. *) let cmp (a,_,_) (b,_,_) = let chars = "-" in let a = String.strip ~chars a and b = String.strip ~chars b in compare a b in let argspec = List.sort ~cmp argspec in (* Make the options line up nicely. *) Arg.align argspec in (* Parse the command line. This will exit if --version or --help found. *) Arg.parse argspec anon_arg usage_msg; let testimages = !testimages in let debug = !debug in let uri = if !uri = "" then None else Some !uri in (* Discard the first anonymous argument if, above, we previously * found it contained the tool name. *) let anon_args = List.rev !anon_args in let anon_args = if ignore_first_anon_arg then List.tl anon_args else anon_args in (* At this point, either --help was specified on the command line * (and so the program has exited) or we must have determined tool, * or the user didn't give us a valid tool (eg. "virt-mem foobar"). * Detect that final case now and give an error. *) let name, _, _, needs_ksyms, needs_utsname, needs_tasks, needs_net_devices, needs_everything, run, external_cmd, extra_args, argcheck = match tool with | Some t -> t | None -> prerr_endline (s_"\ virt-mem: I could not work out which tool you are trying to run. Use 'virt-mem --help' for more help or read the manual page virt-mem(1)"); exit 1 in if debug then eprintf "tool = %s\n%!" name; (* Optional argument checking in the tool. *) (match argcheck with | None -> () | Some argcheck -> argcheck debug ); (* Get the kernel images. *) let images = if testimages = [] then ( let conn = let name = uri in try C.connect_readonly ?name () with Libvirt.Virterror err -> prerr_endline (Libvirt.Virterror.to_string err); (* If non-root and no explicit connection URI, print a warning. *) if Unix.geteuid () <> 0 && name = None then ( print_endline (s_ "NB: If you want to monitor a local Xen hypervisor, you usually need to be root"); ); exit 1 in (* If we have a list of parameters, then it is the domain names / UUIDs / * IDs ONLY that we wish to display. Otherwise, display all active. *) let doms = if anon_args = [] then ( (* List of active domains. *) let nr_active_doms = C.num_of_domains conn in let active_doms = Array.to_list (C.list_domains conn nr_active_doms) in List.map (D.lookup_by_id conn) active_doms ) else ( List.map ( fun arg -> let dom = try D.lookup_by_uuid_string conn arg with _ -> try D.lookup_by_name conn arg with _ -> try D.lookup_by_id conn (int_of_string arg) with _ -> failwith (sprintf (f_"%s: unknown domain (not a UUID, name or ID of any active domain)") arg) in (* XXX Primitive test to see if the domain is active. *) let is_active = try D.get_id dom >= 0 with _ -> false in if not is_active then failwith (sprintf (f_"%s: domain is not running") arg); dom ) anon_args ) in (* Get their XML. *) let xmls = List.map (fun dom -> dom, D.get_xml_desc dom) doms in (* Parse the XML. *) let xmls = List.map (fun (dom, xml) -> dom, Xml.parse_string xml) xmls in (* XXX Do something with the XML XXX * such as detecting arch, wordsize, endianness. * XXXXXXXXXXXXXX * * * *) List.map ( fun (dom, _) -> let domname = D.get_name dom in let wordsize = match !def_wordsize with | None -> failwith (sprintf (f_"%s: use -W to define word size for this image") domname); | Some ws -> ws in let endian = match !def_endian with | None -> failwith (sprintf (f_"%s: use -E to define endianness for this image") domname); | Some e -> e in let arch = match !def_architecture with | Some I386 -> I386 | Some X86_64 -> X86_64 | _ -> failwith (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") domname) in if !def_text_addr = 0L || !def_kernel_min = 0L || !def_kernel_max = 0L then failwith (sprintf (f_"%s: use -T to define kernel load address for this image") domname); (* Download the static part of the kernel. *) let start_t = gettimeofday () in let image = try load_static_memory ~dom ~domname ~arch ~wordsize ~endian ~kernel_min:!def_kernel_min ~kernel_max:!def_kernel_max !def_text_addr kernel_size with | LoadMemoryError (AddressOutOfRange, _) -> prerr_endline (s_"virt-mem: error loading kernel memory: address out of range Possibly the '-T' command line parameter was used inconsistently."); exit 1 (* Allow any other exceptions to escape & kill the program. *) in if debug then ( let end_t = gettimeofday () in eprintf "timing: downloading kernel took %f seconds\n%!" (end_t -. start_t) ); image ) xmls ) else ( (* One or more -t options passed. *) if anon_args <> [] then failwith (s_"virt-mem: if -t given on command line, then no domain arguments should be listed"); List.map ( fun (wordsize, endian, arch, text_addr, kernel_min, kernel_max, filename) -> (* Quite a lot of limitations on the kernel images we can * handle at the moment ... *) (* XXX We could auto-detect wordsize easily. *) let wordsize = match wordsize with | None -> failwith (sprintf (f_"%s: use -W to define word size for this image") filename); | Some ws -> ws in let endian = match endian with | None -> failwith (sprintf (f_"%s: use -E to define endianness for this image") filename); | Some e -> e in let arch = match arch with | Some I386 -> I386 | Some X86_64 -> X86_64 | _ -> failwith (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") filename) in if text_addr = 0L then failwith (sprintf (f_"%s: use -T to define kernel load address for this image") filename); (* Map the virtual memory. *) let fd = openfile filename [O_RDONLY] 0 in let mem = Virt_mem_mmap.of_file fd text_addr in (* Force the wordsize and endianness. *) let mem = Virt_mem_mmap.set_wordsize mem wordsize in let mem = Virt_mem_mmap.set_endian mem endian in { dom = None; domname = filename; mem = mem; arch = arch; kernel_min = kernel_min; kernel_max = kernel_max } ) testimages ) in (* Now build the kdata, depending on what the tool asked for. *) let images = List.map ( fun image -> let kdata = { ksyms = None; utsname = None; tasks = None; net_devices = None } in image, kdata ) images in (* Certain needs are dependent on others ... *) let needs_ksyms = if needs_utsname then true else needs_ksyms in let needs_ksyms, needs_utsname = if needs_tasks then true, true else needs_ksyms, needs_utsname in let needs_ksyms, needs_utsname = if needs_net_devices then true, true else needs_ksyms, needs_utsname in let needs_ksyms, needs_utsname, needs_tasks, needs_net_devices = if needs_everything then true, true, true, true else needs_ksyms, needs_utsname, needs_tasks, needs_net_devices in (* Do the kernel symbol analysis. *) let images = if not needs_ksyms then images else List.map ( fun (image, kdata) -> (* Look for ordinary kernel symbols: *) let image, ksyms = Virt_mem_ksyms.find_kernel_symbols debug image in match ksyms with | None -> image, kdata | Some ksyms -> (* Look for kallsyms: *) let image, kallsyms = Virt_mem_kallsyms.find_kallsyms debug image ksyms in let ksyms = match kallsyms with | None -> ksyms (* no kallsyms, just use module symbols *) | Some kallsyms -> kallsyms (* ksyms + kallsyms *) in image, { kdata with ksyms = Some ksyms } ) images in (* Get the kernel version (utsname analysis). *) let images = if not needs_utsname then images else List.map ( fun (image, ({ ksyms = ksyms } as kdata)) -> match ksyms with | None -> image, kdata | Some ksyms -> let image, utsname = Virt_mem_utsname.find_utsname debug image ksyms in let kdata = { kdata with utsname = utsname } in image, kdata ) images in (* Get the tasks. *) let images = if not needs_tasks then images else List.map ( fun (image, ({ ksyms = ksyms; utsname = utsname } as kdata)) -> match ksyms, utsname with | Some ksyms, Some { uts_kernel_release = kversion } -> let image, tasks = Virt_mem_tasks.find_tasks debug image ksyms kversion in let kdata = { kdata with tasks = tasks } in image, kdata | _, _ -> image, kdata ) images in (* Get the net devices. *) let images = if not needs_net_devices then images else List.map ( fun (image, ({ ksyms = ksyms; utsname = utsname } as kdata)) -> match ksyms, utsname with | Some ksyms, Some { uts_kernel_release = kversion } -> let image, net_devices = Virt_mem_net_devices.find_net_devices debug image ksyms kversion in let kdata = { kdata with net_devices = net_devices } in image, kdata | _, _ -> image, kdata ) images in (* Run the tool's main function. *) let errors = ref 0 in List.iter ( fun (image, kdata) -> try if not needs_everything then ( if needs_ksyms && kdata.ksyms = None then failwith (s_"could not read kernel symbols") else if needs_utsname && kdata.utsname = None then failwith (s_"could not read kernel version") else if needs_tasks && kdata.tasks = None then failwith (s_"could not read process table") else if needs_net_devices && kdata.net_devices = None then failwith (s_"could not read net device table") ); run debug image kdata with exn -> eprintf "%s: %s\n" image.domname (Printexc.to_string exn); incr errors ) images; exit (if !errors > 0 then 1 else 0)