summaryrefslogblamecommitdiffstats
path: root/libsyscalls_get_struct_description.c
blob: cc1c42b212803bd31d04330979228d134ec21b64 (plain) (tree)










































                                                                                   


                                                                                                        






























                                                                                                   

                                                                   





























































































































                                                                                                                                   

                                                                       

                                       

                                                                        




                                                     


                                                                                


                                                                             

                                                               
                                                                                     

                                                                         











                                                                                     

                                                                       
                                                                                             

                                                                                 

























                                                                                                             

                                                                                                   









































































































































                                                                                                                                



                                                                           

















                                                                                                                          
                                                                                                                    






















                                                                                                         
                                                                                         


















































                                                                                                                    



                                                                                              











                                                                     
                                                                           



































































                                                                                                 

                                                                                        


































                                                                                             



                                                                                              





















                                                                                                   
                                                  








                                                      

                                                                        
                                               

                                                                                



                                       
/* See LICENSE file for copyright and license details. */
#define REQUIRE_FLEXABLE_OR_NFIELDS
#include "common.h"


#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-macros"
#endif

#define IN .input_field = 1
#define OUT .output_field = 1
#define ERR .error_field = 1
#define SYM .symbolic_field = 1
#define PARTIAL .partial_init_ok = 1
#define INPTR .input_pointer = 1
#define OUTPTR .output_pointer = 1

#define BITFIELD(N) .bitlength = (N)
#define FIELD(NAME, TYPE, ...)\
	{.name = NAME,\
	 .type = LIBSYSCALLS_TYPE_##TYPE\
	 __VA_OPT__(, __VA_ARGS__)}
#define STRUCT(PRINTER, ...) STRUCT_OR_UNION_(0, PRINTER __VA_OPT__(, __VA_ARGS__))
#define UNION(PRINTER, ...) STRUCT_OR_UNION_(1, PRINTER __VA_OPT__(, __VA_ARGS__))
#define STRUCT_OR_UNION_(IS_UNION, PRINTER, ...)\
	{.is_union = (IS_UNION),\
	 .symbol_printer = (PRINTER),\
	 .num_fields = COUNT_ARGS(__VA_ARGS__)\
	 __VA_OPT__(, .fields = {__VA_ARGS__})}

#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif


struct padding {
	unsigned short int size;
	unsigned short int after;
};


static enum libsyscalls_error
get_struct_description(enum libsyscalls_os os, enum libsyscalls_arch arch,
                       enum libsyscalls_datatype datatype, const void *data, size_t data_size,
                       struct libsyscalls_structure_description **description_out, size_t *extra_sizep);


static struct libsyscalls_structure_description *
copy_struct(const struct libsyscalls_structure_description *prototype)
{
	struct libsyscalls_structure_description *ret;
	size_t size = offsetof(struct libsyscalls_structure_description, fields);
	size += prototype->num_fields * sizeof(*prototype->fields);
	ret = malloc(size);
	if (!ret)
		return NULL;
	memcpy(ret, prototype, size);
	return ret;
}


static struct libsyscalls_datatype_description *
create_pad_description(enum libsyscalls_os os, enum libsyscalls_arch arch, unsigned short int size)
{
	struct libsyscalls_datatype_description *ret;
	ret = calloc(1, sizeof(*ret));
	if (!ret)
		return NULL;
	if (libsyscalls_get_datatype_description(os, arch, LIBSYSCALLS_TYPE_CHAR, ret))
		abort();
	ret->width_in_bits = size;
	return ret;
}


