[BACK]Return to ctfdump.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ctfdump

File: [local] / src / usr.bin / ctfdump / ctfdump.c (download)

Revision 1.28, Thu Feb 22 13:21:03 2024 UTC (2 months, 3 weeks ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.27: +7 -6 lines

Print the size of more objects (basic types and enums) based on their
ctt_size info. This helps to ensure that the reported sizes match.
OK mpi@

/*	$OpenBSD: ctfdump.c,v 1.28 2024/02/22 13:21:03 claudio Exp $ */

/*
 * Copyright (c) 2016 Martin Pieuchot <mpi@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ctf.h>

#include <err.h>
#include <fcntl.h>
#include <gelf.h>
#include <libelf.h>
#include <locale.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef ZLIB
#include <zlib.h>
#endif /* ZLIB */

#ifndef nitems
#define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
#endif

#define DUMP_OBJECT	(1 << 0)
#define DUMP_FUNCTION	(1 << 1)
#define DUMP_HEADER	(1 << 2)
#define DUMP_LABEL	(1 << 3)
#define DUMP_STRTAB	(1 << 4)
#define DUMP_STATISTIC	(1 << 5)
#define DUMP_TYPE	(1 << 6)

int		 dump(const char *, uint8_t);
int		 isctf(const char *, size_t);
__dead void	 usage(void);

int		 ctf_dump(const char *, size_t, uint8_t);
void		 ctf_dump_type(struct ctf_header *, const char *, size_t,
		     uint32_t, uint32_t *, uint32_t);
const char	*ctf_kind2name(uint16_t);
const char	*ctf_enc2name(uint16_t);
const char	*ctf_fpenc2name(uint16_t);
const char	*ctf_off2name(struct ctf_header *, const char *, size_t,
		     uint32_t);

char		*decompress(const char *, size_t, size_t);
int		 elf_dump(uint8_t);
const char	*elf_idx2sym(size_t *, uint8_t);

int
main(int argc, char *argv[])
{
	const char *filename;
	uint8_t flags = 0;
	int ch, error = 0;

	setlocale(LC_ALL, "");

	if (pledge("stdio rpath", NULL) == -1)
		err(1, "pledge");

	while ((ch = getopt(argc, argv, "dfhlst")) != -1) {
		switch (ch) {
		case 'd':
			flags |= DUMP_OBJECT;
			break;
		case 'f':
			flags |= DUMP_FUNCTION;
			break;
		case 'h':
			flags |= DUMP_HEADER;
			break;
		case 'l':
			flags |= DUMP_LABEL;
			break;
		case 's':
			flags |= DUMP_STRTAB;
			break;
		case 't':
			flags |= DUMP_TYPE;
			break;
		default:
			usage();
		}
	}

	argc -= optind;
	argv += optind;

	if (argc <= 0)
		usage();

	/* Dump everything by default */
	if (flags == 0)
		flags = 0xff;

	if (elf_version(EV_CURRENT) == EV_NONE)
		errx(1, "elf_version: %s", elf_errmsg(-1));

	while ((filename = *argv++) != NULL)
		error |= dump(filename, flags);

	return error;
}

Elf	*e;
Elf_Scn	*scnsymtab;
size_t	 strtabndx, strtabsz, nsymb;

int
dump(const char *path, uint8_t flags)
{
	struct stat	 st;
	char		*p;
	int		 fd, error = 1;

	fd = open(path, O_RDONLY);
	if (fd == -1) {
		warn("open");
		return 1;
	}

	if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
		warnx("elf_begin: %s", elf_errmsg(-1));
		goto done;
	}

	if (elf_kind(e) == ELF_K_ELF) {
		error = elf_dump(flags);
		elf_end(e);
		goto done;
	}
	elf_end(e);

	if (fstat(fd, &st) == -1) {
		warn("fstat");
		goto done;
	}
	if ((uintmax_t)st.st_size > SIZE_MAX) {
		warnx("file too big to fit memory");
		goto done;
	}

	p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
	if (p == MAP_FAILED)
		err(1, "mmap");

	if (isctf(p, st.st_size))
		error = ctf_dump(p, st.st_size, flags);

	munmap(p, st.st_size);

 done:
	close(fd);
	return error;
}

