init -> swapper
[virt-mem.git] / ps / virt_ps.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 open Printf
21
22 open Virt_mem_gettext.Gettext
23 open Virt_mem_utils
24 open Virt_mem_types
25 open Virt_mem_mmap
26
27 (* The implementation of 'ps' has gone through a number of complete
28  * rewrites.  This explains the "leading theories" of how to implement
29  * this.  Probably some sort of hybrid method is the way to go.
30  *
31  * General comments: The init_task ksym points to the initial
32  * task_struct (PID 0, swapper).  Out from this init task_struct
33  * we find linked lists (struct list_head) which cover all
34  * tasks and subsets of the tasks (eg. parent/children).
35  * So if we have init_task and we know what the task_struct
36  * looks like, then we can follow these chains of pointers
37  * to find all processes in the kernel.
38  *
39  * task_struct varies greatly depending on: word size, kernel
40  * version, CONFIG_* settings, and vendor/additional patches.
41  *
42  * (Theory 1) Precompiled task_struct.  We can easily and reliably
43  * determine the Linux kernel version (see virt-uname).  In
44  * theory we could compile a list of known kernel versions,
45  * check out their sources beforehand, and find the absolute
46  * layout of the task_struct (eg. using CIL).  This method would
47  * only work for known kernel versions, but has the advantage
48  * that all fields in the task_struct would be known.
49  *
50  * (Theory 2) Fuzzy matched task_struct.  The task_struct has
51  * a certain internal structure which is stable even over many
52  * kernel revisions.  For example, groups of pointers always
53  * occur together.  We search through init_task looking for
54  * these characteristic features and where a pointer is found
55  * to another task_struct we search that (recursively) on the
56  * assumption that those contain the same features at the same
57  * location.  This works well for pointers, but not so well for
58  * finding other fields (eg. uids, process name, etc).  We can
59  * defray the cost of searches by caching the results between
60  * runs.
61  *)
62
63 (* This accumulator stores a map of task_struct address to
64  * task_struct content.  It can also store the address of a
65  * task_struct which isn't fully parsed yet.
66  *   key:   address (int64)
67  *   value: task_struct option (None if not parsed yet)
68  *)
69 type task_struct = {
70   ts_addr : int64;
71 }
72
73 module Accum = struct
74   include Map.Make (Int64)
75
76   let contains_addr accum addr = mem addr accum
77   let add_address accum addr = add addr None accum
78   let add_task_struct accum addr ts = add addr (Some ts) accum
79 end
80
81 exception ShapeError of int
82
83 (* Parse (recursively) all task_structs, starting at init_task.
84  *
85  * Either returns an accumulator of task_structs, or raises an
86  * exception.
87  *
88  * On failure, the caller can try again with a different shape.
89  * The exception gives some information back to the caller about
90  * where the match failed, allowing faster, directed searches.
91  *)
92 let get_task_struct debug mem ((ws,e) as wse) ((n1,n2) as shape)
93     addr accum =
94
95   (* The 'struct list_head', the standard double-linked list used
96    * by Linux, contains next and prev pointers.  However these point
97    * not to the beginning of the struct, but to the offset of the
98    * list_head within the struct.  This function adjusts the pointer
99    * to get back to the beginning of the struct.
100    *)
101   let container_of addr bitoffset =
102     if addr <> 0L then (
103       let offset = Int64.of_int (bitoffset lsr 3) in
104       addr -^ offset
105     ) else 0L
106   in
107
108   (* Do get_task_struct as a recursive subfunction so we don't have
109    * to pass around all the fixed arguments on the stack.
110    *)
111   let rec get_task_struct ~i addr accum =
112     if Accum.contains_addr accum addr then accum
113     else (
114       (* NOTE: The order of the following three statements is crucial
115        * to avoid cycles and malicious guests.  Do not change it!
116        *)
117       let bits =
118         try Bitstring.bitstring_of_string (get_bytes mem addr (4096*8))
119         with Invalid_argument "get_bytes" -> raise (ShapeError i) in
120       let accum = Accum.add_address accum addr in
121       let accum = ref accum in
122
123       if debug then
124         eprintf "trying to match task_struct, i=%d %Lx\n%!" i addr;
125
126       bitmatch bits with
127       | { _ : n1*ws : bitstring;    (* Ignore start of task_struct. *)
128
129           (* struct list_head tasks *)
130           tasks_next : ws : endian(e),
131             save_offset_to (offset),
132             bind (let addr = container_of tasks_next offset in
133               eprintf "offset = %d, tasks_next = %Lx, addr = %Lx\n"
134                 offset tasks_next addr;
135                   accum := get_task_struct ~i:1 addr !accum;
136                   addr);
137           tasks_prev : ws : endian(e),
138             bind (let addr = container_of tasks_prev offset in
139               eprintf "offset = %d, tasks_prev = %Lx, addr = %Lx\n"
140                 offset tasks_prev addr;
141                   accum := get_task_struct ~i:1 addr !accum;
142                   addr);
143
144           _ : n2*ws : bitstring;
145
146           (* struct task_struct *parent *)
147           parent : ws : endian(e),
148             bind (let addr = parent in
149                   accum := get_task_struct ~i:2 addr !accum;
150                   addr);
151
152           (* struct list_head children *)
153           children_next : ws : endian(e),
154             save_offset_to (offset),
155             bind (let addr = container_of children_next offset in
156                   accum := get_task_struct ~i:2 addr !accum;
157                   addr);
158
159           children_prev : ws : endian(e),
160             bind (let addr = container_of children_prev offset in
161                   accum := get_task_struct ~i:2 addr !accum;
162                   addr);
163
164           (* struct list_head sibling *)
165           sibling_next : ws : endian(e),
166             save_offset_to (offset),
167             bind (let addr = container_of sibling_next offset in
168                   accum := get_task_struct ~i:2 addr !accum;
169                   addr);
170
171           sibling_prev : ws : endian(e),
172             bind (let addr = container_of sibling_prev offset in
173                   accum := get_task_struct ~i:2 addr !accum;
174                   addr);
175
176           (* struct task_struct *group_leader *)
177           group_leader : ws : endian(e),
178             bind (let addr = group_leader in
179                   accum := get_task_struct ~i:2 addr !accum;
180                   addr)
181         } ->
182           (* Successful match, so return the updated accumulator. *)
183           let accum = !accum in
184           let accum = Accum.add_task_struct accum addr { ts_addr = addr } in
185           accum
186
187       | { _ } ->
188           (* Unsuccessful match, throw an exception. *)
189           raise (ShapeError (-1))
190     )
191   in
192   get_task_struct ~i:0 addr accum
193
194 (* This is the directed search function. *)
195 let search debug mem ksymmap =
196   let ws = get_wordsize mem in
197   let ws = match ws with W32 -> 32 | W64 -> 64 in
198   let e = get_endian mem in
199   let wse = ws, e in
200
201   let init_task =
202     try Ksymmap.find "init_task" ksymmap
203     with Not_found ->
204       eprintf "virt-ps: cannot find kernel symbol 'init_task'\n";
205       exit 1 in
206
207   let accum = Accum.empty in
208
209   let rec loop n1 n2 =
210     try
211       if debug then eprintf "search: trying (%d, %d)\n" n1 n2;
212       let ts = get_task_struct debug mem wse (n1, n2) init_task accum in
213       if debug then eprintf "search: success (%d, %d)\n" n1 n2;
214       ts
215     with
216     | ShapeError ((-1|0|1) as i) ->
217         if debug then eprintf "search: ShapeError %d\n" i;
218         loop (n1+1) n2
219     | ShapeError 2 ->
220         if debug then eprintf "search: ShapeError 2\n";
221         loop n1 (n2+1)
222   in
223   let ts = loop 0 0 in
224   ()
225
226 let run debug ({ mem = mem }, ksymmap, _) =
227   search debug mem ksymmap
228
229 let summary = s_"list processes in virtual machine"
230 let description = s_"\
231 virt-ps prints a process listing for virtual machines running under
232 libvirt."
233
234 let () = Virt_mem.register "ps" summary description ~run