static enum libsyscalls_error
shift_fields(enum libsyscalls_os os, enum libsyscalls_arch arch,
             struct libsyscalls_structure_description *description,
             const struct padding *paddings, size_t npaddings)
{
	signed short int shift, map[description->num_fields], abs, rel;
	unsigned short int i, j, k;
	int is_struct;

	/* Adjust field references */
	for (i = 0; i < description->num_fields; i++)
		map[i] = (signed short int)i;
	shift = 0;
	i = j = 0;
	while (i < (unsigned short int)npaddings && j < description->num_fields) {
		if (j > paddings[i].after)
			shift += 1;
		map[j] += shift;
	}
	for (; j < description->num_fields; j++)
		map[j] += shift;
	for (i = 0; i < description->num_fields; i++) {
		is_struct = (description->fields[i].type & ~LIBSYSCALLS_TYPEBITSMASK) >= LIBSYSCALLS_TYPEOFFSET_STRUCTS_AND_UNIONS;

		if (is_struct) {
			abs = description->fields[i].type_description.structure->absolute_position_of_size;
			rel = description->fields[i].type_description.structure->relative_position_of_size;
		} else {
			abs = description->fields[i].type_description.nonstructure->absolute_position_of_array_size;
			rel = description->fields[i].type_description.nonstructure->relative_position_of_array_size;
		}

		if (abs >= 0) {
			if ((unsigned short int)abs > description->num_fields)
				abort();
			abs = map[abs];
		}
		if (rel != 0) {
			if (rel + (signed short int)i < 0)
				abort();
			if ((unsigned short int)(rel + (signed short int)i) > description->num_fields)
				abort();
			rel = map[rel + (signed short int)i];
			rel -= (signed short int)i;
		}

#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wconversion"
#endif
		if (is_struct) {
			description->fields[i].type_description.structure->absolute_position_of_size = abs;
			description->fields[i].type_description.structure->relative_position_of_size = rel;
		} else {
			description->fields[i].type_description.nonstructure->absolute_position_of_array_size = abs;
			description->fields[i].type_description.nonstructure->relative_position_of_array_size = rel;
		}
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
	}

	/* Insert padding fields */
	i = description->num_fields;
	j = i + (unsigned short int)shift;
	k = (unsigned short int)(npaddings - 1);
	while (i) {
		memcpy(&description->fields[--j], &description->fields[--i], sizeof(*description->fields));
		while (i <= paddings[k].after) {
			j--;
			description->size += paddings[k].size;
			memset(&description->fields[j], 0, sizeof(*description->fields));
			description->fields[j].name = NULL;
			description->fields[j].type = LIBSYSCALLS_TYPE_VOID;
			description->fields[j].unused = 1;
			description->fields[j].type_description.nonstructure = create_pad_description(os, arch, paddings[k].size);
			if (!description->fields[j].type_description.structure)
				return LIBSYSCALLS_E_NOMEM;
			if (!k--)
				return LIBSYSCALLS_E_OK;
		}
	}

	return LIBSYSCALLS_E_OK;
}


static void
free_subdescriptions(struct libsyscalls_structure_description *description)
{
	unsigned short int i;
	for (i = 0; i < description->num_fields; i++) {
		if ((description->fields[i].type & ~LIBSYSCALLS_TYPEBITSMASK) >= LIBSYSCALLS_TYPEOFFSET_STRUCTS_AND_UNIONS)
			free_subdescriptions(description->fields[i].type_description.structure);
		free(description->fields[i].type_description.structure);
	}
}


static void
move_in_subdescriptions(char *buffer, size_t *offsetp, struct libsyscalls_structure_description *description,
                        int include_nonstructures, int include_structures)
{
	unsigned short int i;
	size_t size;
	for (i = 0; i < description->num_fields; i++) {
		if ((description->fields[i].type & ~LIBSYSCALLS_TYPEBITSMASK) >= LIBSYSCALLS_TYPEOFFSET_STRUCTS_AND_UNIONS) {
			move_in_subdescriptions(buffer, offsetp, description->fields[i].type_description.structure,
			                        include_nonstructures, include_structures);
			if (!include_structures)
				continue;
			size = (size_t)description->fields[i].type_description.structure->num_fields;
			size *= sizeof(*description->fields[i].type_description.structure->fields);
			size += offsetof(struct libsyscalls_structure_description, fields);
		} else {
			if (!include_nonstructures)
				continue;
			size = sizeof(struct libsyscalls_datatype_description);
		}
		memcpy(&buffer[*offsetp], description->fields[i].type_description.structure, size);
		free(description->fields[i].type_description.structure);
		description->fields[i].type_description.structure = (void *)&buffer[*offsetp];
		*offsetp += size;
	}
}


