view usr/src/cmd/ctfconvert/ctfconvert.c @ 25635:ce2b70e7aab0

[illumos-gate merge] commit 0a554e9f2c0d440dc40a97fae2d18f1d428ca786 13404 man page spelling errors commit 9f76c6ed5b6ee0cc0bf631daca15ac3dc5fc70c4 13400 zfs-tests: implicit conversion from 'enum dmu_objset_type' to 'enum lzc_dataset_type' commit ef96fc31fc4f4306719704352d5c3e33573c039f 13399 zfs: error: implicit conversion from 'boolean_t' to 'ds_hold_flags_t' commit 56870e8c76c2675bcef1fcee5d519585ce9c768e 13393 cheetah: case value '47616' not in enumerated type commit 8247326397b1a16f37e70cf13f5b7a4f50d06712 13403 zfs: symbol 'g_zfs' is multiply-defined commit 436b964b19ef06803ad9165542d80d9d731d6486 13402 zpool: symbol 'g_zfs' is multiply-defined commit 99308ed0417a2b8ab73c5856a8a5345ce2a7aea7 13396 PoolsExecption typo in resource pools javadoc commit 1575b751c16622553e958c1e5c45e59c86b15c6e 13392 px: case value '3' not in enumerated type commit 9b0429a10eec9313ec782d8421272aff70adbfdc 13339 Add support for Hygon Dhyana Family 18h processor commit d20422bd742384b77102bb3bd09e0dc4b7372e50 13351 loader: vbe_find_mode_xydm() is using wrong safety and iteration is buggy commit 174b8e455f9a6974e69fa4e28792580acde0892d 13311 uptime(1) dazed and confused for a minute after boot commit f816551bb187d104fbf2757703d7a5d2189a3a18 13401 eeprom: 'lv' may be used uninitialized in this function commit 5e96da73c99d9d17ff5a58b793fff2ab6dcadf25 13391 fm: build errors with gcc 7 on SPARC commit 58b55f701e285559e4799354996fd284238ed0d4 13398 libstand: xdrproc_t should return bool commit c6a28d7650029501a356f7b75b2a10a5c4430cef 13394 fhc: case value '4294967295' not in enumerated type commit 58d4b16fe601073f2408de78e3db7e9bfa9abfd2 13355 remove topo module warning gags commit 1473b8d60e902819558a8b0e8a257eb0d754c3c3 13388 ZFS list bookmark not working on zvols commit 4bba12ca5cd6f92aaf0d4c0d19d05528110bc095 13368 libbe_py should support temporary BE activation commit a92282e44f968185a6bba094d1e5fece2da819cf 13376 fm: variable may be used uninitialized commit 8b1df8bf71b7b62e7e4d46fe6b457d4d6447b2b8 13367 beadm activate -t should not promote new BE datasets commit 9704bf7fb82e71b685e194a967937ff03843e73a 13317 Decrease contention on dn_struct_rwlock commit 88a08813800ed7ba7c927986421cee437f7f2233 13363 ctfconvert could support more granular ignore for missing debug data commit 3dd4cd56e7843e01a8ab147a0d102cd4f6d732c1 13342 ctfconvert could encode _Float128 for 32-bit objects commit 73197b540cc5f0434c409b68ca9e1a514a6ce91b 13336 ctfconvert should be able to unconditionally attempt conversion commit dd4422524768709a579a2a93a10c78a88a6b0ecb 13280 CTF: provide option to truncate and continue Conflicts & other fixes (with help from Jason King <jbk@joyent.com>): usr/src/lib/fm/topo/modules/common/ipmi/ipmi_enum.c usr/src/lib/libctf/common/ctf_convert.c usr/src/lib/libctf/common/ctf_lib.c usr/src/lib/libctf/common/libctf.h usr/src/lib/libproc/common/Psymtab.c usr/src/man/man1/ld.so.1.1 usr/src/man/man4/process.4
author Dan McDonald <danmcd@joyent.com>
date Mon, 04 Jan 2021 14:49:49 -0500
parents 790afcb3bacd 790618c19823
children
line wrap: on
line source

