diff --git a/evaluate.c b/evaluate.c
index b96696d..ffb4af6 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2243,6 +2243,297 @@ static struct symbol *evaluate_alignof(struct expression *expr)
 	return size_t_ctype;
 }
 
+struct format_type {
+	const char	*format;
+	int		(*test)(void *data, struct expression **expr, struct symbol *ctype, struct symbol **target, const char **typediff);
+	void		*data;
+};
+
+struct format_state {
+	struct expression	*expr;
+	unsigned int		va_start;
+	unsigned int		fmt_index;
+	unsigned int		arg_index;
+	unsigned int		used_position: 1;
+};
+
+static int printf_fmt_inttype(void *data, struct expression **expr, struct symbol *ctype, struct symbol **target, const char **typediff)
+{
+	struct symbol *type = data;
+	*target = type;
+	return ctype == type;
+}
+
+// todo - comparing pointers isn't easy...
+static int printf_fmt_string(void *data, struct expression **expr, struct symbol *ctype, struct symbol **target, const char **typediff)
+{
+	*target = &string_ctype;
+	return check_assignment_types(*target, expr, typediff);
+}
+
+static int printf_fmt_pointer(void *data, struct expression **expr, struct symbol *ctype, struct symbol **target, const char **typediff)
+{
+	*target = &ptr_ctype;
+	// todo - deal with not caring about address-space //
+	return check_assignment_types(*target, expr, typediff);
+}
+
+static int printf_fmt_print_pointer(void *data, struct expression **expr, struct symbol *ctype, struct symbol **target, const char **typediff)
+{
+	*target = &ptr_ctype;
+	return check_assignment_types(*target, expr, typediff);
+}
+
+struct format_type printf_fmts[] = {
+	{ "c",	.test = printf_fmt_inttype, &char_ctype },
+	// todo lc (wchar_t)
+	// todo h/hh (char types)
+	{ "f",	.test = printf_fmt_inttype, &double_ctype },
+	{ "g",	.test = printf_fmt_inttype, &double_ctype },
+	// todo j (intmax/unitmax)
+	{ "s",	.test = printf_fmt_string, },
+	// todo n (pointer to integer)
+	{ "p",	.test = printf_fmt_print_pointer, },
+	{ "px", .test = printf_fmt_print_pointer, },
+	// todo q (modifier?)
+	{ "d",	.test = printf_fmt_inttype, &int_ctype },
+	{ "ld", .test = printf_fmt_inttype, &long_ctype },
+	{ "lld", .test = printf_fmt_inttype, &llong_ctype },
+	{ "u",	.test = printf_fmt_inttype, &uint_ctype },
+	{ "lu", .test = printf_fmt_inttype, &ulong_ctype },
+	{ "llu", .test = printf_fmt_inttype, &ullong_ctype },
+	{ "L",	.test = printf_fmt_inttype, &ldouble_ctype },
+	{ "x",	.test = printf_fmt_inttype, &uint_ctype },
+	{ "lx", .test = printf_fmt_inttype, &ulong_ctype },
+	{ "llx", .test = printf_fmt_inttype, &ullong_ctype },
+	{ "X",	.test = printf_fmt_inttype, &int_ctype },
+	{ "lX", .test = printf_fmt_inttype, &ulong_ctype },
+	{ "llX", .test = printf_fmt_inttype, &ullong_ctype },
+	{ "L", .test = printf_fmt_inttype, &ldouble_ctype },
+	// todo z/Z sizeof ?
+	{ },
+};
+
+static struct format_type printf_fmt_ptr_ref = { "p", .test = printf_fmt_pointer, };
+
+static struct expression *get_expression_n(struct expression_list *head, int nr)
+{
+	return ptr_list_nth_entry((struct ptr_list *)head, nr);
+}
+
+static struct format_type *parse_printf_get_fmt(char *msg, char **msgout)
+{
+	struct format_type *type;
+	char *end;
+	int len;
+
+	for (end = msg; *end > ' '; end++);
+	*msgout = end+1;
+
+	len = (end - msg);
+	for (type = printf_fmts; type->format != NULL; type++)
+		if (!strncmp(msg, type->format, len))
+			return type;
+
+	return NULL;
+}
+
+static int is_printf_flag(char ch)
+{
+	return ch == '0' || ch == '+' || ch == '-' || ch == ' ' || ch == '#';
+}
+
+static int printf_check_position(char **fmt)
+{
+	char *ptr= *fmt;
+
+	while (isdigit(*ptr))
+		ptr++;
+	if (*ptr == '$') {
+		char *pos = *fmt;
+		*fmt = ptr+1;
+		return atoi(pos);
+	}
+	return -1;
+}
+
+static void parse_format_printf_checkpos(struct format_state *state, const char *which)
+{
+	if (state->used_position)
+		warning(state->expr->pos,
+			"format %d: %s: no position specified",
+			state->arg_index-1, which);
+}
+
+static int parse_format_printf_argfield(char **fmtptr, struct format_state *state, struct expression_list *head, int *pos, const char *which)
+{
+	struct expression *expr;
+	char *fmt = *fmtptr;
+	int argpos = -1;
+
+	/* check for simple digit-string width/precision specifier first */
+	if (*fmt != '*') {
+		while (isdigit(*fmt))
+			fmt++;
+		*fmtptr = fmt;
+		return 0;
+	}
+
+	fmt++;
+	argpos = printf_check_position(&fmt);
+
+	if (argpos > 0) {
+		argpos += state->va_start - 1;
+		state->used_position = 1;
+	} else {
+		argpos = (*pos)++;
+		state->arg_index++;
+		parse_format_printf_checkpos(state, which);
+	}
+
+	*fmtptr = fmt;
+	expr = get_expression_n(head, argpos-1);
+	if (!expr) {
+		warning(state->expr->pos, "%s: no argument at position %d", which, argpos);
+		return 1;
+	}
+
+	/* todo - verify we got an actual integer argument */
+	return 0;
+}
+
+/* printf format parsing code
+ *
+ * TODO:
+ * - fix up type checking of castable types (such as int vs long vs long long)
+ * - validate all arguments specified are also used...
+ * - possibly remove return code from this function
+ */
+static int parse_format_printf(const char **fmtstring, struct format_state *state, struct expression_list *head)
+{
+	char buff[strlen(*fmtstring)+1];
+	struct format_type *type;
+	struct expression *expr;
+	const char *string = *fmtstring;
+	char *fmtpost = NULL, *fmt = buff;
+	int pos = state->arg_index;
+	int error = 0;
+	int ret;
+
+	/* trivial check for %% */
+	string++;
+	if (string[0] == '%') {
+		*fmtstring = string;
+		return 0;
+	}
+
+	state->arg_index++;
+	state->fmt_index++;
+
+	strcpy(buff, string);
+	ret = printf_check_position(&fmt);
+	if (ret == 0) {
+		/* we got an invalid position argument */
+		error++;
+	} else if (ret < 0) {
+		parse_format_printf_checkpos(state, "position");
+	} else {
+		state->used_position = 1;
+		pos = ret + state->va_start - 1;
+	}
+
+	/* get rid of any formatting flag bits */
+	while (is_printf_flag(*fmt))
+		fmt++;
+
+	/* now there is the posibility of a width specifier */
+	if (parse_format_printf_argfield(&fmt, state, head, &pos, "width"))
+		error++;
+
+	/* now we might have the precision specifier */
+	if (*fmt == '.') {
+		fmt++;
+		if (parse_format_printf_argfield(&fmt, state, head, &pos, "position"))
+			error++;
+	}
+
+	type = parse_printf_get_fmt(fmt, &fmtpost);
+	*fmtstring += fmtpost - buff;
+	fmtpost[-1] = '\0';
+
+	if (!type && fmt[0] == 'p')
+		type = &printf_fmt_ptr_ref;	/* probably some extension */
+
+	if (type) {
+		struct symbol *ctype, *source, *target = NULL;
+		const char *typediff = "different types";
+		int ret;
+
+		expr = get_expression_n(head, pos-1);
+		if (!expr)
+			return -2;
+
+		ctype = evaluate_expression(expr);
+		if (!ctype)
+			return -3;
+
+		source = degenerate(expr);
+		ret = (type->test)(type->data, &expr, ctype, &target, &typediff);
+
+		if (ret == 0) {
+			warning(expr->pos, "incorrect type in argument %d (%s)", pos, typediff);
+			info(expr->pos, "   expected %s", show_typename(target));
+			info(expr->pos, "   got %s", show_typename(source));
+		}
+	} else {
+		warning(expr->pos, "cannot evaluate type '%s'", buff);
+		return -1;
+	}
+
+	return 1;
+}
+
+/* attempt to run through a printf format string and work out the types
+ * it specifies. The format is parsed from the __attribute__(format())
+ * in the parser code which stores the positions of the message and arg
+ * start in the ctype.
+ */
+static void evaluate_format_printf(struct symbol *fn, struct expression_list *head)
+{
+	struct format_state state = { };
+	struct expression *expr;
+	const char *fmt_string = NULL;
+
+	expr = get_expression_n(head, fn->ctype.printf_msg-1);
+	if (!expr)
+		return;
+	if (expr->string && expr->string->length)
+		fmt_string = expr->string->data;
+	if (!fmt_string) {
+		struct symbol *sym = evaluate_expression(expr);
+
+		/* attempt to find initialiser for this */
+		if (sym && sym->initializer && sym->initializer->string)
+			fmt_string = sym->initializer->string->data;
+	}
+
+	state.expr = expr;
+	state.va_start = fn->ctype.printf_va_start;
+	state.arg_index = fn->ctype.printf_va_start;
+
+	if (!fmt_string) {
+		warning(expr->pos, "not a format string?");
+	} else {
+		const char *string = fmt_string;
+
+		for (; string[0] != '\0'; string++) {
+			if (string[0] != '%')
+				continue;
+			parse_format_printf(&string, &state, head);
+		}
+	}
+}
+
 static int evaluate_arguments(struct symbol *fn, struct expression_list *head)
 {
 	struct expression *expr;
@@ -2250,6 +2541,12 @@ static int evaluate_arguments(struct symbol *fn, struct expression_list *head)
 	struct symbol *argtype;
 	int i = 1;
 
+	/* do this first, otherwise the arugment info may get lost or changed
+	 * later on in the evaluation loop.
+	 */
+	if (fn->ctype.printf_va_start)
+		evaluate_format_printf(fn, head);
+
 	PREPARE_PTR_LIST(argument_types, argtype);
 	FOR_EACH_PTR (head, expr) {
 		struct expression **p = THIS_ADDRESS(expr);
