]>
Commit | Line | Data |
---|---|---|
35a04c8e FCE |
1 | /* |
2 | crash shared object for retrieving systemtap buffer | |
982b7e15 | 3 | Copyright (c) 2007, Hitachi, Ltd., |
1673e81e | 4 | Created by Satoru Moriya <satoru.moriya.br@hitachi.com> |
982b7e15 MH |
5 | Updated by Masami Hiramatsu <mhiramat@redhat.com> |
6 | ||
35a04c8e FCE |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by | |
9 | the Free Software Foundation; either version 2 of the License, or | |
10 | (at your option) any later version. | |
11 | ||
12 | This program is distributed in the hope that it will be useful, | |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | GNU General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
18 | along with this program; if not, write to the Free Software | |
19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
20 | */ | |
21 | ||
22 | ||
23 | /* crash/defs.h defines NR_CPUS based upon architecture macros | |
24 | X86, X86_64, etc. See crash/configure.c (!). */ | |
25 | #ifdef __alpha__ | |
26 | #define ALPHA | |
27 | #endif | |
28 | #ifdef __i386__ | |
29 | #define X86 | |
30 | #endif | |
31 | #ifdef __powerpc__ | |
32 | #define PPC | |
33 | #endif | |
34 | #ifdef __ia64__ | |
35 | #define IA64 | |
36 | #endif | |
37 | #ifdef __s390__ | |
38 | #define S390 | |
39 | #endif | |
40 | #ifdef __s390x__ | |
41 | #define S390X | |
42 | #endif | |
43 | #ifdef __powerpc64__ | |
44 | #define PPC64 | |
45 | #endif | |
46 | #ifdef __x86_64__ | |
47 | #define X86_64 | |
48 | #endif | |
49 | ||
50 | #include <crash/defs.h> | |
51 | ||
35a04c8e FCE |
52 | struct rchan_offsets { |
53 | long subbuf_size; | |
54 | long n_subbufs; | |
55 | long buf; | |
56 | long buf_start; | |
57 | long buf_offset; | |
58 | long buf_subbufs_produced; | |
59 | long buf_padding; | |
60 | }; | |
61 | ||
62 | struct fake_rchan_buf { | |
63 | void *start; | |
64 | size_t offset; | |
65 | size_t subbufs_produced; | |
66 | size_t *padding; | |
67 | }; | |
68 | ||
69 | struct fake_rchan { | |
70 | size_t subbuf_size; | |
71 | size_t n_subbufs; | |
72 | }; | |
73 | ||
74 | struct per_cpu_data { | |
75 | struct fake_rchan_buf buf; | |
76 | }; | |
77 | ||
78 | static struct rchan_offsets rchan_offsets; | |
79 | static struct fake_rchan chan; | |
80 | static struct per_cpu_data per_cpu[NR_CPUS]; | |
982b7e15 MH |
81 | static FILE *outfp = NULL; |
82 | static char *subbuf = NULL; | |
83 | static int is_global = 0; | |
84 | static int old_format = 0; | |
85 | static int retrieve_all = 0; | |
35a04c8e FCE |
86 | |
87 | void cmd_staplog(void); | |
88 | void cmd_staplog_cleanup(void); | |
89 | char *help_staplog[]; | |
90 | char *help_staplog_cleanup[]; | |
91 | ||
92 | static struct command_table_entry command_table[] = { | |
93 | {"staplog", cmd_staplog, help_staplog, 0}, | |
94 | {"staplog_cleanup", cmd_staplog_cleanup, help_staplog_cleanup, CLEANUP}, | |
95 | {NULL, NULL, NULL, 0}, | |
96 | }; | |
97 | ||
98 | static void get_rchan_offsets(void) | |
99 | { | |
100 | rchan_offsets.subbuf_size = MEMBER_OFFSET("rchan", "subbuf_size"); | |
101 | if (rchan_offsets.subbuf_size < 0) | |
102 | goto ERR; | |
103 | rchan_offsets.n_subbufs = MEMBER_OFFSET("rchan", "n_subbufs"); | |
104 | if (rchan_offsets.n_subbufs < 0) | |
105 | goto ERR; | |
106 | rchan_offsets.buf = MEMBER_OFFSET("rchan", "buf"); | |
107 | if (rchan_offsets.buf < 0) | |
108 | goto ERR; | |
109 | rchan_offsets.buf_start = MEMBER_OFFSET("rchan_buf", "start"); | |
110 | if (rchan_offsets.buf_start < 0) | |
111 | goto ERR; | |
112 | rchan_offsets.buf_offset = MEMBER_OFFSET("rchan_buf", "offset"); | |
113 | if (rchan_offsets.buf_offset < 0) | |
114 | goto ERR; | |
115 | rchan_offsets.buf_subbufs_produced | |
116 | = MEMBER_OFFSET("rchan_buf", "subbufs_produced"); | |
117 | if (rchan_offsets.buf_subbufs_produced < 0) | |
118 | goto ERR; | |
119 | rchan_offsets.buf_padding = MEMBER_OFFSET("rchan_buf", "padding"); | |
120 | if (rchan_offsets.buf_padding < 0) | |
121 | goto ERR; | |
122 | return; | |
123 | ERR: | |
124 | error(FATAL, "cannot get rchan offset\n"); | |
125 | } | |
126 | ||
982b7e15 | 127 | static ulong get_rchan(ulong chan_addr) |
35a04c8e FCE |
128 | { |
129 | ulong rchan; | |
130 | ||
131 | readmem(chan_addr, KVADDR, &rchan, sizeof(void*), | |
132 | "stp_channel", FAULT_ON_ERROR); | |
133 | readmem(rchan + rchan_offsets.subbuf_size, | |
134 | KVADDR, &chan.subbuf_size, sizeof(size_t), | |
135 | "stp_channel.subbuf_size", FAULT_ON_ERROR); | |
136 | readmem(rchan + rchan_offsets.n_subbufs, | |
137 | KVADDR, &chan.n_subbufs, sizeof(size_t), | |
138 | "stp_channel.n_subbufs", FAULT_ON_ERROR); | |
139 | return rchan; | |
140 | } | |
141 | ||
982b7e15 | 142 | static void get_rchan_buf(int cpu, ulong rchan) |
35a04c8e FCE |
143 | { |
144 | ulong rchan_buf; | |
145 | struct per_cpu_data *pcd; | |
146 | ||
147 | pcd = &per_cpu[cpu]; | |
148 | readmem(rchan + rchan_offsets.buf + sizeof(void*) * cpu, | |
149 | KVADDR, &rchan_buf, sizeof(void*), | |
150 | "stp_channel.buf", FAULT_ON_ERROR); | |
151 | readmem(rchan_buf + rchan_offsets.buf_start, | |
152 | KVADDR, &pcd->buf.start, sizeof(void*), | |
153 | "stp_channel.buf.start", FAULT_ON_ERROR); | |
154 | readmem(rchan_buf + rchan_offsets.buf_offset, | |
155 | KVADDR, &pcd->buf.offset, sizeof(size_t), | |
156 | "stp_channel.buf.offset", FAULT_ON_ERROR); | |
157 | readmem(rchan_buf + rchan_offsets.buf_subbufs_produced, | |
158 | KVADDR, &pcd->buf.subbufs_produced, sizeof(size_t), | |
159 | "stp_channel.buf.subbufs_produced", FAULT_ON_ERROR); | |
160 | readmem(rchan_buf + rchan_offsets.buf_padding, | |
161 | KVADDR, &pcd->buf.padding, sizeof(size_t*), | |
162 | "stp_channel.buf.padding", FAULT_ON_ERROR); | |
163 | return; | |
164 | } | |
165 | ||
166 | static ulong get_rchan_addr(ulong stp_utt_addr) | |
167 | { | |
168 | ulong stp_utt; | |
faf96009 | 169 | long offset; |
35a04c8e FCE |
170 | |
171 | readmem(stp_utt_addr, KVADDR, &stp_utt, sizeof(void*), | |
172 | "stp_utt", FAULT_ON_ERROR); | |
faf96009 FCE |
173 | |
174 | /* | |
175 | * If we couldn't get the member offset of struct utt_trace.rchan, | |
176 | * i.e. the debuginfo of the trace module isn't available, we use | |
177 | * sizeof(long) as the offset instead. Currently struct utt_trace | |
178 | * is defined as below: | |
179 | * | |
180 | * struct utt_trace { | |
181 | * int trace_state; | |
182 | * struct rchan *rchan; | |
183 | * ... | |
184 | * } | |
185 | * | |
186 | * Although the type of the preceding member is int, sizeof(long) | |
187 | * is OK, because rchan is aligned with long size on both 32-bit | |
188 | * and 64-bit environment. When the definision of struct utt_trace | |
189 | * changed, we must check if this code is correct. | |
190 | */ | |
191 | if ((offset = MEMBER_OFFSET("utt_trace", "rchan")) < 0) { | |
192 | error(WARNING, "The debuginfo of the trace module hasn't been loaded. " | |
193 | "You may not be able to retrieve the correct trace data.\n"); | |
194 | offset = sizeof(long); | |
195 | } | |
196 | ||
197 | return (stp_utt + (ulong)offset); | |
35a04c8e FCE |
198 | } |
199 | ||
200 | static int check_global_buffer(ulong rchan) | |
201 | { | |
202 | int cpu; | |
203 | ulong rchan_buf[2]; | |
982b7e15 | 204 | |
35a04c8e FCE |
205 | for (cpu = 0; cpu < 2; cpu++) { |
206 | readmem(rchan + rchan_offsets.buf + sizeof(void*) * cpu, | |
207 | KVADDR, &rchan_buf[cpu], sizeof(void*), | |
208 | "stp_channel.buf", FAULT_ON_ERROR); | |
209 | } | |
210 | if (rchan_buf[0] == rchan_buf[1]) | |
211 | return 1; | |
212 | return 0; | |
213 | } | |
214 | ||
982b7e15 | 215 | static void setup_global_data(char *module) |
35a04c8e FCE |
216 | { |
217 | int i; | |
218 | ulong stp_utt_addr = 0; | |
219 | ulong stp_rchan_addr = 0; | |
220 | ulong rchan; | |
221 | ||
222 | stp_utt_addr = symbol_value_module("_stp_utt", module); | |
223 | if (stp_utt_addr == 0) { | |
224 | stp_rchan_addr = symbol_value_module("_stp_chan", module); | |
225 | if (stp_rchan_addr == 0) { | |
226 | error(FATAL, "Failed to find _stp_utt/_stp_chan.\n", | |
227 | module); | |
228 | } | |
229 | old_format = 1; | |
230 | } else { | |
231 | stp_rchan_addr = get_rchan_addr(stp_utt_addr); | |
232 | if (stp_rchan_addr == 0) { | |
233 | error(FATAL, "Failed to find _stp_utt/_stp_chan.\n", | |
234 | module); | |
235 | } | |
236 | } | |
237 | rchan = get_rchan(stp_rchan_addr); | |
238 | for (i = 0; i < kt->cpus; i++) | |
239 | get_rchan_buf(i, rchan); | |
240 | ||
241 | if (kt->cpus > 1) { | |
242 | is_global = check_global_buffer(rchan); | |
243 | } | |
244 | return; | |
245 | } | |
246 | ||
982b7e15 | 247 | static void create_output_filename(char *buf, int len, int cpu) |
1673e81e | 248 | { |
1673e81e | 249 | if (is_global) { |
982b7e15 | 250 | snprintf(buf, len, "global"); |
1673e81e | 251 | } else { |
982b7e15 | 252 | snprintf(buf, len, "cpu%d", cpu); |
1673e81e | 253 | } |
1673e81e MH |
254 | } |
255 | ||
982b7e15 | 256 | static void create_output_dir(const char *dirname) |
1673e81e MH |
257 | { |
258 | DIR *dir; | |
259 | dir = opendir(dirname); | |
260 | if (dir) { | |
261 | closedir(dir); | |
262 | } else { | |
263 | if (mkdir(dirname, S_IRWXU) < 0) { | |
264 | error(FATAL, "cannot create log directory '%s\n'", dirname); | |
265 | } | |
266 | } | |
267 | } | |
268 | ||
982b7e15 | 269 | static FILE *open_output_file(const char *dname, const char *fname) |
1673e81e MH |
270 | { |
271 | FILE *filp = NULL; | |
272 | char *output_file; | |
1673e81e | 273 | |
982b7e15 MH |
274 | output_file = GETBUF(sizeof(char) * (strlen(dname) + strlen(fname) + 2)); |
275 | if (output_file == NULL) { | |
276 | error(FATAL, "cannot allocate memory for logfile name\n"); | |
277 | } | |
1673e81e MH |
278 | |
279 | create_output_dir(dname); | |
280 | sprintf(output_file,"%s/%s", dname, fname); | |
281 | ||
282 | filp = fopen(output_file, "w"); | |
283 | if (!filp) { | |
284 | error(FATAL, "cannot create log file '%s'\n", output_file); | |
285 | } | |
982b7e15 | 286 | FREEBUF(output_file); |
1673e81e MH |
287 | |
288 | return filp; | |
289 | } | |
290 | ||
982b7e15 MH |
291 | #define MAX_FNAME 128 |
292 | ||
faf96009 | 293 | static void output_cpu_logs(char *dirname) |
35a04c8e | 294 | { |
1673e81e | 295 | int i; |
35a04c8e | 296 | struct per_cpu_data *pcd; |
1673e81e | 297 | size_t n, idx, start, end, len; |
faf96009 | 298 | size_t padding; |
982b7e15 | 299 | char *source, fname[MAX_FNAME + 1]; |
35a04c8e | 300 | |
35a04c8e FCE |
301 | /* allocate subbuf memory */ |
302 | subbuf = GETBUF(chan.subbuf_size); | |
303 | if (!subbuf) { | |
304 | error(FATAL, "cannot allocate memory\n"); | |
305 | } | |
306 | ||
35a04c8e | 307 | for (i = 0; i < kt->cpus; i++) { |
35a04c8e FCE |
308 | pcd = &per_cpu[i]; |
309 | ||
1673e81e | 310 | if (pcd->buf.subbufs_produced == 0 && pcd->buf.offset == 0) { |
faf96009 FCE |
311 | if (is_global == 1) { |
312 | error(WARNING, "There is no data in the relay buffer.\n"); | |
313 | break; | |
314 | } else { | |
315 | error(WARNING, "[cpu:%d]There is no data in the relay buffer.\n", i); | |
316 | continue; | |
317 | } | |
318 | } | |
35a04c8e | 319 | |
982b7e15 | 320 | end = pcd->buf.subbufs_produced + 1; |
1673e81e | 321 | if (pcd->buf.subbufs_produced >= chan.n_subbufs) { |
982b7e15 | 322 | start = end - chan.n_subbufs; |
35a04c8e FCE |
323 | } else { |
324 | start = 0; | |
35a04c8e | 325 | } |
1673e81e | 326 | |
982b7e15 MH |
327 | create_output_filename(fname, MAX_FNAME, i); |
328 | fprintf(fp, "--- generating '%s/%s' ---\n", dirname, fname); | |
329 | fprintf(fp, " subbufs ready on relayfs:%ld\n", (long)end); | |
330 | fprintf(fp, " n_subbufs:%ld, read subbuf from:%ld(%ld) " | |
331 | "to:%ld(%ld) (offset:0-%ld)\n\n", | |
332 | (long)chan.n_subbufs, | |
333 | (long)start, | |
334 | (long)(start % chan.n_subbufs), | |
335 | (long)end-1, | |
336 | (long)((end-1) % chan.n_subbufs), | |
337 | (long) pcd->buf.offset); | |
1673e81e MH |
338 | outfp = open_output_file(dirname, fname); |
339 | ||
35a04c8e FCE |
340 | for (n = start; n < end; n++) { |
341 | /* read relayfs subbufs and write to log file */ | |
342 | idx = n % chan.n_subbufs; | |
343 | source = pcd->buf.start + idx * chan.subbuf_size; | |
344 | readmem((ulong)pcd->buf.padding + sizeof(padding) * idx, | |
345 | KVADDR, &padding, sizeof(padding), | |
346 | "padding", FAULT_ON_ERROR); | |
982b7e15 | 347 | if (n == end - 1) { |
35a04c8e FCE |
348 | len = pcd->buf.offset; |
349 | } else { | |
350 | len = chan.subbuf_size; | |
982b7e15 | 351 | } |
35a04c8e | 352 | if (old_format == 1) { |
1673e81e MH |
353 | source += sizeof(unsigned int); |
354 | len -= sizeof(unsigned int) + padding; | |
35a04c8e FCE |
355 | } else { |
356 | len -= padding; | |
357 | } | |
982b7e15 | 358 | if (len > 0) { |
35a04c8e FCE |
359 | readmem((ulong)source, KVADDR, subbuf, len, |
360 | "subbuf", FAULT_ON_ERROR); | |
361 | if (fwrite(subbuf, len, 1, outfp) != 1) { | |
362 | error(FATAL, "cannot write log data\n"); | |
363 | } | |
364 | } | |
365 | } | |
366 | fclose(outfp); | |
367 | outfp = NULL; | |
1673e81e MH |
368 | |
369 | /* | |
982b7e15 | 370 | * -a option retrieve the old data of subbuffer where the |
1673e81e MH |
371 | * probe record is written at that time. |
372 | */ | |
982b7e15 MH |
373 | if (retrieve_all == 1 && start != 0) { |
374 | strncat(fname, ".may_broken", MAX_FNAME); | |
375 | fprintf(fp, "--- generating '%s/%s' ---\n", dirname, fname); | |
376 | fprintf(fp, " read subbuf %ld(%ld) (offset:%ld-%ld)\n", | |
377 | (long)start-1, | |
378 | (long)((start-1) % chan.n_subbufs), | |
379 | (long)pcd->buf.offset, | |
380 | (long)chan.subbuf_size); | |
1673e81e | 381 | outfp = open_output_file(dirname, fname); |
982b7e15 MH |
382 | |
383 | idx = (start - 1) % chan.n_subbufs; | |
384 | source = pcd->buf.start + idx * chan.subbuf_size + | |
1673e81e | 385 | pcd->buf.offset; |
1673e81e MH |
386 | len = chan.subbuf_size - pcd->buf.offset; |
387 | if (len) { | |
388 | readmem((ulong)source, KVADDR, subbuf, len, | |
389 | "may_broken_subbuf", FAULT_ON_ERROR); | |
390 | if(fwrite(subbuf, len, 1, outfp) != 1) { | |
982b7e15 | 391 | error(FATAL, |
1673e81e MH |
392 | "cannot write log data(may_broken)\n"); |
393 | } | |
394 | } | |
395 | fclose(outfp); | |
396 | outfp = NULL; | |
1673e81e | 397 | } |
35a04c8e FCE |
398 | if (is_global == 1) |
399 | break; | |
400 | } | |
401 | if (subbuf) { | |
402 | FREEBUF(subbuf); | |
403 | subbuf = NULL; | |
404 | } | |
405 | return; | |
406 | } | |
407 | ||
35a04c8e FCE |
408 | void cmd_staplog(void) |
409 | { | |
410 | ||
411 | int c; | |
412 | char *module = NULL; | |
faf96009 | 413 | char *dirname = NULL; |
35a04c8e | 414 | |
1673e81e | 415 | while ((c = getopt(argcnt, args, "+ao:")) != EOF) { |
35a04c8e | 416 | switch (c) { |
1673e81e MH |
417 | case 'a': |
418 | retrieve_all = 1; | |
419 | break; | |
35a04c8e | 420 | case 'o': |
faf96009 | 421 | dirname = optarg; |
35a04c8e FCE |
422 | break; |
423 | default: | |
424 | argerrs++; | |
425 | break; | |
426 | } | |
427 | } | |
428 | module = args[optind]; | |
429 | ||
430 | if (!module || argerrs) | |
431 | cmd_usage(pc->curcmd, SYNOPSIS); | |
432 | ||
faf96009 FCE |
433 | if (dirname == NULL && module != NULL) |
434 | dirname = module; | |
982b7e15 MH |
435 | |
436 | setup_global_data(module); | |
437 | output_cpu_logs(dirname); | |
35a04c8e FCE |
438 | return; |
439 | } | |
440 | ||
441 | void cmd_staplog_cleanup(void) | |
442 | { | |
443 | if (outfp) { | |
444 | fclose(outfp); | |
445 | outfp = NULL; | |
446 | } | |
447 | return; | |
448 | } | |
449 | ||
450 | char *help_staplog[] = { | |
451 | "systemtaplog", | |
452 | "Retrieve SystemTap log data", | |
1673e81e | 453 | "[-a] [-o dir_name] module_name", |
35a04c8e | 454 | " Retrieve SystemTap's log data and write them to files.\n", |
1673e81e MH |
455 | " All valid SystemTap's log data made by the trace module which name", |
456 | " is 'module_name' are written into log files. This command starts", | |
457 | " to retrieve log data from the subbuffer which is next to current", | |
458 | " written subbuffer. Therefore some old data in the current written", | |
982b7e15 | 459 | " subbuffer may not be retrieved. But -a option retrieves these data", |
1673e81e MH |
460 | " and write them into another log file which have the special ", |
461 | " postfix `.may_broken`.", | |
462 | " If you don't use -o option, the log files are created in", | |
463 | " `module_name` directory. The name of each log file is cpu0, cpu1..", | |
982b7e15 | 464 | " ...cpuN. This command doesn't change the log data format, but remove", |
1673e81e | 465 | " only padding.", |
35a04c8e | 466 | "", |
1673e81e | 467 | " -a Retrieve the old data which is recorded in", |
982b7e15 MH |
468 | " current written subbuffer and create another files", |
469 | " which have the special postfix `.may_broken`", | |
1673e81e | 470 | " for these data.", |
35a04c8e FCE |
471 | " -o file_name Specify the output directory.", |
472 | NULL, | |
473 | }; | |
474 | ||
475 | char *help_staplog_cleanup[] = { | |
476 | "systemtaplog cleanup (hidden)", | |
477 | "Cleanup command for staplog", | |
478 | "", | |
479 | " This command is called during restore_sanity() prior to each ", | |
480 | " command prompt to close the files which was opened and failed to", | |
481 | " close by staplog command.", | |
482 | NULL, | |
483 | }; | |
484 | ||
982b7e15 | 485 | static void __attribute__ ((constructor)) _init(void) |
35a04c8e FCE |
486 | { |
487 | get_rchan_offsets(); | |
488 | register_extension(command_table); | |
489 | return; | |
490 | } | |
491 | ||
492 | ||
493 | static void __attribute__ ((destructor)) _fini(void) | |
494 | { | |
495 | return; | |
496 | } |