(* 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. Find kallsyms in a kernel image. *) open Unix open Printf open ExtList open ExtString open Virt_mem_gettext.Gettext open Virt_mem_utils open Virt_mem_types let min_kallsyms_tabsize = 1_000L let max_kallsyms_tabsize = 250_000L type kallsyms_compr = | Compressed of (string * Virt_mem_mmap.addr) list * Virt_mem_mmap.addr | Uncompressed of (string * Virt_mem_mmap.addr) list let find_kallsyms debug ({ domname = domname; mem = mem; ksyms = ksyms } as kimage) = let start_t = gettimeofday () in (* Now try to find the /proc/kallsyms table. This is in an odd * compressed format (but not a very successful compression * format). However if it exists we know that it will contain * addresses of the ordinary ksyms, and it has some * characteristics which make it easy to detect in the * memory. * * kallsyms contains a complete list of symbols so is much * more useful than the basic list of exports. *) let ksym_addrs = List.filter_map ( fun ksym -> try Some (Ksymmap.find ksym ksyms) with Not_found -> None ) Virt_mem_ksyms.common_ksyms in (* Search for those kernel addresses in the image. We're looking * for the table kallsyms_addresses followed by kallsyms_num_syms * (number of symbols in the table). *) let ksym_addrs = List.map (Virt_mem_mmap.find_pointer_all mem) ksym_addrs in let ksym_addrs = List.concat ksym_addrs in (* Test each one to see if it's a candidate list of kernel * addresses followed by length of list. *) let kallsymtabs = List.filter_map ( fun addr -> (* Search upwards from address until we find the length field. * If found, jump backwards by length and check all addresses. *) if debug then eprintf "%s: testing candidate kallsyms at %Lx\n" domname addr; let rec loop addr = let addrp = Virt_mem_mmap.follow_pointer mem addr in if Virt_mem_mmap.is_mapped mem addrp then (* continue up the table *) loop (Virt_mem_mmap.succ_long mem addr) else if addrp >= min_kallsyms_tabsize && addrp <= max_kallsyms_tabsize then ( (* addrp might be the symbol count. Count backwards and * check the full table. *) let num_entries = Int64.to_int addrp in let entry_size = bytes_of_wordsize (Virt_mem_mmap.get_wordsize mem) in let start_addr = addr -^ Int64.of_int (entry_size * num_entries) in let end_addr = addr in let rec loop2 addr = if addr < end_addr then ( let addrp = Virt_mem_mmap.follow_pointer mem addr in if Virt_mem_mmap.is_mapped mem addrp then loop2 (Virt_mem_mmap.succ_long mem addr) else None (* can't verify the full address table *) ) else (* ok! *) let names_addr = Virt_mem_mmap.succ_long mem end_addr in if debug then eprintf "%s: candidate kallsyms found at %Lx (names_addr at %Lx, num_entries %d)\n" domname start_addr names_addr num_entries; Some (start_addr, num_entries, names_addr) in loop2 start_addr ) else None (* forget it *) in match loop addr with | None -> None | Some (start_addr, num_entries, names_addr) -> (* As an additional verification, check the list of * kallsyms_names. *) try (* If the first byte is '\000' and is followed by a * C identifier, then this is old-school list of * symbols with prefix compression as in 2.6.9. * Otherwise Huffman-compressed kallsyms as in * 2.6.25. *) if Virt_mem_mmap.get_byte mem names_addr = 0 && Virt_mem_mmap.is_C_identifier mem (names_addr+^1L) then ( let names = ref [] in let prev = ref "" in let rec loop names_addr start_addr num = if num > 0 then ( let prefix = Virt_mem_mmap.get_byte mem names_addr in let prefix = String.sub !prev 0 prefix in let name = Virt_mem_mmap.get_string mem (names_addr+^1L) in let len = String.length name in let name = prefix ^ name in prev := name; let names_addr = names_addr +^ Int64.of_int len +^ 2L in let sym_value = Virt_mem_mmap.follow_pointer mem start_addr in let start_addr = Virt_mem_mmap.succ_long mem start_addr in (*eprintf "%S -> %Lx\n" name sym_value;*) names := (name, sym_value) :: !names; loop names_addr start_addr (num-1) ) in loop names_addr start_addr num_entries; let names = List.rev !names in Some (start_addr, num_entries, names_addr, Uncompressed names) ) else ( (* new-style "compressed" names. *) let compressed_names = ref [] in let rec loop names_addr start_addr num = if num > 0 then ( let len = Virt_mem_mmap.get_byte mem names_addr in let name = Virt_mem_mmap.get_bytes mem (names_addr+^1L) len in let names_addr = names_addr +^ Int64.of_int len +^ 1L in let sym_value = Virt_mem_mmap.follow_pointer mem start_addr in let start_addr = Virt_mem_mmap.succ_long mem start_addr in compressed_names := (name, sym_value) :: !compressed_names; loop names_addr start_addr (num-1) ) else names_addr in let markers_addr = loop names_addr start_addr num_entries in let markers_addr = Virt_mem_mmap.align mem markers_addr in let compressed_names = List.rev !compressed_names in Some (start_addr, num_entries, names_addr, Compressed (compressed_names, markers_addr)) ) with Invalid_argument _ -> None (* bad names list *) ) ksym_addrs in if debug then ( eprintf "%s: candidate kallsyms at:\n" domname; List.iter ( function | (start_addr, num_entries, names_addr, Uncompressed _) -> eprintf "\t%Lx %d entries names_addr=%Lx old-style\n%!" start_addr num_entries names_addr | (start_addr, num_entries, names_addr, Compressed (_, markers_addr)) -> eprintf "\t%Lx %d entries names_addr=%Lx markers_addr=%Lx\n%!" start_addr num_entries names_addr markers_addr ) kallsymtabs ); (* Vote for the most popular symbol table candidate and * enhance the function for looking up ksyms. *) let ksyms, have_kallsyms = let freqs = frequency kallsymtabs in match freqs with | [] -> (* Can't find any kallsymtabs. *) ksyms, false | (_, (_, _, _, Uncompressed names)) :: _ -> let rec loop ksyms = function | (name, value) :: names -> loop (Ksymmap.add name value ksyms) names | [] -> ksyms in loop ksyms names, true | (_, (start_addr, num_entries, names_addr, Compressed (compressed_names, markers_addr))) :: _ -> (* Skip the markers and look for the token table. *) let num_markers = Int64.of_int ((num_entries + 255) / 256) in let marker_size = Int64.of_int (bytes_of_wordsize (Virt_mem_mmap.get_wordsize mem)) in let tokens_addr = markers_addr +^ marker_size *^ num_markers in (* Now read out the compression tokens, which are just * 256 ASCIIZ strings that map bytes in the compression * names to substrings. *) let tokens = Array.make 256 "" in let rec loop i addr = if i < 256 then ( let str = Virt_mem_mmap.get_string mem addr in let len = String.length str in let addr = addr +^ Int64.of_int (len+1) in tokens.(i) <- str; loop (i+1) addr ) in loop 0 tokens_addr; (* Expand the compressed names using the tokens. *) let names = List.filter_map ( fun (name, sym_value) -> let f c = tokens.(Char.code c) in let name = String.replace_chars f name in (* First character in uncompressed output is the symbol * type, eg. 'T'/'t' for text etc. *) (* NOTE: Symbol names are NOT unique * (eg. 'con_start' is both a function and data in * some kernels). XXX We need to handle this situation * better. *) (*let typ = name.[0] in*) let name = String.sub name 1 (String.length name - 1) in (*eprintf "%S -> %Lx\n" name sym_value;*) Some (name, sym_value) ) compressed_names in let rec loop ksyms = function | (name, value) :: names -> loop (Ksymmap.add name value ksyms) names | [] -> ksyms in loop ksyms names, true in if debug then ( let end_t = gettimeofday () in eprintf "timing: searching for kallsyms took %f seconds\n%!" (end_t -. start_t) ); { kimage with ksyms = ksyms; have_kallsyms = have_kallsyms }