const char *
elf_idx2sym(size_t *idx, uint8_t type)
{
	GElf_Sym	 sym;
	Elf_Data	*data;
	char		*name;
	size_t		 i;

	if (scnsymtab == NULL || strtabndx == 0)
		return NULL;

	data = NULL;
	while ((data = elf_rawdata(scnsymtab, data)) != NULL) {
		for (i = *idx + 1; i < nsymb; i++) {
			if (gelf_getsym(data, i, &sym) != &sym)
				continue;
			if (GELF_ST_TYPE(sym.st_info) != type)
				continue;
			if (sym.st_name >= strtabsz)
				break;
			if ((name = elf_strptr(e, strtabndx,
			    sym.st_name)) == NULL)
				continue;

			*idx = i;
			return name;
		}
	}

	return NULL;
}

int
elf_dump(uint8_t flags)
{
	GElf_Shdr	 shdr;
	Elf_Scn		*scn, *scnctf;
	Elf_Data	*data;
	char		*name;
	size_t		 shstrndx;
	int		 error = 0;

	if (elf_getshdrstrndx(e, &shstrndx) != 0) {
		warnx("elf_getshdrstrndx: %s", elf_errmsg(-1));
		return 1;
	}

	scn = scnctf = NULL;
	while ((scn = elf_nextscn(e, scn)) != NULL) {
		if (gelf_getshdr(scn, &shdr) != &shdr) {
			warnx("elf_getshdr: %s", elf_errmsg(-1));
			return 1;
		}

		if ((name = elf_strptr(e, shstrndx, shdr.sh_name)) == NULL) {
			warnx("elf_strptr: %s", elf_errmsg(-1));
			return 1;
		}

		if (strcmp(name, ELF_CTF) == 0)
			scnctf = scn;

		if (strcmp(name, ELF_SYMTAB) == 0 &&
		    shdr.sh_type == SHT_SYMTAB && shdr.sh_entsize != 0) {
			scnsymtab = scn;
			nsymb = shdr.sh_size / shdr.sh_entsize;
		}

		if (strcmp(name, ELF_STRTAB) == 0 &&
		    shdr.sh_type == SHT_STRTAB) {
			strtabndx = elf_ndxscn(scn);
			strtabsz = shdr.sh_size;
		}
	}

	if (scnctf == NULL) {
		warnx("%s section not found", ELF_CTF);
		return 1;
	}

	if (scnsymtab == NULL)
		warnx("symbol table not found");

	data = NULL;
	while ((data = elf_rawdata(scnctf, data)) != NULL) {
		if (data->d_buf == NULL) {
			warnx("%s section size is zero", ELF_CTF);
			return 1;
		}

		if (isctf(data->d_buf, data->d_size))
			error |= ctf_dump(data->d_buf, data->d_size, flags);
	}

	return error;
}

int
isctf(const char *p, size_t filesize)
{
	struct ctf_header	 cth;
	size_t			 dlen;

	if (filesize < sizeof(struct ctf_header)) {
		warnx("file too small to be CTF");
		return 0;
	}

	memcpy(&cth, p, sizeof(struct ctf_header));
	if (cth.cth_magic != CTF_MAGIC || cth.cth_version != CTF_VERSION)
		return 0;

	dlen = cth.cth_stroff + cth.cth_strlen;
	if (dlen > filesize && !(cth.cth_flags & CTF_F_COMPRESS)) {
		warnx("bogus file size");
		return 0;
	}

	if ((cth.cth_lbloff & 3) || (cth.cth_objtoff & 1) ||
	    (cth.cth_funcoff & 1) || (cth.cth_typeoff & 3)) {
		warnx("wrongly aligned offset");
		return 0;
	}

	if ((cth.cth_lbloff >= dlen) || (cth.cth_objtoff >= dlen) ||
	    (cth.cth_funcoff >= dlen) || (cth.cth_typeoff >= dlen)) {
		warnx("truncated file");
		return 0;
	}

	if ((cth.cth_lbloff > cth.cth_objtoff) ||
	    (cth.cth_objtoff > cth.cth_funcoff) ||
	    (cth.cth_funcoff > cth.cth_typeoff) ||
	    (cth.cth_typeoff > cth.cth_stroff)) {
		warnx("corrupted file");
		return 0;
	}

	return 1;
}

