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

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

Revision 1.6, Wed Feb 21 13:16:14 2024 UTC (2 months, 3 weeks ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.5: +5 -2 lines

Make sure dw_at2name() never returns NULL. This call is used in various
printf calls that clang decided to optimise into puts calls that crash
with a NULL argument.
Also add DW_AT_noreturn which caused this when running ./ctfconv -d ./ctfconv
OK mpi@

/*	$OpenBSD: dw.c,v 1.6 2024/02/21 13:16:14 claudio Exp $ */

/*
 * Copyright (c) 2016 Martin Pieuchot
 * Copyright (c) 2014 Matthew Dempsky <matthew@dempsky.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/queue.h>

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dw.h"
#include "dwarf.h"
#include "pool.h"

#ifndef NOPOOL
struct pool dcu_pool, die_pool, dav_pool, dab_pool, dat_pool;
#endif /* NOPOOL */

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

static int	 dw_read_u8(struct dwbuf *, uint8_t *);
static int	 dw_read_u16(struct dwbuf *, uint16_t *);
static int	 dw_read_u32(struct dwbuf *, uint32_t *);
static int	 dw_read_u64(struct dwbuf *, uint64_t *);

static int	 dw_read_sleb128(struct dwbuf *, int64_t *);
static int	 dw_read_uleb128(struct dwbuf *, uint64_t *);

static int	 dw_read_bytes(struct dwbuf *, void *, size_t);
static int	 dw_read_string(struct dwbuf *, const char **);
static int	 dw_read_buf(struct dwbuf *, struct dwbuf *, size_t);

static int	 dw_skip_bytes(struct dwbuf *, size_t);

static int	 dw_attr_parse(struct dwbuf *, struct dwattr *, uint8_t,
		     struct dwaval_queue *);
static void	 dw_attr_purge(struct dwaval_queue *);
static int	 dw_die_parse(struct dwbuf *, size_t, uint8_t,
		     struct dwabbrev_queue *, struct dwdie_queue *);
static void	 dw_die_purge(struct dwdie_queue *);

static int
dw_read_bytes(struct dwbuf *d, void *v, size_t n)
{
	if (d->len < n)
		return -1;
	memcpy(v, d->buf, n);
	d->buf += n;
	d->len -= n;
	return 0;
}

static int
dw_read_u8(struct dwbuf *d, uint8_t *v)
{
	return dw_read_bytes(d, v, sizeof(*v));
}

static int
dw_read_u16(struct dwbuf *d, uint16_t *v)
{
	return dw_read_bytes(d, v, sizeof(*v));
}

static int
dw_read_u32(struct dwbuf *d, uint32_t *v)
{
	return dw_read_bytes(d, v, sizeof(*v));
}

static int
dw_read_u64(struct dwbuf *d, uint64_t *v)
{
	return dw_read_bytes(d, v, sizeof(*v));
}

/* Read a DWARF LEB128 (little-endian base-128) value. */
static inline int
dw_read_leb128(struct dwbuf *d, uint64_t *v, int signextend)
{
	unsigned int shift = 0;
	uint64_t res = 0;
	uint8_t x;

	while (shift < 64 && !dw_read_u8(d, &x)) {
		res |= (uint64_t)(x & 0x7f) << shift;
		shift += 7;
		if ((x & 0x80) == 0) {
			if (signextend && shift < 64 && (x & 0x40) != 0)
				res |= ~(uint64_t)0 << shift;
			*v = res;
			return 0;
		}
	}
	return -1;
}

static int
dw_read_sleb128(struct dwbuf *d, int64_t *v)
{
	return dw_read_leb128(d, (uint64_t *)v, 1);
}

static int
dw_read_uleb128(struct dwbuf *d, uint64_t *v)
{
	return dw_read_leb128(d, v, 0);
}

/* Read a NUL terminated string. */
static int
dw_read_string(struct dwbuf *d, const char **s)
{
	const char *end = memchr(d->buf, '\0', d->len);
	size_t n;

	if (end == NULL)
		return -1;

	n = end - d->buf + 1;
	*s = d->buf;
	d->buf += n;
	d->len -= n;
	return 0;
}

static int
dw_read_buf(struct dwbuf *d, struct dwbuf *v, size_t n)
{
	if (d->len < n)
		return -1;
	v->buf = d->buf;
	v->len = n;
	d->buf += n;
	d->len -= n;
	return 0;
}

static int
dw_skip_bytes(struct dwbuf *d, size_t n)
{
	if (d->len < n)
		return -1;
	d->buf += n;
	d->len -= n;
	return 0;
}

const char *
dw_tag2name(uint64_t tag)
{
	static const char *dw_tags[] = { DW_TAG_NAMES };

	if (tag <= nitems(dw_tags))
		return dw_tags[tag - 1];

	if (tag == DW_TAG_lo_user)
		return "DW_TAG_lo_user";
	if (tag == DW_TAG_hi_user)
		return "DW_TAG_hi_user";

	return NULL;
}

const char *
dw_at2name(uint64_t at)
{
	static const char *dw_attrs[] = { DW_AT_NAMES };
	static char buf[64];

	if (at <= nitems(dw_attrs))
		return dw_attrs[at - 1];

	if (at == DW_AT_lo_user)
		return "DW_AT_lo_user";
	if (at == DW_AT_hi_user)
		return "DW_AT_hi_user";

	snprintf(buf, sizeof(buf), "#%llu", at);
	return buf;
}

const char *
dw_form2name(uint64_t form)
{
	static const char *dw_forms[] = { DW_FORM_NAMES };

	if (form <= nitems(dw_forms))
		return dw_forms[form - 1];

	if (form == DW_FORM_GNU_ref_alt)
		return "DW_FORM_GNU_ref_alt";
	if (form == DW_FORM_GNU_strp_alt)
		return "DW_FORM_GNU_strp_alt";

	return NULL;
}

const char *
dw_op2name(uint8_t op)
{
	static const char *dw_ops[] = { DW_OP_NAMES };

	if (op <= nitems(dw_ops))
		return dw_ops[op - 1];

	if (op == DW_OP_lo_user)
		return "DW_OP_lo_user";
	if (op == DW_OP_hi_user)
		return "DW_OP_hi_user";

	return NULL;
}

static int
dw_attr_parse(struct dwbuf *dwbuf, struct dwattr *dat, uint8_t psz,
    struct dwaval_queue *davq)
{
	struct dwaval	*dav;
	uint64_t	 form = dat->dat_form;
	int		 error = 0, i = 0;

	while (form == DW_FORM_indirect) {
		/* XXX loop prevention not strict enough? */
		if (dw_read_uleb128(dwbuf, &form) || (++i > 3))
			return ELOOP;
	}

	dav = pzalloc(&dav_pool, sizeof(*dav));
	if (dav == NULL)
		return ENOMEM;

	dav->dav_dat = dat;

	switch (form) {
	case DW_FORM_addr:
	case DW_FORM_ref_addr:
		if (psz == sizeof(uint32_t))
			error = dw_read_u32(dwbuf, &dav->dav_u32);
		else
			error = dw_read_u64(dwbuf, &dav->dav_u64);
		break;
	case DW_FORM_block1:
		error = dw_read_u8(dwbuf, &dav->dav_u8);
		if (error == 0)
			error = dw_read_buf(dwbuf, &dav->dav_buf, dav->dav_u8);
		break;
	case DW_FORM_block2:
		error = dw_read_u16(dwbuf, &dav->dav_u16);
		if (error == 0)
			error = dw_read_buf(dwbuf, &dav->dav_buf, dav->dav_u16);
		break;
	case DW_FORM_block4:
		error = dw_read_u32(dwbuf, &dav->dav_u32);
		if (error == 0)
			error = dw_read_buf(dwbuf, &dav->dav_buf, dav->dav_u32);
		break;
	case DW_FORM_block:
		error = dw_read_uleb128(dwbuf, &dav->dav_u64);
		if (error == 0)
			error = dw_read_buf(dwbuf, &dav->dav_buf, dav->dav_u64);
		break;
	case DW_FORM_data1:
	case DW_FORM_flag:
	case DW_FORM_ref1:
		error = dw_read_u8(dwbuf, &dav->dav_u8);
		break;
	case DW_FORM_data2:
	case DW_FORM_ref2:
		error = dw_read_u16(dwbuf, &dav->dav_u16);
		break;
	case DW_FORM_data4:
	case DW_FORM_ref4:
		error = dw_read_u32(dwbuf, &dav->dav_u32);
		break;
	case DW_FORM_data8:
	case DW_FORM_ref8:
		error = dw_read_u64(dwbuf, &dav->dav_u64);
		break;
	case DW_FORM_ref_udata:
	case DW_FORM_udata:
		error = dw_read_uleb128(dwbuf, &dav->dav_u64);
		break;
	case DW_FORM_sdata:
		error = dw_read_sleb128(dwbuf, &dav->dav_s64);
		break;
	case DW_FORM_string:
		error = dw_read_string(dwbuf, &dav->dav_str);
		break;
	case DW_FORM_strp:
		error = dw_read_u32(dwbuf, &dav->dav_u32);
		break;
	case DW_FORM_flag_present:
		dav->dav_u8 = 1;
		break;
	default:
		error = ENOENT;
		break;
	}

	if (error) {
		pfree(&dav_pool, dav);
		return error;
	}

	SIMPLEQ_INSERT_TAIL(davq, dav, dav_next);
	return 0;
}

static void
dw_attr_purge(struct dwaval_queue *davq)
{
	struct dwaval	*dav;

	while ((dav = SIMPLEQ_FIRST(davq)) != NULL) {
		SIMPLEQ_REMOVE_HEAD(davq, dav_next);
		pfree(&dav_pool, dav);
	}

	SIMPLEQ_INIT(davq);
}

static int
dw_die_parse(struct dwbuf *dwbuf, size_t nextoff, uint8_t psz,
    struct dwabbrev_queue *dabq, struct dwdie_queue *dieq)
{
	struct dwdie	*die;
	struct dwabbrev	*dab;
	struct dwattr	*dat;
	uint64_t	 code;
	size_t		 doff;
	uint8_t		 lvl = 0;
	int		 error;


	while (dwbuf->len > 0) {
		doff = nextoff - dwbuf->len;
		if (dw_read_uleb128(dwbuf, &code))
			return -1;

		if (code == 0) {
			lvl--;
			continue;
		}

		SIMPLEQ_FOREACH(dab, dabq, dab_next) {
			if (dab->dab_code == code)
				break;
		}
		if (dab == NULL)
			return ESRCH;

		die = pmalloc(&die_pool, sizeof(*die));
		if (die == NULL)
			return ENOMEM;

		die->die_lvl = lvl;
		die->die_dab = dab;
		die->die_offset = doff;
		SIMPLEQ_INIT(&die->die_avals);

		SIMPLEQ_FOREACH(dat, &dab->dab_attrs, dat_next) {
			error = dw_attr_parse(dwbuf, dat, psz, &die->die_avals);
			if (error != 0) {
				dw_attr_purge(&die->die_avals);
				return error;
			}
		}

		if (dab->dab_children == DW_CHILDREN_yes)
			lvl++;

		SIMPLEQ_INSERT_TAIL(dieq, die, die_next);
	}

	return 0;
}

static void
dw_die_purge(struct dwdie_queue *dieq)
{
	struct dwdie	*die;

	while ((die = SIMPLEQ_FIRST(dieq)) != NULL) {
		SIMPLEQ_REMOVE_HEAD(dieq, die_next);
		dw_attr_purge(&die->die_avals);
		pfree(&die_pool, die);
	}

	SIMPLEQ_INIT(dieq);
}

int
dw_ab_parse(struct dwbuf *abseg, struct dwabbrev_queue *dabq)
{
	struct dwabbrev	*dab;
	uint64_t	 code, tag;
	uint8_t		 children;

	if (abseg->len == 0)
		return EINVAL;

	for (;;) {
		if (dw_read_uleb128(abseg, &code) || (code == 0))
			break;

		if (dw_read_uleb128(abseg, &tag) ||
		    dw_read_u8(abseg, &children))
			return -1;

		dab = pmalloc(&dab_pool, sizeof(*dab));
		if (dab == NULL)
			return ENOMEM;

		dab->dab_code = code;
		dab->dab_tag = tag;
		dab->dab_children = children;
		SIMPLEQ_INIT(&dab->dab_attrs);

		SIMPLEQ_INSERT_TAIL(dabq, dab, dab_next);

		for (;;) {
			struct dwattr *dat;
			uint64_t attr = 0, form = 0;

			if (dw_read_uleb128(abseg, &attr) ||
			    dw_read_uleb128(abseg, &form))
				return -1;

			if ((attr == 0) && (form == 0))
				break;

			dat = pmalloc(&dat_pool, sizeof(*dat));
			if (dat == NULL)
				return ENOMEM;

			dat->dat_attr = attr;
			dat->dat_form = form;

			SIMPLEQ_INSERT_TAIL(&dab->dab_attrs, dat, dat_next);
		}
	}

	return 0;
}

void
dw_dabq_purge(struct dwabbrev_queue *dabq)
{
	struct dwabbrev	*dab;

	while ((dab = SIMPLEQ_FIRST(dabq)) != NULL) {
		struct dwattr *dat;

		SIMPLEQ_REMOVE_HEAD(dabq, dab_next);
		while ((dat = SIMPLEQ_FIRST(&dab->dab_attrs)) != NULL) {
			SIMPLEQ_REMOVE_HEAD(&dab->dab_attrs, dat_next);
			pfree(&dat_pool, dat);
		}

		pfree(&dab_pool, dab);
	}

	SIMPLEQ_INIT(dabq);
}

int
dw_cu_parse(struct dwbuf *info, struct dwbuf *abbrev, size_t seglen,
    struct dwcu **dcup)
{
	struct dwbuf	 abseg = *abbrev;
	struct dwbuf	 dwbuf;
	size_t		 segoff, nextoff, addrsize;
	struct dwcu	*dcu = NULL;
	uint32_t	 length = 0, abbroff = 0;
	uint16_t	 version;
	uint8_t		 psz;
	int		 error;
#ifndef NOPOOL
	static int 	 dw_pool_inited = 0;

	if (!dw_pool_inited) {
		pool_init(&dcu_pool, "dcu", 1, sizeof(struct dwcu));
		pool_init(&dab_pool, "dab", 32, sizeof(struct dwabbrev));
		pool_init(&dat_pool, "dat", 32, sizeof(struct dwattr));
		pool_init(&die_pool, "die", 512, sizeof(struct dwdie));
		pool_init(&dav_pool, "dav", 1024, sizeof(struct dwaval));
		dw_pool_inited = 1;
	}
#endif /* NOPOOL */

	if (info->len == 0 || abbrev->len == 0)
		return EINVAL;

	/* Offset in the segment of the current Compile Unit. */
	segoff = seglen - info->len;

	if (dw_read_u32(info, &length))
		return -1;

	if (length >= 0xfffffff0 || length > info->len)
		return EOVERFLOW;

	/* Offset of the next Compile Unit. */
	nextoff = segoff + length + sizeof(uint32_t);

	if (dw_read_buf(info, &dwbuf, length))
		return -1;

	addrsize = 4; /* XXX */

	if (dw_read_u16(&dwbuf, &version) ||
	    dw_read_bytes(&dwbuf, &abbroff, addrsize) ||
	    dw_read_u8(&dwbuf, &psz))
		return -1;

	if (dw_skip_bytes(&abseg, abbroff))
		return -1;

	/* Only DWARF2 until extended. */
	if (version != 2)
		return ENOTSUP;

	dcu = pmalloc(&dcu_pool, sizeof(*dcu));
	if (dcu == NULL)
		return ENOMEM;

	dcu->dcu_offset = segoff;
	dcu->dcu_length = length;
	dcu->dcu_version = version;
	dcu->dcu_abbroff = abbroff;
	dcu->dcu_psize = psz;
	SIMPLEQ_INIT(&dcu->dcu_abbrevs);
	SIMPLEQ_INIT(&dcu->dcu_dies);

	error = dw_ab_parse(&abseg, &dcu->dcu_abbrevs);
	if (error != 0) {
		dw_dcu_free(dcu);
		return error;
	}

	error = dw_die_parse(&dwbuf, nextoff, psz, &dcu->dcu_abbrevs,
	    &dcu->dcu_dies);
	if (error != 0) {
		dw_dcu_free(dcu);
		return error;
	}

	if (dcup != NULL)
		*dcup = dcu;
	else
		dw_dcu_free(dcu);

	return 0;
}

void
dw_dcu_free(struct dwcu *dcu)
{
	if (dcu == NULL)
		return;

	dw_die_purge(&dcu->dcu_dies);
	dw_dabq_purge(&dcu->dcu_abbrevs);
	pfree(&dcu_pool, dcu);
}

int
dw_loc_parse(struct dwbuf *dwbuf, uint8_t *pop, uint64_t *poper1,
    uint64_t *poper2)
{
	uint64_t oper1 = 0, oper2 = 0;
	uint8_t op;

	if (dw_read_u8(dwbuf, &op))
		return -1;

	if (pop != NULL)
		*pop = op;

	switch (op) {
	case DW_OP_constu:
	case DW_OP_plus_uconst:
	case DW_OP_regx:
	case DW_OP_piece:
		dw_read_uleb128(dwbuf, &oper1);
		break;

	case DW_OP_consts:
	case DW_OP_breg0 ... DW_OP_breg31:
	case DW_OP_fbreg:
		dw_read_sleb128(dwbuf, &oper1);
		break;
	default:
		return ENOTSUP;
	}

	if (poper1 != NULL)
		*poper1 = oper1;
	if (poper2 != NULL)
		*poper2 = oper2;

	return 0;
}