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
30 module MMap = Virt_mem_mmap
32 let min_kallsyms_tabsize = 1_000L
33 let max_kallsyms_tabsize = 250_000L
35 (* Make the kernel size around 16 MB, but just a bit smaller than
36 * maximum string length so we can still run this on a 32 bit platform.
39 if Sys.word_size = 32 then Sys.max_string_length
41 let max_memory_peek = 65536 (* XXX Use D.max_peek function *)
47 * Virt_mem_utils.architecture
48 * ([`Wordsize], [`Endian]) Virt_mem_mmap.t
52 | Compressed of (string * MMap.addr) list * MMap.addr
53 | Uncompressed of (string * MMap.addr) list
55 (* When tools register themselves, they are added to this list.
56 * Later, we will alphabetize the list.
61 s_"capture memory image for post-mortem analysis",
62 s_"Capture a memory image to a file for later post-mortem
63 analysis. Use the '-o memoryimage' option to specify the
66 Other tools can load the memory image using the '-t' option.",
72 (* Registration function used by the tools. *)
73 let register name summary description is_cmd run_fn =
74 tools := (name, (name, summary, description, is_cmd, run_fn)) :: !tools
76 (* Main program, called from mem/virt_mem_main.ml when all the
77 * tools have had a chance to register themselves.
80 (* Get the registered tools, alphabetically. *)
82 let tools = List.sort ~cmp:(fun (a,_) (b,_) -> compare a b) tools in
84 (* Which tool did the user want to run? Look at the executable
85 * name (eg. 'virt-dmesg' => tool == dmesg). If we don't recognise
86 * the executable name then we must look for the first parameter
87 * which doesn't begin with a '-' character.
89 * Note that we must do all of this before using the OCaml Arg
90 * module to properly parse the command line (below), so that
91 * we can have a usage message ready.
94 let prog = Sys.executable_name in (* eg. "/usr/bin/virt-dmesg.opt" *)
95 let prog = Filename.basename prog in(* eg. "virt-dmesg.opt" *)
96 let prog = (* eg. "virt-dmesg" *)
97 try Filename.chop_extension prog with Invalid_argument _ -> prog in
98 let prog = (* eg. "dmesg" *)
99 if String.starts_with prog "virt-" then
100 String.sub prog 5 (String.length prog - 5)
102 try Some (List.assoc prog tools)
104 let arg1 = (* First non-option argument. *)
105 match Array.to_list Sys.argv with
108 let rec loop = function
110 | a::args when String.length a > 0 && a.[0] = '-' -> loop args
116 | Some prog -> (* Recognisable first argument? *)
118 try Filename.chop_extension prog with Invalid_argument _ -> prog in
120 if String.starts_with prog "virt-" then
121 String.sub prog 5 (String.length prog - 5)
123 (try Some (List.assoc prog tools) with Not_found -> None) in
125 (* Make a usage message. *)
128 | None -> (* Generic usage message. *)
129 let tools = List.map (
130 fun (name, (_, summary, _, is_cmd, _)) ->
131 if is_cmd then "virt-"^name, summary
132 else "virt-mem "^name, summary
134 (* Maximum width of field in the left hand column. *)
136 List.fold_left max 0 (List.map String.length (List.map fst tools)) in
137 let tools = List.map (fun (l,r) -> pad max_width l, r) tools in
138 let tools = List.map (fun (l,r) -> " " ^ l ^ " - " ^ r) tools in
139 let tools = String.concat "\n" tools in
143 virt-mem: Tools for providing information about virtual machines
145 Currently available tools include:
149 <tool> [-options] [domains...]
151 To display extra help for a single tool, do:
156 (* Tool-specific usage message. *)
157 | Some (name, summary, description, is_cmd, _) ->
158 let cmd = if is_cmd then "virt-" ^ name else "virt-mem " ^ name in
167 Options:") cmd summary description in
169 (* Now begin proper parsing of the command line arguments. *)
171 (* Debug messages. *)
172 let debug = ref false in
174 (* Default wordsize. *)
175 let def_wordsize = ref None in
176 let set_wordsize = function
177 | "32" -> def_wordsize := Some W32
178 | "64" -> def_wordsize := Some W64
179 | "auto" -> def_wordsize := None
180 | str -> failwith (sprintf (f_"set_wordsize: %s: unknown wordsize") str)
183 (* Default endianness. *)
184 let def_endian = ref None in
185 let set_endian = function
186 | "auto" -> def_endian := None
187 | "le" | "little" | "littleendian" | "intel" ->
188 def_endian := Some Bitmatch.LittleEndian
189 | "be" | "big" | "bigendian" | "motorola" ->
190 def_endian := Some Bitmatch.BigEndian
191 | str -> failwith (sprintf (f_"set_endian: %s: unknown endianness") str)
194 (* Default architecture. *)
195 let def_architecture = ref None in
196 let set_architecture = function
197 | "auto" -> def_architecture := None
199 let arch = architecture_of_string arch in
200 def_architecture := Some arch;
201 def_endian := Some (endian_of_architecture arch);
202 def_wordsize := Some (wordsize_of_architecture arch)
205 (* Default text address. *)
206 let def_text_addr = ref 0L (* 0 = auto-detect *) in
207 let set_text_addr = function
208 | "auto" -> def_text_addr := 0L
209 | "i386" -> def_text_addr := 0xc010_0000_L (* common for x86 *)
210 | "x86-64"|"x86_64" -> def_text_addr := 0xffffffff_81000000_L (* x86-64? *)
211 | str -> def_text_addr := Int64.of_string str
214 (* List of kernel images. *)
215 let images = ref [] in
217 let anon_args = ref [] in
219 let memory_image filename =
221 (!def_wordsize, !def_endian, !def_architecture, !def_text_addr, filename)
226 printf "virt-mem %s\n" Virt_mem_version.version;
228 let major, minor, release =
229 let v, _ = Libvirt.get_version () in
230 v / 1_000_000, (v / 1_000) mod 1_000, v mod 1_000 in
231 printf "libvirt %d.%d.%d\n" major minor release;
235 let argspec = Arg.align [
236 "-A", Arg.String set_architecture,
237 "arch " ^ s_"Set kernel architecture, endianness and word size";
238 "-E", Arg.String set_endian,
239 "endian " ^ s_"Set kernel endianness";
240 "-T", Arg.String set_text_addr,
241 "addr " ^ s_"Set kernel text address";
242 "-W", Arg.String set_wordsize,
243 "addr " ^ s_"Set kernel word size";
244 "-c", Arg.Set_string uri,
245 "uri " ^ s_ "Connect to URI";
246 "--connect", Arg.Set_string uri,
247 "uri " ^ s_ "Connect to URI";
248 "--debug", Arg.Set debug,
249 " " ^ s_"Debug mode (default: false)";
250 "-t", Arg.String memory_image,
251 "image " ^ s_"Use saved kernel memory image";
252 "--version", Arg.Unit version,
253 " " ^ s_"Display version and exit";
256 let anon_arg str = anon_args := str :: !anon_args in
257 Arg.parse argspec anon_arg usage_msg;
259 let images = !images in
260 let debug = !debug in
261 let uri = if !uri = "" then None else Some !uri in
262 let anon_args = List.rev !anon_args in
264 (* At this point, either --help was specified on the command line
265 * (and so the program has exited) or we must have determined tool,
266 * or the user didn't give us a valid tool (eg. "virt-mem foobar").
267 * Detect that final case now and give an error.
269 let name, _, _, _, run_fn =
274 virt-mem: I could not work out which tool you are trying to run.
275 Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
278 if debug then eprintf "tool = %s\n%!" name;
280 (* Get the kernel images. *)
282 if images = [] then (
285 try C.connect_readonly ?name ()
286 with Libvirt.Virterror err ->
287 prerr_endline (Libvirt.Virterror.to_string err);
288 (* If non-root and no explicit connection URI, print a warning. *)
289 if Unix.geteuid () <> 0 && name = None then (
290 print_endline (s_ "NB: If you want to monitor a local Xen hypervisor, you usually need to be root");
294 (* If we have a list of parameters, then it is the domain names / UUIDs /
295 * IDs ONLY that we wish to display. Otherwise, display all active.
298 if anon_args = [] then (
299 (* List of active domains. *)
300 let nr_active_doms = C.num_of_domains conn in
302 Array.to_list (C.list_domains conn nr_active_doms) in
303 List.map (D.lookup_by_id conn) active_doms
308 try D.lookup_by_uuid_string conn arg
310 try D.lookup_by_name conn arg
312 try D.lookup_by_id conn (int_of_string arg)
314 failwith (sprintf (f_"%s: unknown domain (not a UUID, name or ID of any active domain)") arg) in
316 (* XXX Primitive test to see if the domain is active. *)
317 let is_active = try D.get_id dom >= 0 with _ -> false in
318 if not is_active then
319 failwith (sprintf (f_"%s: domain is not running") arg);
326 let xmls = List.map (fun dom -> dom, D.get_xml_desc dom) doms in
329 let xmls = List.map (fun (dom, xml) ->
330 dom, Xml.parse_string xml) xmls in
332 (* XXX Do something with the XML XXX
333 * such as detecting arch, wordsize, endianness.
343 let name = D.get_name dom in
346 match !def_wordsize with
349 (sprintf (f_"%s: use -W to define word size for this image")
353 match !def_endian with
356 (sprintf (f_"%s: use -E to define endianness for this image")
361 match !def_architecture with
362 | Some I386 -> I386 | Some X86_64 -> X86_64
365 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") name) in
367 if !def_text_addr = 0L then
369 (sprintf (f_"%s: use -T to define kernel load address for this image") name);
371 let start_t = gettimeofday () in
373 (* Read the kernel memory.
374 * Maximum 64K can be read over remote connections.
376 let str = String.create kernel_size in
378 let remaining = kernel_size - i in
379 if remaining > 0 then (
380 let size = min remaining max_memory_peek in
381 D.memory_peek dom [D.Virtual]
382 (!def_text_addr +^ Int64.of_int i) size str i;
389 let end_t = gettimeofday () in
390 eprintf "timing: downloading kernel took %f seconds\n%!"
394 (* Map the virtual memory. *)
395 let mem = MMap.of_string str !def_text_addr in
397 (* Force the wordsize and endianness. *)
398 let mem = MMap.set_wordsize mem wordsize in
399 let mem = MMap.set_endian mem endian in
404 (* One or more -t options passed. *)
405 if anon_args <> [] then
406 failwith (s_"virt-mem: if -t given on command line, then no domain arguments should be listed");
409 fun (wordsize, endian, arch, text_addr, filename) ->
410 (* Quite a lot of limitations on the kernel images we can
411 * handle at the moment ...
413 (* XXX We could auto-detect wordsize easily. *)
418 (sprintf (f_"%s: use -W to define word size for this image")
425 (sprintf (f_"%s: use -E to define endianness for this image")
431 | Some I386 -> I386 | Some X86_64 -> X86_64
434 (sprintf (f_"%s: use -A to define architecture (i386/x86-64 only) for this image") filename) in
436 if text_addr = 0L then
438 (sprintf (f_"%s: use -T to define kernel load address for this image")
441 (* Map the virtual memory. *)
442 let fd = openfile filename [O_RDONLY] 0 in
443 let mem = MMap.of_file fd text_addr in
445 (* Force the wordsize and endianness. *)
446 let mem = MMap.set_wordsize mem wordsize in
447 let mem = MMap.set_endian mem endian in
449 (filename, arch, mem)
455 fun (name, arch, mem) ->
456 (* Look for some common entries in the exported symbol table and
457 * from that find the symbol table itself. These are just
458 * supposed to be symbols which are very likely to be present
459 * in any Linux kernel, although we only need one of them to be
460 * present to find the symbol table.
462 * NB. Must not be __initdata, must be in EXPORT_SYMBOL.
465 "init_task"; (* first task_struct *)
466 "root_mountflags"; (* flags for mounting root fs *)
467 "init_uts_ns"; (* uname strings *)
468 "sys_open"; (* open(2) entry point *)
469 "sys_chdir"; (* chdir(2) entry point *)
470 "sys_chroot"; (* chroot(2) entry point *)
471 "sys_umask"; (* umask(2) entry point *)
472 "schedule"; (* scheduler entry point *)
474 (* Searching for <NUL>string<NUL> *)
475 let common_ksyms_nul = List.map (sprintf "\000%s\000") common_ksyms in
477 let start_t = gettimeofday () in
479 (* Search for these strings in the memory image. *)
480 let ksym_strings = List.map (MMap.find_all mem) common_ksyms_nul in
481 let ksym_strings = List.concat ksym_strings in
482 (* Adjust found addresses to start of the string (skip <NUL>). *)
483 let ksym_strings = List.map Int64.succ ksym_strings in
486 let end_t = gettimeofday () in
487 eprintf "timing: searching for common_ksyms took %f seconds\n%!"
491 let start_t = gettimeofday () in
493 (* For any we found, try to look up the symbol table
494 * base addr and size.
496 let ksymtabs = List.map (
498 (* Search for 'addr' appearing in the image. *)
499 let addrs = MMap.find_pointer_all mem addr in
501 (* Now consider each of these addresses and search back
502 * until we reach the beginning of the (possible) symbol
505 * Kernel symbol table struct is:
506 * struct kernel_symbol {
507 * unsigned long value;
508 * const char *name; <-- initial pointer
511 let pred_long2 addr =
512 MMap.pred_long mem (MMap.pred_long mem addr)
514 let base_addrs = List.map (
517 (* '*addr' should point to a C identifier. If it does,
518 * step backwards to the previous symbol table entry.
520 let addrp = MMap.follow_pointer mem addr in
521 if MMap.is_C_identifier mem addrp then
522 loop (pred_long2 addr)
524 MMap.succ_long mem addr
529 (* Also look for the end of the symbol table and
530 * calculate its size.
532 let base_addrs_sizes = List.map (
535 let addr2 = MMap.succ_long mem addr in
536 let addr2p = MMap.follow_pointer mem addr2 in
537 if MMap.is_C_identifier mem addr2p then
538 loop (MMap.succ_long mem addr2)
542 let end_addr = loop base_addr in
543 base_addr, end_addr -^ base_addr
548 let ksymtabs = List.concat ksymtabs in
550 (* Simply ignore any symbol table candidates which are too small. *)
551 let ksymtabs = List.filter (fun (_, size) -> size > 64L) ksymtabs in
554 eprintf "%s: candidate symbol tables at:\n" name;
557 eprintf "\t%Lx\t%Lx\t%!" addr size;
558 eprintf "first symbol: %s\n%!"
560 (MMap.follow_pointer mem
561 (MMap.succ_long mem addr)))
565 (* Vote for the most popular symbol table candidate and from this
566 * generate a function to look up ksyms.
569 let freqs = frequency ksymtabs in
572 eprintf (f_"%s: cannot find start of kernel symbol table\n") name;
573 (fun _ -> raise Not_found)
575 | (_, (ksymtab_addr, ksymtab_size)) :: _ ->
578 "%s: Kernel symbol table found at %Lx, size %Lx bytes\n%!"
579 name ksymtab_addr ksymtab_size;
581 (* Load the whole symbol table as a bitstring. *)
583 Bitmatch.bitstring_of_string
584 (MMap.get_bytes mem ksymtab_addr
585 (Int64.to_int ksymtab_size)) in
587 (* Function to look up an address in the symbol table. *)
588 let lookup_ksym sym =
589 let bits = bits_of_wordsize (MMap.get_wordsize mem) in
590 let e = MMap.get_endian mem in
593 | { value : bits : endian(e);
594 name_ptr : bits : endian(e) }
595 when MMap.get_string mem name_ptr = sym ->
597 | { _ : bits : endian(e);
598 _ : bits : endian(e);
599 bs : -1 : bitstring } ->
601 | { _ } -> raise Not_found
610 let end_t = gettimeofday () in
611 eprintf "timing: searching for ordinary ksyms took %f seconds\n%!"
615 let start_t = gettimeofday () in
617 (* Now try to find the /proc/kallsyms table. This is in an odd
618 * compressed format (but not a very successful compression
619 * format). However if it exists we know that it will contain
620 * addresses of the common ksyms above, and it has some
621 * characteristics which make it easy to detect in the
624 * kallsyms contains a complete list of symbols so is much
625 * more useful than the basic list of exports.
627 let ksym_addrs = List.filter_map (
628 fun ksym -> try Some (lookup_ksym ksym) with Not_found -> None
631 (* Search for those kernel addresses in the image. We're looking
632 * for the table kallsyms_addresses followed by kallsyms_num_syms
633 * (number of symbols in the table).
635 let ksym_addrs = List.map (MMap.find_pointer_all mem) ksym_addrs in
636 let ksym_addrs = List.concat ksym_addrs in
638 (* Test each one to see if it's a candidate list of kernel
639 * addresses followed by length of list.
641 let kallsymtabs = List.filter_map (
643 (* Search upwards from address until we find the length field.
644 * If found, jump backwards by length and check all addresses.
647 eprintf "%s: testing candidate kallsyms at %Lx\n" name addr;
649 let addrp = MMap.follow_pointer mem addr in
650 if MMap.is_mapped mem addrp then
651 loop (MMap.succ_long mem addr) (* continue up the table *)
653 if addrp >= min_kallsyms_tabsize &&
654 addrp <= max_kallsyms_tabsize then (
655 (* addrp might be the symbol count. Count backwards and
656 * check the full table.
658 let num_entries = Int64.to_int addrp in
659 let entry_size = bytes_of_wordsize (MMap.get_wordsize mem) in
661 addr -^ Int64.of_int (entry_size * num_entries) in
662 let end_addr = addr in
664 if addr < end_addr then (
665 let addrp = MMap.follow_pointer mem addr in
666 if MMap.is_mapped mem addrp then
667 loop2 (MMap.succ_long mem addr)
669 None (* can't verify the full address table *)
672 let names_addr = MMap.succ_long mem end_addr in
674 eprintf "%s: candidate kallsyms found at %Lx (names_addr at %Lx, num_entries %d)\n"
675 name start_addr names_addr num_entries;
676 Some (start_addr, num_entries, names_addr)
685 | Some (start_addr, num_entries, names_addr) ->
686 (* As an additional verification, check the list of
690 (* If the first byte is '\000' and is followed by a
691 * C identifier, then this is old-school list of
692 * symbols with prefix compression as in 2.6.9.
693 * Otherwise Huffman-compressed kallsyms as in
696 if MMap.get_byte mem names_addr = 0 &&
697 MMap.is_C_identifier mem (names_addr+^1L) then (
698 let names = ref [] in
700 let rec loop names_addr start_addr num =
702 let prefix = MMap.get_byte mem names_addr in
703 let prefix = String.sub !prev 0 prefix in
704 let name = MMap.get_string mem (names_addr+^1L) in
705 let len = String.length name in
706 let name = prefix ^ name in
708 let names_addr = names_addr +^ Int64.of_int len +^ 2L in
709 let sym_value = MMap.follow_pointer mem start_addr in
710 let start_addr = MMap.succ_long mem start_addr in
711 (*eprintf "%S -> %Lx\n" name sym_value;*)
712 names := (name, sym_value) :: !names;
713 loop names_addr start_addr (num-1)
716 loop names_addr start_addr num_entries;
717 let names = List.rev !names in
719 Some (start_addr, num_entries, names_addr,
722 else ( (* new-style "compressed" names. *)
723 let compressed_names = ref [] in
724 let rec loop names_addr start_addr num =
726 let len = MMap.get_byte mem names_addr in
727 let name = MMap.get_bytes mem (names_addr+^1L) len in
728 let names_addr = names_addr +^ Int64.of_int len +^ 1L in
729 let sym_value = MMap.follow_pointer mem start_addr in
730 let start_addr = MMap.succ_long mem start_addr in
732 (name, sym_value) :: !compressed_names;
733 loop names_addr start_addr (num-1)
737 let markers_addr = loop names_addr start_addr num_entries in
738 let markers_addr = MMap.align mem markers_addr in
739 let compressed_names = List.rev !compressed_names in
741 Some (start_addr, num_entries, names_addr,
742 Compressed (compressed_names, markers_addr))
745 Invalid_argument _ -> None (* bad names list *)
749 eprintf "%s: candidate kallsyms at:\n" name;
752 | (start_addr, num_entries, names_addr, Uncompressed _) ->
753 eprintf "\t%Lx %d entries names_addr=%Lx old-style\n%!"
754 start_addr num_entries names_addr
755 | (start_addr, num_entries, names_addr,
756 Compressed (_, markers_addr)) ->
757 eprintf "\t%Lx %d entries names_addr=%Lx markers_addr=%Lx\n%!"
758 start_addr num_entries names_addr markers_addr
762 (* Vote for the most popular symbol table candidate and
763 * enhance the function for looking up ksyms.
766 let freqs = frequency kallsymtabs in
769 (* Can't find any kallsymtabs, just return the lookup_ksym
770 * function generated previously from the exported symbols.
774 | (_, (_, _, _, Uncompressed names)) :: _ ->
775 let lookup_ksym name =
776 try (* first look it up in kallsyms table. *)
777 List.assoc name names
778 with Not_found -> (* try the old exports table instead *)
783 | (_, (start_addr, num_entries, names_addr,
784 Compressed (compressed_names, markers_addr))) :: _ ->
785 (* Skip the markers and look for the token table. *)
786 let num_markers = Int64.of_int ((num_entries + 255) / 256) in
788 Int64.of_int (bytes_of_wordsize (MMap.get_wordsize mem)) in
789 let tokens_addr = markers_addr +^ marker_size *^ num_markers in
791 (* Now read out the compression tokens, which are just
792 * 256 ASCIIZ strings that map bytes in the compression
793 * names to substrings.
795 let tokens = Array.make 256 "" in
796 let rec loop i addr =
798 let str = MMap.get_string mem addr in
799 let len = String.length str in
800 let addr = addr +^ Int64.of_int (len+1) in
807 (* Expand the compressed names using the tokens. *)
808 let names = List.filter_map (
809 fun (name, sym_value) ->
810 let f c = tokens.(Char.code c) in
811 let name = String.replace_chars f name in
812 (* First character in uncompressed output is the symbol
813 * type, eg. 'T'/'t' for text etc.
815 (* NOTE: Symbol names are NOT unique
816 * (eg. 'con_start' is both a function and data in
817 * some kernels). XXX We need to handle this situation
820 (*let typ = name.[0] in*)
821 let name = String.sub name 1 (String.length name - 1) in
822 (*eprintf "%S -> %Lx\n" name sym_value;*)
823 Some (name, sym_value)
824 ) compressed_names in
826 let lookup_ksym name =
827 try (* first look it up in kallsyms table. *)
828 List.assoc name names
829 with Not_found -> (* try the old exports table instead *)
836 let end_t = gettimeofday () in
837 eprintf "timing: searching for kallsyms took %f seconds\n%!"
841 (* Just wrap the lookup_ksym call in something which prints
842 * the query when debug is set.
846 let lookup_ksym sym =
848 let value = lookup_ksym sym in
849 eprintf "lookup_ksym %S = %Lx\n%!" sym value;
852 eprintf "lookup_ksym %S failed\n%!" sym;
860 ((name, arch, mem, lookup_ksym) : image)
863 (* Run the actual tool. *)