int
ctf_dump(const char *p, size_t size, uint8_t flags)
{
	struct ctf_header	 cth;
	size_t			 dlen;
	char			*data;

	memcpy(&cth, p, sizeof(struct ctf_header));
	dlen = cth.cth_stroff + cth.cth_strlen;
	if (cth.cth_flags & CTF_F_COMPRESS) {
		data = decompress(p + sizeof(cth), size - sizeof(cth), dlen);
		if (data == NULL)
			return 1;
	} else {
		data = (char *)p + sizeof(cth);
	}

	if (flags & DUMP_HEADER) {
		printf("  cth_magic    = 0x%04x\n", cth.cth_magic);
		printf("  cth_version  = %u\n", cth.cth_version);
		printf("  cth_flags    = 0x%02x\n", cth.cth_flags);
		printf("  cth_parlabel = %s\n",
		    ctf_off2name(&cth, data, dlen, cth.cth_parlabel));
		printf("  cth_parname  = %s\n",
		    ctf_off2name(&cth, data, dlen, cth.cth_parname));
		printf("  cth_lbloff   = %u\n", cth.cth_lbloff);
		printf("  cth_objtoff  = %u\n", cth.cth_objtoff);
		printf("  cth_funcoff  = %u\n", cth.cth_funcoff);
		printf("  cth_typeoff  = %u\n", cth.cth_typeoff);
		printf("  cth_stroff   = %u\n", cth.cth_stroff);
		printf("  cth_strlen   = %u\n", cth.cth_strlen);
		printf("\n");
	}

	if (flags & DUMP_LABEL) {
		uint32_t		 lbloff = cth.cth_lbloff;
		struct ctf_lblent	*ctl;

		while (lbloff < cth.cth_objtoff) {
			ctl = (struct ctf_lblent *)(data + lbloff);

			printf("  %5u %s\n", ctl->ctl_typeidx,
			    ctf_off2name(&cth, data, dlen, ctl->ctl_label));

			lbloff += sizeof(*ctl);
		}
		printf("\n");
	}

	if (flags & DUMP_OBJECT) {
		uint32_t		 objtoff = cth.cth_objtoff;
		size_t			 idx = 0, i = 0;
		uint16_t		*dsp;
		const char		*s;
		int			 l;

		while (objtoff < cth.cth_funcoff) {
			dsp = (uint16_t *)(data + objtoff);

			l = printf("  [%zu] %u", i++, *dsp);
			if ((s = elf_idx2sym(&idx, STT_OBJECT)) != NULL)
				printf("%*s %s (%zu)\n", (14 - l), "", s, idx);
			else
				printf("\n");

			objtoff += sizeof(*dsp);
		}
		printf("\n");
	}

	if (flags & DUMP_FUNCTION) {
		uint16_t		*fsp, kind, vlen;
		uint16_t		*fstart, *fend;
		size_t			 idx = 0, i = -1;
		const char		*s;
		int			 l;

		fstart = (uint16_t *)(data + cth.cth_funcoff);
		fend = (uint16_t *)(data + cth.cth_typeoff);

		fsp = fstart;
		while (fsp < fend) {
			kind = CTF_INFO_KIND(*fsp);
			vlen = CTF_INFO_VLEN(*fsp);
			s = elf_idx2sym(&idx, STT_FUNC);
			fsp++;
			i++;

			if (kind == CTF_K_UNKNOWN && vlen == 0)
				continue;

			l = printf("  [%zu] FUNC ", i);
			if (s != NULL)
				printf("(%s) ", s);
			printf("returns: %u args: (", *fsp++);
			while (vlen-- > 0 && fsp < fend)
				printf("%u%s", *fsp++, (vlen > 0) ? ", " : "");
			printf(")\n");
		}
		printf("\n");
	}

	if (flags & DUMP_TYPE) {
		uint32_t		 idx = 1, offset = cth.cth_typeoff;
		uint32_t		 stroff = cth.cth_stroff;

		while (offset < stroff) {
			ctf_dump_type(&cth, data, dlen, stroff, &offset, idx++);
		}
		printf("\n");
	}

	if (flags & DUMP_STRTAB) {
		uint32_t		 offset = 0;
		const char		*str;

		while (offset < cth.cth_strlen) {
			str = ctf_off2name(&cth, data, dlen, offset);

			printf("  [%u] ", offset);
			if (strcmp(str, "(anon)"))
				offset += printf("%s\n", str);
			else {
				printf("\\0\n");
				offset++;
			}
		}
		printf("\n");
	}

	if (cth.cth_flags & CTF_F_COMPRESS)
		free(data);

	return 0;
}

