Integrated image/kdata into kimage structure. Removed dead-code.
[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; ksyms = ksyms }
40                            as kimage) =
41   let start_t = gettimeofday () in
42
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
48    * memory.
49    *
50    * kallsyms contains a complete list of symbols so is much
51    * more useful than the basic list of exports.
52    *)
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
56
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).
60    *)
61   let ksym_addrs =
62     List.map (Virt_mem_mmap.find_pointer_all mem) ksym_addrs in
63   let ksym_addrs = List.concat ksym_addrs in
64
65   (* Test each one to see if it's a candidate list of kernel
66    * addresses followed by length of list.
67    *)
68   let kallsymtabs = List.filter_map (
69     fun addr ->
70       (* Search upwards from address until we find the length field.
71        * If found, jump backwards by length and check all addresses.
72        *)
73       if debug then
74         eprintf "%s: testing candidate kallsyms at %Lx\n" domname addr;
75       let rec loop 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)
80         else
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.
85                *)
86               let num_entries = Int64.to_int addrp in
87               let entry_size =
88                 bytes_of_wordsize (Virt_mem_mmap.get_wordsize mem) in
89               let start_addr =
90                 addr -^ Int64.of_int (entry_size * num_entries) in
91               let end_addr = addr in
92               let rec loop2 addr =
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)
97                   else
98                     None (* can't verify the full address table *)
99                 ) else
100                   (* ok! *)
101                   let names_addr = Virt_mem_mmap.succ_long mem end_addr in
102                   if debug then
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)
106               in
107               loop2 start_addr
108             )
109           else
110             None (* forget it *)
111       in
112       match loop addr with
113       | None -> None
114       | Some (start_addr, num_entries, names_addr) ->
115           (* As an additional verification, check the list of
116            * kallsyms_names.
117            *)
118           try
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
123              * 2.6.25.
124              *)
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
128                 let prev = ref "" in
129                 let rec loop names_addr start_addr num =
130                   if num > 0 then (
131                     let prefix = Virt_mem_mmap.get_byte mem names_addr in
132                     let prefix = String.sub !prev 0 prefix in
133                     let name =
134                       Virt_mem_mmap.get_string mem (names_addr+^1L) in
135                     let len = String.length name in
136                     let name = prefix ^ name in
137                     prev := name;
138                     let names_addr = names_addr +^ Int64.of_int len +^ 2L in
139                     let sym_value =
140                       Virt_mem_mmap.follow_pointer mem start_addr in
141                     let start_addr =
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)
146                   )
147                 in
148                 loop names_addr start_addr num_entries;
149                 let names = List.rev !names in
150
151                 Some (start_addr, num_entries, names_addr,
152                       Uncompressed names)
153               )
154             else ( (* new-style "compressed" names. *)
155               let compressed_names = ref [] in
156               let rec loop names_addr start_addr num =
157                 if num > 0 then (
158                   let len = Virt_mem_mmap.get_byte mem names_addr in
159                   let name =
160                     Virt_mem_mmap.get_bytes mem (names_addr+^1L) len in
161                   let names_addr = names_addr +^ Int64.of_int len +^ 1L in
162                   let sym_value =
163                     Virt_mem_mmap.follow_pointer mem start_addr in
164                   let start_addr =
165                     Virt_mem_mmap.succ_long mem start_addr in
166                   compressed_names :=
167                     (name, sym_value) :: !compressed_names;
168                   loop names_addr start_addr (num-1)
169                 ) else
170                   names_addr
171               in
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
175
176               Some (start_addr, num_entries, names_addr,
177                     Compressed (compressed_names, markers_addr))
178             )
179           with
180             Invalid_argument _ -> None (* bad names list *)
181   ) ksym_addrs in
182
183   if debug then (
184     eprintf "%s: candidate kallsyms at:\n" domname;
185     List.iter (
186       function
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
194     ) kallsymtabs
195   );
196
197   (* Vote for the most popular symbol table candidate and
198    * enhance the function for looking up ksyms.
199    *)
200   let ksyms, have_kallsyms =
201     let freqs = frequency kallsymtabs in
202     match freqs with
203     | [] ->
204         (* Can't find any kallsymtabs. *)
205         ksyms, false
206
207     | (_, (_, _, _, Uncompressed names)) :: _ ->
208         let rec loop ksyms = function
209           | (name, value) :: names ->
210               loop (Ksymmap.add name value ksyms) names
211           | [] -> ksyms
212         in
213         loop ksyms names, true
214
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
219         let marker_size =
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
223
224         (* Now read out the compression tokens, which are just
225          * 256 ASCIIZ strings that map bytes in the compression
226          * names to substrings.
227          *)
228         let tokens = Array.make 256 "" in
229         let rec loop i addr =
230           if i < 256 then (
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
234             tokens.(i) <- str;
235             loop (i+1) addr
236           )
237         in
238         loop 0 tokens_addr;
239
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.
247              *)
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
251              * better.
252              *)
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
258
259         let rec loop ksyms = function
260           | (name, value) :: names ->
261               loop (Ksymmap.add name value ksyms) names
262           | [] -> ksyms
263         in
264         loop ksyms names, true in
265
266   if debug then (
267     let end_t = gettimeofday () in
268     eprintf "timing: searching for kallsyms took %f seconds\n%!"
269       (end_t -. start_t)
270   );
271
272   { kimage with ksyms = ksyms; have_kallsyms = have_kallsyms }