From 97131d94f5513b732f8f8d310984e71d8201cadf Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 1 Jan 1970 00:00:00 +0000 Subject: [PATCH] Preparation for capture subcommand: - extra hooks at various stages - allow tools to provide extra command line options - updated MANIFEST --- MANIFEST | 1 + dmesg/virt_dmesg.ml | 4 +- lib/.depend | 2 + lib/Makefile.in | 3 +- lib/virt_mem.ml | 171 +++++++++++++++++++++++++++++++----------------- lib/virt_mem.mli | 44 ++++++++++--- lib/virt_mem_capture.ml | 90 +++++++++++++++++++++++++ mem/.depend | 4 +- mem/virt_mem_main.ml | 12 +++- ps/virt_ps.ml | 2 +- uname/virt_uname.ml | 4 +- 11 files changed, 260 insertions(+), 77 deletions(-) create mode 100644 lib/virt_mem_capture.ml diff --git a/MANIFEST b/MANIFEST index 976b11f..916849a 100644 --- a/MANIFEST +++ b/MANIFEST @@ -9,6 +9,7 @@ HACKING install-sh lib/.depend lib/Makefile.in +lib/virt_mem_capture.ml lib/virt_mem.ml lib/virt_mem.mli lib/virt_mem_mmap.ml diff --git a/dmesg/virt_dmesg.ml b/dmesg/virt_dmesg.ml index 0931e91..dd41d26 100644 --- a/dmesg/virt_dmesg.ml +++ b/dmesg/virt_dmesg.ml @@ -25,7 +25,7 @@ open Virt_mem_mmap let run debug images = List.iter ( - fun (name, arch, mem, lookup_ksym) -> + fun (_, name, arch, mem, lookup_ksym) -> try (* I don't know why but this symbol doesn't exist in 2.6.9 * even in kallsyms. Hence this won't work with that kernel. @@ -80,4 +80,4 @@ virt-dmesg prints the kernel messages for virtual machines running under libvirt. The output is similar to the ordinary dmesg command run inside the virtual machine." -let () = Virt_mem.register "dmesg" summary description true run +let () = Virt_mem.register "dmesg" summary description ~run diff --git a/lib/.depend b/lib/.depend index 33e3139..552adb7 100644 --- a/lib/.depend +++ b/lib/.depend @@ -1,5 +1,7 @@ virt_mem.cmi: virt_mem_utils.cmo virt_mem_mmap.cmi virt_mem_mmap.cmi: virt_mem_utils.cmo +virt_mem_capture.cmo: virt_mem_gettext.cmo virt_mem.cmi +virt_mem_capture.cmx: virt_mem_gettext.cmx virt_mem.cmx virt_mem.cmo: virt_mem_version.cmo virt_mem_utils.cmo virt_mem_mmap.cmi \ virt_mem_gettext.cmo virt_mem.cmi virt_mem.cmx: virt_mem_version.cmx virt_mem_utils.cmx virt_mem_mmap.cmx \ diff --git a/lib/Makefile.in b/lib/Makefile.in index 0a757e8..373e452 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -52,7 +52,8 @@ OBJS = virt_mem_gettext.cmo \ virt_mem_utils.cmo \ virt_mem_mmap_c.o \ virt_mem_mmap.cmo \ - virt_mem.cmo + virt_mem.cmo \ + virt_mem_capture.cmo XOBJS = $(OBJS:%.cmo=%.cmx) all: $(TARGETS) diff --git a/lib/virt_mem.ml b/lib/virt_mem.ml index 1f21a66..61d5968 100644 --- a/lib/virt_mem.ml +++ b/lib/virt_mem.ml @@ -43,7 +43,14 @@ let max_memory_peek = 65536 (* XXX Use D.max_peek function *) type ksym = string type image = - string + int option + * string + * Virt_mem_utils.architecture + * ([`Wordsize], [`Endian]) Virt_mem_mmap.t + +type image_with_ksyms = + int option + * string * Virt_mem_utils.architecture * ([`Wordsize], [`Endian]) Virt_mem_mmap.t * (ksym -> MMap.addr) @@ -55,23 +62,16 @@ type kallsyms_compr = (* When tools register themselves, they are added to this list. * Later, we will alphabetize the list. *) -let tools = ref [ - "capture", ( - "capture", - s_"capture memory image for post-mortem analysis", - s_"Capture a memory image to a file for later post-mortem -analysis. Use the '-o memoryimage' option to specify the -output file. - -Other tools can load the memory image using the '-t' option.", - false, - (fun _ _ -> ()) - ); -] +let tools = ref [] (* Registration function used by the tools. *) -let register name summary description is_cmd run_fn = - tools := (name, (name, summary, description, is_cmd, run_fn)) :: !tools +let register ?(external_cmd = true) ?(extra_args = []) + ?argcheck ?beforeksyms ?run + name summary description = + tools := + (name, (name, summary, description, external_cmd, extra_args, + argcheck, beforeksyms, run)) + :: !tools (* Main program, called from mem/virt_mem_main.ml when all the * tools have had a chance to register themselves. @@ -90,7 +90,7 @@ let main () = * module to properly parse the command line (below), so that * we can have a usage message ready. *) - let tool = + 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" *) @@ -99,7 +99,7 @@ let main () = if String.starts_with prog "virt-" then String.sub prog 5 (String.length prog - 5) else prog in - try Some (List.assoc prog tools) + try Some (List.assoc prog tools), false with Not_found -> let arg1 = (* First non-option argument. *) match Array.to_list Sys.argv with @@ -112,7 +112,7 @@ let main () = in loop args in match arg1 with - | None -> None + | None -> None, false | Some prog -> (* Recognisable first argument? *) let prog = try Filename.chop_extension prog with Invalid_argument _ -> prog in @@ -120,16 +120,17 @@ let main () = if String.starts_with prog "virt-" then String.sub prog 5 (String.length prog - 5) else prog in - (try Some (List.assoc prog tools) with Not_found -> None) 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, _, is_cmd, _)) -> - if is_cmd then "virt-"^name, summary - else "virt-mem "^name, summary + 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 = @@ -154,8 +155,9 @@ To display extra help for a single tool, do: Options:") tools (* Tool-specific usage message. *) - | Some (name, summary, description, is_cmd, _) -> - let cmd = if is_cmd then "virt-" ^ name else "virt-mem " ^ name in + | Some (name, summary, description, external_cmd, _, _, _, _) -> + let cmd = + if external_cmd then "virt-" ^ name else "virt-mem " ^ name in sprintf (f_"\ @@ -167,9 +169,10 @@ Description: Options:") cmd summary description in (* Now begin proper parsing of the command line arguments. *) - - (* Debug messages. *) let debug = ref false in + let images = ref [] in + let uri = ref "" in + let anon_args = ref [] in (* Default wordsize. *) let def_wordsize = ref None in @@ -211,17 +214,14 @@ Options:") cmd summary description in | str -> def_text_addr := Int64.of_string str in - (* List of kernel images. *) - let images = ref [] in - let uri = ref "" in - let anon_args = ref [] in - + (* Handle -t option. *) let memory_image filename = images := (!def_wordsize, !def_endian, !def_architecture, !def_text_addr, filename) :: !images in + (* Handle --version option. *) let version () = printf "virt-mem %s\n" Virt_mem_version.version; @@ -232,41 +232,67 @@ Options:") cmd summary description in exit 0 in - let argspec = Arg.align [ - "-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)"; - "-t", Arg.String memory_image, - "image " ^ s_"Use saved kernel memory image"; - "--version", Arg.Unit version, - " " ^ s_"Display version and exit"; - ] 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)"; + "-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 images = !images 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, _, _, _, run_fn = + let name, _, _, _, _, argcheck, beforeksyms, run = match tool with | Some t -> t | None -> @@ -277,6 +303,12 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(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 images = [] then ( @@ -340,6 +372,7 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)"); List.map ( fun (dom, _) -> + let id = D.get_id dom in let name = D.get_name dom in let wordsize = @@ -398,7 +431,7 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)"); let mem = MMap.set_wordsize mem wordsize in let mem = MMap.set_endian mem endian in - (name, arch, mem) + ((Some id, name, arch, mem) : image) ) xmls ) else ( (* One or more -t options passed. *) @@ -446,13 +479,27 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)"); let mem = MMap.set_wordsize mem wordsize in let mem = MMap.set_endian mem endian in - (filename, arch, mem) + ((None, filename, arch, mem) : image) ) images ) in + (* Optional callback into the tool before we start looking for + * kernel symbols. + *) + (match beforeksyms with + | None -> () + | Some beforeksyms -> beforeksyms debug images + ); + + (* If there is no run function, then there is no point continuing + * with the rest of the program (kernel symbol analysis) ... + *) + if run = None then exit 0; + + (* Now kernel symbol analysis starts ... *) let images = List.map ( - fun (name, arch, mem) -> + fun (domid, name, arch, mem) -> (* Look for some common entries in the exported symbol table and * from that find the symbol table itself. These are just * supposed to be symbols which are very likely to be present @@ -857,8 +904,12 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)"); lookup_ksym in - ((name, arch, mem, lookup_ksym) : image) + ((domid, name, arch, mem, lookup_ksym) : image_with_ksyms) ) images in - (* Run the actual tool. *) - run_fn debug images + (* Run the tool's main function. *) + (match run with + | None -> () + | Some run -> + run debug images + ) diff --git a/lib/virt_mem.mli b/lib/virt_mem.mli index 1dc1c23..58b879e 100644 --- a/lib/virt_mem.mli +++ b/lib/virt_mem.mli @@ -21,15 +21,23 @@ type ksym = string (** A kernel symbol name. *) type image = - string + int option + * string + * Virt_mem_utils.architecture + * ([`Wordsize], [`Endian]) Virt_mem_mmap.t + (** A memory image from a domain. *) + +type image_with_ksyms = + int option + * string * Virt_mem_utils.architecture * ([`Wordsize], [`Endian]) Virt_mem_mmap.t * (ksym -> Virt_mem_mmap.addr) - (** An image after it has been processed by the code common to - all commands. + (** An image after it has been processed to find kernel symbols. The tuple fields are: - - name + - domain ID (if known) + - name, usually the domain name - architecture (eg. I386) - kernel memory map (wordsize & endianness already determined) - a function to look up kernel symbols. It raises [Not_found] @@ -37,15 +45,35 @@ type image = table could not be found at all. *) -val register : string -> string -> Arg.usage_msg -> bool -> (bool -> image list -> unit) -> unit +val register : + ?external_cmd:bool -> + ?extra_args:(Arg.key * Arg.spec * Arg.doc) list -> + ?argcheck:(bool -> unit) -> + ?beforeksyms:(bool -> image list -> unit) -> + ?run:(bool -> image_with_ksyms list -> unit) -> + string -> string -> Arg.usage_msg -> + unit (** Tools register themselves with this call. - The parameters are: + The anonymous parameters are: - tool name (eg. "uname") - short summary - full usage message - - is it a virt-cmd? - - run function (invoked as [run verbose images]) + + The optional callback functions are: + - [?argcheck] called after arguments have been fully parsed + so that the program can do any additional checks needed (eg. + on [extra_args]), + - [?beforeksyms] called after images are loaded and before + kernel symbols are analyzed, + - [?run] called after kernel symbols have been analyzed + (almost all tools supply this callback function). + + Pass [~external_cmd:false] if this tool doesn't have an + external 'virt-tool' link. + + Pass [~extra_args:...] if this tool needs extra command + line options. *) val main : unit -> unit diff --git a/lib/virt_mem_capture.ml b/lib/virt_mem_capture.ml new file mode 100644 index 0000000..413af56 --- /dev/null +++ b/lib/virt_mem_capture.ml @@ -0,0 +1,90 @@ +(* Memory info command 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. + + Implements 'virt-mem capture' command. + *) + +open Printf +open ExtString + +open Virt_mem_gettext.Gettext + +(* This will contain what is passed by the user as '-o' option. *) +let output_filename = ref "" + +(* Early argument check. *) +let argcheck debug = + (* -o flag must have been specified. *) + let output_filename = !output_filename in + if output_filename = "" then ( + prerr_endline (s_"virt-mem capture: '-o memoryimage' option is required"); + exit 1 + ) + +(* Capture the images before kernel symbol analysis is attempted. + * Just save them to the output file(s). + *) +let rec beforeksyms debug = function + | [] -> + prerr_endline + (s_"virt-mem capture: warning: no kernel images were captured") + | [image] -> + (* Single image is saved to output_filename. *) + save_image image !output_filename + | images -> + (* Multiple images are saved to output_filename.ID where ID + * is the domain ID (if known) or a mangled domain name. + *) + List.iter ( + fun ((domid, domname, _, _) as image) -> + let filename = + !output_filename ^ "." ^ + match domid with + | Some id -> string_of_int id + | None -> + let f = function + | ('a'..'z'|'A'..'Z'|'0'..'9'|'_' as c) -> String.make 1 c + | _ -> "" + in + String.replace_chars f domname in + save_image image filename + ) images + +and save_image (_, domname, arch, mmap) filename = + printf (f_"virt-mem capture: saving kernel image from %s to filename %s\n") + domname filename; + + assert false + +let summary = s_"capture memory image for post-mortem analysis" +let description = s_"Capture a memory image to a file for later post-mortem +analysis. Use the '-o memoryimage' option to specify the +output file. + +Other tools can load the memory image using the '-t' option." + +let extra_args = [ + "-o", Arg.Set_string output_filename, + "memoryimage " ^s_"Set output filename" +] + +let () = + Virt_mem.register + ~external_cmd:false ~extra_args + ~argcheck ~beforeksyms + "capture" summary description diff --git a/mem/.depend b/mem/.depend index 2ad2fc5..fd7322a 100644 --- a/mem/.depend +++ b/mem/.depend @@ -1,2 +1,2 @@ -virt_mem_main.cmo: ../lib/virt_mem.cmi -virt_mem_main.cmx: ../lib/virt_mem.cmx +virt_mem_main.cmo: ../lib/virt_mem_capture.cmo ../lib/virt_mem.cmi +virt_mem_main.cmx: ../lib/virt_mem_capture.cmx ../lib/virt_mem.cmx diff --git a/mem/virt_mem_main.ml b/mem/virt_mem_main.ml index 32d89ff..78c7ab7 100644 --- a/mem/virt_mem_main.ml +++ b/mem/virt_mem_main.ml @@ -19,4 +19,14 @@ Links everything into a single executable and invokes 'main'. *) -Virt_mem.main () +(* Because internal commands like 'virt-mem capture' are stored + * in a library [*.cma/*.cmxa], they don't automatically get + * loaded/registered unless someone seems to be using them. So + * we need to pretend to be using them here. External commands + * like 'virt-ps' are OK because they are linked as *.cmo/*.cmx + * files. + *) +let _ = Virt_mem_capture.summary + +(* Call main program. *) +let () = Virt_mem.main () diff --git a/ps/virt_ps.ml b/ps/virt_ps.ml index 4c995d8..065b4c3 100644 --- a/ps/virt_ps.ml +++ b/ps/virt_ps.ml @@ -30,4 +30,4 @@ let description = s_"\ virt-ps prints a process listing for virtual machines running under libvirt." -let () = Virt_mem.register "ps" summary description true run +let () = Virt_mem.register "ps" summary description ~run diff --git a/uname/virt_uname.ml b/uname/virt_uname.ml index f2f6ddc..e90b5a6 100644 --- a/uname/virt_uname.ml +++ b/uname/virt_uname.ml @@ -57,7 +57,7 @@ let run debug images = in List.iter ( - fun (name, arch, mem, lookup_ksym) -> + fun (_, name, arch, mem, lookup_ksym) -> (* In Linux 2.6.25, the symbol is init_uts_ns. * http://lxr.linux.no/linux/init/version.c *) @@ -94,4 +94,4 @@ virt-uname prints the uname information such as OS version, architecture and node name for virtual machines running under libvirt." -let () = Virt_mem.register "uname" summary description true run +let () = Virt_mem.register "uname" summary description ~run -- 1.8.3.1