static unsigned long long int
read_field(const void *data, size_t data_size, const size_t *field_offsets,
           const struct libsyscalls_structure_description *description,
           unsigned short int i, int *undetermined_out)
{
	size_t offset, width, bits = 0;
	enum libsyscalls_datatype type;
	const struct libsyscalls_datatype_description *type_description;
	unsigned splits, expected_splits, end;
	unsigned long long int result = 0, subresult;
	signed long long int sresult;
	int is_negative;

	type = description->fields[i].type;
	type_description = description->fields[i].type_description.nonstructure;

	if ((type & LIBSYSCALLS_TYPEBITSMASK) != LIBSYSCALLS_TYPEBITS_SCALAR)
		abort();
	type ^= LIBSYSCALLS_TYPEBITS_SCALAR;
	if (type >= LIBSYSCALLS_TYPEOFFSET_SPLIT_PRIMITIVES &&
	    type < LIBSYSCALLS_TYPEOFFSET_COMPOSITE_PRIMITIVES)
		splits = LIBSYSCALLS_GET_SECTION_FRACTION(type_description->section);
	else if (type >= LIBSYSCALLS_TYPEOFFSET_UNANNOTATED_NUMERICALS &&
	         type < LIBSYSCALLS_TYPEOFFSET_FIXED_ARRAYS)
		splits = 1;
	else
		abort();

	expected_splits = splits;
	end = i + splits;
	goto start;

	for (; i < end; i++) {
		if ((type & LIBSYSCALLS_TYPEBITSMASK) != LIBSYSCALLS_TYPEBITS_SCALAR)
			abort();
		type ^= LIBSYSCALLS_TYPEBITS_SCALAR;
		if (type >= LIBSYSCALLS_TYPEOFFSET_SPLIT_PRIMITIVES &&
		    type < LIBSYSCALLS_TYPEOFFSET_COMPOSITE_PRIMITIVES)
			splits = LIBSYSCALLS_GET_SECTION_FRACTION(type_description->section);
		else if (type >= LIBSYSCALLS_TYPEOFFSET_UNANNOTATED_NUMERICALS &&
		         type < LIBSYSCALLS_TYPEOFFSET_FIXED_ARRAYS)
			splits = 1;
		else
			abort();

		if (splits != expected_splits)
			abort();

	start:
		offset = field_offsets[i];
		width = (size_t)type_description->width_in_bits;
		bits += width;

		if (offset > data_size || width > data_size - offset)
			goto undetermined;

		if (libsyscalls_to_tracer_endian(&((const char *)data)[offset / CHAR_BIT], offset % CHAR_BIT,
		                                 type_description, &subresult))
			abort();

		if (libsyscalls_unsection_value(subresult, width, type_description->section, &subresult))
			abort();

		result |= subresult;
	}

	if (type_description->is_signed) {
		if (libsyscalls_parse_signed_integer(result, type_description->sign_representation,
		                                     bits, &result, &is_negative))
			abort();
		if (is_negative) {
#if LLONG_MIN == -LLONG_MAX
			if (result >> (bits - 1))
				goto undetermined;
#endif
			sresult = (signed long long int)result;
			sresult = -sresult;
		}
		result = *(unsigned long long int *)&sresult;
	}

	return result;

undetermined:
	*undetermined_out = 1;
	return 0;
}


static enum libsyscalls_error
pad_structure_into_alignment(struct padding **paddingsp, size_t *npaddingsp, size_t *paddings_sizep,
                             const struct libsyscalls_structure_description *description)
{
	unsigned short int alignment_mask, misalignment;
	void *new;

	alignment_mask = (unsigned short int)(description->alignment - 1U);
	misalignment = (unsigned short int)(description->size & alignment_mask);
	if (misalignment) {
		if (*npaddingsp == *paddings_sizep) {
			*paddings_sizep += 16;
			new = realloc(*paddingsp, *paddings_sizep * sizeof(**paddingsp));
			if (!new)
				return LIBSYSCALLS_E_NOMEM;
			*paddingsp = new;
		}
		(*paddingsp)[*npaddingsp].size = (unsigned short int)(description->alignment - misalignment);
		(*paddingsp)[*npaddingsp].after = description->num_fields;
		*npaddingsp += 1;
	}

	return LIBSYSCALLS_E_OK;
}


