1 (* Memory info command 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.
19 Find kallsyms in a kernel image.
28 open Virt_mem_gettext.Gettext
32 let min_kallsyms_tabsize = 1_000L
33 let max_kallsyms_tabsize = 250_000L
36 | Compressed of (string * Virt_mem_mmap.addr) list * Virt_mem_mmap.addr
37 | Uncompressed of (string * Virt_mem_mmap.addr) list
39 let find_kallsyms debug ({ domname = domname; mem = mem } as image) ksymmap =
40 let start_t = gettimeofday () in
42 (* Now try to find the /proc/kallsyms table. This is in an odd
43 * compressed format (but not a very successful compression
44 * format). However if it exists we know that it will contain
45 * addresses of the ordinary ksyms, and it has some
46 * characteristics which make it easy to detect in the
49 * kallsyms contains a complete list of symbols so is much
50 * more useful than the basic list of exports.
52 let ksym_addrs = List.filter_map (
53 fun ksym -> try Some (Ksymmap.find ksym ksymmap) with Not_found -> None
54 ) Virt_mem_ksyms.common_ksyms in
56 (* Search for those kernel addresses in the image. We're looking
57 * for the table kallsyms_addresses followed by kallsyms_num_syms
58 * (number of symbols in the table).
61 List.map (Virt_mem_mmap.find_pointer_all mem) ksym_addrs in
62 let ksym_addrs = List.concat ksym_addrs in
64 (* Test each one to see if it's a candidate list of kernel
65 * addresses followed by length of list.
67 let kallsymtabs = List.filter_map (
69 (* Search upwards from address until we find the length field.
70 * If found, jump backwards by length and check all addresses.
73 eprintf "%s: testing candidate kallsyms at %Lx\n" domname addr;
75 let addrp = Virt_mem_mmap.follow_pointer mem addr in
76 if Virt_mem_mmap.is_mapped mem addrp then
77 (* continue up the table *)
78 loop (Virt_mem_mmap.succ_long mem addr)
80 if addrp >= min_kallsyms_tabsize &&
81 addrp <= max_kallsyms_tabsize then (
82 (* addrp might be the symbol count. Count backwards and
83 * check the full table.
85 let num_entries = Int64.to_int addrp in
87 bytes_of_wordsize (Virt_mem_mmap.get_wordsize mem) in
89 addr -^ Int64.of_int (entry_size * num_entries) in
90 let end_addr = addr in
92 if addr < end_addr then (
93 let addrp = Virt_mem_mmap.follow_pointer mem addr in
94 if Virt_mem_mmap.is_mapped mem addrp then
95 loop2 (Virt_mem_mmap.succ_long mem addr)
97 None (* can't verify the full address table *)
100 let names_addr = Virt_mem_mmap.succ_long mem end_addr in
102 eprintf "%s: candidate kallsyms found at %Lx (names_addr at %Lx, num_entries %d)\n"
103 domname start_addr names_addr num_entries;
104 Some (start_addr, num_entries, names_addr)
113 | Some (start_addr, num_entries, names_addr) ->
114 (* As an additional verification, check the list of
118 (* If the first byte is '\000' and is followed by a
119 * C identifier, then this is old-school list of
120 * symbols with prefix compression as in 2.6.9.
121 * Otherwise Huffman-compressed kallsyms as in
124 if Virt_mem_mmap.get_byte mem names_addr = 0 &&
125 Virt_mem_mmap.is_C_identifier mem (names_addr+^1L) then (
126 let names = ref [] in
128 let rec loop names_addr start_addr num =
130 let prefix = Virt_mem_mmap.get_byte mem names_addr in
131 let prefix = String.sub !prev 0 prefix in
133 Virt_mem_mmap.get_string mem (names_addr+^1L) in
134 let len = String.length name in
135 let name = prefix ^ name in
137 let names_addr = names_addr +^ Int64.of_int len +^ 2L in
139 Virt_mem_mmap.follow_pointer mem start_addr in
141 Virt_mem_mmap.succ_long mem start_addr in
142 (*eprintf "%S -> %Lx\n" name sym_value;*)
143 names := (name, sym_value) :: !names;
144 loop names_addr start_addr (num-1)
147 loop names_addr start_addr num_entries;
148 let names = List.rev !names in
150 Some (start_addr, num_entries, names_addr,
153 else ( (* new-style "compressed" names. *)
154 let compressed_names = ref [] in
155 let rec loop names_addr start_addr num =
157 let len = Virt_mem_mmap.get_byte mem names_addr in
159 Virt_mem_mmap.get_bytes mem (names_addr+^1L) len in
160 let names_addr = names_addr +^ Int64.of_int len +^ 1L in
162 Virt_mem_mmap.follow_pointer mem start_addr in
164 Virt_mem_mmap.succ_long mem start_addr in
166 (name, sym_value) :: !compressed_names;
167 loop names_addr start_addr (num-1)
171 let markers_addr = loop names_addr start_addr num_entries in
172 let markers_addr = Virt_mem_mmap.align mem markers_addr in
173 let compressed_names = List.rev !compressed_names in
175 Some (start_addr, num_entries, names_addr,
176 Compressed (compressed_names, markers_addr))
179 Invalid_argument _ -> None (* bad names list *)
183 eprintf "%s: candidate kallsyms at:\n" domname;
186 | (start_addr, num_entries, names_addr, Uncompressed _) ->
187 eprintf "\t%Lx %d entries names_addr=%Lx old-style\n%!"
188 start_addr num_entries names_addr
189 | (start_addr, num_entries, names_addr,
190 Compressed (_, markers_addr)) ->
191 eprintf "\t%Lx %d entries names_addr=%Lx markers_addr=%Lx\n%!"
192 start_addr num_entries names_addr markers_addr
196 (* Vote for the most popular symbol table candidate and
197 * enhance the function for looking up ksyms.
200 let freqs = frequency kallsymtabs in
203 (* Can't find any kallsymtabs. *)
206 | (_, (_, _, _, Uncompressed names)) :: _ ->
207 let rec loop ksymmap = function
208 | (name, value) :: names ->
209 loop (Ksymmap.add name value ksymmap) names
212 Some (loop ksymmap names)
214 | (_, (start_addr, num_entries, names_addr,
215 Compressed (compressed_names, markers_addr))) :: _ ->
216 (* Skip the markers and look for the token table. *)
217 let num_markers = Int64.of_int ((num_entries + 255) / 256) in
219 Int64.of_int (bytes_of_wordsize
220 (Virt_mem_mmap.get_wordsize mem)) in
221 let tokens_addr = markers_addr +^ marker_size *^ num_markers in
223 (* Now read out the compression tokens, which are just
224 * 256 ASCIIZ strings that map bytes in the compression
225 * names to substrings.
227 let tokens = Array.make 256 "" in
228 let rec loop i addr =
230 let str = Virt_mem_mmap.get_string mem addr in
231 let len = String.length str in
232 let addr = addr +^ Int64.of_int (len+1) in
239 (* Expand the compressed names using the tokens. *)
240 let names = List.filter_map (
241 fun (name, sym_value) ->
242 let f c = tokens.(Char.code c) in
243 let name = String.replace_chars f name in
244 (* First character in uncompressed output is the symbol
245 * type, eg. 'T'/'t' for text etc.
247 (* NOTE: Symbol names are NOT unique
248 * (eg. 'con_start' is both a function and data in
249 * some kernels). XXX We need to handle this situation
252 (*let typ = name.[0] in*)
253 let name = String.sub name 1 (String.length name - 1) in
254 (*eprintf "%S -> %Lx\n" name sym_value;*)
255 Some (name, sym_value)
256 ) compressed_names in
258 let rec loop ksymmap = function
259 | (name, value) :: names ->
260 loop (Ksymmap.add name value ksymmap) names
263 Some (loop ksymmap names) in
266 let end_t = gettimeofday () in
267 eprintf "timing: searching for kallsyms took %f seconds\n%!"