/* * This file is part of ltrace. * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2007,2008 Juan Cespedes * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include <assert.h> #include <stdlib.h> #include <limits.h> #include "type.h" #include "sysdep.h" #include "expr.h" #include "lens.h" struct arg_type_info * type_get_simple(enum arg_type type) { #define HANDLE(T) { \ static struct arg_type_info t = { T }; \ case T: \ return &t; \ } switch (type) { HANDLE(ARGTYPE_VOID) HANDLE(ARGTYPE_INT) HANDLE(ARGTYPE_UINT) HANDLE(ARGTYPE_LONG) HANDLE(ARGTYPE_ULONG) HANDLE(ARGTYPE_CHAR) HANDLE(ARGTYPE_SHORT) HANDLE(ARGTYPE_USHORT) HANDLE(ARGTYPE_FLOAT) HANDLE(ARGTYPE_DOUBLE) #undef HANDLE case ARGTYPE_ARRAY: case ARGTYPE_STRUCT: case ARGTYPE_POINTER: assert(!"Not a simple type!"); }; abort(); } struct arg_type_info * type_get_voidptr(void) { struct arg_type_info *void_info = type_get_simple(ARGTYPE_VOID); static struct arg_type_info *ret; if (ret == NULL) { static struct arg_type_info ptr_info; type_init_pointer(&ptr_info, void_info, 0); ret = &ptr_info; } return ret; } static void type_init_common(struct arg_type_info *info, enum arg_type type) { info->type = type; info->lens = NULL; info->own_lens = 0; } struct struct_field { struct arg_type_info *info; int own_info; }; void type_init_struct(struct arg_type_info *info) { type_init_common(info, ARGTYPE_STRUCT); VECT_INIT(&info->u.entries, struct struct_field); } int type_struct_add(struct arg_type_info *info, struct arg_type_info *field_info, int own) { assert(info->type == ARGTYPE_STRUCT); struct struct_field field = { field_info, own }; return VECT_PUSHBACK(&info->u.entries, &field); } struct arg_type_info * type_struct_get(struct arg_type_info *info, size_t idx) { assert(info->type == ARGTYPE_STRUCT); return VECT_ELEMENT(&info->u.entries, struct struct_field, idx)->info; } size_t type_struct_size(struct arg_type_info *info) { assert(info->type == ARGTYPE_STRUCT); return vect_size(&info->u.entries); } static void struct_field_dtor(struct struct_field *field, void *data) { if (field->own_info) { type_destroy(field->info); free(field->info); } } static void type_struct_destroy(struct arg_type_info *info) { VECT_DESTROY(&info->u.entries, struct struct_field, struct_field_dtor, NULL); } static int layout_struct(struct process *proc, struct arg_type_info *info, size_t *sizep, size_t *alignmentp, size_t *offsetofp) { size_t sz = 0; size_t max_alignment = 0; size_t i; size_t offsetof_field = (size_t)-1; if (offsetofp != NULL) offsetof_field = *offsetofp; assert(info->type == ARGTYPE_STRUCT); for (i = 0; i < vect_size(&info->u.entries); ++i) { struct struct_field *field = VECT_ELEMENT(&info->u.entries, struct struct_field, i); size_t alignment = type_alignof(proc, field->info); if (alignment == (size_t)-1) return -1; /* Add padding to SZ to align the next element. */ sz = align(sz, alignment); if (i == offsetof_field) { *offsetofp = sz; if (sizep == NULL && alignmentp == NULL) return 0; } size_t size = type_sizeof(proc, field->info); if (size == (size_t)-1) return -1; sz += size; if (alignment > max_alignment) max_alignment = alignment; } if (max_alignment > 0) sz = align(sz, max_alignment); if (sizep != NULL) *sizep = sz; if (alignmentp != NULL) *alignmentp = max_alignment; return 0; } void type_init_array(struct arg_type_info *info, struct arg_type_info *element_info, int own_info, struct expr_node *length_expr, int own_length) { type_init_common(info, ARGTYPE_ARRAY); info->u.array_info.elt_type = element_info; info->u.array_info.own_info = own_info; info->u.array_info.length = length_expr; info->u.array_info.own_length = own_length; } static void type_array_destroy(struct arg_type_info *info) { if (info->u.array_info.own_info) { type_destroy(info->u.array_info.elt_type); free(info->u.array_info.elt_type); } if (info->u.array_info.own_length) { expr_destroy(info->u.array_info.length); free(info->u.array_info.length); } } void type_init_pointer(struct arg_type_info *info, struct arg_type_info *pointee_info, int own_info) { type_init_common(info, ARGTYPE_POINTER); info->u.ptr_info.info = pointee_info; info->u.ptr_info.own_info = own_info; } static void type_pointer_destroy(struct arg_type_info *info) { if (info->u.ptr_info.own_info) { type_destroy(info->u.ptr_info.info); free(info->u.ptr_info.info); } } void type_destroy(struct arg_type_info *info) { if (info == NULL) return; switch (info->type) { case ARGTYPE_STRUCT: type_struct_destroy(info); break; case ARGTYPE_ARRAY: type_array_destroy(info); break; case ARGTYPE_POINTER: type_pointer_destroy(info); break; case ARGTYPE_VOID: case ARGTYPE_INT: case ARGTYPE_UINT: case ARGTYPE_LONG: case ARGTYPE_ULONG: case ARGTYPE_CHAR: case ARGTYPE_SHORT: case ARGTYPE_USHORT: case ARGTYPE_FLOAT: case ARGTYPE_DOUBLE: break; } if (info->own_lens) { lens_destroy(info->lens); free(info->lens); } } static int type_alloc_and_clone(struct arg_type_info **retpp, struct arg_type_info *info, int own) { *retpp = info; if (own) { *retpp = malloc(sizeof **retpp); if (*retpp == NULL || type_clone(*retpp, info) < 0) { free(*retpp); return -1; } } return 0; } static enum callback_status clone_struct_add_field(const struct struct_field *field, void *data) { struct arg_type_info *retp = data; struct arg_type_info *info; if (type_alloc_and_clone(&info, field->info, field->own_info) < 0) { fail: if (info != field->info) free(info); return CBS_STOP; } if (type_struct_add(retp, info, field->own_info) < 0) { if (field->own_info) type_destroy(info); goto fail; } return CBS_CONT; } int type_clone(struct arg_type_info *retp, const struct arg_type_info *info) { switch (info->type) { case ARGTYPE_STRUCT: type_init_struct(retp); if (VECT_EACH_CST(&info->u.entries, struct struct_field, NULL, clone_struct_add_field, retp) != NULL) { type_destroy(retp); return -1; } break; case ARGTYPE_ARRAY:; struct arg_type_info *elt_type; if (type_alloc_and_clone(&elt_type, info->u.array_info.elt_type, info->u.array_info.own_info) < 0) return -1; assert(!info->u.array_info.own_length); // XXXXXXX type_init_array(retp, elt_type, info->u.array_info.own_info, info->u.array_info.length, info->u.array_info.own_length); break; case ARGTYPE_POINTER:; struct arg_type_info *ninfo; if (type_alloc_and_clone(&ninfo, info->u.ptr_info.info, info->u.ptr_info.own_info) < 0) return -1; type_init_pointer(retp, ninfo, info->u.ptr_info.own_info); break; case ARGTYPE_VOID: case ARGTYPE_INT: case ARGTYPE_UINT: case ARGTYPE_LONG: case ARGTYPE_ULONG: case ARGTYPE_CHAR: case ARGTYPE_SHORT: case ARGTYPE_USHORT: case ARGTYPE_FLOAT: case ARGTYPE_DOUBLE: *retp = *info; break; } assert(!info->own_lens); retp->lens = info->lens; retp->own_lens = info->own_lens; return 0; } #ifdef ARCH_HAVE_SIZEOF size_t arch_type_sizeof(struct process *proc, struct arg_type_info *arg); #else size_t arch_type_sizeof(struct process *proc, struct arg_type_info *arg) { /* Use default value. */ return (size_t)-2; } #endif #ifdef ARCH_HAVE_ALIGNOF size_t arch_type_alignof(struct process *proc, struct arg_type_info *arg); #else size_t arch_type_alignof(struct process *proc, struct arg_type_info *arg) { /* Use default value. */ return (size_t)-2; } #endif /* We need to support alignments that are not power of two. E.g. long * double on x86 has alignment of 12. */ size_t align(size_t sz, size_t alignment) { assert(alignment != 0); if ((sz % alignment) != 0) sz = ((sz / alignment) + 1) * alignment; return sz; } size_t type_sizeof(struct process *proc, struct arg_type_info *type) { size_t arch_size = arch_type_sizeof(proc, type); if (arch_size != (size_t)-2) return arch_size; switch (type->type) { size_t size; case ARGTYPE_CHAR: return sizeof(char); case ARGTYPE_SHORT: case ARGTYPE_USHORT: return sizeof(short); case ARGTYPE_INT: case ARGTYPE_UINT: return sizeof(int); case ARGTYPE_LONG: case ARGTYPE_ULONG: return sizeof(long); case ARGTYPE_FLOAT: return sizeof(float); case ARGTYPE_DOUBLE: return sizeof(double); case ARGTYPE_STRUCT: if (layout_struct(proc, type, &size, NULL, NULL) < 0) return (size_t)-1; return size; case ARGTYPE_POINTER: return sizeof(void *); case ARGTYPE_ARRAY: if (expr_is_compile_constant(type->u.array_info.length)) { long l; if (expr_eval_constant(type->u.array_info.length, &l) < 0) return -1; struct arg_type_info *elt_ti = type->u.array_info.elt_type; size_t elt_size = type_sizeof(proc, elt_ti); if (elt_size == (size_t)-1) return (size_t)-1; return ((size_t)l) * elt_size; } else { /* Flexible arrays don't count into the * sizeof. */ return 0; } case ARGTYPE_VOID: return 0; } abort(); } #undef alignof #define alignof(field,st) ((size_t) ((char*) &st.field - (char*) &st)) size_t type_alignof(struct process *proc, struct arg_type_info *type) { size_t arch_alignment = arch_type_alignof(proc, type); if (arch_alignment != (size_t)-2) return arch_alignment; struct { char c; char C; } cC; struct { char c; short s; } cs; struct { char c; int i; } ci; struct { char c; long l; } cl; struct { char c; void* p; } cp; struct { char c; float f; } cf; struct { char c; double d; } cd; static size_t char_alignment = alignof(C, cC); static size_t short_alignment = alignof(s, cs); static size_t int_alignment = alignof(i, ci); static size_t long_alignment = alignof(l, cl); static size_t ptr_alignment = alignof(p, cp); static size_t float_alignment = alignof(f, cf); static size_t double_alignment = alignof(d, cd); switch (type->type) { size_t alignment; case ARGTYPE_LONG: case ARGTYPE_ULONG: return long_alignment; case ARGTYPE_CHAR: return char_alignment; case ARGTYPE_SHORT: case ARGTYPE_USHORT: return short_alignment; case ARGTYPE_FLOAT: return float_alignment; case ARGTYPE_DOUBLE: return double_alignment; case ARGTYPE_POINTER: return ptr_alignment; case ARGTYPE_ARRAY: return type_alignof(proc, type->u.array_info.elt_type); case ARGTYPE_STRUCT: if (layout_struct(proc, type, NULL, &alignment, NULL) < 0) return (size_t)-1; return alignment; default: return int_alignment; } } size_t type_offsetof(struct process *proc, struct arg_type_info *type, size_t emt) { assert(type->type == ARGTYPE_STRUCT || type->type == ARGTYPE_ARRAY); switch (type->type) { size_t alignment; size_t size; case ARGTYPE_ARRAY: alignment = type_alignof(proc, type->u.array_info.elt_type); if (alignment == (size_t)-1) return (size_t)-1; size = type_sizeof(proc, type->u.array_info.elt_type); if (size == (size_t)-1) return (size_t)-1; return emt * align(size, alignment); case ARGTYPE_STRUCT: if (layout_struct(proc, type, NULL, NULL, &emt) < 0) return (size_t)-1; return emt; default: abort(); } } struct arg_type_info * type_element(struct arg_type_info *info, size_t emt) { assert(info->type == ARGTYPE_STRUCT || info->type == ARGTYPE_ARRAY); switch (info->type) { case ARGTYPE_ARRAY: return info->u.array_info.elt_type; case ARGTYPE_STRUCT: assert(emt < type_struct_size(info)); return type_struct_get(info, emt); default: abort(); } } size_t type_aggregate_size(struct arg_type_info *info) { assert(info->type == ARGTYPE_STRUCT || info->type == ARGTYPE_ARRAY); switch (info->type) { long ret; case ARGTYPE_ARRAY: if (expr_eval_constant(info->u.array_info.length, &ret) < 0) return (size_t)-1; return (size_t)ret; case ARGTYPE_STRUCT: return type_struct_size(info); default: abort(); } } int type_is_integral(enum arg_type type) { switch (type) { case ARGTYPE_INT: case ARGTYPE_UINT: case ARGTYPE_LONG: case ARGTYPE_ULONG: case ARGTYPE_CHAR: case ARGTYPE_SHORT: case ARGTYPE_USHORT: return 1; case ARGTYPE_VOID: case ARGTYPE_FLOAT: case ARGTYPE_DOUBLE: case ARGTYPE_ARRAY: case ARGTYPE_STRUCT: case ARGTYPE_POINTER: return 0; } abort(); } int type_is_signed(enum arg_type type) { assert(type_is_integral(type)); switch (type) { case ARGTYPE_CHAR: return CHAR_MIN != 0; case ARGTYPE_SHORT: case ARGTYPE_INT: case ARGTYPE_LONG: return 1; case ARGTYPE_UINT: case ARGTYPE_ULONG: case ARGTYPE_USHORT: return 0; case ARGTYPE_VOID: case ARGTYPE_FLOAT: case ARGTYPE_DOUBLE: case ARGTYPE_ARRAY: case ARGTYPE_STRUCT: case ARGTYPE_POINTER: abort(); } abort(); } struct arg_type_info * type_get_fp_equivalent(struct arg_type_info *info) { /* Extract innermost structure. Give up early if any * component has more than one element. */ while (info->type == ARGTYPE_STRUCT) { if (type_struct_size(info) != 1) return NULL; info = type_element(info, 0); } switch (info->type) { case ARGTYPE_CHAR: case ARGTYPE_SHORT: case ARGTYPE_INT: case ARGTYPE_LONG: case ARGTYPE_UINT: case ARGTYPE_ULONG: case ARGTYPE_USHORT: case ARGTYPE_VOID: case ARGTYPE_ARRAY: case ARGTYPE_POINTER: return NULL; case ARGTYPE_FLOAT: case ARGTYPE_DOUBLE: return info; case ARGTYPE_STRUCT: abort(); } abort(); } struct arg_type_info * type_get_hfa_type(struct arg_type_info *info, size_t *countp) { assert(info != NULL); if (info->type != ARGTYPE_STRUCT && info->type != ARGTYPE_ARRAY) return NULL; size_t n = type_aggregate_size(info); if (n == (size_t)-1) return NULL; struct arg_type_info *ret = NULL; *countp = 0; while (n-- > 0) { struct arg_type_info *emt = type_element(info, n); size_t emt_count = 1; if (emt->type == ARGTYPE_STRUCT || emt->type == ARGTYPE_ARRAY) emt = type_get_hfa_type(emt, &emt_count); if (emt == NULL) return NULL; if (ret == NULL) { if (emt->type != ARGTYPE_FLOAT && emt->type != ARGTYPE_DOUBLE) return NULL; ret = emt; } if (emt->type != ret->type) return NULL; *countp += emt_count; } return ret; }