static enum libsyscalls_error
pad_field_into_alignment(struct padding **paddingsp, size_t *npaddingsp, size_t *paddings_sizep,
                         const struct libsyscalls_structure_description *description,
			 unsigned short int i, unsigned subalign, size_t *field_offsetp)
{
	unsigned short int misalignment, alignment_mask, padding;
	void *new;

	alignment_mask = (unsigned short int)(subalign - 1U);
	misalignment = (unsigned short int)(description->size & alignment_mask);

	if (misalignment) {
		if (i == 0)
			abort();
		padding = (unsigned short int)(subalign - (unsigned)misalignment);
		if (*npaddingsp == *paddings_sizep) {
			*paddings_sizep += 16;
			new = realloc(*paddingsp, *paddings_sizep * sizeof(**paddingsp));
			if (!new)
				return LIBSYSCALLS_E_NOMEM;
			*paddingsp = new;
		}
		*field_offsetp += (size_t)padding;
		(*paddingsp)[*npaddingsp].size = (unsigned short int)padding;
		(*paddingsp)[*npaddingsp].after = (unsigned short int)(i - 1U);
		*npaddingsp += 1;
	}

	return LIBSYSCALLS_E_OK;
}


static enum libsyscalls_error
apply_padding(enum libsyscalls_os os, enum libsyscalls_arch arch, struct padding *paddings,
              size_t npaddings, struct libsyscalls_structure_description **descriptionp)
{
	struct libsyscalls_structure_description *description = *descriptionp;
	enum libsyscalls_error r;
	size_t size;

	if (npaddings && description->alignment > 1) {
		size = offsetof(struct libsyscalls_structure_description, fields);
		size += (size_t)(description->num_fields + npaddings) * sizeof(*description->fields);
		description = realloc(description, size);
		if (!description)
			return LIBSYSCALLS_E_NOMEM;
		*descriptionp = description;

		r = shift_fields(os, arch, description, paddings, npaddings);
		if (r)
			return r;
	}

	return LIBSYSCALLS_E_OK;
}


static enum libsyscalls_error
adjust_for_nonstruct(enum libsyscalls_os os, enum libsyscalls_arch arch,
                     const struct libsyscalls_datatype_description *int_description,
                     struct libsyscalls_structure_description *description, unsigned short int i,
                     unsigned *subsizep, unsigned *subalignp, size_t *extra_sizep)
{
	enum libsyscalls_error r;

	*subsizep = int_description->width_in_bits;
	/* TODO deal with array_size, keep in mind that it can be 0 because we have (type[]) or (type *); be aware of padding */
	r = libsyscalls_get_integer_alignment(os, arch, *subsizep, subalignp);
	if (r)
		return r;

	description->fields[i].type_description.nonstructure = malloc(sizeof(*int_description));
	if (!description->fields[i].type_description.nonstructure)
		return LIBSYSCALLS_E_NOMEM;
	memcpy(description->fields[i].type_description.nonstructure, int_description, sizeof(*int_description));
	*extra_sizep += sizeof(*int_description);

	return LIBSYSCALLS_E_OK;
}