/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2019 Joyent, Inc.
 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
 */

/*
 * Create CTF from extant debugging information
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <libelf.h>
#include <libctf.h>
#include <string.h>
#include <libgen.h>
#include <limits.h>
#include <strings.h>
#include <sys/debug.h>

#define	CTFCONVERT_OK		0
#define	CTFCONVERT_FATAL	1
#define	CTFCONVERT_USAGE	2

static char *ctfconvert_progname;

static void
ctfconvert_fatal(const char *fmt, ...)
{
	va_list ap;

	(void) fprintf(stderr, "%s: ", ctfconvert_progname);
	va_start(ap, fmt);
	(void) vfprintf(stderr, fmt, ap);
	va_end(ap);

	exit(CTFCONVERT_FATAL);
}

static void
ctfconvert_warning(void *arg, const char *fmt, ...)
{
	va_list ap;
	char *buf;

	va_start(ap, fmt);
	if (vasprintf(&buf, fmt, ap) != -1) {
		(void) fprintf(stderr, "%s: WARNING: %s", ctfconvert_progname,
		    buf);
		free(buf);
	}
	va_end(ap);
}

static void
ctfconvert_usage(const char *fmt, ...)
{
	if (fmt != NULL) {
		va_list ap;

		(void) fprintf(stderr, "%s: ", ctfconvert_progname);
		va_start(ap, fmt);
		(void) vfprintf(stderr, fmt, ap);
		va_end(ap);
	}

	(void) fprintf(stderr, "Usage: %s [-fikms] [-j nthrs] [-l label | "
	    "-L labelenv] [-b batchsize]\n"
	    "                  [-o outfile] [-M ignorefile] input\n"
	    "\n"
	    "\t-b  batch process this many dies at a time (default %d)\n"
	    "\t-f  always attempt to convert files\n"
	    "\t-i  ignore files not built partially from C sources\n"
	    "\t-j  use nthrs threads to perform the merge (default %d)\n"
	    "\t-k  keep around original input file on failure\n"
	    "\t-l  set output container's label to specified value\n"
	    "\t-L  set output container's label to value from environment\n"
	    "\t-m  allow input to have missing debug info\n"
	    "\t-M  allow files listed in ignorefile to have missing debug\n"
	    "\t-o  copy input to outfile and add CTF\n"
	    "\t-s  allow truncation of data that cannot be fully converted\n",
	    ctfconvert_progname,
	    CTF_CONVERT_DEFAULT_BATCHSIZE,
	    CTF_CONVERT_DEFAULT_NTHREADS);
}

/*
 * This is a bit unfortunate. Traditionally we do type uniquification across all
 * modules in the kernel, including ip and unix against genunix. However, when
 * _MACHDEP is defined, then the cpu_t ends up having an additional member
 * (cpu_m), thus changing the ability for us to uniquify against it. This in
 * turn causes a lot of type sprawl, as there's a lot of things that end up
 * referring to the cpu_t and it chains out from there.
 *
 * So, if we find that a cpu_t has been defined and it has a couple of useful
 * sentinel members and it does *not* have the cpu_m member, then we will try
 * and lookup or create a forward declaration to the machcpu, append it to the
 * end, and update the file.
 *
 * This currently is only invoked if an undocumented option -X is passed. This
 * value is private to illumos and it can be changed at any time inside of it,
 * so if -X wants to be used for something, it should be. The ability to rely on
 * -X for others is strictly not an interface in any way, shape, or form.
 *
 * The following struct contains most of the information that we care about and
 * that we want to validate exists before we decide what to do.
 */

typedef struct ctfconvert_fixup {
	boolean_t	cf_cyclic;	/* Do we have a cpu_cyclic member */
	boolean_t	cf_mcpu;	/* We have a cpu_m member */
	boolean_t	cf_lastpad;	/* Is the pad member the last entry */
	ulong_t		cf_padoff;	/* offset of the pad */
} ctfconvert_fixup_t;

