diff --git a/cobc/ChangeLog b/cobc/ChangeLog index 0c5659e71..7fd2c44c4 100644 --- a/cobc/ChangeLog +++ b/cobc/ChangeLog @@ -1,4 +1,15 @@ +2024-01-10 David Declerck + + * debuggen.c: implement a feature to collect and dump debug information + relevant for debugging in a debug database file in binary format + * cobc.h: export the new `debuggen` functions + * codegen.c: add calls to `debuggen` at appropriate locations + * flag.def: add a -fgen-debug-db flag to enable debug database generation + * cobc.c: ensure proper options are passed to the C compiler when + using the -fgen-debug-db flag + * Makefile.am: add `debuggen.c` to the list of source files + 2023-07-26 Simon Sobisch * typeck.c (search_set_keys): improving SEARCH ALL syntax checks diff --git a/cobc/Makefile.am b/cobc/Makefile.am index 444987fc6..b74ef2dfe 100644 --- a/cobc/Makefile.am +++ b/cobc/Makefile.am @@ -23,7 +23,7 @@ bin_PROGRAMS = cobc cobc_SOURCES = cobc.c cobc.h ppparse.y pplex.c parser.y scanner.c config.c \ reserved.c error.c tree.c tree.h field.c typeck.c codegen.c help.c \ config.def flag.def warning.def codeoptim.def ppparse.def \ - codeoptim.c replace.c + codeoptim.c replace.c debuggen.c #cobc_SOURCES = cobc.c cobc.h ppparse.y pplex.l parser.y scanner.l config.c diff --git a/cobc/cobc.c b/cobc/cobc.c index b3a52303c..b2e352844 100644 --- a/cobc/cobc.c +++ b/cobc/cobc.c @@ -3327,6 +3327,15 @@ process_command_line (const int argc, char **argv) cb_flag_stack_extended = 1; /* for extended stack output */ } + /* If generating a debug database, must also + pass the appropriate flags to the C compiler */ + if (cb_flag_gen_debug_db && !cb_source_debugging) { + cb_source_debugging = 1; +#ifdef COB_DEBUG_FLAGS + COBC_ADD_STR (cobc_cflags, " ", cobc_debug_flags, NULL); +#endif + } + cob_optind = 1; cob_opterr = 0; /* all error handling was done in the call above */ while ((c = cob_getopt_long_long (argc, argv, short_options, diff --git a/cobc/cobc.h b/cobc/cobc.h index 73f3d7a23..12ed8757b 100644 --- a/cobc/cobc.h +++ b/cobc/cobc.h @@ -684,4 +684,10 @@ extern int cb_strcasecmp (const void *, const void *); extern unsigned char cb_toupper (const unsigned char); extern unsigned char cb_tolower (const unsigned char); +/* debuggen.c */ + +extern void debuggen_init(void); +extern void debuggen_add_ref(int c_line, const char *cob_file_name, int cob_line); +extern void debuggen_finalize(const struct cb_program *prog, const char *c_file); + #endif /* CB_COBC_H */ diff --git a/cobc/codegen.c b/cobc/codegen.c index f32391606..c42e45f23 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -5299,6 +5299,9 @@ output_init_comment_and_source_ref (struct cb_field *f) output_c_info (); } #endif + if (cb_flag_gen_debug_db) { + debuggen_add_ref(output_line_number, f->common.source_file, f->common.source_line); + } } static void @@ -8482,6 +8485,10 @@ output_source_reference (cb_tree tree, const enum cob_statement statement) } output_trace_info (tree, statement); } + + if (cb_flag_gen_debug_db) { + debuggen_add_ref(output_line_number, tree->source_file, tree->source_line); + } } static void @@ -13618,6 +13625,9 @@ codegen (struct cb_program *prog, const char *translate_name) const int set_xref = cb_listing_xref; int subsequent_call = 0; int has_global_file_level = 0 ; + if (cb_flag_gen_debug_db) { + debuggen_init(); + } codegen_init (prog, translate_name); /* Temporarily disable cross-reference during C generation */ @@ -13650,6 +13660,9 @@ codegen (struct cb_program *prog, const char *translate_name) cb_listing_xref = set_xref; codegen_finalize (); + if (cb_flag_gen_debug_db) { + debuggen_finalize(prog, translate_name); + } } void diff --git a/cobc/debuggen.c b/cobc/debuggen.c new file mode 100644 index 000000000..c59648754 --- /dev/null +++ b/cobc/debuggen.c @@ -0,0 +1,358 @@ +/* + Copyright (C) 2023 Free Software Foundation, Inc. + Written by David Declerck. + + This file is part of GnuCOBOL. + + The GnuCOBOL compiler 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 3 of the + License, or (at your option) any later version. + + GnuCOBOL 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 GnuCOBOL. If not, see . +*/ + +#include +#include +#include + +#include "cobc.h" +#include "tree.h" + +/* List of known COBOL files (including copies), each with an id */ +struct file_list { + struct file_list *next; + const char *name; + int id; +}; + +static struct file_list *files = NULL; +static int nb_files = 0; +static int file_id = 0; + +/* Mapping of C file locations to COBOL file locations */ +struct ref_list { + struct ref_list *next; + int c_line; + int cob_file_id; + int cob_line; +}; + +static struct ref_list *refs = NULL; +static int nb_refs = 0; + +/* Add a file to the list of known files */ +static int +add_file (const char *file_name) +{ + struct file_list *f = files; + while (f != NULL) { + if (strcmp(file_name, f->name) == 0) { + return f->id; + } + f = f->next; + } + f = malloc(sizeof(struct file_list)); + f->name = file_name; + f->id = ++file_id; + f->next = files; + files = f; + ++nb_files; + return f->id; +} + +/* Reverse the list of known files */ +static void +rev_files (void) +{ + struct file_list *f = files, *pf = NULL, *nf = NULL; + while (f != NULL) { + nf = f->next; + f->next = pf; + pf = f; + f = nf; + } + files = pf; +} + +/* Clear the list of known files */ +static void +clear_files (void) +{ + struct file_list *f = files, *nf = NULL; + while (f != NULL) { + nf = f->next; + free(f); + f = nf; + } + files = NULL; + nb_files = 0; + file_id = 0; +} + +/* Add a C/COBOL line mapping to the the source map */ +static void +add_ref (int c_line, const char *cob_file_name, int cob_line) +{ + struct ref_list *r = NULL; + if (!strcmp(cob_file_name, "register-definition")) { + /* Skip those, as they are not user code */ + return; + } + r = malloc(sizeof(struct ref_list)); + r->c_line = c_line; + r->cob_file_id = add_file(cob_file_name); + r->cob_line = cob_line; + r->next = refs; + refs = r; + ++nb_refs; +} + +/* Reverse the source map */ +static void +rev_refs (void) +{ + struct ref_list *r = refs, *pr = NULL, *nr = NULL; + while (r != NULL) { + nr = r->next; + r->next = pr; + pr = r; + r = nr; + } + refs = pr; +} + +/* Clear the source map */ +static void +clear_refs (void) +{ + struct ref_list *r = refs, *nr = NULL; + while (r != NULL) { + nr = r->next; + free(r); + r = nr; + } + refs = NULL; + nb_refs = 0; +} + +/* Output an 8-bit integer */ +static size_t +output_int8 (char i8, FILE *stream) +{ + return fwrite(&i8, 1, 1, stream); +} + +/* Output a 16-bit integer in big-endian format */ +static size_t +output_int16 (short i16, FILE *stream) +{ + char buf[2]; + buf[0] = (char)((i16 & 0xFF00) >> 8); + buf[1] = (char)(i16 & 0x00FF); + return fwrite(buf, 2, 1, stream); +} + +/* Output a 32-bit integer in big-endian format */ +static size_t +output_int32 (int i32, FILE *stream) +{ + char buf[4]; + buf[0] = (char)((i32 & 0xFF000000) >> 24); + buf[1] = (char)((i32 & 0x00FF0000) >> 16); + buf[2] = (char)((i32 & 0x0000FF00) >> 8); + buf[3] = (char)(i32 & 0x000000FF); + return fwrite(buf, 4, 1, stream); +} + +/* Output a non-null-terminated string prefixed with its 8-bit size */ +static size_t +output_string_sz8 (const char *str, FILE *stream) +{ + size_t sz = strlen(str); + output_int8((char)sz, stream); + fputs(str, stream); + return sz + 1; +} + +/* Output a non-null-terminated string prefixed with its 16-bit size */ +static size_t +output_string_sz16 (const char *str, FILE *stream) +{ + size_t sz = strlen(str); + output_int16((short)sz, stream); + fputs(str, stream); + return sz + 2; +} + +/* Output the debug informations relative to fields */ +static void +output_debug_infos_fields (const struct cb_field *fields, FILE *df) +{ + const struct cb_field *f = NULL; + char buf[16]; + int nb_fields = 0; + int i = 0; + + /* Compute and output the number of relevant fields */ + for (f = fields; f != NULL; f = f->sister) { + if (!f->flag_internal_register && strcmp(f->name, "COB-CRT-STATUS")) { + ++nb_fields; + } + } + output_int32(nb_fields, df); + + /* Output relevant information for each field */ + for (f = fields; f != NULL; f = f->sister) { + if (!f->flag_internal_register && strcmp(f->name, "COB-CRT-STATUS")) { + + /* (1 byte) Level */ + output_int8(f->level, df); + + /* (string) COBOL field name */ + output_string_sz8(f->name, df); + + /* (1 byte) Indexes */ + output_int8((char)f->indexes, df); + + /* (4 byte) Occurs */ + output_int32(f->occurs_min, df); + + /* (4 byte) Occurs */ + output_int32(f->occurs_max, df); + + /* (1 byte) Usage */ + output_int8((char)f->usage, df); + + /* (1 byte) Field type/category */ + output_int8((char)CB_TREE_CATEGORY(f), df); + + /* (1 byte) Flag: has picture */ + output_int8((f->pic == NULL) ? 0 : 1, df); + + /* Handle picture if present */ + if (f->pic != NULL) { + + /* (1 byte) Digits */ + output_int8((char)f->pic->digits, df); + + /* (1 byte) Scale (1.10 ^ scale) */ + output_int8((char)f->pic->scale, df); + + /* (1 byte) Sign */ + output_int8((char)f->pic->have_sign, df); + + /* (1 byte) Number of symbols/count */ + output_int8((char)f->pic->lenstr, df); + for (i = 0; i < f->pic->lenstr; ++i) { + output_int8((char)f->pic->str[i].symbol, df); + output_int8((char)f->pic->str[i].times_repeated, df); + } + } + + /* (string) C variable name */ + snprintf(buf, sizeof(buf), "b_%d", f->id); + output_string_sz8(buf, df); + + /* (4 bytes) Offset from level 01 */ + output_int32(f->offset, df); + + /* (4 bytes) Field size */ + output_int32(f->size, df); + + /* Output children fields (if any) */ + output_debug_infos_fields(f->children, df); + } + } +} + +/* Output all debug informations */ +static void +output_debug_infos (const struct cb_program *prog, const char *c_file) +{ + const struct file_list *f = NULL; + const struct ref_list *r = NULL; + const struct cb_program *p = NULL; + FILE *df = NULL; + char fname[1024]; + int nb_progs = 0; + int i = 0; + + /* Build the debug database file name */ + strncpy(fname, prog->common.source_file, sizeof(fname) - 1); + i = strnlen(fname, sizeof(fname)); + while ((i >= 0) && (fname[i] != '.')) --i; + strncpy(&fname[i+1], "cdb", sizeof(fname) - (i + 1)); + + /* Open the debug database file */ + df = fopen(fname, "wb"); + if (df == NULL) { + return; + } + + /* Output file signature and version (GCDB = GnuCobol DataBase) */ + fputs("GCDB", df); + output_int16(0x0001, df); + + /* Output the list of COBOL files with their ids */ + output_int32(nb_files, df); + for (f = files; f != NULL; f = f->next) { + output_string_sz16(f->name, df); + } + + /* Output the name of the C file */ + output_string_sz16(c_file, df); + + /* Output the source mapping */ + output_int32(nb_refs, df); + for (r = refs; r != NULL; r = r->next) { + output_int32(r->c_line, df); + output_int16((short)r->cob_file_id, df); + output_int32(r->cob_line, df); + } + + /* Compute and output the number of programs */ + for (p = prog; p != NULL; p = p->next_program) { + ++nb_progs; + } + output_int32(nb_progs, df); + + /* For each program, output its various storages */ + for (p = prog; p != NULL; p = p->next_program) { + output_string_sz8(p->orig_program_id, df); + output_debug_infos_fields(p->working_storage, df); + output_debug_infos_fields(p->local_storage, df); + output_debug_infos_fields(p->linkage_storage, df); + output_debug_infos_fields(p->screen_storage, df); + output_debug_infos_fields(p->report_storage, df); + } + + fclose(df); +} + +void +debuggen_init (void) +{ + clear_files(); + clear_refs(); +} + +void +debuggen_add_ref (int c_line, const char *cob_file_name, int cob_line) +{ + add_ref(c_line, cob_file_name, cob_line); +} + +void +debuggen_finalize (const struct cb_program *prog, const char *c_file) +{ + rev_files(); + rev_refs(); + output_debug_infos(prog, c_file); +} diff --git a/cobc/flag.def b/cobc/flag.def index 4850daa60..6a03abe36 100644 --- a/cobc/flag.def +++ b/cobc/flag.def @@ -254,3 +254,6 @@ CB_FLAG_ON (cb_diagnostics_show_caret, 1, "diagnostics-show-caret", CB_FLAG_ON (cb_diagnostics_show_line_numbers, 1, "diagnostics-show-line-numbers", _(" -fno-diagnostics-show-line-numbers\tsuppress display of line numbers in diagnostics")) + +CB_FLAG (cb_flag_gen_debug_db, 1, "gen-debug-db", + _(" -fgen-debug-db generate debugging database"))