void
ctf_dump_type(struct ctf_header *cth, const char *data, size_t dlen,
    uint32_t stroff, uint32_t *offset, uint32_t idx)
{
	const char		*p = data + *offset;
	const struct ctf_type	*ctt = (struct ctf_type *)p;
	const struct ctf_array	*cta;
	uint16_t		*argp, i, kind, vlen, root;
	uint32_t		 eob, toff;
	uint64_t		 size;
	const char		*name, *kname;

	kind = CTF_INFO_KIND(ctt->ctt_info);
	vlen = CTF_INFO_VLEN(ctt->ctt_info);
	root = CTF_INFO_ISROOT(ctt->ctt_info);
	name = ctf_off2name(cth, data, dlen, ctt->ctt_name);

	if (root)
		printf("  <%u> ", idx);
	else
		printf("  [%u] ", idx);

	if ((kname = ctf_kind2name(kind)) != NULL)
		printf("%s %s", kname, name);

	if (ctt->ctt_size <= CTF_MAX_SIZE) {
		size = ctt->ctt_size;
		toff = sizeof(struct ctf_stype);
	} else {
		size = CTF_TYPE_LSIZE(ctt);
		toff = sizeof(struct ctf_type);
	}

	switch (kind) {
	case CTF_K_UNKNOWN:
	case CTF_K_FORWARD:
		break;
	case CTF_K_INTEGER:
		eob = *((uint32_t *)(p + toff));
		toff += sizeof(uint32_t);
		printf(" encoding=%s offset=%u bits=%u (%llu bytes)",
		    ctf_enc2name(CTF_INT_ENCODING(eob)), CTF_INT_OFFSET(eob),
		    CTF_INT_BITS(eob), size);
		break;
	case CTF_K_FLOAT:
		eob = *((uint32_t *)(p + toff));
		toff += sizeof(uint32_t);
		printf(" encoding=%s offset=%u bits=%u (%llu bytes)",
		    ctf_fpenc2name(CTF_FP_ENCODING(eob)), CTF_FP_OFFSET(eob),
		    CTF_FP_BITS(eob), size);
		break;
	case CTF_K_ARRAY:
		cta = (struct ctf_array *)(p + toff);
		printf(" content: %u index: %u nelems: %u\n", cta->cta_contents,
		    cta->cta_index, cta->cta_nelems);
		toff += sizeof(struct ctf_array);
		break;
	case CTF_K_FUNCTION:
		argp = (uint16_t *)(p + toff);
		printf(" returns: %u args: (%u", ctt->ctt_type, *argp);
		for (i = 1; i < vlen; i++) {
			argp++;
			if ((const char *)argp > data + dlen)
				errx(1, "offset exceeds CTF section");

			printf(", %u", *argp);
		}
		printf(")");
		toff += (vlen + (vlen & 1)) * sizeof(uint16_t);
		break;
	case CTF_K_STRUCT:
	case CTF_K_UNION:
		printf(" (%llu bytes)\n", size);

		if (size < CTF_LSTRUCT_THRESH) {
			for (i = 0; i < vlen; i++) {
				struct ctf_member	*ctm;

				if (p + toff > data + dlen)
					errx(1, "offset exceeds CTF section");

				if (toff > (stroff - sizeof(*ctm)))
					break;

				ctm = (struct ctf_member *)(p + toff);
				toff += sizeof(struct ctf_member);

				printf("\t%s type=%u off=%u\n",
				    ctf_off2name(cth, data, dlen,
					ctm->ctm_name),
				    ctm->ctm_type, ctm->ctm_offset);
			}
		} else {
			for (i = 0; i < vlen; i++) {
				struct ctf_lmember	*ctlm;

				if (p + toff > data + dlen)
					errx(1, "offset exceeds CTF section");

				if (toff > (stroff - sizeof(*ctlm)))
					break;

				ctlm = (struct ctf_lmember *)(p + toff);
				toff += sizeof(struct ctf_lmember);

				printf("\t%s type=%u off=%llu\n",
				    ctf_off2name(cth, data, dlen,
					ctlm->ctlm_name),
				    ctlm->ctlm_type, CTF_LMEM_OFFSET(ctlm));
			}
		}
		break;
	case CTF_K_ENUM:
		printf(" (%llu bytes)\n", size);

		for (i = 0; i < vlen; i++) {
			struct ctf_enum	*cte;

			if (p + toff > data + dlen)
				errx(1, "offset exceeds CTF section");

			if (toff > (stroff - sizeof(*cte)))
				break;

			cte = (struct ctf_enum *)(p + toff);
			toff += sizeof(struct ctf_enum);

			printf("\t%s = %d\n",
			    ctf_off2name(cth, data, dlen, cte->cte_name),
			    cte->cte_value);
		}
		break;
	case CTF_K_POINTER:
	case CTF_K_TYPEDEF:
	case CTF_K_VOLATILE:
	case CTF_K_CONST:
	case CTF_K_RESTRICT:
		printf(" refers to %u", ctt->ctt_type);
		break;
	default:
		errx(1, "incorrect type %u at offset %u", kind, *offset);
	}

	printf("\n");

	*offset += toff;
}

