/* link.c qc object file linking Copyright (C) 2002 Bill Currie Author: Bill Currie Date: 2002/7/3 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif static __attribute__ ((unused)) const char rcsid[] = "$Id$"; #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_IO_H # include #endif #include #include #include #include #include "QF/dstring.h" #include "QF/hash.h" #include "QF/pakfile.h" #include "QF/va.h" #include "def.h" #include "emit.h" #include "expr.h" #include "immediate.h" #include "linker.h" #include "obj_file.h" #include "options.h" #include "qfcc.h" #include "reloc.h" #include "strpool.h" #include "type.h" #define Xgroup(X)\ typedef struct {\ qfo_##X##_t *X##s;\ int num_##X##s;\ int max_##X##s;\ } X##group_t; Xgroup(def) // defgroup_t Xgroup(reloc) // relocgroup_t Xgroup(func) // funcgroup_t typedef struct path_s { struct path_s *next; const char *path; } path_t; typedef union defref_s { int def; union defref_s *next; } defref_t; typedef struct builtin_sym_s { const char *name; type_t *type; unsigned flags; } builtin_sym_t; static builtin_sym_t builtin_symbols[] = { {".zero", &type_zero, QFOD_NOSAVE}, {".return", &type_param, QFOD_NOSAVE}, {".param_0", &type_param, QFOD_NOSAVE}, {".param_1", &type_param, QFOD_NOSAVE}, {".param_2", &type_param, QFOD_NOSAVE}, {".param_3", &type_param, QFOD_NOSAVE}, {".param_4", &type_param, QFOD_NOSAVE}, {".param_5", &type_param, QFOD_NOSAVE}, {".param_6", &type_param, QFOD_NOSAVE}, {".param_7", &type_param, QFOD_NOSAVE}, }; static defref_t *free_defrefs; static hashtab_t *extern_defs; static hashtab_t *defined_defs; static hashtab_t *field_defs; static codespace_t *code; static defspace_t *data; static defspace_t *far_data; static defspace_t *entity; static strpool_t *strings; static strpool_t *type_strings; static relocgroup_t relocs, final_relocs; static defgroup_t global_defs, local_defs, fields, defs; static funcgroup_t funcs; static struct { pr_lineno_t *lines; int num_lines; int max_lines; } lines; static int code_base; static int data_base; static int far_data_base; static int entity_base; static int reloc_base; static int func_base; static int line_base; static path_t *path_head; static path_t **path_tail = &path_head; #define DATA(x) (data->data + (x)) #define STRING(x) (strings->strings + (x)) #define TYPE_STRING(x) (type_strings->strings + (x)) #define Xgroup_add(X)\ static void \ X##group_add_##X##s (X##group_t *X##group, qfo_##X##_t *X##s, int num_##X##s)\ {\ if (X##group->num_##X##s + num_##X##s > X##group->max_##X##s) {\ X##group->max_##X##s = RUP (X##group->num_##X##s + num_##X##s, 16384);\ X##group->X##s = realloc (X##group->X##s,\ X##group->max_##X##s * sizeof (qfo_##X##_t));\ }\ memcpy (X##group->X##s + X##group->num_##X##s, X##s,\ num_##X##s * sizeof (qfo_##X##_t));\ X##group->num_##X##s += num_##X##s;\ } Xgroup_add(def); // defgroup_add_defs Xgroup_add(reloc); // relocgroup_add_relocs Xgroup_add(func); // funcgroup_add_funcs static void def_error (qfo_def_t *def, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); static void def_warning (qfo_def_t *def, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); static defref_t * get_defref (qfo_def_t *def, defgroup_t *defgroup) { defref_t *defref; ALLOC (16384, defref_t, defrefs, defref); defref->def = def - defgroup->defs; return defref; } static qfo_def_t * deref_def (defref_t *defref, defgroup_t *defgroup) { return defgroup->defs + defref->def; } static const char * defs_get_key (void *_defref, void *_defgroup) { defref_t *defref = (defref_t *) _defref; defgroup_t *defgroup = (defgroup_t *) _defgroup; qfo_def_t *def = deref_def (defref, defgroup); return STRING (def->name); } static void add_strings (qfo_t *qfo) { int i; for (i = 0; i < qfo->strings_size; i += strlen (qfo->strings + i) + 1) strpool_addstr (strings, qfo->strings + i); } static void add_relocs (qfo_t *qfo) { int i; relocgroup_add_relocs (&relocs, qfo->relocs, qfo->num_relocs); for (i = reloc_base; i < relocs.num_relocs; i++) { qfo_reloc_t *reloc = relocs.relocs + i; switch ((reloc_type)reloc->type) { case rel_none: break; case rel_op_a_def: case rel_op_b_def: case rel_op_c_def: case rel_op_a_def_ofs: case rel_op_b_def_ofs: case rel_op_c_def_ofs: reloc->ofs += code_base; break; case rel_op_a_op: case rel_op_b_op: case rel_op_c_op: // these are relative and fixed up before the .qfo is written break; case rel_def_op: reloc->ofs += data_base; reloc->def += code_base; break; case rel_def_func: reloc->ofs += data_base; reloc->def += func_base; DATA (reloc->ofs)->func_var = reloc->def + 1; break; case rel_def_def: case rel_def_def_ofs: reloc->ofs += data_base; break; case rel_def_string: reloc->ofs += data_base; DATA (reloc->ofs)->string_var = strpool_addstr (strings, qfo->strings + DATA (reloc->ofs)->string_var); break; case rel_def_field: //FIXME more? reloc->ofs += data_base; break; } } } static void linker_type_mismatch (qfo_def_t *def, qfo_def_t *d) { def_error (def, "type mismatch for `%s' `%s'", TYPE_STRING (def->name), TYPE_STRING (def->full_type)); def_error (d, "previous definition `%s'", TYPE_STRING (d->full_type)); } static void process_def (qfo_def_t *def) { defref_t *_d; qfo_def_t *d; if (def->flags & QFOD_EXTERNAL) { if (!def->num_relocs) return; if ((_d = Hash_Find (defined_defs, STRING (def->name)))) { d = deref_def (_d, &global_defs); if (d->full_type != def->full_type) { linker_type_mismatch (def, d); return; } def->ofs = d->ofs; def->flags = d->flags; Hash_Add (defined_defs, get_defref (def, &global_defs)); } else { Hash_Add (extern_defs, get_defref (def, &global_defs)); } } else if (def->flags & QFOD_GLOBAL) { if ((_d = Hash_Find (defined_defs, STRING (def->name)))) { d = deref_def (_d, &global_defs); if (d->flags & QFOD_SYSTEM) { int i, size; if (d->full_type != def->full_type) { linker_type_mismatch (def, d); return; } d->flags &= ~QFOD_SYSTEM; d->flags |= def->flags & (QFOD_INITIALIZED | QFOD_CONSTANT); size = type_size (parse_type (TYPE_STRING (d->full_type))); memcpy (DATA (d->ofs), DATA (def->ofs), size * sizeof (pr_type_t)); for (i = 0; i < relocs.num_relocs; i++) { qfo_reloc_t *reloc = relocs.relocs + i; if (reloc->type >= rel_def_def && reloc->type <= rel_def_field) if (reloc->ofs == def->ofs) reloc->ofs = d->ofs; } def->ofs = d->ofs; def->flags = d->flags; } else { def_error (def, "%s redefined", STRING (def->name)); def_error (d, "previous definition"); } } while ((_d = Hash_Del (extern_defs, STRING (def->name)))) { Hash_Add (defined_defs, _d); d = deref_def (_d, &global_defs); if (d->full_type != def->full_type) { linker_type_mismatch (def, d); continue; } d->ofs = def->ofs; d->flags = def->flags; } Hash_Add (defined_defs, get_defref (def, &global_defs)); } } static void process_field (qfo_def_t *def) { defref_t *_d; qfo_def_t *field_def; pr_type_t *var = DATA (def->ofs); if ((_d = Hash_Find (field_defs, STRING (def->name)))) { field_def = deref_def (_d, &fields); def_error (def, "%s redefined", STRING (def->name)); def_error (field_def, "previous definition"); } defgroup_add_defs (&fields, def, 1); field_def = fields.defs + fields.num_defs - 1; field_def->ofs = var->integer_var + entity_base; Hash_Add (field_defs, get_defref (field_def, &fields)); } static void fixup_def (qfo_t *qfo, qfo_def_t *def, int def_num) { int i; qfo_reloc_t *reloc; qfo_func_t *func; def->full_type = strpool_addstr (type_strings, qfo->types + def->full_type); def->name = strpool_addstr (strings, qfo->strings + def->name); def->relocs += reloc_base; for (i = 0, reloc = relocs.relocs + def->relocs; i < def->num_relocs; i++, reloc++) reloc->def = def_num; def->file = strpool_addstr (strings, qfo->strings + def->file); if ((def->flags & (QFOD_LOCAL | QFOD_ABSOLUTE))) return; if (!(def->flags & QFOD_EXTERNAL)) { def->ofs += data_base; if (def->flags & QFOD_INITIALIZED) { pr_type_t *var = DATA (def->ofs); switch (def->basic_type) { case ev_func: if (var->func_var) { func = funcs.funcs + var->func_var - 1; func->def = def_num; } break; case ev_field: process_field (def); break; default: break; } } } process_def (def); } static void add_defs (qfo_t *qfo) { qfo_def_t *s, *e; for (s = e = qfo->defs; s - qfo->defs < qfo->num_defs; s = e) { int def_num = global_defs.num_defs; qfo_def_t *d; while (e - qfo->defs < qfo->num_defs && !(e->flags & QFOD_LOCAL)) e++; defgroup_add_defs (&global_defs, s, e - s); for (d = global_defs.defs + def_num; def_num < global_defs.num_defs; d++, def_num++) fixup_def (qfo, d, def_num); while (e - qfo->defs < qfo->num_defs && (e->flags & QFOD_LOCAL)) e++; } } static void add_funcs (qfo_t *qfo) { int i; funcgroup_add_funcs (&funcs, qfo->funcs, qfo->num_funcs); for (i = func_base; i < funcs.num_funcs; i++) { qfo_func_t *func = funcs.funcs + i; func->name = strpool_addstr (strings, qfo->strings + func->name); func->file = strpool_addstr (strings, qfo->strings + func->file); if (func->code) func->code += code_base; if (func->num_local_defs) { int def_num = local_defs.num_defs; qfo_def_t *d; defgroup_add_defs (&local_defs, qfo->defs + func->local_defs, func->num_local_defs); func->local_defs = def_num; for (d = local_defs.defs + def_num; def_num < local_defs.num_defs; d++, def_num++) fixup_def (qfo, d, def_num); } if (func->line_info) func->line_info += line_base; func->relocs += reloc_base; } } static void add_lines (qfo_t *qfo) { int i; if (lines.num_lines + qfo->num_lines > lines.max_lines) { lines.max_lines = RUP (lines.num_lines + qfo->num_lines, 16384); lines.lines = realloc (lines.lines, lines.max_lines * sizeof (pr_lineno_t)); } lines.num_lines += qfo->num_lines; memcpy (lines.lines + line_base, qfo->lines, qfo->num_lines * sizeof (pr_lineno_t)); for (i = line_base; i < lines.num_lines; i++) { pr_lineno_t *line = lines.lines + i; if (line->line) line->fa.addr += code_base; else line->fa.func += func_base; } } static void fixup_relocs (void) { defref_t *_d; qfo_reloc_t *reloc; qfo_def_t *def, *field_def; qfo_func_t *func; int i; for (i = 0; i < final_relocs.num_relocs; i++) { reloc = final_relocs.relocs + i; def = 0; switch ((reloc_type)reloc->type) { case rel_none: break; case rel_op_a_def: case rel_op_b_def: case rel_op_c_def: case rel_def_def: case rel_def_field: case rel_op_a_def_ofs: case rel_op_b_def_ofs: case rel_op_c_def_ofs: case rel_def_def_ofs: def = defs.defs + reloc->def; if (def->flags & (QFOD_EXTERNAL | QFOD_LOCAL | QFOD_ABSOLUTE)) continue; break; case rel_def_op: case rel_op_a_op: case rel_op_b_op: case rel_op_c_op: case rel_def_func: case rel_def_string: break; } switch ((reloc_type)reloc->type) { case rel_none: break; case rel_op_a_def: code->code[reloc->ofs].a = def->ofs; break; case rel_op_b_def: code->code[reloc->ofs].b = def->ofs; break; case rel_op_c_def: code->code[reloc->ofs].c = def->ofs; break; case rel_op_a_op: case rel_op_b_op: case rel_op_c_op: case rel_op_a_def_ofs: case rel_op_b_def_ofs: case rel_op_c_def_ofs: case rel_def_def_ofs: break; case rel_def_op: DATA (reloc->ofs)->integer_var = reloc->def; break; case rel_def_def: DATA (reloc->ofs)->integer_var = def->ofs; break; case rel_def_func: break; case rel_def_string: break; case rel_def_field: _d = Hash_Find (field_defs, STRING (def->name)); if (_d) { // null if not initialized field_def = deref_def (_d, &fields); DATA (reloc->ofs)->integer_var = field_def->ofs; } break; } } for (i = 0; i < defs.num_defs; i++) { def = defs.defs + i; if (def->basic_type == ev_func && (def->flags & QFOD_INITIALIZED) && !(def->flags & (QFOD_LOCAL | QFOD_EXTERNAL | QFOD_ABSOLUTE))) { pr_type_t *var = DATA (def->ofs); if (var->func_var) { func = funcs.funcs + var->func_var - 1; func->def = i; } } } } static void move_def (hashtab_t *deftab, qfo_def_t *d) { defref_t *_d; qfo_def_t *def; int def_num = defs.num_defs; int j; defgroup_add_defs (&defs, d, 1); def = defs.defs + def_num; def->relocs = final_relocs.num_relocs; relocgroup_add_relocs (&final_relocs, relocs.relocs + d->relocs, d->num_relocs); for (j = 0; j < d->num_relocs; j++) { relocs.relocs[d->relocs + j].type = rel_none; final_relocs.relocs[def->relocs + j].def = def_num; } if ((d->flags & QFOD_CONSTANT) && d->basic_type == ev_func) { qfo_func_t *func; pr_type_t *var = DATA (d->ofs); if (var->func_var) { func = funcs.funcs + var->func_var - 1; func->def = def_num; } } if (deftab) { while ((_d = Hash_Del (deftab, STRING (def->name)))) { int def_relocs; d = deref_def (_d, &global_defs); relocgroup_add_relocs (&final_relocs, relocs.relocs + d->relocs, d->num_relocs); def_relocs = def->relocs + def->num_relocs; def->num_relocs += d->num_relocs; for (j = 0; j < d->num_relocs; j++) { relocs.relocs[d->relocs + j].type = rel_none; final_relocs.relocs[def_relocs + j].def = def_num; } memset (d, 0, sizeof (*d)); } } } static void merge_defgroups (void) { int local_base, i, j; qfo_def_t *def; defref_t *d; qfo_func_t *func; static qfo_def_t null_def; for (i = 0; i < global_defs.num_defs; i++) { const char *name; def = global_defs.defs + i; if (memcmp (def, &null_def, sizeof (*def)) == 0) continue; name = STRING (def->name); if ((d = Hash_Del (defined_defs, name))) move_def (defined_defs, deref_def (d, &global_defs)); else if ((d = Hash_Del (extern_defs, name))) move_def (extern_defs, deref_def (d, &global_defs)); else if (!(def->flags & (QFOD_GLOBAL | QFOD_EXTERNAL))) move_def (0, def); } local_base = defs.num_defs; for (i = 0; i < local_defs.num_defs; i++) { move_def (0, local_defs.defs + i); } for (i = 0; i < funcs.num_funcs; i++) { int r = final_relocs.num_relocs; func = funcs.funcs + i; func->local_defs += local_base; relocgroup_add_relocs (&final_relocs, relocs.relocs + func->relocs, func->num_relocs); for (j = 0; j < func->num_relocs; j++) relocs.relocs[func->relocs + j].type = rel_none; func->relocs = r; } for (i = 0; i < relocs.num_relocs; i = j) { j = i; while (j < relocs.num_relocs && relocs.relocs[j].type != rel_none) j++; relocgroup_add_relocs (&final_relocs, relocs.relocs + i, j - i); while (j < relocs.num_relocs && relocs.relocs[j].type == rel_none) j++; } } static void define_def (const char *name, etype_t basic_type, const char *full_type, unsigned flags, int size, int v) { qfo_def_t d; pr_type_t *val = calloc (size, sizeof (pr_type_t)); val->integer_var = v; memset (&d, 0, sizeof (d)); d.basic_type = basic_type; d.full_type = strpool_addstr (type_strings, full_type); d.name = strpool_addstr (strings, name); d.ofs = data->size; d.flags = QFOD_GLOBAL | flags; if (basic_type == ev_field) { d.relocs = relocs.num_relocs; d.num_relocs = 1; } defspace_adddata (data, val, size); defgroup_add_defs (&global_defs, &d, 1); process_def (global_defs.defs + global_defs.num_defs - 1); if (basic_type == ev_field) { int def_num = global_defs.num_defs - 1; qfo_def_t *def = global_defs.defs + def_num; qfo_reloc_t rel = {def->ofs, rel_def_field, def_num}; relocgroup_add_relocs (&relocs, &rel, 1); process_field (def); } free (val); } void linker_begin (void) { size_t i; extern_defs = Hash_NewTable (16381, defs_get_key, 0, &global_defs); defined_defs = Hash_NewTable (16381, defs_get_key, 0, &global_defs); field_defs = Hash_NewTable (16381, defs_get_key, 0, &fields); code = codespace_new (); data = new_defspace (); far_data = new_defspace (); entity = new_defspace (); strings = strpool_new (); type_strings = strpool_new (); pr.strings = strings; if (!options.partial_link) { dstring_t *encoding = dstring_new (); for (i = 0; i < sizeof (builtin_symbols) / sizeof (builtin_symbols[0]); i++) { etype_t basic_type = builtin_symbols[i].type->type; int size = type_size (builtin_symbols[i].type); dstring_clearstr (encoding); encode_type (encoding, builtin_symbols[i].type); define_def (builtin_symbols[i].name, basic_type, encoding->str, builtin_symbols[i].flags, size, 0); } } } static void linker_add_qfo (qfo_t *qfo) { code_base = code->size; data_base = data->size; far_data_base = far_data->size; reloc_base = relocs.num_relocs; func_base = funcs.num_funcs; line_base = lines.num_lines; entity_base = entity->size; codespace_addcode (code, qfo->code, qfo->code_size); defspace_adddata (data, qfo->data, qfo->data_size); defspace_adddata (far_data, qfo->far_data, qfo->far_data_size); defspace_adddata (entity, 0, qfo->entity_fields); add_strings (qfo); add_relocs (qfo); add_funcs (qfo); add_defs (qfo); add_lines (qfo); } int linker_add_object_file (const char *filename) { qfo_t *qfo; qfo = qfo_open (filename); if (!qfo) return 1; if (options.verbosity >= 2) puts (filename); linker_add_qfo (qfo); qfo_delete (qfo); return 0; } int linker_add_lib (const char *libname) { pack_t *pack = 0; path_t start = {path_head, "."}; path_t *path = &start; const char *path_name = 0; int i, j; int did_something; if (strncmp (libname, "-l", 2) == 0) { while (path) { path_name = va ("%s/lib%s.a", path->path, libname + 2); pack = pack_open (path_name); if (pack) break; if (errno != ENOENT) { if (errno) perror (libname); return 1; } path = path->next; } } else { path_name = libname; pack = pack_open (path_name); } if (!pack) { if (errno) perror (libname); return 1; } if (options.verbosity > 1) puts (path_name); do { did_something = 0; for (i = 0; i < pack->numfiles; i++) { QFile *f; qfo_t *qfo; f = Qsubopen (path_name, pack->files[i].filepos, pack->files[i].filelen, 1); qfo = qfo_read (f); Qclose (f); if (!qfo) return 1; for (j = 0; j < qfo->num_defs; j++) { qfo_def_t *def = qfo->defs + j; if ((def->flags & QFOD_GLOBAL) && !(def->flags & QFOD_EXTERNAL) && Hash_Find (extern_defs, qfo->strings + def->name)) { if (options.verbosity >= 2) printf ("adding %s because of %s\n", pack->files[i].name, qfo->strings + def->name); linker_add_qfo (qfo); did_something = 1; break; } } qfo_delete (qfo); } } while (did_something); pack_del (pack); return 0; } static void undefined_def (qfo_def_t *def) { qfo_def_t line_def; int i; qfo_reloc_t *reloc = relocs.relocs + def->relocs; for (i = 0; i < def->num_relocs; i++, reloc++) { if ((reloc->type == rel_op_a_def || reloc->type == rel_op_b_def || reloc->type == rel_op_c_def || reloc->type == rel_op_a_def_ofs || reloc->type == rel_op_b_def_ofs || reloc->type == rel_op_c_def_ofs) && lines.lines) { qfo_func_t *func = funcs.funcs; qfo_func_t *best = func; int best_dist = reloc->ofs - func->code; pr_lineno_t *line; while (best_dist && func - funcs.funcs < funcs.num_funcs) { if (func->code <= reloc->ofs) { if (best_dist < 0 || reloc->ofs - func->code < best_dist) { best = func; best_dist = reloc->ofs - func->code; } } func++; } line = lines.lines + best->line_info; line_def.file = best->file; line_def.line = best->line; if (!line->line && line->fa.func == best - funcs.funcs) { while (line - lines.lines < lines.num_lines - 1 && line[1].line && line[1].fa.addr <= reloc->ofs) line++; line_def.line = line->line + best->line; } def_error (&line_def, "undefined symbol %s", STRING (def->name)); } else { def_error (def, "undefined symbol %s", STRING (def->name)); } } } qfo_t * linker_finish (void) { defref_t **undef_defs, **defref; qfo_t *qfo; if (!options.partial_link) { int did_self = 0, did_this = 0; undef_defs = (defref_t **) Hash_GetList (extern_defs); for (defref = undef_defs; *defref; defref++) { qfo_def_t *def = deref_def (*defref, &global_defs); const char *name = STRING (def->name); if (strcmp (name, ".self") == 0 && !did_self) { defref_t *_d = Hash_Find (defined_defs, "self"); if (_d) { qfo_def_t *d = deref_def (_d, &global_defs); if (d->basic_type == ev_entity) def_warning (d, "@self and self used together"); } define_def (".self", ev_entity, "E", 0, 1, 0); did_self = 1; } else if (strcmp (name, ".this") == 0 && !did_this) { entity_base = 0; define_def (".this", ev_field, "F@", QFOD_NOSAVE, 1, entity->size); defspace_adddata (entity, 0, 1); did_this = 1; } } free (undef_defs); undef_defs = (defref_t **) Hash_GetList (extern_defs); for (defref = undef_defs; *defref; defref++) { qfo_def_t *def = deref_def (*defref, &global_defs); undefined_def (def); } free (undef_defs); if (pr.error_count) return 0; } merge_defgroups (); fixup_relocs (); qfo = qfo_new (); qfo_add_code (qfo, code->code, code->size); qfo_add_data (qfo, data->data, data->size); qfo_add_far_data (qfo, far_data->data, far_data->size); qfo_add_strings (qfo, strings->strings, strings->size); qfo_add_relocs (qfo, final_relocs.relocs, final_relocs.num_relocs); qfo_add_defs (qfo, defs.defs, defs.num_defs); qfo_add_funcs (qfo, funcs.funcs, funcs.num_funcs); qfo_add_lines (qfo, lines.lines, lines.num_lines); qfo_add_types (qfo, type_strings->strings, type_strings->size); qfo->entity_fields = entity->size; return qfo; } void linker_add_path (const char *path) { path_t *p = malloc (sizeof (path_t)); p->next = 0; p->path = path; *path_tail = p; path_tail = &p->next; } static void def_error (qfo_def_t *def, const char *fmt, ...) { va_list args; static dstring_t *string; if (!string) string = dstring_new (); va_start (args, fmt); dvsprintf (string, fmt, args); va_end (args); pr.source_file = def->file; pr.source_line = def->line; pr.strings = strings; error (0, "%s", string->str); } static void def_warning (qfo_def_t *def, const char *fmt, ...) { va_list args; static dstring_t *string; if (!string) string = dstring_new (); va_start (args, fmt); dvsprintf (string, fmt, args); va_end (args); pr.source_file = def->file; pr.source_line = def->line; pr.strings = strings; warning (0, "%s", string->str); }