Get rid of lookup_ksym function, replace with a map.
[virt-mem.git] / lib / virt_mem_kallsyms.ml
1 (* Memory info command for virtual domains.
2    (C) Copyright 2008 Richard W.M. Jones, Red Hat Inc.
3    http://libvirt.org/
4
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.
9
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.
14
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.
18
19    Find kallsyms in a kernel image.
20  *)
21
22 open Unix
23 open Printf
24
25 open ExtList
26 open ExtString
27
28 open Virt_mem_gettext.Gettext
29 open Virt_mem_utils
30 open Virt_mem_types
31
32 let min_kallsyms_tabsize = 1_000L
33 let max_kallsyms_tabsize = 250_000L
34
35 type kallsyms_compr =
36   | Compressed of (string * Virt_mem_mmap.addr) list * Virt_mem_mmap.addr
37   | Uncompressed of (string * Virt_mem_mmap.addr) list
38
39 let find_kallsyms debug (({ domname = domname; mem = mem } as image), ksymmap) =
40   let start_t = gettimeofday () in
41
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
47    * memory.
48    *
49    * kallsyms contains a complete list of symbols so is much
50    * more useful than the basic list of exports.
51    *)
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
55
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).
59    *)
60   let ksym_addrs =
61     List.map (Virt_mem_mmap.find_pointer_all mem) ksym_addrs in
62   let ksym_addrs = List.concat ksym_addrs in
63
64   (* Test each one to see if it's a candidate list of kernel
65    * addresses followed by length of list.
66    *)
67   let kallsymtabs = List.filter_map (
68     fun addr ->
69       (* Search upwards from address until we find the length field.
70        * If found, jump backwards by length and check all addresses.
71        *)
72       if debug then
73         eprintf "%s: testing candidate kallsyms at %Lx\n" domname addr;
74       let rec loop 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)
79         else
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.
84                *)
85               let num_entries = Int64.to_int addrp in
86               let entry_size =
87                 bytes_of_wordsize (Virt_mem_mmap.get_wordsize mem) in
88               let start_addr =
89                 addr -^ Int64.of_int (entry_size * num_entries) in
90               let end_addr = addr in
91               let rec loop2 addr =
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)
96                   else
97                     None (* can't verify the full address table *)
98                 ) else
99                   (* ok! *)
100                   let names_addr = Virt_mem_mmap.succ_long mem end_addr in
101                   if debug then
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)
105               in
106               loop2 start_addr
107             )
108           else
109             None (* forget it *)
110       in
111       match loop addr with
112       | None -> None
113       | Some (start_addr, num_entries, names_addr) ->
114           (* As an additional verification, check the list of
115            * kallsyms_names.
116            *)
117           try
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
122              * 2.6.25.
123              *)
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
127                 let prev = ref "" in
128                 let rec loop names_addr start_addr num =
129                   if num > 0 then (
130                     let prefix = Virt_mem_mmap.get_byte mem names_addr in
131                     let prefix = String.sub !prev 0 prefix in
132                     let name =
133                       Virt_mem_mmap.get_string mem (names_addr+^1L) in
134                     let len = String.length name in
135                     let name = prefix ^ name in
136                     prev := name;
137                     let names_addr = names_addr +^ Int64.of_int len +^ 2L in
138                     let sym_value =
139                       Virt_mem_mmap.follow_pointer mem start_addr in
140                     let start_addr =
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)
145                   )
146                 in
147                 loop names_addr start_addr num_entries;
148                 let names = List.rev !names in
149
150                 Some (start_addr, num_entries, names_addr,
151                       Uncompressed names)
152               )
153             else ( (* new-style "compressed" names. *)
154               let compressed_names = ref [] in
155               let rec loop names_addr start_addr num =
156                 if num > 0 then (
157                   let len = Virt_mem_mmap.get_byte mem names_addr in
158                   let name =
159                     Virt_mem_mmap.get_bytes mem (names_addr+^1L) len in
160                   let names_addr = names_addr +^ Int64.of_int len +^ 1L in
161                   let sym_value =
162                     Virt_mem_mmap.follow_pointer mem start_addr in
163                   let start_addr =
164                     Virt_mem_mmap.succ_long mem start_addr in
165                   compressed_names :=
166                     (name, sym_value) :: !compressed_names;
167                   loop names_addr start_addr (num-1)
168                 ) else
169                   names_addr
170               in
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
174
175               Some (start_addr, num_entries, names_addr,
176                     Compressed (compressed_names, markers_addr))
177             )
178           with
179             Invalid_argument _ -> None (* bad names list *)
180   ) ksym_addrs in
181
182   if debug then (
183     eprintf "%s: candidate kallsyms at:\n" domname;
184     List.iter (
185       function
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
193     ) kallsymtabs
194   );
195
196   (* Vote for the most popular symbol table candidate and
197    * enhance the function for looking up ksyms.
198    *)
199   let ksymmap =
200     let freqs = frequency kallsymtabs in
201     match freqs with
202     | [] ->
203         (* Can't find any kallsymtabs, just return the ksymmap
204          * map generated previously from the exported symbols.
205          *)
206         ksymmap
207
208     | (_, (_, _, _, Uncompressed names)) :: _ ->
209         let rec loop ksymmap = function
210           | (name, value) :: names ->
211               loop (Ksymmap.add name value ksymmap) names
212           | [] -> ksymmap
213         in
214         loop ksymmap names
215
216     | (_, (start_addr, num_entries, names_addr,
217            Compressed (compressed_names, markers_addr))) :: _ ->
218         (* Skip the markers and look for the token table. *)
219         let num_markers = Int64.of_int ((num_entries + 255) / 256) in
220         let marker_size =
221           Int64.of_int (bytes_of_wordsize
222                           (Virt_mem_mmap.get_wordsize mem)) in
223         let tokens_addr = markers_addr +^ marker_size *^ num_markers in
224
225         (* Now read out the compression tokens, which are just
226          * 256 ASCIIZ strings that map bytes in the compression
227          * names to substrings.
228          *)
229         let tokens = Array.make 256 "" in
230         let rec loop i addr =
231           if i < 256 then (
232             let str = Virt_mem_mmap.get_string mem addr in
233             let len = String.length str in
234             let addr = addr +^ Int64.of_int (len+1) in
235             tokens.(i) <- str;
236             loop (i+1) addr
237           )
238         in
239         loop 0 tokens_addr;
240
241         (* Expand the compressed names using the tokens. *)
242         let names = List.filter_map (
243           fun (name, sym_value) ->
244             let f c = tokens.(Char.code c) in
245             let name = String.replace_chars f name in
246             (* First character in uncompressed output is the symbol
247              * type, eg. 'T'/'t' for text etc.
248              *)
249             (* NOTE: Symbol names are NOT unique
250              * (eg. 'con_start' is both a function and data in
251              * some kernels).  XXX We need to handle this situation
252              * better.
253              *)
254             (*let typ = name.[0] in*)
255             let name = String.sub name 1 (String.length name - 1) in
256             (*eprintf "%S -> %Lx\n" name sym_value;*)
257             Some (name, sym_value)
258         ) compressed_names in
259
260         let rec loop ksymmap = function
261           | (name, value) :: names ->
262               loop (Ksymmap.add name value ksymmap) names
263           | [] -> ksymmap
264         in
265         loop ksymmap names in
266
267   if debug then (
268     let end_t = gettimeofday () in
269     eprintf "timing: searching for kallsyms took %f seconds\n%!"
270       (end_t -. start_t)
271   );
272
273   ((image, ksymmap) : image1)