blob: b4b1b56573df73fa8afe6c9b896e9f6d71e1a6ab [file] [log] [blame]
#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "common.h"
static int line_no;
static char *filename;
static int error_count = 0;
static arg_type_info *parse_type(char **str);
Function *list_of_functions = NULL;
/* Map of strings to type names. These do not need to be in any
* particular order */
static struct list_of_pt_t {
char *name;
enum arg_type pt;
} list_of_pt[] = {
{
"void", ARGTYPE_VOID}, {
"int", ARGTYPE_INT}, {
"uint", ARGTYPE_UINT}, {
"long", ARGTYPE_LONG}, {
"ulong", ARGTYPE_ULONG}, {
"octal", ARGTYPE_OCTAL}, {
"char", ARGTYPE_CHAR}, {
"short", ARGTYPE_SHORT}, {
"ushort", ARGTYPE_USHORT}, {
"float", ARGTYPE_FLOAT}, {
"double", ARGTYPE_DOUBLE}, {
"addr", ARGTYPE_ADDR}, {
"file", ARGTYPE_FILE}, {
"format", ARGTYPE_FORMAT}, {
"string", ARGTYPE_STRING}, {
"array", ARGTYPE_ARRAY}, {
"struct", ARGTYPE_STRUCT}, {
"enum", ARGTYPE_ENUM}, {
NULL, ARGTYPE_UNKNOWN} /* Must finish with NULL */
};
/* Array of prototype objects for each of the types. The order in this
* array must exactly match the list of enumerated values in
* common.h */
static arg_type_info arg_type_prototypes[] = {
{ ARGTYPE_VOID },
{ ARGTYPE_INT },
{ ARGTYPE_UINT },
{ ARGTYPE_LONG },
{ ARGTYPE_ULONG },
{ ARGTYPE_OCTAL },
{ ARGTYPE_CHAR },
{ ARGTYPE_SHORT },
{ ARGTYPE_USHORT },
{ ARGTYPE_FLOAT },
{ ARGTYPE_DOUBLE },
{ ARGTYPE_ADDR },
{ ARGTYPE_FILE },
{ ARGTYPE_FORMAT },
{ ARGTYPE_STRING },
{ ARGTYPE_STRING_N },
{ ARGTYPE_ARRAY },
{ ARGTYPE_ENUM },
{ ARGTYPE_STRUCT },
{ ARGTYPE_POINTER },
{ ARGTYPE_UNKNOWN }
};
arg_type_info *
lookup_prototype(enum arg_type at) {
if (at >= 0 && at <= ARGTYPE_COUNT)
return &arg_type_prototypes[at];
else
return &arg_type_prototypes[ARGTYPE_COUNT]; /* UNKNOWN */
}
static arg_type_info *
str2type(char **str) {
struct list_of_pt_t *tmp = &list_of_pt[0];
while (tmp->name) {
if (!strncmp(*str, tmp->name, strlen(tmp->name))
&& index(" ,()#*;012345[", *(*str + strlen(tmp->name)))) {
*str += strlen(tmp->name);
return lookup_prototype(tmp->pt);
}
tmp++;
}
return lookup_prototype(ARGTYPE_UNKNOWN);
}
static void
eat_spaces(char **str) {
while (**str == ' ') {
(*str)++;
}
}
static char *
xstrndup(char *str, size_t len) {
char *ret = (char *) malloc(len + 1);
strncpy(ret, str, len);
ret[len] = 0;
return ret;
}
static char *
parse_ident(char **str) {
char *ident = *str;
if (!isalnum(**str) && **str != '_') {
output_line(0, "Syntax error in `%s', line %d: Bad identifier",
filename, line_no);
error_count++;
return NULL;
}
while (**str && (isalnum(**str) || **str == '_')) {
++(*str);
}
return xstrndup(ident, *str - ident);
}
/*
Returns position in string at the left parenthesis which starts the
function's argument signature. Returns NULL on error.
*/
static char *
start_of_arg_sig(char *str) {
char *pos;
int stacked = 0;
if (!strlen(str))
return NULL;
pos = &str[strlen(str)];
do {
pos--;
if (pos < str)
return NULL;
while ((pos > str) && (*pos != ')') && (*pos != '('))
pos--;
if (*pos == ')')
stacked++;
else if (*pos == '(')
stacked--;
else
return NULL;
} while (stacked > 0);
return (stacked == 0) ? pos : NULL;
}
static int
parse_int(char **str) {
char *end;
long n = strtol(*str, &end, 0);
if (end == *str) {
output_line(0, "Syntax error in `%s', line %d: Bad number (%s)",
filename, line_no, *str);
error_count++;
return 0;
}
*str = end;
return n;
}
/*
* Input:
* argN : The value of argument #N, counting from 1 (arg0 = retval)
* eltN : The value of element #N of the containing structure
* retval : The return value
* 0 : Error
* N : The numeric value N, if N > 0
*
* Output:
* > 0 actual numeric value
* = 0 return value
* < 0 (arg -n), counting from one
*/
static int
parse_argnum(char **str) {
int multiplier = 1;
int n = 0;
if (strncmp(*str, "arg", 3) == 0) {
(*str) += 3;
multiplier = -1;
} else if (strncmp(*str, "elt", 3) == 0) {
(*str) += 3;
multiplier = -1;
} else if (strncmp(*str, "retval", 6) == 0) {
(*str) += 6;
return 0;
}
n = parse_int(str);
return n * multiplier;
}
struct typedef_node_t {
char *name;
arg_type_info *info;
struct typedef_node_t *next;
} *typedefs = NULL;
static arg_type_info *
lookup_typedef(char **str) {
struct typedef_node_t *node;
char *end = *str;
while (*end && (isalnum(*end) || *end == '_'))
++end;
if (end == *str)
return NULL;
for (node = typedefs; node != NULL; node = node->next) {
if (strncmp(*str, node->name, end - *str) == 0) {
(*str) += strlen(node->name);
return node->info;
}
}
return NULL;
}
static void
parse_typedef(char **str) {
char *name;
arg_type_info *info;
struct typedef_node_t *binding;
(*str) += strlen("typedef");
eat_spaces(str);
// Grab out the name of the type
name = parse_ident(str);
// Skip = sign
eat_spaces(str);
if (**str != '=') {
output_line(0,
"Syntax error in `%s', line %d: expected '=', got '%c'",
filename, line_no, **str);
error_count++;
return;
}
(*str)++;
eat_spaces(str);
// Parse the type
info = parse_type(str);
// Insert onto beginning of linked list
binding = malloc(sizeof(*binding));
binding->name = name;
binding->info = info;
binding->next = typedefs;
typedefs = binding;
}
static size_t
arg_sizeof(arg_type_info * arg) {
if (arg->type == ARGTYPE_CHAR) {
return sizeof(char);
} else if (arg->type == ARGTYPE_SHORT || arg->type == ARGTYPE_USHORT) {
return sizeof(short);
} else if (arg->type == ARGTYPE_FLOAT) {
return sizeof(float);
} else if (arg->type == ARGTYPE_DOUBLE) {
return sizeof(double);
} else if (arg->type == ARGTYPE_ENUM) {
return sizeof(int);
} else if (arg->type == ARGTYPE_STRUCT) {
return arg->u.struct_info.size;
} else if (arg->type == ARGTYPE_POINTER) {
return sizeof(void*);
} else if (arg->type == ARGTYPE_ARRAY) {
if (arg->u.array_info.len_spec > 0)
return arg->u.array_info.len_spec * arg->u.array_info.elt_size;
else
return sizeof(void *);
} else {
return sizeof(int);
}
}
#undef alignof
#define alignof(field,st) ((size_t) ((char*) &st.field - (char*) &st))
static size_t
arg_align(arg_type_info * arg) {
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 (arg->type) {
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_ADDR:
case ARGTYPE_FILE:
case ARGTYPE_FORMAT:
case ARGTYPE_STRING:
case ARGTYPE_STRING_N:
case ARGTYPE_POINTER:
return ptr_alignment;
case ARGTYPE_ARRAY:
return arg_align(&arg->u.array_info.elt_type[0]);
case ARGTYPE_STRUCT:
return arg_align(arg->u.struct_info.fields[0]);
default:
return int_alignment;
}
}
static size_t
align_skip(size_t alignment, size_t offset) {
if (offset % alignment)
return alignment - (offset % alignment);
else
return 0;
}
/* I'm sure this isn't completely correct, but just try to get most of
* them right for now. */
static void
align_struct(arg_type_info* info) {
size_t offset;
int i;
if (info->u.struct_info.size != 0)
return; // Already done
// Compute internal padding due to alignment constraints for
// various types.
offset = 0;
for (i = 0; info->u.struct_info.fields[i] != NULL; i++) {
arg_type_info *field = info->u.struct_info.fields[i];
offset += align_skip(arg_align(field), offset);
info->u.struct_info.offset[i] = offset;
offset += arg_sizeof(field);
}
info->u.struct_info.size = offset;
}
static arg_type_info *
parse_nonpointer_type(char **str) {
arg_type_info *simple;
arg_type_info *info;
if (strncmp(*str, "typedef", 7) == 0) {
parse_typedef(str);
return lookup_prototype(ARGTYPE_UNKNOWN);
}
simple = str2type(str);
if (simple->type == ARGTYPE_UNKNOWN) {
info = lookup_typedef(str);
if (info)
return info;
else
return simple; // UNKNOWN
}
info = malloc(sizeof(*info));
info->type = simple->type;
/* Code to parse parameterized types will go into the following
switch statement. */
switch (info->type) {
/* Syntax: array ( type, N|argN ) */
case ARGTYPE_ARRAY:
(*str)++; // Get past open paren
eat_spaces(str);
if ((info->u.array_info.elt_type = parse_type(str)) == NULL)
return NULL;
info->u.array_info.elt_size =
arg_sizeof(info->u.array_info.elt_type);
(*str)++; // Get past comma
eat_spaces(str);
info->u.array_info.len_spec = parse_argnum(str);
(*str)++; // Get past close paren
return info;
/* Syntax: enum ( keyname=value,keyname=value,... ) */
case ARGTYPE_ENUM:{
struct enum_opt {
char *key;
int value;
struct enum_opt *next;
};
struct enum_opt *list = NULL;
struct enum_opt *p;
int entries = 0;
int ii;
eat_spaces(str);
(*str)++; // Get past open paren
eat_spaces(str);
while (**str && **str != ')') {
p = (struct enum_opt *) malloc(sizeof(*p));
eat_spaces(str);
p->key = parse_ident(str);
if (error_count) {
free(p);
return NULL;
}
eat_spaces(str);
if (**str != '=') {
free(p->key);
free(p);
output_line(0,
"Syntax error in `%s', line %d: expected '=', got '%c'",
filename, line_no, **str);
error_count++;
return NULL;
}
++(*str);
eat_spaces(str);
p->value = parse_int(str);
p->next = list;
list = p;
++entries;
// Skip comma
eat_spaces(str);
if (**str == ',') {
(*str)++;
eat_spaces(str);
}
}
info->u.enum_info.entries = entries;
info->u.enum_info.keys =
(char **) malloc(entries * sizeof(char *));
info->u.enum_info.values =
(int *) malloc(entries * sizeof(int));
for (ii = 0, p = NULL; list; ++ii, list = list->next) {
if (p)
free(p);
info->u.enum_info.keys[ii] = list->key;
info->u.enum_info.values[ii] = list->value;
p = list;
}
if (p)
free(p);
return info;
}
case ARGTYPE_STRING:
if (!isdigit(**str) && **str != '[') {
/* Oops, was just a simple string after all */
free(info);
return simple;
}
info->type = ARGTYPE_STRING_N;
/* Backwards compatibility for string0, string1, ... */
if (isdigit(**str)) {
info->u.string_n_info.size_spec = -parse_int(str);
return info;
}
(*str)++; // Skip past opening [
eat_spaces(str);
info->u.string_n_info.size_spec = parse_argnum(str);
eat_spaces(str);
(*str)++; // Skip past closing ]
return info;
// Syntax: struct ( type,type,type,... )
case ARGTYPE_STRUCT:{
int field_num = 0;
(*str)++; // Get past open paren
info->u.struct_info.fields =
malloc((MAX_ARGS + 1) * sizeof(void *));
info->u.struct_info.offset =
malloc((MAX_ARGS + 1) * sizeof(size_t));
info->u.struct_info.size = 0;
eat_spaces(str); // Empty arg list with whitespace inside
while (**str && **str != ')') {
if (field_num == MAX_ARGS) {
output_line(0,
"Error in `%s', line %d: Too many structure elements",
filename, line_no);
error_count++;
return NULL;
}
eat_spaces(str);
if (field_num != 0) {
(*str)++; // Get past comma
eat_spaces(str);
}
if ((info->u.struct_info.fields[field_num++] =
parse_type(str)) == NULL)
return NULL;
// Must trim trailing spaces so the check for
// the closing paren is simple
eat_spaces(str);
}
(*str)++; // Get past closing paren
info->u.struct_info.fields[field_num] = NULL;
align_struct(info);
return info;
}
default:
if (info->type == ARGTYPE_UNKNOWN) {
output_line(0, "Syntax error in `%s', line %d: "
"Unknown type encountered",
filename, line_no);
free(info);
error_count++;
return NULL;
} else {
return info;
}
}
}
static arg_type_info *
parse_type(char **str) {
arg_type_info *info = parse_nonpointer_type(str);
while (**str == '*') {
arg_type_info *outer = malloc(sizeof(*info));
outer->type = ARGTYPE_POINTER;
outer->u.ptr_info.info = info;
(*str)++;
info = outer;
}
return info;
}
static Function *
process_line(char *buf) {
Function fun;
Function *fun_p;
char *str = buf;
char *tmp;
int i;
int float_num = 0;
line_no++;
debug(3, "Reading line %d of `%s'", line_no, filename);
eat_spaces(&str);
fun.return_info = parse_type(&str);
if (fun.return_info == NULL)
return NULL;
if (fun.return_info->type == ARGTYPE_UNKNOWN) {
debug(3, " Skipping line %d", line_no);
return NULL;
}
debug(4, " return_type = %d", fun.return_info->type);
eat_spaces(&str);
tmp = start_of_arg_sig(str);
if (!tmp) {
output_line(0, "Syntax error in `%s', line %d", filename,
line_no);
error_count++;
return NULL;
}
*tmp = '\0';
fun.name = strdup(str);
str = tmp + 1;
debug(3, " name = %s", fun.name);
fun.params_right = 0;
for (i = 0; i < MAX_ARGS; i++) {
eat_spaces(&str);
if (*str == ')') {
break;
}
if (str[0] == '+') {
fun.params_right++;
str++;
} else if (fun.params_right) {
fun.params_right++;
}
fun.arg_info[i] = parse_type(&str);
if (fun.arg_info[i] == NULL) {
output_line(0, "Syntax error in `%s', line %d"
": unknown argument type",
filename, line_no);
error_count++;
return NULL;
}
if (fun.arg_info[i]->type == ARGTYPE_FLOAT)
fun.arg_info[i]->u.float_info.float_index = float_num++;
else if (fun.arg_info[i]->type == ARGTYPE_DOUBLE)
fun.arg_info[i]->u.double_info.float_index = float_num++;
eat_spaces(&str);
if (*str == ',') {
str++;
continue;
} else if (*str == ')') {
continue;
} else {
if (str[strlen(str) - 1] == '\n')
str[strlen(str) - 1] = '\0';
output_line(0, "Syntax error in `%s', line %d at ...\"%s\"",
filename, line_no, str);
error_count++;
return NULL;
}
}
fun.num_params = i;
fun_p = malloc(sizeof(Function));
if (!fun_p) {
perror("ltrace: malloc");
exit(1);
}
memcpy(fun_p, &fun, sizeof(Function));
return fun_p;
}
void
read_config_file(char *file) {
FILE *stream;
char buf[1024];
filename = file;
stream = fopen(filename, "r");
if (!stream) {
return;
}
debug(1, "Reading config file `%s'...", filename);
line_no = 0;
while (fgets(buf, 1024, stream)) {
Function *tmp;
error_count = 0;
tmp = process_line(buf);
if (tmp) {
debug(2, "New function: `%s'", tmp->name);
tmp->next = list_of_functions;
list_of_functions = tmp;
}
}
fclose(stream);
}