const char *
ctf_kind2name(uint16_t kind)
{
	static const char *kind_name[] = { NULL, "INTEGER", "FLOAT", "POINTER",
	   "ARRAY", "FUNCTION", "STRUCT", "UNION", "ENUM", "FORWARD",
	   "TYPEDEF", "VOLATILE", "CONST", "RESTRICT" };

	if (kind >= nitems(kind_name))
		return NULL;

	return kind_name[kind];
}

const char *
ctf_enc2name(uint16_t enc)
{
	static const char *enc_name[] = { "SIGNED", "CHAR", "SIGNED CHAR",
	    "BOOL", "SIGNED BOOL" };
	static char invalid[7];

	if (enc == CTF_INT_VARARGS)
		return "VARARGS";

	if (enc > 0 && enc <= nitems(enc_name))
		return enc_name[enc - 1];

	snprintf(invalid, sizeof(invalid), "0x%x", enc);
	return invalid;
}

const char *
ctf_fpenc2name(uint16_t enc)
{
	static const char *enc_name[] = { "SINGLE", "DOUBLE", NULL, NULL,
	    NULL, "LDOUBLE" };
	static char invalid[7];

	if (enc > 0 && enc <= nitems(enc_name) && enc_name[enc - 1] != NULL)
		return enc_name[enc - 1];

	snprintf(invalid, sizeof(invalid), "0x%x", enc);
	return invalid;
}

const char *
ctf_off2name(struct ctf_header *cth, const char *data, size_t dlen,
    uint32_t offset)
{
	const char		*name;

	if (CTF_NAME_STID(offset) != CTF_STRTAB_0)
		return "external";

	if (CTF_NAME_OFFSET(offset) >= cth->cth_strlen)
		return "exceeds strlab";

	if (cth->cth_stroff + CTF_NAME_OFFSET(offset) >= dlen)
		return "invalid";

	name = data + cth->cth_stroff + CTF_NAME_OFFSET(offset);
	if (*name == '\0')
		return "(anon)";

	return name;
}

char *
decompress(const char *buf, size_t size, size_t len)
{
#ifdef ZLIB
	z_stream		 stream;
	char			*data;
	int			 error;

	data = malloc(len);
	if (data == NULL) {
		warn(NULL);
		return NULL;
	}

	memset(&stream, 0, sizeof(stream));
	stream.next_in = (void *)buf;
	stream.avail_in = size;
	stream.next_out = (uint8_t *)data;
	stream.avail_out = len;

	if ((error = inflateInit(&stream)) != Z_OK) {
		warnx("zlib inflateInit failed: %s", zError(error));
		goto exit;
	}

	if ((error = inflate(&stream, Z_FINISH)) != Z_STREAM_END) {
		warnx("zlib inflate failed: %s", zError(error));
		inflateEnd(&stream);
		goto exit;
	}

	if ((error = inflateEnd(&stream)) != Z_OK) {
		warnx("zlib inflateEnd failed: %s", zError(error));
		goto exit;
	}

	if (stream.total_out != len) {
		warnx("decompression failed: %lu != %zu",
		    stream.total_out, len);
		goto exit;
	}

	return data;

exit:
	free(data);
#endif /* ZLIB */
	return NULL;
}

__dead void
usage(void)
{
	fprintf(stderr, "usage: %s [-dfhlst] file ...\n",
	    getprogname());
	exit(1);
}