Code generation phase.
[virt-mem.git] / extract / codegen / kerneldb_to_parser.ml
1 (* Memory info 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
20 (* This program takes the kernel database (in kernels/ in toplevel
21    directory) and generates parsing code for the various structures
22    in the kernel that we are interested in.
23
24    The output programs -- *.ml, *.mli files of generated code -- go
25    into lib/ at the toplevel, eg. lib/kernel_task_struct.ml
26
27    The stuff at the top of this file determine what structures
28    and fields we try to parse.
29 *)
30
31 let what = [
32   "task_struct", (
33     "struct task_struct {", "};", true,
34     [ "state"; "prio"; "normal_prio"; "static_prio";
35       "tasks'prev"; "tasks'next"; "comm"]
36   );
37 (*
38   "mm_struct", (
39     "struct mm_struct {", "};", true,
40     [ ]
41   );
42 *)
43   "net_device", (
44     "struct net_device {", "};", true,
45     [ "name"; "dev_addr" ]
46   );
47 ]
48
49 let debug = true
50
51 open Camlp4.PreCast
52 open Syntax
53 (*open Ast*)
54
55 open ExtList
56 open ExtString
57 open Printf
58
59 let (//) = Filename.concat
60
61 let () =
62   let args = Array.to_list Sys.argv in
63
64   let kernelsdir, outputdir =
65     match args with
66     | [_;kd;od] -> kd,od
67     | _ ->
68         let arg0 = Filename.basename Sys.executable_name in
69         eprintf "%s - Turn kernels database into code modules.
70
71 Usage:
72   %s <kernelsdir> <outputdir>
73
74 Example (from toplevel of virt-mem source tree):
75   %s kernels/ lib/
76 " arg0 arg0 arg0;
77         exit 2 in
78
79   (* Get the *.info files from the kernels database. *)
80   let infos = Sys.readdir kernelsdir in
81   let infos = Array.to_list infos in
82   let infos = List.filter (fun name -> String.ends_with name ".info") infos in
83   let infos = List.map ((//) kernelsdir) infos in
84
85   (* Regular expressions.  We really really should use ocaml-mikmatch ... *)
86   let re_oldformat = Pcre.regexp "^RPM: \\d+: \\(build \\d+\\) ([-\\w]+) ([\\w.]+) ([\\w.]+) \\(.*?\\) (\\w+)" in
87   let re_keyvalue = Pcre.regexp "^(\\w+): (.*)" in
88
89   (* Parse in the *.info files.  These have historically had a few different
90    * formats that we need to support.
91    *)
92   let infos = List.map (
93     fun filename ->
94       (* Get the basename (for getting the .data file later on). *)
95       let basename = Filename.chop_suffix filename ".info" in
96
97       let chan = open_in filename in
98       let line = input_line chan in
99
100       (* Kernel version string. *)
101       let version, arch =
102         if Pcre.pmatch ~rex:re_oldformat line then (
103           (* If the file starts with "RPM: \d+: ..." then it's the
104            * original Fedora format.  Everything in one line.
105            *)
106           let subs = Pcre.exec ~rex:re_oldformat line in
107           (* let name = Pcre.get_substring subs 1 in *)
108           let version = Pcre.get_substring subs 2 in
109           let release = Pcre.get_substring subs 3 in
110           let arch = Pcre.get_substring subs 4 in
111           close_in chan;
112           (* XXX Map name -> PAE, hugemem etc. *)
113           (* name, *) sprintf "%s-%s.%s" version release arch, arch
114         ) else (
115           (* New-style "key: value" entries, up to end of file or the first
116            * blank line.
117            *)
118           let (*name,*) version, release, arch =
119             (*ref "",*) ref "", ref "", ref "" in
120           let rec loop line =
121             try
122               let subs = Pcre.exec ~rex:re_keyvalue line in
123               let key = Pcre.get_substring subs 1 in
124               let value = Pcre.get_substring subs 2 in
125               (*if key = "Name" then name := value
126               else*) if key = "Version" then version := value
127               else if key = "Release" then release := value
128               else if key = "Architecture" then arch := value;
129               let line = input_line chan in
130               loop line
131             with
132               Not_found | End_of_file ->
133                 close_in chan
134           in
135           loop line;
136           let (*name,*) version, release, arch =
137             (*!name,*) !version, !release, !arch in
138           if (*name = "" ||*) version = "" || release = "" || arch = "" then
139             failwith (sprintf "%s: missing Name, Version, Release or Architecture key" filename);
140           (* XXX Map name -> PAE, hugemem etc. *)
141           (* name, *) sprintf "%s-%s.%s" version release arch, arch
142         ) in
143
144       (*printf "%s -> %s %s\n%!" basename version arch;*)
145
146       (basename, version, arch)
147   ) infos in
148
149   (* For quick access to the opener strings, build a hash. *)
150   let openers = Hashtbl.create 13 in
151   List.iter (
152     fun (name, (opener, closer, _, _)) ->
153       Hashtbl.add openers opener (closer, name)
154   ) what;
155
156   (* Now read the data files and parse out the structures of interest. *)
157   let datas = List.map (
158     fun (basename, version, arch) ->
159       let file_exists name =
160         try Unix.access name [Unix.F_OK]; true
161         with Unix.Unix_error _ -> false
162       in
163       let close_process_in cmd chan =
164         match Unix.close_process_in chan with
165         | Unix.WEXITED 0 -> ()
166         | Unix.WEXITED i ->
167             eprintf "%s: command exited with code %d\n" cmd i; exit i
168         | Unix.WSIGNALED i ->
169             eprintf "%s: command exited with signal %d\n" cmd i; exit 1
170         | Unix.WSTOPPED i ->
171             eprintf "%s: command stopped by signal %d\n" cmd i; exit 1
172       in
173
174       (* Open the data file, uncompressing it on the fly if necessary. *)
175       let chan, close =
176         if file_exists (basename ^ ".data") then
177           open_in (basename ^ ".data"), close_in
178         else if file_exists (basename ^ ".data.gz") then (
179           let cmd =
180             sprintf "gzip -cd %s" (Filename.quote (basename ^ ".data.gz")) in
181           Unix.open_process_in cmd, close_process_in cmd
182         )
183         else if file_exists (basename ^ ".data.bz2") then (
184           let cmd =
185             sprintf "bzip2 -cd %s" (Filename.quote (basename ^ ".data.bz2")) in
186           Unix.open_process_in cmd, close_process_in cmd
187         ) else
188           failwith
189             (sprintf "%s: cannot find corresponding data file" basename) in
190
191       (* Read the data file in, looking for structures of interest to us. *)
192       let bodies = Hashtbl.create 13 in
193       let rec loop () =
194         let line = input_line chan in
195
196         (* If the line is an opener for one of the structures we
197          * are looking for, then for now just save all the text until
198          * we get to the closer line.
199          *)
200         (try
201            let closer, name = Hashtbl.find openers line in
202            let rec loop2 lines =
203              let line = input_line chan in
204              let lines = line :: lines in
205              if String.starts_with line closer then List.rev lines
206              else loop2 lines
207            in
208
209            let body =
210              try loop2 [line]
211              with End_of_file ->
212                failwith (sprintf "%s: %s: %S not matched by closing %S" basename name line closer) in
213
214            Hashtbl.replace bodies name body
215          with Not_found -> ());
216
217         loop ()
218       in
219       (try loop () with End_of_file -> ());
220
221       close chan;
222
223       (* Make sure we got all the mandatory structures. *)
224       List.iter (
225          fun (name, (_, _, mandatory, _)) ->
226            if mandatory && not (Hashtbl.mem bodies name) then
227              failwith (sprintf "%s: structure %s not found in this kernel" basename name)
228       ) what;
229
230       (basename, version, arch, bodies)
231   ) infos in
232
233   (* Now parse each structure body.
234    * XXX This would be better as a proper lex/yacc parser.
235    * XXX Even better would be to have a proper interface to libdwarves.
236    *)
237   let re_offsetsize = Pcre.regexp "/\\*\\s+(\\d+)\\s+(\\d+)\\s+\\*/" in
238   let re_intfield = Pcre.regexp "int\\s+(\\w+);" in
239   let re_ptrfield = Pcre.regexp "struct\\s+(\\w+)\\s*\\*\\s*(\\w+);" in
240   let re_strfield = Pcre.regexp "char\\s+(\\w+)\\[(\\d+)\\];" in
241   let re_structopener = Pcre.regexp "(struct|union)\\s+.*{$" in
242   let re_structcloser = Pcre.regexp "}\\s*(\\w+)?(\\[\\d+\\])?;" in
243
244   (* 'basename' is the source file, and second parameter ('body') is
245    * the list of text lines which covers this structure (minus the
246    * opener line).  Result is the list of parsed fields from this
247    * structure.
248    *)
249   let rec parse basename = function
250     | [] -> assert false
251     | [_] -> []                  (* Just the closer line, finished. *)
252     | line :: lines when Pcre.pmatch ~rex:re_structopener line ->
253       (* Recursively parse a sub-structure.  First search for the
254        * corresponding closer line.
255        *)
256       let rec loop depth acc = function
257         | [] ->
258             eprintf "%s: %S has no matching close structure line\n%!"
259               basename line;
260             assert false
261         | line :: lines when Pcre.pmatch ~rex:re_structopener line ->
262           loop (depth+1) (line :: acc) lines
263         | line :: lines
264             when depth = 0 && Pcre.pmatch ~rex:re_structcloser line ->
265           (line :: acc), lines
266         | line :: lines
267             when depth > 0 && Pcre.pmatch ~rex:re_structcloser line ->
268           loop (depth-1) (line :: acc) lines
269         | line :: lines -> loop depth (line :: acc) lines
270       in
271       let nested_body, rest = loop 0 [] lines in
272
273       (* Then parse the sub-structure. *)
274       let struct_name, nested_body =
275         match nested_body with
276         | [] -> assert false
277         | closer :: _ ->
278             let subs = Pcre.exec ~rex:re_structcloser closer in
279             let struct_name =
280               try Some (Pcre.get_substring subs 1) with Not_found -> None in
281             struct_name, List.rev nested_body in
282       let nested_fields = parse basename nested_body in
283
284       (* Prefix the sub-fields with the name of the structure. *)
285       let nested_fields =
286         match struct_name with
287         | None -> nested_fields
288         | Some prefix ->
289             List.map (
290               fun (name, details) -> (prefix ^ "'" ^ name, details)
291             ) nested_fields in
292
293       (* Parse the rest. *)
294       nested_fields @ parse basename rest
295
296     | line :: lines when Pcre.pmatch ~rex:re_intfield line ->
297       (* An int field. *)
298       let subs = Pcre.exec ~rex:re_intfield line in
299       let name = Pcre.get_substring subs 1 in
300       (try
301          let subs = Pcre.exec ~rex:re_offsetsize line in
302          let offset = int_of_string (Pcre.get_substring subs 1) in
303          let size = int_of_string (Pcre.get_substring subs 2) in
304          (name, (`Int, offset, size)) :: parse basename lines
305        with
306          Not_found -> parse basename lines
307       );
308
309     | line :: lines when Pcre.pmatch ~rex:re_ptrfield line ->
310       (* A pointer-to-struct field. *)
311       let subs = Pcre.exec ~rex:re_ptrfield line in
312       let struct_name = Pcre.get_substring subs 1 in
313       let name = Pcre.get_substring subs 2 in
314       (try
315          let subs = Pcre.exec ~rex:re_offsetsize line in
316          let offset = int_of_string (Pcre.get_substring subs 1) in
317          let size = int_of_string (Pcre.get_substring subs 2) in
318          (name, (`Ptr struct_name, offset, size)) :: parse basename lines
319        with
320          Not_found -> parse basename lines
321       );
322
323     | line :: lines when Pcre.pmatch ~rex:re_strfield line ->
324       (* A string (char array) field. *)
325       let subs = Pcre.exec ~rex:re_strfield line in
326       let name = Pcre.get_substring subs 1 in
327       let width = int_of_string (Pcre.get_substring subs 2) in
328       (try
329          let subs = Pcre.exec ~rex:re_offsetsize line in
330          let offset = int_of_string (Pcre.get_substring subs 1) in
331          let size = int_of_string (Pcre.get_substring subs 2) in
332          (name, (`Str width, offset, size)) :: parse basename lines
333        with
334          Not_found -> parse basename lines
335       );
336
337     | _ :: lines ->
338         (* Just ignore any other field we can't parse. *)
339         parse basename lines
340
341   in
342
343   let datas = List.map (
344     fun (basename, version, arch, bodies) ->
345       let structures = List.filter_map (
346         fun (name, (_, _, _, wanted_fields)) ->
347           let body =
348             try Some (Hashtbl.find bodies name) with Not_found -> None in
349           match body with
350           | None -> None
351           | Some body ->
352               let body = List.tl body in (* Don't care about opener line. *)
353               let fields = parse basename body in
354
355               (* That got us all the fields, but we only care about
356                * the wanted_fields.
357                *)
358               let fields = List.filter (
359                 fun (name, _) -> List.mem name wanted_fields
360               ) fields in
361
362               (* Also check we have all the wanted fields. *)
363               List.iter (
364                 fun wanted_field ->
365                   if not (List.mem_assoc wanted_field fields) then
366                     failwith (sprintf "%s: structure %s is missing required field %s" basename name wanted_field)
367               ) wanted_fields;
368
369               Some (name, fields)
370       ) what in
371
372       (basename, version, arch, structures)
373   ) datas in
374
375   if debug then
376     List.iter (
377       fun (basename, version, arch, structures) ->
378         printf "%s (version: %s, arch: %s):\n" basename version arch;
379         List.iter (
380           fun (struct_name, fields) ->
381             printf "  struct %s {\n" struct_name;
382             List.iter (
383               fun (field_name, (typ, offset, size)) ->
384                 (match typ with
385                  | `Int ->
386                      printf "    int %s; " field_name
387                  | `Ptr struct_name ->
388                      printf "    struct %s *%s; " struct_name field_name
389                  | `Str width ->
390                      printf "    char %s[%d]; " field_name width
391                 );
392                 printf " /* offset = %d, size = %d */\n" offset size
393             ) fields;
394             printf "  }\n\n";
395         ) structures;
396     ) datas;
397
398   (* We'll generate a code file for each structure type (eg. task_struct
399    * across all kernel versions), so rearrange 'datas' for that purpose.
400    *
401    * XXX This loop is O(n^3), luckily n is small!
402    *)
403   let files =
404     List.map (
405       fun (name, _) ->
406         name,
407         List.filter_map (
408           fun (basename, version, arch, structures) ->
409             try Some (basename, version, arch, List.assoc name structures)
410             with Not_found -> None
411         ) datas
412     ) what in
413
414   let datas = () in ignore datas; (* garbage collect *)
415
416   (* Get just the field types.  It's plausible that a field with the
417    * same name has a different type between kernel versions, so we must
418    * check that didn't happen.
419    *)
420   let files = List.map (
421     fun (struct_name, kernels) ->
422       let field_types =
423         match kernels with
424         | [] -> []
425         | (_, _, _, fields) :: kernels ->
426             let field_types_of_fields fields =
427               List.sort (
428                 List.map (
429                   fun (field_name, (typ, _, _)) -> field_name, typ
430                 ) fields
431               )
432             in
433             let field_types = field_types_of_fields fields in
434             List.iter (
435               fun (_, _, _, fields) ->
436                 if field_types <> field_types_of_fields fields then
437                   failwith (sprintf "%s: one of the structure fields changed type between kernel versions" struct_name)
438             ) kernels;
439             field_types in
440       (struct_name, kernels, field_types)
441   ) files in
442
443   (* To minimize generated code size, we want to fold together all
444    * structures where the particulars (eg. offsets, sizes, endianness)
445    * of the fields we care about are the same -- eg. between kernel
446    * versions which are very similar.
447    *)
448   let endian_of_architecture arch =
449     if String.starts_with arch "i386" ||
450       String.starts_with arch "i486" ||
451       String.starts_with arch "i586" ||
452       String.starts_with arch "i686" ||
453       String.starts_with arch "x86_64" ||
454       String.starts_with arch "x86-64" then
455         Bitstring.LittleEndian
456     else if String.starts_with arch "ia64" then
457       Bitstring.LittleEndian (* XXX usually? *)
458     else if String.starts_with arch "ppc" then
459       Bitstring.BigEndian
460     else if String.starts_with arch "sparc" then
461       Bitstring.BigEndian
462     else
463       failwith (sprintf "endian_of_architecture: cannot parse %S" arch)
464   in
465
466   let files =
467     List.map (
468       fun (struct_name, kernels, field_types) ->
469         let hash = Hashtbl.create 13 in
470         let i = ref 0 in
471         let xs = ref [] in
472         let kernels =
473           List.map (
474             fun (basename, version, arch, fields) ->
475               let key = endian_of_architecture arch, fields in
476               let j =
477                 try Hashtbl.find hash key
478                 with Not_found ->
479                   incr i;
480                   xs := (!i, key) :: !xs; Hashtbl.add hash key !i;
481                   !i in
482               (basename, version, arch, j)
483           ) kernels in
484         struct_name, kernels, field_types, List.rev !xs
485     ) files in
486
487   (* How much did we save by sharing? *)
488   if debug then
489     List.iter (
490       fun (struct_name, kernels, _, parsers) ->
491         printf "struct %s:\n" struct_name;
492         printf "  number of kernel versions: %d\n" (List.length kernels);
493         printf "  number of parser functions needed after sharing: %d\n"
494           (List.length parsers)
495     ) files;
496
497   (* Let's generate some code! *)
498   let files =
499     List.map (
500       fun (struct_name, kernels, field_types, parsers) ->
501         (* Dummy location required - there are no real locations for
502          * output files.
503          *)
504         let _loc = Loc.ghost in
505
506         (* The structure type. *)
507         let struct_type, struct_sig =
508           let fields = List.map (
509             function
510             | (name, `Int) ->
511                 <:ctyp< $lid:name$ : int >>
512             | (name, `Ptr struct_name) ->
513                 <:ctyp< $lid:name$ : (*`$str:struct_name$*) int64 >>
514             | (name, `Str _) ->
515                 <:ctyp< $lid:name$ : string >>
516           ) field_types in
517           let f, fs = match fields with
518             | [] -> failwith (sprintf "%s: structure has no fields" struct_name)
519             | f :: fs -> f, fs in
520           let fields = List.fold_left (
521             fun fs f -> <:ctyp< $fs$ ; $f$ >>
522           ) f fs in
523
524           let struct_type = <:str_item< type t = { $fields$ } >> in
525           let struct_sig = <:sig_item< type t = { $fields$ } >> in
526           struct_type, struct_sig in
527
528         let code = <:str_item<
529           $struct_type$
530         >> in
531
532         let interface = <:sig_item<
533           $struct_sig$
534         >> in
535
536         (struct_name, code, interface)
537     ) files in
538
539   (* Finally generate the output files. *)
540   List.iter (
541     fun (struct_name, code, interface) ->
542       let output_file = outputdir // "kernel_" ^ struct_name ^ ".ml" in
543       Printers.OCaml.print_implem ~output_file code;
544
545       let output_file = outputdir // "kernel_" ^ struct_name ^ ".mli" in
546       Printers.OCaml.print_interf ~output_file interface
547   ) files