/* ARGSUSED */
static int
ctfconvert_fixup_genunix_cb(const char *name, ctf_id_t tid, ulong_t off,
    void *arg)
{
	ctfconvert_fixup_t *cfp = arg;

	cfp->cf_lastpad = B_FALSE;
	if (strcmp(name, "cpu_cyclic") == 0) {
		cfp->cf_cyclic = B_TRUE;
		return (0);
	}

	if (strcmp(name, "cpu_m") == 0) {
		cfp->cf_mcpu = B_TRUE;
		return (0);
	}

	if (strcmp(name, "cpu_m_pad") == 0) {
		cfp->cf_lastpad = B_TRUE;
		cfp->cf_padoff = off;
		return (0);
	}

	return (0);
}

static void
ctfconvert_fixup_genunix(ctf_file_t *fp)
{
	ctf_id_t cpuid, mcpu;
	ssize_t sz;
	ctfconvert_fixup_t cf;
	int model, ptrsz;

	cpuid = ctf_lookup_by_name(fp, "struct cpu");
	if (cpuid == CTF_ERR)
		return;

	if (ctf_type_kind(fp, cpuid) != CTF_K_STRUCT)
		return;

	if ((sz = ctf_type_size(fp, cpuid)) == CTF_ERR)
		return;

	model = ctf_getmodel(fp);
	VERIFY(model == CTF_MODEL_ILP32 || model == CTF_MODEL_LP64);
	ptrsz = model == CTF_MODEL_ILP32 ? 4 : 8;

	bzero(&cf, sizeof (ctfconvert_fixup_t));
	if (ctf_member_iter(fp, cpuid, ctfconvert_fixup_genunix_cb, &cf) ==
	    CTF_ERR)
		return;

	/*
	 * Finally, we want to verify that the cpu_m is actually the last member
	 * that we have here.
	 */
	if (cf.cf_cyclic == B_FALSE || cf.cf_mcpu == B_TRUE ||
	    cf.cf_lastpad == B_FALSE) {
		return;
	}

	if (cf.cf_padoff + ptrsz * NBBY != sz * NBBY) {
		return;
	}

	/*
	 * Okay, we're going to do this, try to find a struct machcpu. We either
	 * want a forward or a struct. If we find something else, error. If we
	 * find nothing, add a forward and then add the member.
	 */
	mcpu = ctf_lookup_by_name(fp, "struct machcpu");
	if (mcpu == CTF_ERR) {
		mcpu = ctf_add_forward(fp, CTF_ADD_NONROOT, "machcpu",
		    CTF_K_STRUCT);
		if (mcpu == CTF_ERR) {
			ctfconvert_fatal("failed to add 'struct machcpu' "
			    "forward: %s\n", ctf_errmsg(ctf_errno(fp)));
		}
	} else {
		int kind;
		if ((kind = ctf_type_kind(fp, mcpu)) == CTF_ERR) {
			ctfconvert_fatal("failed to get the type kind for "
			    "the struct machcpu: %s\n",
			    ctf_errmsg(ctf_errno(fp)));
		}

		if (kind != CTF_K_STRUCT && kind != CTF_K_FORWARD)
			ctfconvert_fatal("encountered a struct machcpu of the "
			    "wrong type, found type kind %d\n", kind);
	}

	if (ctf_update(fp) == CTF_ERR) {
		ctfconvert_fatal("failed to update output file: %s\n",
		    ctf_errmsg(ctf_errno(fp)));
	}

	if (ctf_add_member(fp, cpuid, "cpu_m", mcpu, sz * NBBY) == CTF_ERR) {
		ctfconvert_fatal("failed to add the m_cpu member: %s\n",
		    ctf_errmsg(ctf_errno(fp)));
	}

	if (ctf_update(fp) == CTF_ERR) {
		ctfconvert_fatal("failed to update output file: %s\n",
		    ctf_errmsg(ctf_errno(fp)));
	}

	VERIFY(ctf_type_size(fp, cpuid) == sz);
}

