Split out the kernel symbol detection code.
[virt-mem.git] / lib / virt_mem_ksyms.ml
diff --git a/lib/virt_mem_ksyms.ml b/lib/virt_mem_ksyms.ml
new file mode 100644 (file)
index 0000000..d70ace1
--- /dev/null
@@ -0,0 +1,191 @@
+(* 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.
+
+   Ordinary kernel symbol lookups.
+ *)
+
+open Unix
+open Printf
+
+open Virt_mem_gettext.Gettext
+open Virt_mem_utils
+open Virt_mem_types
+
+(* 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
+ * in any Linux kernel, although we only need one of them to be
+ * present to find the symbol table.
+ *
+ * NB. Must not be __initdata, must be in EXPORT_SYMBOL.
+ *)
+let common_ksyms = [
+  "init_task";                         (* first task_struct *)
+  "root_mountflags";                   (* flags for mounting root fs *)
+  "init_uts_ns";                       (* uname strings *)
+  "sys_open";                          (* open(2) entry point *)
+  "sys_chdir";                         (* chdir(2) entry point *)
+  "sys_chroot";                                (* chroot(2) entry point *)
+  "sys_umask";                         (* umask(2) entry point *)
+  "schedule";                          (* scheduler entry point *)
+]
+
+let find_kernel_symbols debug (domid, name, arch, mem) =
+  (* Searching for <NUL>string<NUL> *)
+  let common_ksyms_nul = List.map (sprintf "\000%s\000") common_ksyms in
+
+  let start_t = gettimeofday () in
+
+  (* Search for these strings in the memory image. *)
+  let ksym_strings =
+    List.map (Virt_mem_mmap.find_all mem) common_ksyms_nul in
+  let ksym_strings = List.concat ksym_strings in
+  (* Adjust found addresses to start of the string (skip <NUL>). *)
+  let ksym_strings = List.map Int64.succ ksym_strings in
+
+  if debug then (
+    let end_t = gettimeofday () in
+    eprintf "timing: searching for common_ksyms took %f seconds\n%!"
+      (end_t -. start_t)
+  );
+
+  let start_t = gettimeofday () in
+
+  (* For any we found, try to look up the symbol table
+   * base addr and size.
+   *)
+  let ksymtabs = List.map (
+    fun addr ->
+      (* Search for 'addr' appearing in the image. *)
+      let addrs = Virt_mem_mmap.find_pointer_all mem addr in
+
+      (* Now consider each of these addresses and search back
+       * until we reach the beginning of the (possible) symbol
+       * table.
+       *
+       * Kernel symbol table struct is:
+       * struct kernel_symbol {
+       *   unsigned long value;
+       *   const char *name;    <-- initial pointer
+       * } symbols[];
+       *)
+      let pred_long2 addr =
+       Virt_mem_mmap.pred_long mem (Virt_mem_mmap.pred_long mem addr)
+      in
+      let base_addrs = List.map (
+       fun addr ->
+         let rec loop addr =
+           (* '*addr' should point to a C identifier.  If it does,
+            * step backwards to the previous symbol table entry.
+            *)
+           let addrp = Virt_mem_mmap.follow_pointer mem addr in
+           if Virt_mem_mmap.is_C_identifier mem addrp then
+             loop (pred_long2 addr)
+           else
+             Virt_mem_mmap.succ_long mem addr
+         in
+         loop addr
+      ) addrs in
+
+      (* Also look for the end of the symbol table and
+       * calculate its size.
+       *)
+      let base_addrs_sizes = List.map (
+       fun base_addr ->
+         let rec loop addr =
+           let addr2 = Virt_mem_mmap.succ_long mem addr in
+           let addr2p = Virt_mem_mmap.follow_pointer mem addr2 in
+           if Virt_mem_mmap.is_C_identifier mem addr2p then
+             loop (Virt_mem_mmap.succ_long mem addr2)
+           else
+             addr
+         in
+         let end_addr = loop base_addr in
+         base_addr, end_addr -^ base_addr
+      ) base_addrs in
+
+      base_addrs_sizes
+  ) ksym_strings in
+  let ksymtabs = List.concat ksymtabs in
+
+  (* Simply ignore any symbol table candidates which are too small. *)
+  let ksymtabs = List.filter (fun (_, size) -> size > 64L) ksymtabs in
+
+  if debug then (
+    eprintf "%s: candidate symbol tables at:\n" name;
+    List.iter (
+      fun (addr, size) ->
+       eprintf "\t%Lx\t%Lx\t%!" addr size;
+       eprintf "first symbol: %s\n%!"
+         (Virt_mem_mmap.get_string mem
+            (Virt_mem_mmap.follow_pointer mem
+               (Virt_mem_mmap.succ_long mem addr)))
+    ) ksymtabs
+  );
+
+  (* Vote for the most popular symbol table candidate and from this
+   * generate a function to look up ksyms.
+   *)
+  let lookup_ksym =
+    let freqs = frequency ksymtabs in
+    match freqs with
+    | [] ->
+       eprintf (f_"%s: cannot find start of kernel symbol table\n") name;
+       (fun _ -> raise Not_found)
+
+    | (_, (ksymtab_addr, ksymtab_size)) :: _ ->
+       if debug then
+         eprintf
+           "%s: Kernel symbol table found at %Lx, size %Lx bytes\n%!"
+           name ksymtab_addr ksymtab_size;
+
+       (* Load the whole symbol table as a bitstring. *)
+       let ksymtab =
+         Bitstring.bitstring_of_string
+           (Virt_mem_mmap.get_bytes mem ksymtab_addr
+              (Int64.to_int ksymtab_size)) in
+
+       (* Function to look up an address in the symbol table. *)
+       let lookup_ksym sym =
+         let bits = bits_of_wordsize (Virt_mem_mmap.get_wordsize mem) in
+         let e = Virt_mem_mmap.get_endian mem in
+         let rec loop bs =
+           bitmatch bs with
+           | { value : bits : endian(e);
+               name_ptr : bits : endian(e) }
+               when Virt_mem_mmap.get_string mem name_ptr = sym ->
+               value
+           | { _ : bits : endian(e);
+               _ : bits : endian(e);
+               bs : -1 : bitstring } ->
+               loop bs
+           | { _ } -> raise Not_found
+         in
+         loop ksymtab
+       in
+
+       lookup_ksym
+  in
+
+  if debug then (
+    let end_t = gettimeofday () in
+    eprintf "timing: searching for ordinary ksyms took %f seconds\n%!"
+      (end_t -. start_t)
+  );
+
+  ((domid, name, arch, mem, lookup_ksym) : image1)