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; ksyms = ksyms }
41 let start_t = gettimeofday () in
43 (* Now try to find the /proc/kallsyms table. This is in an odd
44 * compressed format (but not a very successful compression
45 * format). However if it exists we know that it will contain
46 * addresses of the ordinary ksyms, and it has some
47 * characteristics which make it easy to detect in the
50 * kallsyms contains a complete list of symbols so is much
51 * more useful than the basic list of exports.
53 let ksym_addrs = List.filter_map (
54 fun ksym -> try Some (Ksymmap.find ksym ksyms) with Not_found -> None
55 ) Virt_mem_ksyms.common_ksyms in
57 (* Search for those kernel addresses in the image. We're looking
58 * for the table kallsyms_addresses followed by kallsyms_num_syms
59 * (number of symbols in the table).
62 List.map (Virt_mem_mmap.find_pointer_all mem) ksym_addrs in
63 let ksym_addrs = List.concat ksym_addrs in
65 (* Test each one to see if it's a candidate list of kernel
66 * addresses followed by length of list.
68 let kallsymtabs = List.filter_map (
70 (* Search upwards from address until we find the length field.
71 * If found, jump backwards by length and check all addresses.
74 eprintf "%s: testing candidate kallsyms at %Lx\n" domname addr;
76 let addrp = Virt_mem_mmap.follow_pointer mem addr in
77 if Virt_mem_mmap.is_mapped mem addrp then
78 (* continue up the table *)
79 loop (Virt_mem_mmap.succ_long mem addr)
81 if addrp >= min_kallsyms_tabsize &&
82 addrp <= max_kallsyms_tabsize then (
83 (* addrp might be the symbol count. Count backwards and
84 * check the full table.
86 let num_entries = Int64.to_int addrp in
88 bytes_of_wordsize (Virt_mem_mmap.get_wordsize mem) in
90 addr -^ Int64.of_int (entry_size * num_entries) in
91 let end_addr = addr in
93 if addr < end_addr then (
94 let addrp = Virt_mem_mmap.follow_pointer mem addr in
95 if Virt_mem_mmap.is_mapped mem addrp then
96 loop2 (Virt_mem_mmap.succ_long mem addr)
98 None (* can't verify the full address table *)
101 let names_addr = Virt_mem_mmap.succ_long mem end_addr in
103 eprintf "%s: candidate kallsyms found at %Lx (names_addr at %Lx, num_entries %d)\n"
104 domname start_addr names_addr num_entries;
105 Some (start_addr, num_entries, names_addr)
114 | Some (start_addr, num_entries, names_addr) ->
115 (* As an additional verification, check the list of
119 (* If the first byte is '\000' and is followed by a
120 * C identifier, then this is old-school list of
121 * symbols with prefix compression as in 2.6.9.
122 * Otherwise Huffman-compressed kallsyms as in
125 if Virt_mem_mmap.get_byte mem names_addr = 0 &&
126 Virt_mem_mmap.is_C_identifier mem (names_addr+^1L) then (
127 let names = ref [] in
129 let rec loop names_addr start_addr num =
131 let prefix = Virt_mem_mmap.get_byte mem names_addr in
132 let prefix = String.sub !prev 0 prefix in
134 Virt_mem_mmap.get_string mem (names_addr+^1L) in
135 let len = String.length name in
136 let name = prefix ^ name in
138 let names_addr = names_addr +^ Int64.of_int len +^ 2L in
140 Virt_mem_mmap.follow_pointer mem start_addr in
142 Virt_mem_mmap.succ_long mem start_addr in
143 (*eprintf "%S -> %Lx\n" name sym_value;*)
144 names := (name, sym_value) :: !names;
145 loop names_addr start_addr (num-1)
148 loop names_addr start_addr num_entries;
149 let names = List.rev !names in
151 Some (start_addr, num_entries, names_addr,
154 else ( (* new-style "compressed" names. *)
155 let compressed_names = ref [] in
156 let rec loop names_addr start_addr num =
158 let len = Virt_mem_mmap.get_byte mem names_addr in
160 Virt_mem_mmap.get_bytes mem (names_addr+^1L) len in
161 let names_addr = names_addr +^ Int64.of_int len +^ 1L in
163 Virt_mem_mmap.follow_pointer mem start_addr in
165 Virt_mem_mmap.succ_long mem start_addr in
167 (name, sym_value) :: !compressed_names;
168 loop names_addr start_addr (num-1)
172 let markers_addr = loop names_addr start_addr num_entries in
173 let markers_addr = Virt_mem_mmap.align mem markers_addr in
174 let compressed_names = List.rev !compressed_names in
176 Some (start_addr, num_entries, names_addr,
177 Compressed (compressed_names, markers_addr))
180 Invalid_argument _ -> None (* bad names list *)
184 eprintf "%s: candidate kallsyms at:\n" domname;
187 | (start_addr, num_entries, names_addr, Uncompressed _) ->
188 eprintf "\t%Lx %d entries names_addr=%Lx old-style\n%!"
189 start_addr num_entries names_addr
190 | (start_addr, num_entries, names_addr,
191 Compressed (_, markers_addr)) ->
192 eprintf "\t%Lx %d entries names_addr=%Lx markers_addr=%Lx\n%!"
193 start_addr num_entries names_addr markers_addr
197 (* Vote for the most popular symbol table candidate and
198 * enhance the function for looking up ksyms.
200 let ksyms, have_kallsyms =
201 let freqs = frequency kallsymtabs in
204 (* Can't find any kallsymtabs. *)
207 | (_, (_, _, _, Uncompressed names)) :: _ ->
208 let rec loop ksyms = function
209 | (name, value) :: names ->
210 loop (Ksymmap.add name value ksyms) names
213 loop ksyms names, true
215 | (_, (start_addr, num_entries, names_addr,
216 Compressed (compressed_names, markers_addr))) :: _ ->
217 (* Skip the markers and look for the token table. *)
218 let num_markers = Int64.of_int ((num_entries + 255) / 256) in
220 Int64.of_int (bytes_of_wordsize
221 (Virt_mem_mmap.get_wordsize mem)) in
222 let tokens_addr = markers_addr +^ marker_size *^ num_markers in
224 (* Now read out the compression tokens, which are just
225 * 256 ASCIIZ strings that map bytes in the compression
226 * names to substrings.
228 let tokens = Array.make 256 "" in
229 let rec loop i addr =
231 let str = Virt_mem_mmap.get_string mem addr in
232 let len = String.length str in
233 let addr = addr +^ Int64.of_int (len+1) in
240 (* Expand the compressed names using the tokens. *)
241 let names = List.filter_map (
242 fun (name, sym_value) ->
243 let f c = tokens.(Char.code c) in
244 let name = String.replace_chars f name in
245 (* First character in uncompressed output is the symbol
246 * type, eg. 'T'/'t' for text etc.
248 (* NOTE: Symbol names are NOT unique
249 * (eg. 'con_start' is both a function and data in
250 * some kernels). XXX We need to handle this situation
253 (*let typ = name.[0] in*)
254 let name = String.sub name 1 (String.length name - 1) in
255 (*eprintf "%S -> %Lx\n" name sym_value;*)
256 Some (name, sym_value)
257 ) compressed_names in
259 let rec loop ksyms = function
260 | (name, value) :: names ->
261 loop (Ksymmap.add name value ksyms) names
264 loop ksyms names, true in
267 let end_t = gettimeofday () in
268 eprintf "timing: searching for kallsyms took %f seconds\n%!"
272 { kimage with ksyms = ksyms; have_kallsyms = have_kallsyms }