/*	$OpenBSD: db_struct.c,v 1.3 2013/10/19 21:02:50 guenther Exp $	*/

/*
 * Copyright (c) 2009 Miodrag Vallat.
 *
 * 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.
 */

/*
 * ddb routines to describe struct information
 */

#include <sys/param.h>
#include <sys/systm.h>

#include <machine/db_machdep.h>

#include <ddb/db_lex.h>
#include <ddb/db_output.h>
#include <ddb/db_access.h>
#include <ddb/db_command.h>
#include <ddb/db_extern.h>
#include <ddb/db_interface.h>
#include <ddb/db_var.h>

#include "db_structinfo.h"

void	db_struct_print_field(uint, int, db_expr_t);

/*
 * Flags to pass db_struct_printf().
 */

#define	DBSP_STRUCT_NAME	0x01	/* prepend struct name */
#define	DBSP_VALUE		0x02	/* display field value */

void
db_struct_print_field(uint fidx, int flags, db_expr_t baseaddr)
{
	const struct ddb_field_info *field;
	const struct ddb_struct_info *struc;
	db_expr_t value;
	uint tmp;
	size_t namelen;
	int width, basecol, curcol;
	char tmpfmt[28];

	field = &ddb_field_info[fidx];
	basecol = 0;

	if (ISSET(flags, DBSP_STRUCT_NAME)) {
		struc = &ddb_struct_info[field->sidx];
		namelen = strlen(ddb_structfield_strings + struc->name);
		db_printf("%-30s ", ddb_structfield_strings + struc->name);
		if (namelen > 30)
			basecol += namelen + 1;
		else
			basecol += 30 + 1;
	}

	namelen = strlen(ddb_structfield_strings + field->name);
	if (field->nitems == 1) {
		db_printf("%-30s ", ddb_structfield_strings + field->name);
		if (namelen > 30)
			basecol += namelen + 1;
		else
			basecol += 30 + 1;
	} else {
		width = 30 - 2;
		tmp = field->nitems;
		while (tmp != 0) {
			width--;
			tmp /= 10;
		}
		if (namelen >= width) {
			db_printf("%s[%hu] ",
			    ddb_structfield_strings + field->name,
			    field->nitems);
			basecol += namelen + (30 - width) + 1;
		} else {
			db_printf("%s[%hu]%*s ",
			    ddb_structfield_strings + field->name,
			    field->nitems, width - (int)namelen, "");
			/* namelen + (30-width) + (width-namelen) + 1 */
			basecol += 30 + 1;
		}
	}

	if (field->size == 0) {
		db_printf("bitfield");
		/* basecol irrelevant from there on */
	} else {
		snprintf(tmpfmt, sizeof tmpfmt, "%u", (u_int)field->size);
		basecol += strlen(tmpfmt) + 1;
		db_printf("%s ", tmpfmt);
	}

	if (ISSET(flags, DBSP_VALUE)) {
		/* only print the field value if it has a friendly size. */
		switch (field->size) {
		case 1:
			width = 4;
			break;
		case 2:
			width = 8;
			break;
		case 4:
			width = 12;
			break;
#ifdef __LP64__
		case 8:
			width = 20;
			break;
#endif
		default:
			width = 0;
		}
		if (width != 0) {
			baseaddr += field->offs;
			curcol = basecol;
			for (tmp = field->nitems; tmp != 0; tmp--) {
				value = db_get_value(baseaddr, field->size,
				    FALSE); /* assume unsigned */
				db_format(tmpfmt, sizeof tmpfmt, (long)value,
				    DB_FORMAT_N, 0, width);
				if (field->nitems > 1)
					db_printf("%s", tmpfmt);
				else
					db_printf("%20s", tmpfmt);
				baseaddr += field->size;

				/*
				 * Try to fit array elements on as few lines
				 * as possible.
				 */
				if (field->nitems > 1 && tmp > 1) {
					curcol += width + 1;
					if (basecol >= db_max_width ||
					    curcol + width >= db_max_width) {
						/* new line */
						db_printf("\n");
						if (basecol + width >=
						    db_max_width) {
							db_printf("\t");
							curcol = 8;
						} else {
							db_printf("%*s",
							    basecol, "");
							curcol = basecol;
						}
					} else
						db_printf(" ");
				}
			}
		}
	}

	db_printf("\n");
}


/*
 * show offset <value>: displays the list of struct fields which exist
 * at that particular offset from the beginning of the struct.
 */
void
db_struct_offset_cmd(db_expr_t addr, int have_addr, db_expr_t count,
    char *modifiers)
{
	db_expr_t offset = 0;
	const struct ddb_field_offsets *field;
	const ddb_field_off *fidx;
	uint oidx;
	int width;
	char tmpfmt[28];

	/*
	 * Read the offset from the debuggger input.
	 * We don't want to get it from the standard parsing code, because
	 * this would set `dot' to this value, which doesn't make sense.
	 */

	if (!db_expression(&offset) || offset < 0) {
		db_printf("not a valid offset\n");
		db_flush_lex();
		return;
	}

	db_skip_to_eol();

	for (field = ddb_field_offsets, oidx = 0; oidx < NOFFS; field++, oidx++)
		if (field->offs == (size_t)offset)
			break;

	if (oidx == NOFFS) {
		db_format(tmpfmt, sizeof tmpfmt, (long)offset,
		    DB_FORMAT_N, 0, width);
		db_printf("no known structure element at offset %-*s\n",
		    width, tmpfmt);
		db_flush_lex();
		return;
	}

	db_printf("%-30s %-30s size\n", "struct", "member");
	for (fidx = ddb_fields_by_offset + field->list; *fidx != 0; fidx++)
		db_struct_print_field(*fidx, DBSP_STRUCT_NAME, 0);
}

/*
 * show struct <struct name> [addr]: displays the data starting at addr
 * (`dot' if unspecified) as a struct of the given type.
 */
void
db_struct_layout_cmd(db_expr_t addr, int have_addr, db_expr_t count,
    char *modifiers)
{
	const struct ddb_struct_info *struc;
	uint sidx, fidx;
	int t;

	/*
	 * Read the struct name from the debugger input.
	 */

	t = db_read_token();
	if (t != tIDENT) {
		db_printf("Bad struct name\n");
		db_flush_lex();
		return;
	}

	for (struc = ddb_struct_info, sidx = 0; sidx < NSTRUCT;
	    struc++, sidx++)
		if (strcmp(ddb_structfield_strings + struc->name,
		    db_tok_string) == 0)
			break;

	if (sidx == NSTRUCT) {
		db_printf("unknown struct %s\n", db_tok_string);
		db_flush_lex();
		return;
	}

	/*
	 * Read the address, if any, from the debugger input.
	 * In that case, update `dot' value.
	 */

	if (db_expression(&addr)) {
		db_dot = (db_addr_t)addr;
		db_last_addr = db_dot;
	} else
		addr = (db_expr_t)db_dot;

	db_skip_to_eol();

	/*
	 * Display the structure contents.
	 */

	db_printf("struct %s at %p (%u bytes)\n",
	    ddb_structfield_strings + struc->name, (void *)addr,
	    (u_int)struc->size);
	for (fidx = struc->fmin; fidx <= struc->fmax; fidx++)
		db_struct_print_field(fidx, DBSP_VALUE, addr);
}