static enum libsyscalls_error
adjust_for_struct(enum libsyscalls_os os, enum libsyscalls_arch arch, const void *data, size_t data_size,
                  struct libsyscalls_structure_description *description, unsigned short int i, size_t *field_offsets,
                  unsigned *subsizep, unsigned *subalignp, size_t *extra_sizep, int *undetermined_sizep)
{
	struct libsyscalls_structure_description *struct_description;
	size_t misalignment, subextra_size;
	unsigned long long int uvalue;
	unsigned short int field;
	enum libsyscalls_error r;

	r = get_struct_description(os, arch, description->fields[i].type,
	                           &((const char *)data)[field_offsets[i]],
	                           USAT_MINUS(data_size, field_offsets[i]),
	                           &struct_description, extra_sizep);
	if (r)
		return r;

	*subsizep = struct_description->size * struct_description->array_size;
	*subalignp = struct_description->alignment;
	if (struct_description->array_size <= 1) {
		if (struct_description->relative_position_of_size != 0) {
			if (struct_description->relative_position_of_size > 1)
				abort();
			if ((unsigned short int)-struct_description->relative_position_of_size > i)
				abort();
			field = (unsigned short int)((ssize_t)i + (ssize_t)struct_description->relative_position_of_size);
			goto set_subsize;
		} else if (struct_description->absolute_position_of_size != -1) {
			field = (unsigned short int)struct_description->absolute_position_of_size;
			if (field > i)
				abort();
		set_subsize:
			uvalue = read_field(data, data_size, field_offsets, description, field, undetermined_sizep);
			/* TODO set `*subsizep` to `uvalue` (may overflow) */
		}
		if (!struct_description->array_size)
			*subsizep *= struct_description->size;
	}
	if (!*subsizep)
		*undetermined_sizep = 1;

	description->fields[i].type_description.structure = struct_description;
	subextra_size = offsetof(struct libsyscalls_structure_description, fields);
	subextra_size += (size_t)struct_description->num_fields * sizeof(*struct_description->fields);
	misalignment = subextra_size % alignof(struct libsyscalls_structure_description);
	misalignment = alignof(struct libsyscalls_structure_description) - misalignment;
	misalignment %= alignof(struct libsyscalls_structure_description);
	subextra_size += misalignment;
	*extra_sizep += subextra_size;

	return LIBSYSCALLS_E_OK;
}


static enum libsyscalls_error
adjust_for_fields(enum libsyscalls_os os, enum libsyscalls_arch arch, const void *data, size_t data_size,
                  struct padding **paddingsp, size_t *npaddingsp, size_t *paddings_sizep,
                  struct libsyscalls_structure_description *description, int dont_align_fields, size_t *extra_sizep)
{
	size_t field_offsets[description->num_fields];
	struct libsyscalls_datatype_description int_description;
	int undetermined_size = 0;
	size_t field_offset = 0;
	unsigned short int i;
	unsigned subalign, subsize;
	enum libsyscalls_error r;

	/* TODO deal with pointer fields */
	for (i = 0; i < description->num_fields && !undetermined_size; i++) {
		field_offsets[i] = field_offset;
		r = libsyscalls_get_datatype_description(os, arch, description->fields[i].type, &int_description);
		if (r == LIBSYSCALLS_E_OK)
			r = adjust_for_nonstruct(os, arch, &int_description, description, i,
			                         &subsize, &subalign, extra_sizep);
		else if (r == LIBSYSCALLS_E_ISSTRUCT)
			r = adjust_for_struct(os, arch, data, data_size, description, i, field_offsets,
			                      &subsize, &subalign, extra_sizep, &undetermined_size);
		else
			return r;
		if (r)
			return r;

		/* TODO deal with bitlength */

		if (!dont_align_fields) {
			r = pad_field_into_alignment(paddingsp, npaddingsp, paddings_sizep,
			                             description, i, subalign, &field_offset);
			if (r)
				return r;
		}

		field_offset += subsize;
		description->size += (unsigned short int)subsize;
		if (!dont_align_fields)
			description->alignment = (unsigned short int)MAX(description->alignment, subalign);
	}

	if (undetermined_size)
		description->size = 0;

	return LIBSYSCALLS_E_OK;
}


#include "generated/structs.c"