int
main(int argc, char *argv[])
{
	int c, ifd, err;
	boolean_t keep = B_FALSE;
	ctf_convert_flag_t flags = 0;
	uint_t bsize = CTF_CONVERT_DEFAULT_BATCHSIZE;
	uint_t nthreads = CTF_CONVERT_DEFAULT_NTHREADS;
	const char *outfile = NULL;
	const char *label = NULL;
	const char *infile = NULL;
	const char *ignorefile = NULL;
	char *tmpfile;
	ctf_file_t *ofp;
	char buf[4096] = "";
	boolean_t optx = B_FALSE;
	boolean_t ignore_non_c = B_FALSE;
	ctf_convert_t *cch;

	ctfconvert_progname = basename(argv[0]);

	while ((c = getopt(argc, argv, ":b:fij:kl:L:mM:o:sX")) != -1) {
		switch (c) {
		case 'b': {
			long argno;
			const char *errstr;

			argno = strtonum(optarg, 1, UINT_MAX, &errstr);
			if (errstr != NULL) {
				ctfconvert_fatal("invalid argument for -b: "
				    "%s - %s\n", optarg, errstr);
			}
			bsize = (uint_t)argno;
			break;
		}
		case 'f':
			flags |= CTF_FORCE_CONVERSION;
			break;
		case 'i':
			ignore_non_c = B_TRUE;
			break;
		case 'j': {
			long argno;
			const char *errstr;

			argno = strtonum(optarg, 1, 1024, &errstr);
			if (errstr != NULL) {
				ctfconvert_fatal("invalid argument for -j: "
				    "%s - %s\n", optarg, errstr);
			}
			nthreads = (uint_t)argno;
			break;
		}
		case 'k':
			keep = B_TRUE;
			break;
		case 'l':
			label = optarg;
			break;
		case 'L':
			label = getenv(optarg);
			break;
		case 'm':
			flags |= CTF_ALLOW_MISSING_DEBUG;
			break;
		case 'M':
			ignorefile = optarg;
			break;
		case 'o':
			outfile = optarg;
			break;
		case 's':
			flags |= CTF_ALLOW_TRUNCATION;
			break;
		case 'X':
			optx = B_TRUE;
			break;
		case ':':
			ctfconvert_usage("Option -%c requires an operand\n",
			    optopt);
			return (CTFCONVERT_USAGE);
		case '?':
			ctfconvert_usage("Unknown option: -%c\n", optopt);
			return (CTFCONVERT_USAGE);
		}
	}

	argv += optind;
	argc -= optind;

	if (argc != 1) {
		ctfconvert_usage("Exactly one input file is required\n");
		return (CTFCONVERT_USAGE);
	}
	infile = argv[0];

	if (elf_version(EV_CURRENT) == EV_NONE)
		ctfconvert_fatal("failed to initialize libelf: library is "
		    "out of date\n");

	ifd = open(infile, O_RDONLY);
	if (ifd < 0) {
		ctfconvert_fatal("failed to open input file %s: %s\n", infile,
		    strerror(errno));
	}

	/*
	 * By default we remove the input file on failure unless we've been
	 * given an output file or -k has been specified.
	 */
	if (outfile != NULL && strcmp(infile, outfile) != 0)
		keep = B_TRUE;

	cch = ctf_convert_init(&err);
	if (cch == NULL) {
		ctfconvert_fatal(
		    "failed to create libctf conversion handle: %s\n",
		    strerror(err));
	}
	if ((err = ctf_convert_set_nthreads(cch, nthreads)) != 0)
		ctfconvert_fatal("Could not set number of threads: %s\n",
		    strerror(err));
	if ((err = ctf_convert_set_batchsize(cch, bsize)) != 0)
		ctfconvert_fatal("Could not set batch size: %s\n",
		    strerror(err));
	if ((err = ctf_convert_set_flags(cch, flags)) != 0)
		ctfconvert_fatal("Could not set conversion flags: %s\n",
		    strerror(err));
	if (label != NULL && (err = ctf_convert_set_label(cch, label)) != 0)
		ctfconvert_fatal("Could not set label: %s\n",
		    strerror(err));
	if ((err = ctf_convert_set_warncb(cch, ctfconvert_warning, NULL)) != 0)
		ctfconvert_fatal("Could not set warning callback: %s\n",
		    strerror(err));

	if (ignorefile != NULL) {
		char *buf = NULL;
		ssize_t cnt;
		size_t len = 0;
		FILE *fp;

		if ((fp = fopen(ignorefile, "r")) == NULL) {
			ctfconvert_fatal("Could not open ignorefile '%s': %s\n",
			    ignorefile, strerror(errno));
		}

		while ((cnt = getline(&buf, &len, fp)) != -1) {
			char *p = buf;

			if (cnt == 0 || *p == '#')
				continue;

			(void) strsep(&p, "\n");
			if ((err = ctf_convert_add_ignore(cch, buf)) != 0) {
				ctfconvert_fatal(
				    "Failed to add '%s' to ignore list: %s\n",
				    buf, strerror(err));
			}
		}
		free(buf);
		if (cnt == -1 && ferror(fp) != 0) {
			ctfconvert_fatal(
			    "Error reading from ignorefile '%s': %s\n",
			    ignorefile, strerror(errno));
		}

		(void) fclose(fp);
	}

	ofp = ctf_fdconvert(cch, ifd, &err, buf, sizeof (buf));

	ctf_convert_fini(cch);

	if (ofp == NULL) {
		/*
		 * Normally, ctfconvert requires that its input file has at
		 * least one C-source compilation unit, and that every C-source
		 * compilation unit has DWARF. This is to avoid accidentally
		 * leaving out useful CTF.
		 *
		 * However, for the benefit of intransigent build environments,
		 * the -i and -m options can be used to relax this.
		 */
		if (err == ECTF_CONVNOCSRC && ignore_non_c) {
			exit(CTFCONVERT_OK);
		}

		if (err == ECTF_CONVNODEBUG &&
		    (flags & CTF_ALLOW_MISSING_DEBUG) != 0) {
			exit(CTFCONVERT_OK);
		}

		if (keep == B_FALSE)
			(void) unlink(infile);

		switch (err) {
		case ECTF_CONVBKERR:
			ctfconvert_fatal("CTF conversion failed: %s", buf);
			break;
		case ECTF_CONVNODEBUG:
			ctfconvert_fatal("CTF conversion failed due to "
			    "missing debug data; use -m to override\n");
			break;
		default:
			if (*buf != '\0') {
				(void) fprintf(stderr, "%s: %s",
				    ctfconvert_progname, buf);
			}
			ctfconvert_fatal("CTF conversion failed: %s\n",
			    ctf_errmsg(err));
		}
	}

	if (optx == B_TRUE)
		ctfconvert_fixup_genunix(ofp);

	tmpfile = NULL;
	if (outfile == NULL || strcmp(infile, outfile) == 0) {
		if (asprintf(&tmpfile, "%s.ctf", infile) == -1) {
			if (keep == B_FALSE)
				(void) unlink(infile);
			ctfconvert_fatal("failed to allocate memory for "
			    "temporary file: %s\n", strerror(errno));
		}
		outfile = tmpfile;
	}
	err = ctf_elfwrite(ofp, infile, outfile, CTF_ELFWRITE_F_COMPRESS);
	if (err == CTF_ERR) {
		(void) unlink(outfile);
		if (keep == B_FALSE)
			(void) unlink(infile);
		ctfconvert_fatal("failed to write CTF section to output file: "
		    "%s\n", ctf_errmsg(ctf_errno(ofp)));
	}
	ctf_close(ofp);

	if (tmpfile != NULL) {
		if (rename(tmpfile, infile) != 0) {
			int e = errno;
			(void) unlink(outfile);
			if (keep == B_FALSE)
				(void) unlink(infile);
			ctfconvert_fatal("failed to rename temporary file: "
			    "%s\n", strerror(e));
		}
	}
	free(tmpfile);

	return (CTFCONVERT_OK);
}