Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 1 | /* Unwinding of frames like gstack/pstack. |
Jan Kratochvil | 7c6e785 | 2014-01-15 21:16:57 +0100 | [diff] [blame] | 2 | Copyright (C) 2013-2014 Red Hat, Inc. |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 3 | This file is part of elfutils. |
| 4 | |
| 5 | This file 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 3 of the License, or |
| 8 | (at your option) any later version. |
| 9 | |
| 10 | elfutils is distributed in the hope that it will be useful, but |
| 11 | 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, see <http://www.gnu.org/licenses/>. */ |
| 17 | |
| 18 | #include <config.h> |
| 19 | #include <assert.h> |
| 20 | #include <argp.h> |
| 21 | #include <error.h> |
| 22 | #include <stdlib.h> |
| 23 | #include <inttypes.h> |
| 24 | #include <stdio.h> |
| 25 | #include <stdio_ext.h> |
Mark Wielaard | a6141d2 | 2013-12-28 23:25:54 +0100 | [diff] [blame] | 26 | #include <string.h> |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 27 | #include <locale.h> |
| 28 | #include <fcntl.h> |
| 29 | #include ELFUTILS_HEADER(dwfl) |
| 30 | |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 31 | #include <dwarf.h> |
Mark Wielaard | 875cad8 | 2013-11-10 22:17:05 +0100 | [diff] [blame] | 32 | #include <system.h> |
| 33 | |
| 34 | /* Name and version of program. */ |
| 35 | static void print_version (FILE *stream, struct argp_state *state); |
| 36 | ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; |
| 37 | |
| 38 | /* Bug report address. */ |
| 39 | ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT; |
| 40 | |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 41 | /* non-printable argp options. */ |
| 42 | #define OPT_DEBUGINFO 0x100 |
| 43 | #define OPT_COREFILE 0x101 |
| 44 | |
Masatake YAMATO | c8c610b | 2013-12-17 12:03:29 +0900 | [diff] [blame] | 45 | static bool show_activation = false; |
| 46 | static bool show_module = false; |
Mark Wielaard | 99fc3f7 | 2013-12-23 00:47:06 +0100 | [diff] [blame] | 47 | static bool show_build_id = false; |
Masatake YAMATO | c8c610b | 2013-12-17 12:03:29 +0900 | [diff] [blame] | 48 | static bool show_source = false; |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 49 | static bool show_one_tid = false; |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 50 | static bool show_quiet = false; |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 51 | static bool show_raw = false; |
Mark Wielaard | a6141d2 | 2013-12-28 23:25:54 +0100 | [diff] [blame] | 52 | static bool show_modules = false; |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 53 | static bool show_debugname = false; |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 54 | static bool show_inlines = false; |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 55 | |
Mark Wielaard | 8520f70 | 2014-01-27 16:05:54 +0100 | [diff] [blame] | 56 | static int maxframes = 256; |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 57 | |
| 58 | struct frame |
| 59 | { |
| 60 | Dwarf_Addr pc; |
| 61 | bool isactivation; |
| 62 | }; |
| 63 | |
| 64 | struct frames |
| 65 | { |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 66 | int frames; |
| 67 | int allocated; |
| 68 | struct frame *frame; |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 69 | }; |
| 70 | |
| 71 | static Dwfl *dwfl = NULL; |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 72 | static pid_t pid = 0; |
| 73 | static int core_fd = -1; |
| 74 | static Elf *core = NULL; |
| 75 | static const char *exec = NULL; |
| 76 | static char *debuginfo_path = NULL; |
| 77 | |
| 78 | static const Dwfl_Callbacks proc_callbacks = |
| 79 | { |
| 80 | .find_elf = dwfl_linux_proc_find_elf, |
| 81 | .find_debuginfo = dwfl_standard_find_debuginfo, |
| 82 | .debuginfo_path = &debuginfo_path, |
| 83 | }; |
| 84 | |
| 85 | static const Dwfl_Callbacks core_callbacks = |
| 86 | { |
| 87 | .find_elf = dwfl_build_id_find_elf, |
| 88 | .find_debuginfo = dwfl_standard_find_debuginfo, |
| 89 | .debuginfo_path = &debuginfo_path, |
| 90 | }; |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 91 | |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 92 | #ifdef USE_DEMANGLE |
| 93 | static size_t demangle_buffer_len = 0; |
| 94 | static char *demangle_buffer = NULL; |
| 95 | #endif |
| 96 | |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 97 | /* Whether any frames have been shown at all. Determines exit status. */ |
| 98 | static bool frames_shown = false; |
| 99 | |
| 100 | /* Program exit codes. All frames shown without any errors is GOOD. |
| 101 | Some frames shown with some non-fatal errors is an ERROR. A fatal |
| 102 | error or no frames shown at all is BAD. A command line USAGE exit |
| 103 | is generated by argp_error. */ |
| 104 | #define EXIT_OK 0 |
| 105 | #define EXIT_ERROR 1 |
| 106 | #define EXIT_BAD 2 |
| 107 | #define EXIT_USAGE 64 |
| 108 | |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 109 | static int |
Mark Wielaard | a6141d2 | 2013-12-28 23:25:54 +0100 | [diff] [blame] | 110 | get_addr_width (Dwfl_Module *mod) |
| 111 | { |
| 112 | // Try to find the address wide if possible. |
| 113 | static int width = 0; |
| 114 | if (width == 0 && mod) |
| 115 | { |
| 116 | Dwarf_Addr bias; |
| 117 | Elf *elf = dwfl_module_getelf (mod, &bias); |
| 118 | if (elf) |
| 119 | { |
| 120 | GElf_Ehdr ehdr_mem; |
| 121 | GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); |
| 122 | if (ehdr) |
| 123 | width = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 8 : 16; |
| 124 | } |
| 125 | } |
| 126 | if (width == 0) |
| 127 | width = 16; |
| 128 | |
| 129 | return width; |
| 130 | } |
| 131 | |
| 132 | static int |
| 133 | module_callback (Dwfl_Module *mod, void **userdata __attribute__((unused)), |
| 134 | const char *name, Dwarf_Addr start, |
| 135 | void *arg __attribute__((unused))) |
| 136 | { |
| 137 | /* Forces resolving of main elf and debug files. */ |
| 138 | Dwarf_Addr bias; |
| 139 | Elf *elf = dwfl_module_getelf (mod, &bias); |
| 140 | Dwarf *dwarf = dwfl_module_getdwarf (mod, &bias); |
| 141 | |
| 142 | Dwarf_Addr end; |
| 143 | const char *mainfile; |
| 144 | const char *debugfile; |
| 145 | const char *modname = dwfl_module_info (mod, NULL, NULL, &end, NULL, |
| 146 | NULL, &mainfile, &debugfile); |
| 147 | assert (strcmp (modname, name) == 0); |
| 148 | |
| 149 | int width = get_addr_width (mod); |
| 150 | printf ("0x%0*" PRIx64 "-0x%0*" PRIx64 " %s\n", |
| 151 | width, start, width, end, basename (name)); |
| 152 | |
| 153 | const unsigned char *id; |
| 154 | GElf_Addr id_vaddr; |
| 155 | int id_len = dwfl_module_build_id (mod, &id, &id_vaddr); |
| 156 | if (id_len > 0) |
| 157 | { |
| 158 | printf (" ["); |
| 159 | do |
| 160 | printf ("%02" PRIx8, *id++); |
| 161 | while (--id_len > 0); |
| 162 | printf ("]\n"); |
| 163 | } |
| 164 | |
| 165 | if (elf != NULL) |
| 166 | printf (" %s\n", mainfile != NULL ? mainfile : "-"); |
| 167 | if (dwarf != NULL) |
| 168 | printf (" %s\n", debugfile != NULL ? debugfile : "-"); |
| 169 | |
| 170 | return DWARF_CB_OK; |
| 171 | } |
| 172 | |
| 173 | static int |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 174 | frame_callback (Dwfl_Frame *state, void *arg) |
| 175 | { |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 176 | struct frames *frames = (struct frames *) arg; |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 177 | int nr = frames->frames; |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 178 | if (! dwfl_frame_pc (state, &frames->frame[nr].pc, |
| 179 | &frames->frame[nr].isactivation)) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 180 | return -1; |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 181 | |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 182 | frames->frames++; |
| 183 | if (frames->frames == maxframes) |
| 184 | return DWARF_CB_ABORT; |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 185 | |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 186 | if (frames->frames == frames->allocated) |
| 187 | { |
| 188 | frames->allocated *= 2; |
| 189 | frames->frame = realloc (frames->frame, |
| 190 | sizeof (struct frame) * frames->allocated); |
| 191 | if (frames->frame == NULL) |
| 192 | error (EXIT_BAD, errno, "realloc frames.frame"); |
| 193 | } |
| 194 | |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 195 | return DWARF_CB_OK; |
| 196 | } |
| 197 | |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 198 | static const char* |
| 199 | die_name (Dwarf_Die *die) |
| 200 | { |
| 201 | Dwarf_Attribute attr; |
| 202 | const char *name; |
| 203 | name = dwarf_formstring (dwarf_attr_integrate (die, |
| 204 | DW_AT_MIPS_linkage_name, |
| 205 | &attr) |
| 206 | ?: dwarf_attr_integrate (die, |
| 207 | DW_AT_linkage_name, |
| 208 | &attr)); |
| 209 | if (name == NULL) |
| 210 | name = dwarf_diename (die); |
| 211 | |
| 212 | return name; |
| 213 | } |
| 214 | |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 215 | static void |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 216 | print_frame (int nr, Dwarf_Addr pc, bool isactivation, |
| 217 | Dwarf_Addr pc_adjusted, Dwfl_Module *mod, |
| 218 | const char *symname, Dwarf_Die *cudie, |
| 219 | Dwarf_Die *die) |
| 220 | { |
| 221 | int width = get_addr_width (mod); |
| 222 | printf ("#%-2u 0x%0*" PRIx64, nr, width, (uint64_t) pc); |
| 223 | |
| 224 | if (show_activation) |
| 225 | printf ("%4s", ! isactivation ? "- 1" : ""); |
| 226 | |
| 227 | if (symname != NULL) |
| 228 | { |
| 229 | #ifdef USE_DEMANGLE |
| 230 | // Require GNU v3 ABI by the "_Z" prefix. |
| 231 | if (! show_raw && symname[0] == '_' && symname[1] == 'Z') |
| 232 | { |
| 233 | int status = -1; |
| 234 | char *dsymname = __cxa_demangle (symname, demangle_buffer, |
| 235 | &demangle_buffer_len, &status); |
| 236 | if (status == 0) |
| 237 | symname = demangle_buffer = dsymname; |
| 238 | } |
| 239 | #endif |
| 240 | printf (" %s", symname); |
| 241 | } |
| 242 | |
| 243 | const char* fname; |
| 244 | Dwarf_Addr start; |
| 245 | fname = dwfl_module_info(mod, NULL, &start, |
| 246 | NULL, NULL, NULL, NULL, NULL); |
| 247 | if (show_module) |
| 248 | { |
| 249 | if (fname != NULL) |
| 250 | printf (" - %s", fname); |
| 251 | } |
| 252 | |
| 253 | if (show_build_id) |
| 254 | { |
| 255 | const unsigned char *id; |
| 256 | GElf_Addr id_vaddr; |
| 257 | int id_len = dwfl_module_build_id (mod, &id, &id_vaddr); |
| 258 | if (id_len > 0) |
| 259 | { |
| 260 | printf ("\n ["); |
| 261 | do |
| 262 | printf ("%02" PRIx8, *id++); |
| 263 | while (--id_len > 0); |
| 264 | printf ("]@0x%0" PRIx64 "+0x%" PRIx64, |
| 265 | start, pc_adjusted - start); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | if (show_source) |
| 270 | { |
| 271 | int line, col; |
| 272 | const char* sname; |
| 273 | line = col = -1; |
| 274 | sname = NULL; |
| 275 | if (die != NULL) |
| 276 | { |
| 277 | Dwarf_Files *files; |
| 278 | if (dwarf_getsrcfiles (cudie, &files, NULL) == 0) |
| 279 | { |
| 280 | Dwarf_Attribute attr; |
| 281 | Dwarf_Word val; |
| 282 | if (dwarf_formudata (dwarf_attr (die, DW_AT_call_file, &attr), |
| 283 | &val) == 0) |
| 284 | { |
| 285 | sname = dwarf_filesrc (files, val, NULL, NULL); |
| 286 | if (dwarf_formudata (dwarf_attr (die, DW_AT_call_line, |
| 287 | &attr), &val) == 0) |
| 288 | { |
| 289 | line = val; |
| 290 | if (dwarf_formudata (dwarf_attr (die, DW_AT_call_column, |
| 291 | &attr), &val) == 0) |
| 292 | col = val; |
| 293 | } |
| 294 | } |
| 295 | } |
| 296 | } |
| 297 | else |
| 298 | { |
| 299 | Dwfl_Line *lineobj = dwfl_module_getsrc(mod, pc_adjusted); |
| 300 | if (lineobj) |
| 301 | sname = dwfl_lineinfo (lineobj, NULL, &line, &col, NULL, NULL); |
| 302 | } |
| 303 | |
| 304 | if (sname != NULL) |
| 305 | { |
| 306 | printf ("\n %s", sname); |
| 307 | if (line > 0) |
| 308 | { |
| 309 | printf (":%d", line); |
| 310 | if (col > 0) |
| 311 | printf (":%d", col); |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | printf ("\n"); |
| 316 | } |
| 317 | |
| 318 | static void |
| 319 | print_inline_frames (int *nr, Dwarf_Addr pc, bool isactivation, |
| 320 | Dwarf_Addr pc_adjusted, Dwfl_Module *mod, |
| 321 | const char *symname, Dwarf_Die *cudie, Dwarf_Die *die) |
| 322 | { |
| 323 | Dwarf_Die *scopes = NULL; |
| 324 | int nscopes = dwarf_getscopes_die (die, &scopes); |
| 325 | if (nscopes > 0) |
| 326 | { |
| 327 | /* scopes[0] == die, the lowest level, for which we already have |
| 328 | the name. This is the actual source location where it |
| 329 | happened. */ |
| 330 | print_frame ((*nr)++, pc, isactivation, pc_adjusted, mod, symname, |
| 331 | NULL, NULL); |
| 332 | |
| 333 | /* last_scope is the source location where the next frame/function |
| 334 | call was done. */ |
| 335 | Dwarf_Die *last_scope = &scopes[0]; |
| 336 | for (int i = 1; i < nscopes && (maxframes == 0 || *nr < maxframes); i++) |
| 337 | { |
| 338 | Dwarf_Die *scope = &scopes[i]; |
| 339 | int tag = dwarf_tag (scope); |
| 340 | if (tag != DW_TAG_inlined_subroutine |
| 341 | && tag != DW_TAG_entry_point |
| 342 | && tag != DW_TAG_subprogram) |
| 343 | continue; |
| 344 | |
| 345 | symname = die_name (scope); |
| 346 | print_frame ((*nr)++, pc, isactivation, pc_adjusted, mod, symname, |
| 347 | cudie, last_scope); |
| 348 | |
| 349 | /* Found the "top-level" in which everything was inlined? */ |
| 350 | if (tag == DW_TAG_subprogram) |
| 351 | break; |
| 352 | |
| 353 | last_scope = scope; |
| 354 | } |
| 355 | } |
| 356 | free (scopes); |
| 357 | } |
| 358 | |
| 359 | static void |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 360 | print_frames (struct frames *frames, pid_t tid, int dwflerr, const char *what) |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 361 | { |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 362 | if (frames->frames > 0) |
| 363 | frames_shown = true; |
| 364 | |
| 365 | printf ("TID %d:\n", tid); |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 366 | int frame_nr = 0; |
| 367 | for (int nr = 0; nr < frames->frames && (maxframes == 0 |
| 368 | || frame_nr < maxframes); nr++) |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 369 | { |
| 370 | Dwarf_Addr pc = frames->frame[nr].pc; |
| 371 | bool isactivation = frames->frame[nr].isactivation; |
| 372 | Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1); |
| 373 | |
| 374 | /* Get PC->SYMNAME. */ |
| 375 | Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); |
| 376 | const char *symname = NULL; |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 377 | Dwarf_Die die_mem; |
| 378 | Dwarf_Die *die = NULL; |
| 379 | Dwarf_Die *cudie = NULL; |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 380 | if (mod && ! show_quiet) |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 381 | { |
| 382 | if (show_debugname) |
| 383 | { |
| 384 | Dwarf_Addr bias = 0; |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 385 | Dwarf_Die *scopes = NULL; |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 386 | cudie = dwfl_module_addrdie (mod, pc_adjusted, &bias); |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 387 | int nscopes = dwarf_getscopes (cudie, pc_adjusted - bias, |
| 388 | &scopes); |
| 389 | |
| 390 | /* Find the first function-like DIE with a name in scope. */ |
| 391 | for (int i = 0; symname == NULL && i < nscopes; i++) |
| 392 | { |
| 393 | Dwarf_Die *scope = &scopes[i]; |
| 394 | int tag = dwarf_tag (scope); |
| 395 | if (tag == DW_TAG_subprogram |
| 396 | || tag == DW_TAG_inlined_subroutine |
| 397 | || tag == DW_TAG_entry_point) |
| 398 | symname = die_name (scope); |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 399 | |
| 400 | if (symname != NULL) |
| 401 | { |
| 402 | die_mem = *scope; |
| 403 | die = &die_mem; |
| 404 | } |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 405 | } |
| 406 | free (scopes); |
| 407 | } |
| 408 | |
| 409 | if (symname == NULL) |
| 410 | symname = dwfl_module_addrname (mod, pc_adjusted); |
| 411 | } |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 412 | |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 413 | if (show_inlines && die != NULL) |
| 414 | print_inline_frames (&frame_nr, pc, isactivation, pc_adjusted, mod, |
| 415 | symname, cudie, die); |
| 416 | else |
| 417 | print_frame (frame_nr++, pc, isactivation, pc_adjusted, mod, symname, |
| 418 | NULL, NULL); |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 419 | } |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 420 | |
| 421 | if (frames->frames > 0 && frame_nr == maxframes) |
| 422 | error (0, 0, "tid %d: shown max number of frames " |
| 423 | "(%d, use -n 0 for unlimited)", tid, maxframes); |
| 424 | else if (dwflerr != 0) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 425 | { |
| 426 | if (frames->frames > 0) |
| 427 | { |
| 428 | unsigned nr = frames->frames - 1; |
| 429 | Dwarf_Addr pc = frames->frame[nr].pc; |
| 430 | bool isactivation = frames->frame[nr].isactivation; |
| 431 | Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1); |
| 432 | Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); |
| 433 | const char *mainfile = NULL; |
| 434 | const char *modname = dwfl_module_info (mod, NULL, NULL, NULL, NULL, |
| 435 | NULL, &mainfile, NULL); |
| 436 | if (modname == NULL || modname[0] == '\0') |
| 437 | { |
| 438 | if (mainfile != NULL) |
| 439 | modname = mainfile; |
| 440 | else |
| 441 | modname = "<unknown>"; |
| 442 | } |
| 443 | error (0, 0, "%s tid %d at 0x%" PRIx64 " in %s: %s", what, tid, |
| 444 | pc_adjusted, modname, dwfl_errmsg (dwflerr)); |
| 445 | } |
| 446 | else |
| 447 | error (0, 0, "%s tid %d: %s", what, tid, dwfl_errmsg (dwflerr)); |
| 448 | } |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 449 | } |
| 450 | |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 451 | static int |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 452 | thread_callback (Dwfl_Thread *thread, void *thread_arg) |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 453 | { |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 454 | struct frames *frames = (struct frames *) thread_arg; |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 455 | pid_t tid = dwfl_thread_tid (thread); |
| 456 | int err = 0; |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 457 | frames->frames = 0; |
| 458 | switch (dwfl_thread_getframes (thread, frame_callback, thread_arg)) |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 459 | { |
| 460 | case DWARF_CB_OK: |
| 461 | case DWARF_CB_ABORT: |
| 462 | break; |
| 463 | case -1: |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 464 | err = dwfl_errno (); |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 465 | break; |
| 466 | default: |
| 467 | abort (); |
| 468 | } |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 469 | print_frames (frames, tid, err, "dwfl_thread_getframes"); |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 470 | return DWARF_CB_OK; |
| 471 | } |
| 472 | |
Mark Wielaard | 875cad8 | 2013-11-10 22:17:05 +0100 | [diff] [blame] | 473 | static void |
| 474 | print_version (FILE *stream, struct argp_state *state __attribute__ ((unused))) |
| 475 | { |
| 476 | fprintf (stream, "stack (%s) %s\n", PACKAGE_NAME, PACKAGE_VERSION); |
| 477 | } |
| 478 | |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 479 | static error_t |
| 480 | parse_opt (int key, char *arg __attribute__ ((unused)), |
| 481 | struct argp_state *state) |
| 482 | { |
| 483 | switch (key) |
| 484 | { |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 485 | case 'p': |
| 486 | pid = atoi (arg); |
| 487 | if (pid == 0) |
| 488 | argp_error (state, N_("-p PID should be a positive process id.")); |
| 489 | break; |
| 490 | |
| 491 | case OPT_COREFILE: |
| 492 | core_fd = open (arg, O_RDONLY); |
| 493 | if (core_fd < 0) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 494 | error (EXIT_BAD, errno, N_("Cannot open core file '%s'"), arg); |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 495 | elf_version (EV_CURRENT); |
| 496 | core = elf_begin (core_fd, ELF_C_READ_MMAP, NULL); |
| 497 | if (core == NULL) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 498 | error (EXIT_BAD, 0, "core '%s' elf_begin: %s", arg, elf_errmsg(-1)); |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 499 | break; |
| 500 | |
| 501 | case 'e': |
| 502 | exec = arg; |
| 503 | break; |
| 504 | |
| 505 | case OPT_DEBUGINFO: |
| 506 | debuginfo_path = arg; |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 507 | break; |
| 508 | |
Masatake YAMATO | c8c610b | 2013-12-17 12:03:29 +0900 | [diff] [blame] | 509 | case 'm': |
| 510 | show_module = true; |
| 511 | break; |
| 512 | |
| 513 | case 's': |
| 514 | show_source = true; |
| 515 | break; |
| 516 | |
| 517 | case 'a': |
| 518 | show_activation = true; |
| 519 | break; |
| 520 | |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 521 | case 'd': |
| 522 | show_debugname = true; |
| 523 | break; |
| 524 | |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 525 | case 'i': |
| 526 | show_inlines = show_debugname = true; |
| 527 | break; |
| 528 | |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 529 | case 'v': |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 530 | show_activation = show_source = show_module = show_debugname = true; |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 531 | show_inlines = true; |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 532 | break; |
| 533 | |
Mark Wielaard | 99fc3f7 | 2013-12-23 00:47:06 +0100 | [diff] [blame] | 534 | case 'b': |
| 535 | show_build_id = true; |
| 536 | break; |
| 537 | |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 538 | case 'q': |
| 539 | show_quiet = true; |
| 540 | break; |
| 541 | |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 542 | case 'r': |
| 543 | show_raw = true; |
| 544 | break; |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 545 | |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 546 | case '1': |
| 547 | show_one_tid = true; |
| 548 | break; |
| 549 | |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 550 | case 'n': |
| 551 | maxframes = atoi (arg); |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 552 | if (maxframes < 0) |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 553 | { |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 554 | argp_error (state, N_("-n MAXFRAMES should be 0 or higher.")); |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 555 | return EINVAL; |
| 556 | } |
| 557 | break; |
| 558 | |
Mark Wielaard | a6141d2 | 2013-12-28 23:25:54 +0100 | [diff] [blame] | 559 | case 'l': |
| 560 | show_modules = true; |
| 561 | break; |
| 562 | |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 563 | case ARGP_KEY_END: |
| 564 | if (core == NULL && exec != NULL) |
| 565 | argp_error (state, |
| 566 | N_("-e EXEC needs a core given by --core.")); |
| 567 | |
| 568 | if (pid == 0 && show_one_tid == true) |
| 569 | argp_error (state, |
| 570 | N_("-1 needs a thread id given by -p.")); |
| 571 | |
| 572 | if ((pid == 0 && core == NULL) || (pid != 0 && core != NULL)) |
| 573 | argp_error (state, |
| 574 | N_("One of -p PID or --core COREFILE should be given.")); |
| 575 | |
| 576 | if (pid != 0) |
| 577 | { |
| 578 | dwfl = dwfl_begin (&proc_callbacks); |
| 579 | if (dwfl == NULL) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 580 | error (EXIT_BAD, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); |
| 581 | |
| 582 | int err = dwfl_linux_proc_report (dwfl, pid); |
| 583 | if (err < 0) |
| 584 | error (EXIT_BAD, 0, "dwfl_linux_proc_report pid %d: %s", pid, |
| 585 | dwfl_errmsg (-1)); |
| 586 | else if (err > 0) |
| 587 | error (EXIT_BAD, err, "dwfl_linux_proc_report pid %d", pid); |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 588 | } |
| 589 | |
| 590 | if (core != NULL) |
| 591 | { |
| 592 | dwfl = dwfl_begin (&core_callbacks); |
| 593 | if (dwfl == NULL) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 594 | error (EXIT_BAD, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 595 | if (dwfl_core_file_report (dwfl, core, exec) < 0) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 596 | error (EXIT_BAD, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1)); |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 597 | } |
| 598 | |
| 599 | if (dwfl_report_end (dwfl, NULL, NULL) != 0) |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 600 | error (EXIT_BAD, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); |
| 601 | |
Mark Wielaard | 1910801 | 2013-12-30 22:00:57 +0100 | [diff] [blame] | 602 | if (pid != 0) |
| 603 | { |
| 604 | int err = dwfl_linux_proc_attach (dwfl, pid, false); |
| 605 | if (err < 0) |
| 606 | error (EXIT_BAD, 0, "dwfl_linux_proc_attach pid %d: %s", pid, |
| 607 | dwfl_errmsg (-1)); |
| 608 | else if (err > 0) |
| 609 | error (EXIT_BAD, err, "dwfl_linux_proc_attach pid %d", pid); |
| 610 | } |
| 611 | |
| 612 | if (core != NULL) |
| 613 | { |
| 614 | if (dwfl_core_file_attach (dwfl, core) < 0) |
| 615 | error (EXIT_BAD, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1)); |
| 616 | } |
| 617 | |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 618 | /* Makes sure we are properly attached. */ |
| 619 | if (dwfl_pid (dwfl) < 0) |
| 620 | error (EXIT_BAD, 0, "dwfl_pid: %s\n", dwfl_errmsg (-1)); |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 621 | break; |
| 622 | |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 623 | default: |
| 624 | return ARGP_ERR_UNKNOWN; |
| 625 | } |
| 626 | return 0; |
| 627 | } |
| 628 | |
| 629 | int |
| 630 | main (int argc, char **argv) |
| 631 | { |
| 632 | /* We use no threads here which can interfere with handling a stream. */ |
| 633 | __fsetlocking (stdin, FSETLOCKING_BYCALLER); |
| 634 | __fsetlocking (stdout, FSETLOCKING_BYCALLER); |
| 635 | __fsetlocking (stderr, FSETLOCKING_BYCALLER); |
| 636 | |
| 637 | /* Set locale. */ |
| 638 | (void) setlocale (LC_ALL, ""); |
| 639 | |
| 640 | const struct argp_option options[] = |
| 641 | { |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 642 | { NULL, 0, NULL, 0, N_("Input selection options:"), 0 }, |
| 643 | { "pid", 'p', "PID", 0, |
| 644 | N_("Show stack of process PID"), 0 }, |
| 645 | { "core", OPT_COREFILE, "COREFILE", 0, |
| 646 | N_("Show stack found in COREFILE"), 0 }, |
| 647 | { "executable", 'e', "EXEC", 0, N_("(optional) EXECUTABLE that produced COREFILE"), 0 }, |
| 648 | { "debuginfo-path", OPT_DEBUGINFO, "PATH", 0, |
| 649 | N_("Search path for separate debuginfo files"), 0 }, |
| 650 | |
| 651 | { NULL, 0, NULL, 0, N_("Output selection options:"), 0 }, |
Masatake YAMATO | c8c610b | 2013-12-17 12:03:29 +0900 | [diff] [blame] | 652 | { "activation", 'a', NULL, 0, |
| 653 | N_("Additionally show frame activation"), 0 }, |
Mark Wielaard | 531a30a | 2014-01-20 23:09:26 +0100 | [diff] [blame] | 654 | { "debugname", 'd', NULL, 0, |
| 655 | N_("Additionally try to lookup DWARF debuginfo name for frame address"), |
| 656 | 0 }, |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 657 | { "inlines", 'i', NULL, 0, |
| 658 | N_("Additionally show inlined function frames using DWARF debuginfo if available (implies -d)"), 0 }, |
Masatake YAMATO | c8c610b | 2013-12-17 12:03:29 +0900 | [diff] [blame] | 659 | { "module", 'm', NULL, 0, |
| 660 | N_("Additionally show module file information"), 0 }, |
| 661 | { "source", 's', NULL, 0, |
| 662 | N_("Additionally show source file information"), 0 }, |
| 663 | { "verbose", 'v', NULL, 0, |
Mark Wielaard | 13968d9 | 2014-01-21 16:13:49 +0100 | [diff] [blame] | 664 | N_("Show all additional information (activation, debugname, inlines, module and source)"), 0 }, |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 665 | { "quiet", 'q', NULL, 0, |
| 666 | N_("Do not resolve address to function symbol name"), 0 }, |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 667 | { "raw", 'r', NULL, 0, |
| 668 | N_("Show raw function symbol names, do not try to demangle names"), 0 }, |
Mark Wielaard | 99fc3f7 | 2013-12-23 00:47:06 +0100 | [diff] [blame] | 669 | { "build-id", 'b', NULL, 0, |
| 670 | N_("Show module build-id, load address and pc offset"), 0 }, |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 671 | { NULL, '1', NULL, 0, |
| 672 | N_("Show the backtrace of only one thread"), 0 }, |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 673 | { NULL, 'n', "MAXFRAMES", 0, |
Mark Wielaard | 8520f70 | 2014-01-27 16:05:54 +0100 | [diff] [blame] | 674 | N_("Show at most MAXFRAMES per thread (default 256, use 0 for unlimited)"), 0 }, |
Mark Wielaard | a6141d2 | 2013-12-28 23:25:54 +0100 | [diff] [blame] | 675 | { "list-modules", 'l', NULL, 0, |
| 676 | N_("Show module memory map with build-id, elf and debug files detected"), 0 }, |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 677 | { NULL, 0, NULL, 0, NULL, 0 } |
| 678 | }; |
| 679 | |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 680 | const struct argp argp = |
| 681 | { |
| 682 | .options = options, |
| 683 | .parser = parse_opt, |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 684 | .doc = N_("Print a stack for each thread in a process or core file.\v\ |
| 685 | Program exits with return code 0 if all frames were shown without \ |
| 686 | any errors. If some frames were shown, but there were some non-fatal \ |
| 687 | errors, possibly causing an incomplete backtrace, the program exits \ |
| 688 | with return code 1. If no frames could be shown, or a fatal error \ |
| 689 | occured the program exits with return code 2. If the program was \ |
| 690 | invoked with bad or missing arguments it will exit with return code 64.") |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 691 | }; |
| 692 | |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 693 | argp_parse (&argp, argc, argv, 0, NULL, NULL); |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 694 | |
Mark Wielaard | a6141d2 | 2013-12-28 23:25:54 +0100 | [diff] [blame] | 695 | if (show_modules) |
| 696 | { |
| 697 | printf ("PID %d - %s module memory map\n", dwfl_pid (dwfl), |
| 698 | pid != 0 ? "process" : "core"); |
| 699 | if (dwfl_getmodules (dwfl, module_callback, NULL, 0) != 0) |
| 700 | error (EXIT_BAD, 0, "dwfl_getmodules: %s", dwfl_errmsg (-1)); |
| 701 | } |
| 702 | |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 703 | struct frames frames; |
Mark Wielaard | 8520f70 | 2014-01-27 16:05:54 +0100 | [diff] [blame] | 704 | /* When maxframes is zero, then 2048 is just the initial allocation |
| 705 | that will be increased using realloc in framecallback (). */ |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 706 | frames.allocated = maxframes == 0 ? 2048 : maxframes; |
| 707 | frames.frames = 0; |
| 708 | frames.frame = malloc (sizeof (struct frame) * frames.allocated); |
| 709 | if (frames.frame == NULL) |
| 710 | error (EXIT_BAD, errno, "malloc frames.frame"); |
Mark Wielaard | 9fec14a | 2013-12-22 23:48:26 +0100 | [diff] [blame] | 711 | |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 712 | if (show_one_tid) |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 713 | { |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 714 | int err = 0; |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 715 | switch (dwfl_getthread_frames (dwfl, pid, frame_callback, &frames)) |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 716 | { |
| 717 | case DWARF_CB_OK: |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 718 | case DWARF_CB_ABORT: |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 719 | break; |
| 720 | case -1: |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 721 | err = dwfl_errno (); |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 722 | break; |
| 723 | default: |
| 724 | abort (); |
| 725 | } |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 726 | print_frames (&frames, pid, err, "dwfl_getthread_frames"); |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 727 | } |
| 728 | else |
| 729 | { |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 730 | printf ("PID %d - %s\n", dwfl_pid (dwfl), pid != 0 ? "process" : "core"); |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 731 | switch (dwfl_getthreads (dwfl, thread_callback, &frames)) |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 732 | { |
| 733 | case DWARF_CB_OK: |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 734 | case DWARF_CB_ABORT: |
Mark Wielaard | e962ec3 | 2013-12-20 10:09:12 +0100 | [diff] [blame] | 735 | break; |
| 736 | case -1: |
| 737 | error (0, 0, "dwfl_getthreads: %s", dwfl_errmsg (-1)); |
| 738 | break; |
| 739 | default: |
| 740 | abort (); |
| 741 | } |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 742 | } |
Mark Wielaard | 6034e2f | 2014-01-02 22:31:07 +0100 | [diff] [blame] | 743 | free (frames.frame); |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 744 | dwfl_end (dwfl); |
| 745 | |
Mark Wielaard | fe9b95d | 2013-12-23 10:46:54 +0100 | [diff] [blame] | 746 | if (core != NULL) |
| 747 | elf_end (core); |
| 748 | |
| 749 | if (core_fd != -1) |
| 750 | close (core_fd); |
| 751 | |
Mark Wielaard | fdb64e6 | 2013-12-23 21:19:05 +0100 | [diff] [blame] | 752 | #ifdef USE_DEMANGLE |
| 753 | free (demangle_buffer); |
| 754 | #endif |
| 755 | |
Mark Wielaard | 1f7763e | 2013-12-27 10:49:51 +0100 | [diff] [blame] | 756 | if (! frames_shown) |
| 757 | error (EXIT_BAD, 0, N_("Couldn't show any frames.")); |
| 758 | |
| 759 | return error_message_count != 0 ? EXIT_ERROR : EXIT_OK; |
Jan Kratochvil | 0b86746 | 2013-05-30 14:37:38 +0200 | [diff] [blame] | 760 | } |