static enum libsyscalls_error
get_struct_description(enum libsyscalls_os os, enum libsyscalls_arch arch,
                       enum libsyscalls_datatype datatype, const void *data, size_t data_size,
                       struct libsyscalls_structure_description **description_out,
                       size_t *extra_sizep)
{
	unsigned char fill_is_known;
	unsigned short int array_size;
	signed short int relative_position_of_size;
	signed short int absolute_position_of_size;
	struct libsyscalls_structure_description *description = NULL;
	enum libsyscalls_error r;
	unsigned class;
	int dont_align_fields = 0;
	size_t npaddings = 0, paddings_size = 0;
	struct padding *paddings = NULL;

	if ((unsigned)datatype & ~SET_MASK_TRAIL(LIBSYSCALLS_TYPEBITSMASK))
		return LIBSYSCALLS_E_INVAL;

	datatype ^= class = datatype & LIBSYSCALLS_TYPEBITSMASK;

	if (class == LIBSYSCALLS_TYPEBITS_SCALAR) {
		fill_is_known = 1;
		array_size = 1;
		relative_position_of_size = 0;
		absolute_position_of_size = -1;

	} else if (class == LIBSYSCALLS_TYPEBITS_ARRAY) {
		fill_is_known = 1;
		array_size = 0;
		relative_position_of_size = +1;
		absolute_position_of_size = -1;

	} else if (class == LIBSYSCALLS_TYPEBITS_ARRAY_UNKNOWN_FILL) {
		fill_is_known = 0;
		array_size = 0;
		relative_position_of_size = +1;
		absolute_position_of_size = -1;

	} else {
		return LIBSYSCALLS_E_INVAL;
	}

	if (datatype < LIBSYSCALLS_TYPEOFFSET_STRUCTS_AND_UNIONS)
		return LIBSYSCALLS_E_ISNOTSTRUCT;

#define CASE(UPPERCASE, LOWERCASE)\
	case LIBSYSCALLS_OS_##UPPERCASE:\
		r = get_##LOWERCASE##_struct_description(arch, datatype, class, data, data_size,\
		                                         &description, &dont_align_fields);\
		break

	switch ((int)os) {
	LIST_OSES(CASE, ;);
	default:
		r = LIBSYSCALLS_E_OSNOSUP;
		goto fail;
	}
	if (r)
		goto fail;

#undef CASE

#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wconversion"
#endif

	description->fill_is_known = fill_is_known;
	description->array_size = array_size;
	description->relative_position_of_size = relative_position_of_size;
	description->absolute_position_of_size = absolute_position_of_size;
	description->alignment = MAX(description->alignment, 1);

#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif

	r = adjust_for_fields(os, arch, data, data_size, &paddings, &npaddings, &paddings_size,
	                      description, dont_align_fields, extra_sizep);
	if (r)
		goto fail;

#define CASE(UPPERCASE, LOWERCASE)\
	case LIBSYSCALLS_OS_##UPPERCASE:\
		r = fix_##LOWERCASE##_struct_description(arch, datatype, class,\
		                                         data, data_size, description);\
		break

	switch ((int)os) {
	LIST_OSES(CASE, ;);
	default:
		abort();
	}
	if (r)
		goto fail;

#undef CASE

	r = pad_structure_into_alignment(&paddings, &npaddings, &paddings_size, description);
	if (r)
		goto fail;

	r = apply_padding(os, arch, paddings, npaddings, &description);
	if (r)
		goto fail;

	free(paddings);
	*description_out = description;
	return LIBSYSCALLS_E_OK;

fail:
	if (description) {
		free_subdescriptions(description);
		free(description);
	}
	free(paddings);
	return r;
}


enum libsyscalls_error
libsyscalls_get_struct_description(enum libsyscalls_os os, enum libsyscalls_arch arch,
                                   enum libsyscalls_datatype datatype,
                                   const void *data, size_t data_size,
                                   struct libsyscalls_structure_description **description_out)
{
	struct libsyscalls_structure_description *description, *new;
	size_t extra_size = 0, size, nalign, salign, align;
	enum libsyscalls_error r;

	if (!description_out)
		return LIBSYSCALLS_E_INVAL;

	r = get_struct_description(os, arch, datatype, data, data_size, &description, &extra_size);
	if (r)
		return r;
	if (!extra_size) {
		*description_out = description;
		return LIBSYSCALLS_E_OK;
	}

	nalign = alignof(struct libsyscalls_datatype_description);
	salign = alignof(struct libsyscalls_structure_description);
	align = MAX(nalign, salign);

	size = offsetof(struct libsyscalls_structure_description, fields);
	size += (size_t)description->num_fields * sizeof(*description->fields);
	size += ALIGNMENT_ADJUSTMENT(size, align);

	new = realloc(description, size + extra_size);
	if (!new) {
		free_subdescriptions(description);
		free(description);
		return LIBSYSCALLS_E_NOMEM;
	}
	description = new;

	move_in_subdescriptions((void *)description, &size, description,
	                        nalign == align, salign == align);
	if (nalign != align || salign != align)
		move_in_subdescriptions((void *)description, &size, description,
		                        nalign != align, salign != align);

	*description_out = description;
	return LIBSYSCALLS_E_OK;
}