1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright (c) 2018, Joyent, Inc. 14 */ 15 16 /* 17 * Dump information about CTF containers. 18 */ 19 20 #include <stdio.h> 21 #include <unistd.h> 22 #include <libctf.h> 23 #include <libgen.h> 24 #include <stdarg.h> 25 #include <stdlib.h> 26 #include <stddef.h> 27 #include <sys/sysmacros.h> 28 #include <sys/types.h> 29 #include <sys/stat.h> 30 #include <sys/note.h> 31 #include <fcntl.h> 32 #include <errno.h> 33 #include <string.h> 34 #include <strings.h> 35 #include <err.h> 36 37 #define MAX_NAMELEN (512) 38 39 typedef enum ctfdump_arg { 40 CTFDUMP_OBJECTS = 0x001, 41 CTFDUMP_FUNCTIONS = 0x002, 42 CTFDUMP_HEADER = 0x004, 43 CTFDUMP_LABELS = 0x008, 44 CTFDUMP_STRINGS = 0x010, 45 CTFDUMP_STATS = 0x020, 46 CTFDUMP_TYPES = 0x040, 47 CTFDUMP_DEFAULT = 0x07f, 48 CTFDUMP_OUTPUT = 0x080, 49 CTFDUMP_SOURCE = 0x100, 50 } ctfdump_arg_t; 51 52 typedef struct ctfdump_stat { 53 ulong_t cs_ndata; /* number of data objects */ 54 ulong_t cs_nfuncs; /* number of functions */ 55 ulong_t cs_nfuncargs; /* number of function args */ 56 ulong_t cs_nfuncmax; /* largest number of args */ 57 ulong_t cs_ntypes[CTF_K_MAX]; /* number of types */ 58 ulong_t cs_nsmembs; /* number of struct members */ 59 ulong_t cs_nsmax; /* largest number of members */ 60 ulong_t cs_structsz; /* sum of structures sizes */ 61 ulong_t cs_sszmax; /* largest structure */ 62 ulong_t cs_numembs; /* number of union members */ 63 ulong_t cs_numax; /* largest number of members */ 64 ulong_t cs_unionsz; /* sum of unions sizes */ 65 ulong_t cs_uszmax; /* largest union */ 66 ulong_t cs_nemembs; /* number of enum members */ 67 ulong_t cs_nemax; /* largest number of members */ 68 ulong_t cs_nstrings; /* number of strings */ 69 ulong_t cs_strsz; /* string size */ 70 ulong_t cs_strmax; /* longest string */ 71 } ctfdump_stat_t; 72 73 typedef struct { 74 char ci_name[MAX_NAMELEN]; 75 ctf_id_t ci_id; 76 ulong_t ci_symidx; 77 ctf_funcinfo_t ci_funcinfo; 78 } ctf_idname_t; 79 80 static ctf_idname_t *idnames; 81 static const char *g_progname; 82 static ctfdump_arg_t g_dump; 83 static ctf_file_t *g_fp; 84 static ctfdump_stat_t g_stats; 85 static ctf_id_t *g_fargc; 86 static int g_nfargc; 87 88 static int g_exit = 0; 89 90 static const char *ctfdump_fpenc[] = { 91 NULL, 92 "SINGLE", 93 "DOUBLE", 94 "COMPLEX", 95 "DCOMPLEX", 96 "LDCOMPLEX", 97 "LDOUBLE", 98 "INTERVAL", 99 "DINTERVAL", 100 "LDINTERVAL", 101 "IMAGINARY", 102 "DIMAGINARY", 103 "LDIMAGINARY" 104 }; 105 106 /* 107 * When stats are requested, we have to go through everything. To make our lives 108 * easier, we'll just always allow the code to print everything out, but only 109 * output it if we have actually enabled that section. 110 */ 111 static void 112 ctfdump_printf(ctfdump_arg_t arg, const char *fmt, ...) 113 { 114 va_list ap; 115 116 if ((arg & g_dump) == 0) 117 return; 118 119 va_start(ap, fmt); 120 (void) vfprintf(stdout, fmt, ap); 121 va_end(ap); 122 } 123 124 static void 125 ctfdump_fatal(const char *fmt, ...) 126 { 127 va_list ap; 128 129 (void) fprintf(stderr, "%s: ", g_progname); 130 va_start(ap, fmt); 131 (void) vfprintf(stderr, fmt, ap); 132 va_end(ap); 133 134 exit(1); 135 } 136 137 static void 138 ctfdump_usage(const char *fmt, ...) 139 { 140 if (fmt != NULL) { 141 va_list ap; 142 (void) fprintf(stderr, "%s: ", g_progname); 143 va_start(ap, fmt); 144 (void) vfprintf(stderr, fmt, ap); 145 va_end(ap); 146 } 147 148 (void) fprintf(stderr, "Usage: %s [-cdfhlsSt] [-p parent] [-u outfile] " 149 "file\n" 150 "\n" 151 "\t-c dump C-style output\n" 152 "\t-d dump object data\n" 153 "\t-f dump function data\n" 154 "\t-h dump the CTF header\n" 155 "\t-l dump the label table\n" 156 "\t-p use parent to supply additional information\n" 157 "\t-s dump the string table\n" 158 "\t-S dump statistics about the CTF container\n" 159 "\t-t dump type information\n" 160 "\t-u dump uncompressed CTF data to outfile\n", 161 g_progname); 162 } 163 164 static void 165 ctfdump_title(ctfdump_arg_t arg, const char *header) 166 { 167 static const char line[] = "----------------------------------------" 168 "----------------------------------------"; 169 ctfdump_printf(arg, "\n- %s %.*s\n\n", header, (int)78 - strlen(header), 170 line); 171 } 172 173 static int 174 ctfdump_objects_cb(const char *name, ctf_id_t id, ulong_t symidx, void *arg) 175 { 176 _NOTE(ARGUNUSED(arg)); 177 178 int len; 179 180 len = snprintf(NULL, 0, " [%lu] %ld", g_stats.cs_ndata, id); 181 ctfdump_printf(CTFDUMP_OBJECTS, " [%lu] %ld %*s%s (%lu)\n", 182 g_stats.cs_ndata, id, MAX(15 - len, 0), "", name, symidx); 183 g_stats.cs_ndata++; 184 return (0); 185 } 186 187 static void 188 ctfdump_objects(void) 189 { 190 ctfdump_title(CTFDUMP_OBJECTS, "Data Objects"); 191 if (ctf_object_iter(g_fp, ctfdump_objects_cb, NULL) == CTF_ERR) { 192 warnx("failed to dump objects: %s", 193 ctf_errmsg(ctf_errno(g_fp))); 194 g_exit = 1; 195 } 196 } 197 198 static void 199 ctfdump_fargs_grow(int nargs) 200 { 201 if (g_nfargc < nargs) { 202 g_fargc = realloc(g_fargc, sizeof (ctf_id_t) * nargs); 203 if (g_fargc == NULL) 204 ctfdump_fatal("failed to get memory for %d " 205 "ctf_id_t's\n", nargs); 206 g_nfargc = nargs; 207 } 208 } 209 210 static int 211 ctfdump_functions_cb(const char *name, ulong_t symidx, ctf_funcinfo_t *ctc, 212 void *arg) 213 { 214 _NOTE(ARGUNUSED(arg)); 215 int i; 216 217 if (ctc->ctc_argc != 0) { 218 ctfdump_fargs_grow(ctc->ctc_argc); 219 if (ctf_func_args(g_fp, symidx, g_nfargc, g_fargc) == CTF_ERR) 220 ctfdump_fatal("failed to get arguments for function " 221 "%s: %s\n", name, ctf_errmsg(ctf_errno(g_fp))); 222 } 223 224 ctfdump_printf(CTFDUMP_FUNCTIONS, 225 " [%lu] %s (%lu) returns: %ld args: (", g_stats.cs_nfuncs, name, 226 symidx, ctc->ctc_return); 227 for (i = 0; i < ctc->ctc_argc; i++) 228 ctfdump_printf(CTFDUMP_FUNCTIONS, "%ld%s", g_fargc[i], 229 i + 1 == ctc->ctc_argc ? "" : ", "); 230 if (ctc->ctc_flags & CTF_FUNC_VARARG) 231 ctfdump_printf(CTFDUMP_FUNCTIONS, "%s...", 232 ctc->ctc_argc == 0 ? "" : ", "); 233 ctfdump_printf(CTFDUMP_FUNCTIONS, ")\n"); 234 235 g_stats.cs_nfuncs++; 236 g_stats.cs_nfuncargs += ctc->ctc_argc; 237 g_stats.cs_nfuncmax = MAX(ctc->ctc_argc, g_stats.cs_nfuncmax); 238 239 return (0); 240 } 241 242 static void 243 ctfdump_functions(void) 244 { 245 ctfdump_title(CTFDUMP_FUNCTIONS, "Functions"); 246 247 if (ctf_function_iter(g_fp, ctfdump_functions_cb, NULL) == CTF_ERR) { 248 warnx("failed to dump functions: %s", 249 ctf_errmsg(ctf_errno(g_fp))); 250 g_exit = 1; 251 } 252 } 253 254 static void 255 ctfdump_header(void) 256 { 257 const ctf_header_t *hp; 258 const char *parname, *parlabel; 259 260 ctfdump_title(CTFDUMP_HEADER, "CTF Header"); 261 ctf_dataptr(g_fp, (const void **)&hp, NULL); 262 ctfdump_printf(CTFDUMP_HEADER, " cth_magic = 0x%04x\n", 263 hp->cth_magic); 264 ctfdump_printf(CTFDUMP_HEADER, " cth_version = %u\n", 265 hp->cth_version); 266 ctfdump_printf(CTFDUMP_HEADER, " cth_flags = 0x%02x\n", 267 ctf_flags(g_fp)); 268 parname = ctf_parent_name(g_fp); 269 parlabel = ctf_parent_label(g_fp); 270 ctfdump_printf(CTFDUMP_HEADER, " cth_parlabel = %s\n", 271 parlabel == NULL ? "(anon)" : parlabel); 272 ctfdump_printf(CTFDUMP_HEADER, " cth_parname = %s\n", 273 parname == NULL ? "(anon)" : parname); 274 ctfdump_printf(CTFDUMP_HEADER, " cth_lbloff = %u\n", 275 hp->cth_lbloff); 276 ctfdump_printf(CTFDUMP_HEADER, " cth_objtoff = %u\n", 277 hp->cth_objtoff); 278 ctfdump_printf(CTFDUMP_HEADER, " cth_funcoff = %u\n", 279 hp->cth_funcoff); 280 ctfdump_printf(CTFDUMP_HEADER, " cth_typeoff = %u\n", 281 hp->cth_typeoff); 282 ctfdump_printf(CTFDUMP_HEADER, " cth_stroff = %u\n", 283 hp->cth_stroff); 284 ctfdump_printf(CTFDUMP_HEADER, " cth_strlen = %u\n", 285 hp->cth_strlen); 286 } 287 288 static int 289 ctfdump_labels_cb(const char *name, const ctf_lblinfo_t *li, void *arg) 290 { 291 _NOTE(ARGUNUSED(arg)); 292 ctfdump_printf(CTFDUMP_LABELS, " %5ld %s\n", li->ctb_typeidx, name); 293 return (0); 294 } 295 296 static void 297 ctfdump_labels(void) 298 { 299 ctfdump_title(CTFDUMP_LABELS, "Label Table"); 300 if (ctf_label_iter(g_fp, ctfdump_labels_cb, NULL) == CTF_ERR) { 301 warnx("failed to dump labels: %s", 302 ctf_errmsg(ctf_errno(g_fp))); 303 g_exit = 1; 304 } 305 } 306 307 static int 308 ctfdump_strings_cb(const char *s, void *arg) 309 { 310 size_t len = strlen(s) + 1; 311 ulong_t *stroff = arg; 312 ctfdump_printf(CTFDUMP_STRINGS, " [%lu] %s\n", *stroff, 313 *s == '\0' ? "\\0" : s); 314 *stroff = *stroff + len; 315 g_stats.cs_nstrings++; 316 g_stats.cs_strsz += len; 317 g_stats.cs_strmax = MAX(g_stats.cs_strmax, len); 318 return (0); 319 } 320 321 static void 322 ctfdump_strings(void) 323 { 324 ulong_t stroff = 0; 325 326 ctfdump_title(CTFDUMP_STRINGS, "String Table"); 327 if (ctf_string_iter(g_fp, ctfdump_strings_cb, &stroff) == CTF_ERR) { 328 warnx("failed to dump strings: %s", 329 ctf_errmsg(ctf_errno(g_fp))); 330 g_exit = 1; 331 } 332 } 333 334 static void 335 ctfdump_stat_int(const char *name, ulong_t value) 336 { 337 ctfdump_printf(CTFDUMP_STATS, " %-36s= %lu\n", name, value); 338 } 339 340 static void 341 ctfdump_stat_fp(const char *name, float value) 342 { 343 ctfdump_printf(CTFDUMP_STATS, " %-36s= %.2f\n", name, value); 344 } 345 346 static void 347 ctfdump_stats(void) 348 { 349 int i; 350 ulong_t sum; 351 352 ctfdump_title(CTFDUMP_STATS, "CTF Statistics"); 353 354 ctfdump_stat_int("total number of data objects", g_stats.cs_ndata); 355 ctfdump_printf(CTFDUMP_STATS, "\n"); 356 ctfdump_stat_int("total number of functions", g_stats.cs_nfuncs); 357 ctfdump_stat_int("total number of function arguments", 358 g_stats.cs_nfuncargs); 359 ctfdump_stat_int("maximum argument list length", g_stats.cs_nfuncmax); 360 if (g_stats.cs_nfuncs != 0) 361 ctfdump_stat_fp("average argument list length", 362 (float)g_stats.cs_nfuncargs / (float)g_stats.cs_nfuncs); 363 ctfdump_printf(CTFDUMP_STATS, "\n"); 364 365 sum = 0; 366 for (i = 0; i < CTF_K_MAX; i++) 367 sum += g_stats.cs_ntypes[i]; 368 ctfdump_stat_int("total number of types", sum); 369 ctfdump_stat_int("total number of integers", 370 g_stats.cs_ntypes[CTF_K_INTEGER]); 371 ctfdump_stat_int("total number of floats", 372 g_stats.cs_ntypes[CTF_K_FLOAT]); 373 ctfdump_stat_int("total number of pointers", 374 g_stats.cs_ntypes[CTF_K_POINTER]); 375 ctfdump_stat_int("total number of arrays", 376 g_stats.cs_ntypes[CTF_K_ARRAY]); 377 ctfdump_stat_int("total number of func types", 378 g_stats.cs_ntypes[CTF_K_FUNCTION]); 379 ctfdump_stat_int("total number of structs", 380 g_stats.cs_ntypes[CTF_K_STRUCT]); 381 ctfdump_stat_int("total number of unions", 382 g_stats.cs_ntypes[CTF_K_UNION]); 383 ctfdump_stat_int("total number of enums", 384 g_stats.cs_ntypes[CTF_K_ENUM]); 385 ctfdump_stat_int("total number of forward tags", 386 g_stats.cs_ntypes[CTF_K_FORWARD]); 387 ctfdump_stat_int("total number of typedefs", 388 g_stats.cs_ntypes[CTF_K_TYPEDEF]); 389 ctfdump_stat_int("total number of volatile types", 390 g_stats.cs_ntypes[CTF_K_VOLATILE]); 391 ctfdump_stat_int("total number of const types", 392 g_stats.cs_ntypes[CTF_K_CONST]); 393 ctfdump_stat_int("total number of restrict types", 394 g_stats.cs_ntypes[CTF_K_RESTRICT]); 395 ctfdump_stat_int("total number of unknowns (holes)", 396 g_stats.cs_ntypes[CTF_K_UNKNOWN]); 397 398 ctfdump_printf(CTFDUMP_STATS, "\n"); 399 ctfdump_stat_int("total number of struct members", g_stats.cs_nsmembs); 400 ctfdump_stat_int("maximum number of struct members", g_stats.cs_nsmax); 401 ctfdump_stat_int("total size of all structs", g_stats.cs_structsz); 402 ctfdump_stat_int("maximum size of a struct", g_stats.cs_sszmax); 403 if (g_stats.cs_ntypes[CTF_K_STRUCT] != 0) { 404 ctfdump_stat_fp("average number of struct members", 405 (float)g_stats.cs_nsmembs / 406 (float)g_stats.cs_ntypes[CTF_K_STRUCT]); 407 ctfdump_stat_fp("average size of a struct", 408 (float)g_stats.cs_structsz / 409 (float)g_stats.cs_ntypes[CTF_K_STRUCT]); 410 } 411 ctfdump_printf(CTFDUMP_STATS, "\n"); 412 ctfdump_stat_int("total number of union members", g_stats.cs_numembs); 413 ctfdump_stat_int("maximum number of union members", g_stats.cs_numax); 414 ctfdump_stat_int("total size of all unions", g_stats.cs_unionsz); 415 ctfdump_stat_int("maximum size of a union", g_stats.cs_uszmax); 416 if (g_stats.cs_ntypes[CTF_K_UNION] != 0) { 417 ctfdump_stat_fp("average number of union members", 418 (float)g_stats.cs_numembs / 419 (float)g_stats.cs_ntypes[CTF_K_UNION]); 420 ctfdump_stat_fp("average size of a union", 421 (float)g_stats.cs_unionsz / 422 (float)g_stats.cs_ntypes[CTF_K_UNION]); 423 } 424 ctfdump_printf(CTFDUMP_STATS, "\n"); 425 426 ctfdump_stat_int("total number of enum members", g_stats.cs_nemembs); 427 ctfdump_stat_int("maximum number of enum members", g_stats.cs_nemax); 428 if (g_stats.cs_ntypes[CTF_K_ENUM] != 0) { 429 ctfdump_stat_fp("average number of enum members", 430 (float)g_stats.cs_nemembs / 431 (float)g_stats.cs_ntypes[CTF_K_ENUM]); 432 } 433 ctfdump_printf(CTFDUMP_STATS, "\n"); 434 435 ctfdump_stat_int("total number of strings", g_stats.cs_nstrings); 436 ctfdump_stat_int("bytes of string data", g_stats.cs_strsz); 437 ctfdump_stat_int("maximum string length", g_stats.cs_strmax); 438 if (g_stats.cs_nstrings != 0) 439 ctfdump_stat_fp("average string length", 440 (float)g_stats.cs_strsz / (float)g_stats.cs_nstrings); 441 ctfdump_printf(CTFDUMP_STATS, "\n"); 442 } 443 444 static void 445 ctfdump_intenc_name(ctf_encoding_t *cte, char *buf, int len) 446 { 447 int off = 0; 448 boolean_t space = B_FALSE; 449 450 if (cte->cte_format == 0 || (cte->cte_format & 451 ~(CTF_INT_SIGNED | CTF_INT_CHAR | CTF_INT_BOOL | 452 CTF_INT_VARARGS)) != 0) { 453 (void) snprintf(buf, len, "0x%x", cte->cte_format); 454 return; 455 } 456 457 if (cte->cte_format & CTF_INT_SIGNED) { 458 off += snprintf(buf + off, MAX(len - off, 0), "%sSIGNED", 459 space == B_TRUE ? " " : ""); 460 space = B_TRUE; 461 } 462 463 if (cte->cte_format & CTF_INT_CHAR) { 464 off += snprintf(buf + off, MAX(len - off, 0), "%sCHAR", 465 space == B_TRUE ? " " : ""); 466 space = B_TRUE; 467 } 468 469 if (cte->cte_format & CTF_INT_BOOL) { 470 off += snprintf(buf + off, MAX(len - off, 0), "%sBOOL", 471 space == B_TRUE ? " " : ""); 472 space = B_TRUE; 473 } 474 475 if (cte->cte_format & CTF_INT_VARARGS) { 476 off += snprintf(buf + off, MAX(len - off, 0), "%sVARARGS", 477 space == B_TRUE ? " " : ""); 478 space = B_TRUE; 479 } 480 } 481 482 static int 483 ctfdump_member_cb(const char *member, ctf_id_t type, ulong_t off, void *arg) 484 { 485 int *count = arg; 486 ctfdump_printf(CTFDUMP_TYPES, "\t%s type=%ld off=%lu\n", member, type, 487 off); 488 *count = *count + 1; 489 return (0); 490 } 491 492 static int 493 ctfdump_enum_cb(const char *name, int value, void *arg) 494 { 495 int *count = arg; 496 ctfdump_printf(CTFDUMP_TYPES, "\t%s = %d\n", name, value); 497 *count = *count + 1; 498 return (0); 499 } 500 501 static int 502 ctfdump_types_cb(ctf_id_t id, boolean_t root, void *arg) 503 { 504 _NOTE(ARGUNUSED(arg)); 505 int kind, i, count; 506 ctf_id_t ref; 507 char name[MAX_NAMELEN], ienc[128]; 508 const char *encn; 509 ctf_funcinfo_t ctc; 510 ctf_arinfo_t ar; 511 ctf_encoding_t cte; 512 ssize_t size; 513 514 if ((kind = ctf_type_kind(g_fp, id)) == CTF_ERR) 515 ctfdump_fatal("encountered malformed ctf, type %s does not " 516 "have a kind: %s\n", name, ctf_errmsg(ctf_errno(g_fp))); 517 518 if (ctf_type_name(g_fp, id, name, sizeof (name)) == NULL) { 519 if (ctf_errno(g_fp) != ECTF_NOPARENT) 520 ctfdump_fatal("type %ld missing name: %s\n", id, 521 ctf_errmsg(ctf_errno(g_fp))); 522 (void) snprintf(name, sizeof (name), "(unknown %s)", 523 ctf_kind_name(g_fp, kind)); 524 } 525 526 g_stats.cs_ntypes[kind]++; 527 if (root == B_TRUE) 528 ctfdump_printf(CTFDUMP_TYPES, " <%ld> ", id); 529 else 530 ctfdump_printf(CTFDUMP_TYPES, " [%ld] ", id); 531 532 switch (kind) { 533 case CTF_K_UNKNOWN: 534 break; 535 case CTF_K_INTEGER: 536 if (ctf_type_encoding(g_fp, id, &cte) == CTF_ERR) 537 ctfdump_fatal("failed to get encoding information " 538 "for %s: %s\n", name, ctf_errmsg(ctf_errno(g_fp))); 539 ctfdump_intenc_name(&cte, ienc, sizeof (ienc)); 540 ctfdump_printf(CTFDUMP_TYPES, 541 "%s encoding=%s offset=%u bits=%u", 542 name, ienc, cte.cte_offset, cte.cte_bits); 543 break; 544 case CTF_K_FLOAT: 545 if (ctf_type_encoding(g_fp, id, &cte) == CTF_ERR) 546 ctfdump_fatal("failed to get encoding information " 547 "for %s: %s\n", name, ctf_errmsg(ctf_errno(g_fp))); 548 if (cte.cte_format < 1 || cte.cte_format > 12) 549 encn = "unknown"; 550 else 551 encn = ctfdump_fpenc[cte.cte_format]; 552 ctfdump_printf(CTFDUMP_TYPES, "%s encoding=%s offset=%u " 553 "bits=%u", name, encn, cte.cte_offset, cte.cte_bits); 554 break; 555 case CTF_K_POINTER: 556 if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR) 557 ctfdump_fatal("failed to get reference type for %s: " 558 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 559 ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name, 560 ref); 561 break; 562 case CTF_K_ARRAY: 563 if (ctf_array_info(g_fp, id, &ar) == CTF_ERR) 564 ctfdump_fatal("failed to get array information for " 565 "%s: %s\n", name, ctf_errmsg(ctf_errno(g_fp))); 566 ctfdump_printf(CTFDUMP_TYPES, "%s contents: %ld, index: %ld", 567 name, ar.ctr_contents, ar.ctr_index); 568 break; 569 case CTF_K_FUNCTION: 570 if (ctf_func_info_by_id(g_fp, id, &ctc) == CTF_ERR) 571 ctfdump_fatal("failed to get function info for %s: " 572 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 573 if (ctc.ctc_argc > 0) { 574 ctfdump_fargs_grow(ctc.ctc_argc); 575 if (ctf_func_args_by_id(g_fp, id, g_nfargc, g_fargc) == 576 CTF_ERR) 577 ctfdump_fatal("failed to get function " 578 "arguments for %s: %s\n", name, 579 ctf_errmsg(ctf_errno(g_fp))); 580 } 581 ctfdump_printf(CTFDUMP_TYPES, 582 "%s returns: %ld args: (", name, ctc.ctc_return); 583 for (i = 0; i < ctc.ctc_argc; i++) { 584 ctfdump_printf(CTFDUMP_TYPES, "%ld%s", g_fargc[i], 585 i + 1 == ctc.ctc_argc ? "" : ", "); 586 } 587 if (ctc.ctc_flags & CTF_FUNC_VARARG) 588 ctfdump_printf(CTFDUMP_TYPES, "%s...", 589 ctc.ctc_argc == 0 ? "" : ", "); 590 ctfdump_printf(CTFDUMP_TYPES, ")"); 591 break; 592 case CTF_K_STRUCT: 593 case CTF_K_UNION: 594 size = ctf_type_size(g_fp, id); 595 if (size == CTF_ERR) 596 ctfdump_fatal("failed to get size of %s: %s\n", name, 597 ctf_errmsg(ctf_errno(g_fp))); 598 ctfdump_printf(CTFDUMP_TYPES, "%s (%zd bytes)\n", name, size); 599 count = 0; 600 if (ctf_member_iter(g_fp, id, ctfdump_member_cb, &count) != 0) 601 ctfdump_fatal("failed to iterate members of %s: %s\n", 602 name, ctf_errmsg(ctf_errno(g_fp))); 603 if (kind == CTF_K_STRUCT) { 604 g_stats.cs_nsmembs += count; 605 g_stats.cs_nsmax = MAX(count, g_stats.cs_nsmax); 606 g_stats.cs_structsz += size; 607 g_stats.cs_sszmax = MAX(size, g_stats.cs_sszmax); 608 } else { 609 g_stats.cs_numembs += count; 610 g_stats.cs_numax = MAX(count, g_stats.cs_numax); 611 g_stats.cs_unionsz += size; 612 g_stats.cs_uszmax = MAX(count, g_stats.cs_uszmax); 613 } 614 break; 615 case CTF_K_ENUM: 616 ctfdump_printf(CTFDUMP_TYPES, "%s\n", name); 617 count = 0; 618 if (ctf_enum_iter(g_fp, id, ctfdump_enum_cb, &count) != 0) 619 ctfdump_fatal("failed to iterate enumerators of %s: " 620 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 621 g_stats.cs_nemembs += count; 622 g_stats.cs_nemax = MAX(g_stats.cs_nemax, count); 623 break; 624 case CTF_K_FORWARD: 625 ctfdump_printf(CTFDUMP_TYPES, "forward %s\n", name); 626 break; 627 case CTF_K_TYPEDEF: 628 if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR) 629 ctfdump_fatal("failed to get reference type for %s: " 630 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 631 ctfdump_printf(CTFDUMP_TYPES, "typedef %s refers to %ld", name, 632 ref); 633 break; 634 case CTF_K_VOLATILE: 635 if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR) 636 ctfdump_fatal("failed to get reference type for %s: " 637 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 638 ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name, 639 ref); 640 break; 641 case CTF_K_CONST: 642 if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR) 643 ctfdump_fatal("failed to get reference type for %s: " 644 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 645 ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name, 646 ref); 647 break; 648 case CTF_K_RESTRICT: 649 if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR) 650 ctfdump_fatal("failed to get reference type for %s: " 651 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 652 ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name, 653 ref); 654 break; 655 default: 656 ctfdump_fatal("encountered unknown kind for type %s: %d\n", 657 name, kind); 658 } 659 660 ctfdump_printf(CTFDUMP_TYPES, "\n"); 661 662 return (0); 663 } 664 665 static void 666 ctfdump_types(void) 667 { 668 ctfdump_title(CTFDUMP_TYPES, "Types"); 669 670 if (ctf_type_iter(g_fp, B_TRUE, ctfdump_types_cb, NULL) == CTF_ERR) { 671 warnx("failed to dump types: %s", 672 ctf_errmsg(ctf_errno(g_fp))); 673 g_exit = 1; 674 } 675 } 676 677 /* 678 * C-style output. This is designed mainly for comparison purposes, and doesn't 679 * produce directly valid C: 680 * 681 * - the declarations are sorted alphabetically not semantically 682 * - anonymous enums without other users are elided (e.g. IDCS_PROBE_SENT) 683 * - doubly-pointed-to functions are wrong (e.g. in kiconv_ops_t) 684 * - anon unions declared within SOUs aren't expanded 685 * - function arguments aren't expanded recursively 686 */ 687 688 static void 689 ctfsrc_refname(ctf_id_t id, char *buf, size_t bufsize) 690 { 691 ctf_id_t ref; 692 693 if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR) { 694 ctfdump_fatal("failed to get reference type for %ld: " 695 "%s\n", id, ctf_errmsg(ctf_errno(g_fp))); 696 } 697 698 (void) ctf_type_name(g_fp, ref, buf, bufsize); 699 } 700 701 static int 702 ctfsrc_member_cb(const char *member, ctf_id_t type, ulong_t off, void *arg) 703 { 704 _NOTE(ARGUNUSED(arg)); 705 char name[MAX_NAMELEN]; 706 707 if (ctf_type_cname(g_fp, type, name, sizeof (name), member) == NULL) { 708 if (ctf_errno(g_fp) != ECTF_NOPARENT) { 709 ctfdump_fatal("type %ld missing name: %s\n", type, 710 ctf_errmsg(ctf_errno(g_fp))); 711 } 712 713 (void) snprintf(name, sizeof (name), "unknown_t %s", member); 714 } 715 716 /* 717 * A byte offset is friendlier, but we'll print bits too if it's not 718 * aligned (i.e. a bitfield). 719 */ 720 if (off % NBBY != 0) { 721 (void) printf("\t%s; /* offset: %lu bytes (%lu bits) */\n", 722 name, off / NBBY, off); 723 } else { 724 (void) printf("\t%s; /* offset: %lu bytes */\n", 725 name, off / NBBY); 726 } 727 return (0); 728 } 729 730 static int 731 ctfsrc_enum_cb(const char *name, int value, void *arg) 732 { 733 _NOTE(ARGUNUSED(arg)); 734 (void) printf("\t%s = %d,\n", name, value); 735 return (0); 736 } 737 738 static int 739 is_anon_refname(const char *refname) 740 { 741 return ((strcmp(refname, "struct ") == 0 || 742 strcmp(refname, "union ") == 0 || 743 strcmp(refname, "enum ") == 0)); 744 } 745 746 static int 747 ctfsrc_collect_types_cb(ctf_id_t id, boolean_t root, void *arg) 748 { 749 _NOTE(ARGUNUSED(root, arg)); 750 (void) ctf_type_name(g_fp, id, idnames[id].ci_name, 751 sizeof (idnames[id].ci_name)); 752 idnames[id].ci_id = id; 753 return (0); 754 } 755 756 static void 757 ctfsrc_type(ctf_id_t id, const char *name) 758 { 759 char refname[MAX_NAMELEN]; 760 ctf_id_t ref; 761 ssize_t size; 762 int kind; 763 764 if ((kind = ctf_type_kind(g_fp, id)) == CTF_ERR) { 765 ctfdump_fatal("encountered malformed ctf, type %s does not " 766 "have a kind: %s\n", name, ctf_errmsg(ctf_errno(g_fp))); 767 } 768 769 switch (kind) { 770 case CTF_K_STRUCT: 771 case CTF_K_UNION: 772 /* 773 * Delay printing anonymous SOUs; a later typedef will usually 774 * pick them up. 775 */ 776 if (is_anon_refname(name)) 777 break; 778 779 if ((size = ctf_type_size(g_fp, id)) == CTF_ERR) { 780 ctfdump_fatal("failed to get size of %s: %s\n", name, 781 ctf_errmsg(ctf_errno(g_fp))); 782 } 783 784 (void) printf("%s { /* 0x%x bytes */\n", name, size); 785 786 if (ctf_member_iter(g_fp, id, ctfsrc_member_cb, NULL) != 0) { 787 ctfdump_fatal("failed to iterate members of %s: %s\n", 788 name, ctf_errmsg(ctf_errno(g_fp))); 789 } 790 791 (void) printf("};\n\n"); 792 break; 793 case CTF_K_ENUM: 794 /* 795 * This will throw away any anon enum that isn't followed by a 796 * typedef... 797 */ 798 if (is_anon_refname(name)) 799 break; 800 801 (void) printf("%s {\n", name); 802 803 if (ctf_enum_iter(g_fp, id, ctfsrc_enum_cb, NULL) != 0) { 804 ctfdump_fatal("failed to iterate enumerators of %s: " 805 "%s\n", name, ctf_errmsg(ctf_errno(g_fp))); 806 } 807 808 (void) printf("};\n\n"); 809 break; 810 case CTF_K_TYPEDEF: 811 ctfsrc_refname(id, refname, sizeof (refname)); 812 813 if (!is_anon_refname(refname)) { 814 (void) ctf_type_cname(g_fp, 815 ctf_type_reference(g_fp, id), refname, 816 sizeof (refname), name); 817 818 (void) printf("typedef %s;\n\n", refname); 819 break; 820 } 821 822 ref = ctf_type_reference(g_fp, id); 823 824 if (ctf_type_kind(g_fp, ref) == CTF_K_ENUM) { 825 (void) printf("typedef enum {\n"); 826 827 if (ctf_enum_iter(g_fp, ref, 828 ctfsrc_enum_cb, NULL) != 0) { 829 ctfdump_fatal("failed to iterate enumerators " 830 "of %s: %s\n", refname, 831 ctf_errmsg(ctf_errno(g_fp))); 832 } 833 834 (void) printf("} %s;\n\n", name); 835 } else { 836 if ((size = ctf_type_size(g_fp, ref)) == CTF_ERR) { 837 ctfdump_fatal("failed to get size of %s: %s\n", 838 refname, ctf_errmsg(ctf_errno(g_fp))); 839 } 840 841 (void) printf("typedef %s{ /* 0x%zx bytes */\n", 842 refname, size); 843 844 if (ctf_member_iter(g_fp, ref, 845 ctfsrc_member_cb, NULL) != 0) { 846 ctfdump_fatal("failed to iterate members " 847 "of %s: %s\n", refname, 848 ctf_errmsg(ctf_errno(g_fp))); 849 } 850 851 (void) printf("} %s;\n\n", name); 852 } 853 854 break; 855 case CTF_K_FORWARD: 856 (void) printf("%s;\n\n", name); 857 break; 858 case CTF_K_UNKNOWN: 859 case CTF_K_INTEGER: 860 case CTF_K_FLOAT: 861 case CTF_K_POINTER: 862 case CTF_K_ARRAY: 863 case CTF_K_FUNCTION: 864 case CTF_K_VOLATILE: 865 case CTF_K_CONST: 866 case CTF_K_RESTRICT: 867 break; 868 default: 869 ctfdump_fatal("encountered unknown kind for type %s: %d\n", 870 name, kind); 871 break; 872 } 873 } 874 875 static int 876 ctfsrc_collect_objects_cb(const char *name, ctf_id_t id, 877 ulong_t symidx, void *arg) 878 { 879 size_t *count = arg; 880 881 /* local static vars can have an unknown ID */ 882 if (id == 0) 883 return (0); 884 885 (void) strlcpy(idnames[*count].ci_name, name, 886 sizeof (idnames[*count].ci_name)); 887 idnames[*count].ci_id = id; 888 idnames[*count].ci_symidx = symidx; 889 *count = *count + 1; 890 return (0); 891 } 892 893 static void 894 ctfsrc_object(ctf_id_t id, const char *name) 895 { 896 char tname[MAX_NAMELEN]; 897 898 if (ctf_type_cname(g_fp, id, tname, sizeof (tname), name) == NULL) { 899 if (ctf_errno(g_fp) != ECTF_NOPARENT) { 900 ctfdump_fatal("type %ld missing name: %s\n", id, 901 ctf_errmsg(ctf_errno(g_fp))); 902 } 903 (void) snprintf(tname, sizeof (tname), "unknown_t %s", name); 904 } 905 906 (void) printf("extern %s;\n", tname); 907 } 908 909 static int 910 ctfsrc_collect_functions_cb(const char *name, ulong_t symidx, 911 ctf_funcinfo_t *ctc, void *arg) 912 { 913 size_t *count = arg; 914 915 (void) strlcpy(idnames[*count].ci_name, name, 916 sizeof (idnames[*count].ci_name)); 917 bcopy(ctc, &idnames[*count].ci_funcinfo, sizeof (*ctc)); 918 idnames[*count].ci_id = 0; 919 idnames[*count].ci_symidx = symidx; 920 *count = *count + 1; 921 return (0); 922 } 923 924 static void 925 ctfsrc_function(ctf_idname_t *idn) 926 { 927 ctf_funcinfo_t *cfi = &idn->ci_funcinfo; 928 char name[MAX_NAMELEN] = "unknown_t"; 929 930 (void) ctf_type_name(g_fp, cfi->ctc_return, name, sizeof (name)); 931 932 (void) printf("extern %s %s(", name, idn->ci_name); 933 934 if (cfi->ctc_argc != 0) { 935 ctfdump_fargs_grow(cfi->ctc_argc); 936 if (ctf_func_args(g_fp, idn->ci_symidx, 937 g_nfargc, g_fargc) == CTF_ERR) { 938 ctfdump_fatal("failed to get arguments for function " 939 "%s: %s\n", idn->ci_name, 940 ctf_errmsg(ctf_errno(g_fp))); 941 } 942 943 size_t i; 944 for (i = 0; i < cfi->ctc_argc; i++) { 945 ctf_id_t aid = g_fargc[i]; 946 947 name[0] = '\0'; 948 949 (void) ctf_type_name(g_fp, aid, name, sizeof (name)); 950 951 (void) printf("%s%s", name, 952 i + 1 == cfi->ctc_argc ? "" : ", "); 953 } 954 } else { 955 if (!(cfi->ctc_flags & CTF_FUNC_VARARG)) 956 (void) printf("void"); 957 } 958 959 if (cfi->ctc_flags & CTF_FUNC_VARARG) 960 (void) printf("%s...", cfi->ctc_argc == 0 ? "" : ", "); 961 962 (void) printf(");\n"); 963 } 964 965 static int 966 idname_compare(const void *lhs, const void *rhs) 967 { 968 return (strcmp(((ctf_idname_t *)lhs)->ci_name, 969 ((ctf_idname_t *)rhs)->ci_name)); 970 } 971 972 static void 973 ctfdump_source(void) 974 { 975 ulong_t nr_syms = ctf_nr_syms(g_fp); 976 ctf_id_t max_id = ctf_max_id(g_fp); 977 size_t count = 0; 978 979 (void) printf("/* Types */\n\n"); 980 981 if ((idnames = calloc(max_id + 1, sizeof (idnames[0]))) == NULL) { 982 ctfdump_fatal("failed to alloc idnames: %s\n", 983 strerror(errno)); 984 } 985 986 if (ctf_type_iter(g_fp, B_FALSE, ctfsrc_collect_types_cb, 987 idnames) == CTF_ERR) { 988 warnx("failed to collect types: %s", 989 ctf_errmsg(ctf_errno(g_fp))); 990 g_exit = 1; 991 } 992 993 qsort(idnames, max_id, sizeof (ctf_idname_t), idname_compare); 994 995 size_t i; 996 for (i = 0; i < max_id; i++) { 997 if (idnames[i].ci_id != 0) 998 ctfsrc_type(idnames[i].ci_id, idnames[i].ci_name); 999 } 1000 1001 free(idnames); 1002 1003 (void) printf("\n\n/* Data Objects */\n\n"); 1004 1005 if ((idnames = calloc(nr_syms, sizeof (idnames[0]))) == NULL) { 1006 ctfdump_fatal("failed to alloc idnames: %s\n", 1007 strerror(errno)); 1008 } 1009 1010 if (ctf_object_iter(g_fp, ctfsrc_collect_objects_cb, 1011 &count) == CTF_ERR) { 1012 warnx("failed to collect objects: %s", 1013 ctf_errmsg(ctf_errno(g_fp))); 1014 g_exit = 1; 1015 } 1016 1017 qsort(idnames, count, sizeof (ctf_idname_t), idname_compare); 1018 1019 for (i = 0; i < count; i++) 1020 ctfsrc_object(idnames[i].ci_id, idnames[i].ci_name); 1021 1022 free(idnames); 1023 1024 (void) printf("\n\n/* Functions */\n\n"); 1025 1026 if ((idnames = calloc(nr_syms, sizeof (idnames[0]))) == NULL) { 1027 ctfdump_fatal("failed to alloc idnames: %s\n", 1028 strerror(errno)); 1029 } 1030 1031 count = 0; 1032 1033 if (ctf_function_iter(g_fp, ctfsrc_collect_functions_cb, 1034 &count) == CTF_ERR) { 1035 warnx("failed to collect functions: %s", 1036 ctf_errmsg(ctf_errno(g_fp))); 1037 g_exit = 1; 1038 } 1039 1040 qsort(idnames, count, sizeof (ctf_idname_t), idname_compare); 1041 1042 for (i = 0; i < count; i++) 1043 ctfsrc_function(&idnames[i]); 1044 1045 free(idnames); 1046 } 1047 1048 static void 1049 ctfdump_output(const char *out) 1050 { 1051 int fd, ret; 1052 const void *data; 1053 size_t len; 1054 1055 ctf_dataptr(g_fp, &data, &len); 1056 if ((fd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) 1057 ctfdump_fatal("failed to open output file %s: %s\n", out, 1058 strerror(errno)); 1059 1060 while (len > 0) { 1061 ret = write(fd, data, len); 1062 if (ret == -1 && errno == EINTR) 1063 continue; 1064 else if (ret == -1 && (errno == EFAULT || errno == EBADF)) 1065 abort(); 1066 else if (ret == -1) 1067 ctfdump_fatal("failed to write to %s: %s\n", out, 1068 strerror(errno)); 1069 data = ((char *)data) + ret; 1070 len -= ret; 1071 } 1072 1073 do { 1074 ret = close(fd); 1075 } while (ret == -1 && errno == EINTR); 1076 if (ret != 0 && errno == EBADF) 1077 abort(); 1078 if (ret != 0) 1079 ctfdump_fatal("failed to close %s: %s\n", out, strerror(errno)); 1080 } 1081 1082 int 1083 main(int argc, char *argv[]) 1084 { 1085 int c, fd, err; 1086 const char *ufile = NULL, *parent = NULL; 1087 1088 g_progname = basename(argv[0]); 1089 while ((c = getopt(argc, argv, ":cdfhlp:sStu:")) != -1) { 1090 switch (c) { 1091 case 'c': 1092 g_dump |= CTFDUMP_SOURCE; 1093 break; 1094 case 'd': 1095 g_dump |= CTFDUMP_OBJECTS; 1096 break; 1097 case 'f': 1098 g_dump |= CTFDUMP_FUNCTIONS; 1099 break; 1100 case 'h': 1101 g_dump |= CTFDUMP_HEADER; 1102 break; 1103 case 'l': 1104 g_dump |= CTFDUMP_LABELS; 1105 break; 1106 case 'p': 1107 parent = optarg; 1108 break; 1109 case 's': 1110 g_dump |= CTFDUMP_STRINGS; 1111 break; 1112 case 'S': 1113 g_dump |= CTFDUMP_STATS; 1114 break; 1115 case 't': 1116 g_dump |= CTFDUMP_TYPES; 1117 break; 1118 case 'u': 1119 g_dump |= CTFDUMP_OUTPUT; 1120 ufile = optarg; 1121 break; 1122 case '?': 1123 ctfdump_usage("Unknown option: -%c\n", optopt); 1124 return (2); 1125 case ':': 1126 ctfdump_usage("Option -%c requires an operand\n", 1127 optopt); 1128 return (2); 1129 } 1130 } 1131 1132 argc -= optind; 1133 argv += optind; 1134 1135 if ((g_dump & CTFDUMP_SOURCE) && !!(g_dump & ~CTFDUMP_SOURCE)) { 1136 ctfdump_usage("-c must be specified on its own\n"); 1137 return (2); 1138 } 1139 1140 /* 1141 * Dump all information except C source by default. 1142 */ 1143 if (g_dump == 0) 1144 g_dump = CTFDUMP_DEFAULT; 1145 1146 if (argc != 1) { 1147 ctfdump_usage("no file to dump\n"); 1148 return (2); 1149 } 1150 1151 if ((fd = open(argv[0], O_RDONLY)) < 0) 1152 ctfdump_fatal("failed to open file %s: %s\n", argv[0], 1153 strerror(errno)); 1154 1155 g_fp = ctf_fdopen(fd, &err); 1156 if (g_fp == NULL) 1157 ctfdump_fatal("failed to open file %s: %s\n", argv[0], 1158 ctf_errmsg(err)); 1159 1160 /* 1161 * Check to see if this file needs a parent. If it does not and we were 1162 * given one, that should be an error. If it does need one and the 1163 * parent is not specified, that is fine, we just won't know how to 1164 * find child types. If we are given a parent, check at least that the 1165 * labels match. 1166 */ 1167 if (ctf_parent_name(g_fp) == NULL) { 1168 if (parent != NULL) 1169 ctfdump_fatal("cannot use %s as a parent file, %s is " 1170 "not a child\n", parent, argv[0]); 1171 } else if (parent != NULL) { 1172 const char *explabel, *label; 1173 ctf_file_t *pfp = ctf_open(parent, &err); 1174 1175 if (pfp == NULL) 1176 ctfdump_fatal("failed to open parent file %s: %s\n", 1177 parent, ctf_errmsg(err)); 1178 1179 /* 1180 * Before we import the parent into the child, check that the 1181 * labels match. While there is also the notion of the parent 1182 * name, it's less straightforward to match that. Require that 1183 * labels match. 1184 */ 1185 explabel = ctf_parent_label(g_fp); 1186 label = ctf_label_topmost(pfp); 1187 if (explabel == NULL || label == NULL || 1188 strcmp(explabel, label) != 0) { 1189 if (label == NULL) 1190 label = "<missing>"; 1191 if (explabel == NULL) 1192 explabel = "<missing>"; 1193 ctfdump_fatal("label mismatch between parent %s and " 1194 "child %s, parent has %s, child expects %s\n", 1195 parent, argv[0], label, explabel); 1196 } 1197 1198 if (ctf_import(g_fp, pfp) != 0) 1199 ctfdump_fatal("failed to import parent %s: %s\n", 1200 parent, ctf_errmsg(ctf_errno(g_fp))); 1201 } 1202 1203 if (g_dump & CTFDUMP_SOURCE) { 1204 ctfdump_source(); 1205 return (0); 1206 } 1207 1208 /* 1209 * If stats is set, we must run through everything exect CTFDUMP_OUTPUT. 1210 * We also do CTFDUMP_STATS last as a result. 1211 */ 1212 if (g_dump & CTFDUMP_HEADER) 1213 ctfdump_header(); 1214 1215 if (g_dump & (CTFDUMP_LABELS | CTFDUMP_STATS)) 1216 ctfdump_labels(); 1217 1218 if (g_dump & (CTFDUMP_OBJECTS | CTFDUMP_STATS)) 1219 ctfdump_objects(); 1220 1221 if (g_dump & (CTFDUMP_FUNCTIONS | CTFDUMP_STATS)) 1222 ctfdump_functions(); 1223 1224 if (g_dump & (CTFDUMP_TYPES | CTFDUMP_STATS)) 1225 ctfdump_types(); 1226 1227 if (g_dump & (CTFDUMP_STRINGS | CTFDUMP_STATS)) 1228 ctfdump_strings(); 1229 1230 if (g_dump & CTFDUMP_STATS) 1231 ctfdump_stats(); 1232 1233 if (g_dump & CTFDUMP_OUTPUT) 1234 ctfdump_output(ufile); 1235 1236 return (g_exit); 1237 } 1238